54 Commits

Author SHA1 Message Date
46ed966c18 Shamrock: recommend Lagrange
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-02-24 01:01:41 +08:00
623dc5da07 Shamrock: fix all multimedia pic fetch
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-02-24 00:34:28 +08:00
bd6d4f046c Shamrock: Remove rkey cache
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-02-23 20:52:44 +08:00
9ad66f2f92 Shamrock: fix #251
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-02-23 19:33:36 +08:00
b4c40e236a Shamrock: 快速序列化/反序列化 Protobuf
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-02-23 18:23:02 +08:00
9bbcc2f160 Shamrock: 允许私聊接收multimedia资源图片 #251
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-02-23 14:17:52 +08:00
00b355b877 Shamrock: fix #248
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-02-23 08:36:34 +08:00
071ddbb69a Shamrock: update LICENSE
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-02-22 18:13:55 +08:00
4d5c054bc4 Shamrock: 开源开发许可证
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-02-22 18:12:31 +08:00
87629666f2 refactor send_forward_msg(暂时只支持收发文字消息) 2024-02-22 01:04:56 +08:00
75633f78c4 Shamrock: fix #248
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-02-21 21:31:59 +08:00
c940aea153 Shamrock: Reusable and restrictive coroutine context
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-02-21 15:17:53 +08:00
18126b1fda fix bug 2024-02-21 02:16:47 +08:00
e9884a5fa8 fix bug(暂未完成 请勿使用) 2024-02-21 02:16:47 +08:00
aa7b241dba refactor send_forward_msg(暂未完成 请勿使用) 2024-02-21 02:16:46 +08:00
c70f3eabfe Shamrock: typo BaseTransmitServlet.kt
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-02-19 11:30:22 +08:00
1c65aab673 refactor send_forward_msg(暂未完成 请勿使用) 2024-02-19 04:14:26 +08:00
a5cdd64686 upgrade workflow 2024-02-19 04:13:50 +08:00
b07ca5bd03 fix linq 2024-02-19 04:13:49 +08:00
8f8580d542 Shamrock: remove debug log #242
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-02-18 21:16:23 +08:00
0ed4480878 Shamrock: fix #242 2024-02-18 21:16:04 +08:00
c3e0031aa4 Shamrock: debug #242 2024-02-18 20:36:42 +08:00
388c963e88 Shamrock: fix markdown parser 2024-02-18 20:28:45 +08:00
4283651b1e Shamrock: fix comm_elem parser 2024-02-18 20:08:57 +08:00
50d7dfa06d Shamrock: support inline_keyboard msg 2024-02-18 20:00:37 +08:00
b3a2e605fb Shamrock: fix NT image acquisition 2024-02-18 18:52:56 +08:00
14bf5fc0a2 Shamrock: fix fakeReceiveSelfMsg 2024-02-18 07:51:43 +08:00
2c8b57a7dc Shamrock: fix #238 2024-02-18 07:45:48 +08:00
8e6c167987 Shamrock: fix #236 2024-02-18 07:37:47 +08:00
2c8094c8c8 Shamrock: 临时修补rkey缺失导致的qqnt图片无法获取 #236 2024-02-17 19:39:26 +08:00
62385d6f62 Shamrock: 修复合并转发获取图片错误 #236 2024-02-17 15:43:12 +08:00
3b210d7ed0 Shamrock: fix #236 2024-02-17 09:37:01 +08:00
63ce2d40bd Shamrock: fix get role by nt crash 2024-02-16 10:50:00 +08:00
36f8b6e54b Shamrock: Change the image upload source to a camera 2024-02-16 09:50:31 +08:00
58413044e9 Shamrock: typo log 2024-02-16 09:16:05 +08:00
3395cd9d95 Shamrock: 支持群临时消息推送携带群号以及群名称 2024-02-16 09:12:45 +08:00
494b1f1fd0 Shamrock: 允许禁止QQ启动无关紧要的进程服务 2024-02-16 00:09:35 +08:00
cf943fd13a Shamrock: atメッセージ優先nameパラメータ 2024-02-15 13:18:43 +08:00
9608b46799 Shamrock: 是正メッセージプッシュアイデンティティの取得が遅い 2024-02-15 13:14:37 +08:00
502956e3ec Shamrock: エイト・メッセージにニックネームの迅速なクエリを許可する 2024-02-15 13:01:34 +08:00
27b4c26da7 Shamrock: fix GlobalEventTransmitter x2 2024-02-11 14:28:07 +08:00
65f54360f8 Shamrock: not fix GlobalEventTransmitter x2 2024-02-11 14:06:10 +08:00
9a9fad975f Shamrock: not fix GlobalEventTransmitter 2024-02-11 13:57:58 +08:00
7153b21cd4 Shamrock: fix GlobalEventTransmitter 2024-02-11 13:42:28 +08:00
fdb2486090 Shamrock: Disable lost connection detection 2024-02-10 00:41:38 +08:00
d60b2a25d1 Update SECURITY.md 2024-02-09 08:04:40 +08:00
2d8dde6951 add history msg to database 2024-02-08 23:52:21 +08:00
78fd60dade Merge pull request #228 from Mythologyli/master
feat: get group applier uin from request msg
2024-02-08 22:34:40 +08:00
80dbf6af28 feat: get group applier uin from request msg 2024-02-08 22:27:55 +08:00
1e53753b5a Shamrock: fix #227 2024-02-08 20:17:51 +08:00
e727877268 Merge pull request #225 from MrXiaoM/fix-guild-message
修复 频道消息事件不符合 go-cqhttp 规范
2024-02-08 20:16:01 +08:00
63b69df3ea fix missing guild_id and channel_id 2024-02-08 14:51:14 +08:00
b03e02675b Shamrock: add timeout #223 2024-02-05 22:16:12 +08:00
e68a1ffd37 Shamrock: fix guild sync 2024-02-05 22:12:20 +08:00
150 changed files with 4947 additions and 1574 deletions

BIN
.github/jetbrains-variant-3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

View File

@ -22,13 +22,13 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Setup JDK 17 - name: Setup JDK 17
uses: actions/setup-java@v4.0.0 uses: actions/setup-java@v4
with: with:
java-version: 17 java-version: 17
distribution: "adopt" distribution: "adopt"
- name: Cache Gradle Dependencies - name: Cache Gradle Dependencies
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -38,7 +38,7 @@ jobs:
restore-keys: gradle-deps restore-keys: gradle-deps
- name: Cache Gradle Build - name: Cache Gradle Build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: | path: |
~/.gradle/caches/build-cache-* ~/.gradle/caches/build-cache-*
@ -82,19 +82,19 @@ jobs:
echo "|x86_64|$x86_64" >> $GITHUB_STEP_SUMMARY echo "|x86_64|$x86_64" >> $GITHUB_STEP_SUMMARY
- name: Upload ALL APK RELEASE - name: Upload ALL APK RELEASE
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: "${{ env.SHAMROCK_VERSION_ALL }}" name: "${{ env.SHAMROCK_VERSION_ALL }}"
path: "${{ env.APK_FILE_ALL }}" path: "${{ env.APK_FILE_ALL }}"
- name: Upload ARM64 APK RELEASE - name: Upload ARM64 APK RELEASE
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: "${{ env.SHAMROCK_VERSION_ARM64 }}" name: "${{ env.SHAMROCK_VERSION_ARM64 }}"
path: "${{ env.APK_FILE_ARM64 }}" path: "${{ env.APK_FILE_ARM64 }}"
- name: Upload X86_64 APK RELEASE - name: Upload X86_64 APK RELEASE
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: "${{ env.SHAMROCK_VERSION_x86_64 }}" name: "${{ env.SHAMROCK_VERSION_x86_64 }}"
path: "${{ env.APK_FILE_X86_64 }}" path: "${{ env.APK_FILE_X86_64 }}"

View File

@ -29,6 +29,7 @@
- 一键移植:本项目基于 go-cqhttp 的文档进行开发实现。 - 一键移植:本项目基于 go-cqhttp 的文档进行开发实现。
- 平行部署:可多平台部署,未来将会支持 Docker 部署的教程。 - 平行部署:可多平台部署,未来将会支持 Docker 部署的教程。
- 替代方案:[Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core)
## 权限声明 ## 权限声明
@ -46,13 +47,19 @@
<img src="https://github.com/whitechi73/OpenShamrock/assets/98259561/f04d60bc-ec40-41fc-bc15-62c146f1a1f1" width="160px" alt="Shamrock"> **我可爱吗?欢迎你的到来,这里是一个很大的地方,有着无限可能,主要是有你啦!** <img src="https://github.com/whitechi73/OpenShamrock/assets/98259561/f04d60bc-ec40-41fc-bc15-62c146f1a1f1" width="160px" alt="Shamrock"> **我可爱吗?欢迎你的到来,这里是一个很大的地方,有着无限可能,主要是有你啦!**
## 鸣谢
感谢[**JetBrains**](https://www.jetbrains.com/zh-cn/community/opensource/#support)提供的开源开发许可证JetBrains 通过为核心项目贡献者免费提供一套一流的开发者工具来支持非商业开源项目。
[<img src=".github/jetbrains-variant-3.png" width="200"/>](https://www.jetbrains.com/zh-cn/community/opensource/#support)
## 开源协议 ## 开源协议
本项目使用 [GPL-3.0](LICENSE) 协议开放源代码 本项目使用 [GPL-3.0](LICENSE) 协议开放源代码
```text ```text
Shamrock - OneBot standard QQ robot framework based on Xposed implementation Shamrock - OneBot standard QQ robot framework based on Xposed implementation
Copyright (C) 2023 Shamrock Team Copyright (C) 2023 ~ 2024 Shamrock Team
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
@ -102,3 +109,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
[contrib-image]: https://contrib.rocks/image?repo=whitechi73/OpenShamrock [contrib-image]: https://contrib.rocks/image?repo=whitechi73/OpenShamrock
[contrib-link]: https://github.com/whitechi73/OpenShamrock/graphs/contributors [contrib-link]: https://github.com/whitechi73/OpenShamrock/graphs/contributors

View File

@ -1,11 +1,19 @@
# Security Policy # Security Policy
## Support Version ## 支持的版本
| Version | Supported | | 版本 | 支持状态 |
| ------- | ------------------ | | ------- | ------------------ |
| 9.0.15 | :white_check_mark: |
| 8.9.75 | :white_check_mark: | | 8.9.75 | :white_check_mark: |
| 8.9.73 | :white_check_mark: | | 8.9.73 | :white_check_mark: |
| 8.9.98 | :white_check_mark: | | 8.9.98 | :white_check_mark: |
| < 8.9.68| :x: | | < 8.9.68| :x: |
## 频道支持性说明
如果需要使用`频道`相关功能请升级QQ到9.0.8版本
## Riru检测问题
QQ自`9.0.8`开始将会检测riru可能作为封号因素

View File

@ -6,4 +6,9 @@ plugins {
java { java {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
}
dependencies {
implementation(DEPENDENCY_PROTOBUF)
implementation(kotlinx("serialization-protobuf", "1.6.2"))
} }

View File

@ -0,0 +1,16 @@
package moe.fuqiuluo.symbols
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import kotlin.reflect.KClass
interface Protobuf<T: Protobuf<T>>
inline fun <reified T: Protobuf<T>> KClass<T>.decode(data: ByteArray): T {
return ProtoBuf.decodeFromByteArray(data)
}
inline fun <reified T: Protobuf<T>> ByteArray.decodeProtobuf(to: KClass<T>? = null): T {
return ProtoBuf.decodeFromByteArray(this)
}

View File

@ -229,6 +229,16 @@ object ShamrockConfig {
return preferences.getBoolean("anti_qq_trace", true) return preferences.getBoolean("anti_qq_trace", true)
} }
fun isForbidUselessProcess(ctx: Context): Boolean {
val preferences = ctx.getSharedPreferences("config", 0)
return preferences.getBoolean("forbid_useless_process", false)
}
fun setForbidUselessProcess(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0)
preferences.edit().putBoolean("forbid_useless_process", v).apply()
}
fun setAntiTrace(ctx: Context, v: Boolean) { fun setAntiTrace(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0) val preferences = ctx.getSharedPreferences("config", 0)
preferences.edit().putBoolean("anti_qq_trace", v).apply() preferences.edit().putBoolean("anti_qq_trace", v).apply()
@ -333,6 +343,7 @@ object ShamrockConfig {
"alive_reply" to preferences.getBoolean("alive_reply", false), "alive_reply" to preferences.getBoolean("alive_reply", false),
"enable_sync_msg_as_sent_msg" to preferences.getBoolean("enable_sync_msg_as_sent_msg", false), "enable_sync_msg_as_sent_msg" to preferences.getBoolean("enable_sync_msg_as_sent_msg", false),
"disable_auto_sync_setting" to preferences.getBoolean("disable_auto_sync_setting", false), "disable_auto_sync_setting" to preferences.getBoolean("disable_auto_sync_setting", false),
"forbid_useless_process" to preferences.getBoolean("forbid_useless_process", false)
) )
} }

View File

@ -100,17 +100,16 @@ fun LabFragment() {
thickness = 0.2.dp thickness = 0.2.dp
) )
/*
Function( Function(
title = "自动清理QQ垃圾", title = "禁止无用进程",
desc = "也许会导致奇怪的问题(无效)。", desc = "禁止QQ生成无用进程浪费内存",
descColor = color, descColor = color,
isSwitch = ShamrockConfig.isAutoClean(ctx) isSwitch = ShamrockConfig.isForbidUselessProcess(ctx)
) { ) {
ShamrockConfig.setAutoClean(ctx, it) ShamrockConfig.setForbidUselessProcess(ctx, it)
ShamrockConfig.pushUpdate(ctx) ShamrockConfig.pushUpdate(ctx)
return@Function false return@Function true
}*/ }
Function( Function(
title = "自回复测试", title = "自回复测试",

View File

@ -1,6 +1,7 @@
plugins { plugins {
kotlin("jvm") kotlin("jvm")
id("com.google.devtools.ksp") version "1.9.21-1.0.15" id("com.google.devtools.ksp") version "1.9.21-1.0.15"
kotlin("plugin.serialization") version "1.9.21"
} }
ksp { ksp {
@ -14,5 +15,8 @@ dependencies {
implementation("com.google.devtools.ksp:symbol-processing-api:1.9.21-1.0.15") implementation("com.google.devtools.ksp:symbol-processing-api:1.9.21-1.0.15")
implementation("com.squareup:kotlinpoet:1.14.2") implementation("com.squareup:kotlinpoet:1.14.2")
implementation(DEPENDENCY_PROTOBUF)
implementation(kotlinx("serialization-protobuf", "1.6.2"))
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0") ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
} }

View File

@ -6,6 +6,7 @@ package moe.fuqiuluo.ksp.impl
import com.google.devtools.ksp.KspExperimental import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.getClassDeclarationByName import com.google.devtools.ksp.getClassDeclarationByName
import com.google.devtools.ksp.getKotlinClassByName
import com.google.devtools.ksp.processing.CodeGenerator import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.processing.KSPLogger
@ -26,18 +27,18 @@ class OneBotHandlerProcessor(
): SymbolProcessor { ): SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> { override fun process(resolver: Resolver): List<KSAnnotated> {
val ActionManagerNode = resolver.getClassDeclarationByName("moe.fuqiuluo.shamrock.remote.action.ActionManager") val ActionManagerNode = resolver.getClassDeclarationByName("moe.fuqiuluo.shamrock.remote.action.ActionManager")
if (ActionManagerNode == null) { ?: resolver.getKotlinClassByName("moe.fuqiuluo.shamrock.remote.action.ActionManager")
logger.error("OneBotHandlerProcessor: ActionManager not found") ?: resolver.getClassDeclarationByName("ActionManager")
return emptyList()
}
val symbols = resolver.getSymbolsWithAnnotation(OneBotHandler::class.qualifiedName!!) val symbols = resolver.getSymbolsWithAnnotation(OneBotHandler::class.qualifiedName!!)
val unableToProcess = symbols.filterNot { it.validate() } val unableToProcess = symbols.filterNot { it.validate() }
val oneBotHandlers = (symbols.filter { if (ActionManagerNode != null) {
it is KSClassDeclaration && it.validate() && it.classKind == ClassKind.OBJECT val oneBotHandlers = (symbols.filter {
} as Sequence<KSClassDeclaration>).toList() it is KSClassDeclaration && it.validate() && it.classKind == ClassKind.OBJECT
} as Sequence<KSClassDeclaration>).toList()
if (oneBotHandlers.isNotEmpty()) { if (oneBotHandlers.isNotEmpty()) {
ActionManagerNode.accept(ActionManagerVisitor(oneBotHandlers), Unit) ActionManagerNode.accept(ActionManagerVisitor(oneBotHandlers), Unit)
}
} }
return unableToProcess.toList() return unableToProcess.toList()

View File

@ -0,0 +1,77 @@
@file:Suppress("UNCHECKED_CAST")
package moe.fuqiuluo.ksp.impl
import com.google.devtools.ksp.isInternal
import com.google.devtools.ksp.isPrivate
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.validate
import com.squareup.kotlinpoet.FileSpec
import kotlinx.serialization.Serializable
class ProtobufProcessor(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger
): SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation(Serializable::class.qualifiedName!!)
val unableToProcess = symbols.filterNot { it.validate() }
val actions = (symbols.filter {
it is KSClassDeclaration && it.validate() && it.classKind == ClassKind.CLASS
} as Sequence<KSClassDeclaration>).filter {
it.superTypes.any { superType ->
superType.resolve().declaration.qualifiedName?.asString() == "moe.fuqiuluo.symbols.Protobuf"
}
}.toList()
if (actions.isNotEmpty()) {
actions.forEachIndexed { index, clz ->
if (clz.isInternal()) return@forEachIndexed
if (clz.isPrivate()) return@forEachIndexed
val packageName = "protobuf.auto"
val fileSpecBuilder = FileSpec.scriptBuilder("FastProtobuf", packageName)
fileSpecBuilder.addImport("kotlinx.serialization.protobuf", "ProtoBuf")
fileSpecBuilder.addImport("kotlinx.serialization", "decodeFromByteArray")
fileSpecBuilder.addImport("kotlinx.serialization", "encodeToByteArray")
if (clz.parentDeclaration != null) {
fileSpecBuilder.addImport(clz.importPackage, clz.simpleName.asString())
} else {
fileSpecBuilder.addImport(clz.packageName.asString(), clz.simpleName.asString())
}
if (clz.typeParameters.isNotEmpty()) {
val genericType = clz.typeParameters.joinToString(", ") { it.name.asString() }
fileSpecBuilder.addStatement("""inline fun <$genericType> ${clz.simpleName.asString()}<$genericType>.toByteArray() = ProtoBuf.encodeToByteArray(this)""")
} else {
fileSpecBuilder.addStatement("inline fun ${clz.simpleName.asString()}.toByteArray() = ProtoBuf.encodeToByteArray(this)")
}
codeGenerator.createNewFile(
dependencies = Dependencies.ALL_FILES,
packageName = packageName,
fileName = "FP${clz.simpleName.asString().hashCode()}"
).use { outputStream ->
outputStream.writer().use {
fileSpecBuilder.build().writeTo(it)
}
}
}
}
return unableToProcess.toList()
}
private val KSDeclaration.importPackage: String
get() = if (parentDeclaration != null) {
parentDeclaration!!.importPackage + "." + parentDeclaration!!.simpleName.asString()
} else packageName.asString()
}

View File

@ -0,0 +1,17 @@
package moe.fuqiuluo.ksp.providers
import com.google.auto.service.AutoService
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import moe.fuqiuluo.ksp.impl.ProtobufProcessor
@AutoService(SymbolProcessorProvider::class)
class ProtobufProvider: SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return ProtobufProcessor(
environment.codeGenerator,
environment.logger
)
}
}

View File

@ -2,6 +2,7 @@ plugins {
id("com.android.library") id("com.android.library")
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
kotlin("plugin.serialization") version "1.9.21" kotlin("plugin.serialization") version "1.9.21"
id("com.google.devtools.ksp") version "1.9.21-1.0.15"
} }
android { android {
@ -38,4 +39,7 @@ dependencies {
implementation(kotlinx("serialization-protobuf", "1.6.2")) implementation(kotlinx("serialization-protobuf", "1.6.2"))
implementation(kotlinx("serialization-json", "1.6.2")) implementation(kotlinx("serialization-json", "1.6.2"))
implementation(project(":annotations"))
ksp(project(":processor"))
} }

View File

@ -2,9 +2,10 @@ package protobuf.fav
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class WeiyunComm( data class WeiyunComm(
@ProtoNumber(1) val req: WeiyunCommonReq? = null, @ProtoNumber(1) val req: WeiyunCommonReq? = null,
@ProtoNumber(2) val resp: WeiyunCommonResp? = null @ProtoNumber(2) val resp: WeiyunCommonResp? = null
) ): Protobuf<WeiyunComm>

View File

@ -6,6 +6,7 @@ package protobuf.fav
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class WeiyunMsgHead( data class WeiyunMsgHead(
@ -27,4 +28,5 @@ data class WeiyunMsgHead(
@ProtoNumber(103) val promptMsg: String? = null, @ProtoNumber(103) val promptMsg: String? = null,
@ProtoNumber(111) val totalSpace: ULong = ULong.MIN_VALUE, @ProtoNumber(111) val totalSpace: ULong = ULong.MIN_VALUE,
@ProtoNumber(112) val usedSpace: ULong = ULong.MIN_VALUE, @ProtoNumber(112) val usedSpace: ULong = ULong.MIN_VALUE,
) ): Protobuf<WeiyunMsgHead>

View File

@ -6,6 +6,7 @@ import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
import protobuf.qweb.QWebExtInfo import protobuf.qweb.QWebExtInfo
@Serializable @Serializable
@ -19,7 +20,7 @@ data class GetGuildFeedsReq(
@ProtoNumber(7) var u7: Int? = null, @ProtoNumber(7) var u7: Int? = null,
@ProtoNumber(8) var u8: Int? = null, @ProtoNumber(8) var u8: Int? = null,
@ProtoNumber(9) var u9: ByteArray? = null, @ProtoNumber(9) var u9: ByteArray? = null,
) ): Protobuf<GetGuildFeedsReq>
@Serializable @Serializable
data class GetGuildFeedsRsp( data class GetGuildFeedsRsp(
@ -27,7 +28,7 @@ data class GetGuildFeedsRsp(
@ProtoNumber(2) var isFinish: Int = 0, @ProtoNumber(2) var isFinish: Int = 0,
//@ProtoNumber(3) var feedAttchInfo: ByteArray? = null, //@ProtoNumber(3) var feedAttchInfo: ByteArray? = null,
//@ProtoNumber(4) var traceId: String? = null, //@ProtoNumber(4) var traceId: String? = null,
) ): Protobuf<GetGuildFeedsRsp>
@Serializable @Serializable
data class StFeed( data class StFeed(

View File

@ -0,0 +1,37 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.lightapp
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable
data class AdaptShareInfoReq(
//@ProtoNumber(1) var extInfo: Any? = null,
@ProtoNumber(2) var appid: String? = null,
@ProtoNumber(3) var title: String? = null,
@ProtoNumber(4) var desc: String? = null,
@ProtoNumber(5) var time: ULong? = null,
@ProtoNumber(6) var scene: UInt? = null,
@ProtoNumber(7) var templetType: UInt? = null,
@ProtoNumber(8) var businessType: UInt? = null,
@ProtoNumber(9) var picUrl: String? = null,
@ProtoNumber(10) var vidUrl: String? = null,
@ProtoNumber(11) var jumpUrl: String? = null,
@ProtoNumber(12) var iconUrl: String? = null,
@ProtoNumber(13) var verType: UInt? = null,
@ProtoNumber(14) var shareType: UInt? = null,
@ProtoNumber(15) var versionId: String? = null,
@ProtoNumber(16) var withShareTicket: UInt? = null,
@ProtoNumber(17) var webURL: String? = null,
//@ProtoNumber(18) var appidRich: Any? = null,
@ProtoNumber(19) var template: Template? = null,
//@ProtoNumber(20) var rcvOpenId: Any? = null,
): Protobuf<AdaptShareInfoReq>
@Serializable
data class Template(
@ProtoNumber(1) var templateId: UInt? = null,
@ProtoNumber(2) var templateData: ByteArray? = null,
)

View File

@ -5,53 +5,13 @@ import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class MessageBody(
@ProtoNumber(1) val msgHead: MessageHead? = null,
@ProtoNumber(2) val contentHead: MessageContentHead? = null,
@ProtoNumber(3) val richMsg: RichMessage? = null,
)
@Serializable @Serializable
data class RichMessage( data class RichMessage(
@ProtoNumber(1) val elements: MessageElementList? = null, @ProtoNumber(1) val font: Font? = null,
@ProtoNumber(2) val rawBuffer: ByteArray? = null,
)
@Serializable
data class MessageElementList(
@ProtoNumber(2) val elements: List<MessageElement>? = null @ProtoNumber(2) val elements: List<MessageElement>? = null
) )
@Serializable @Serializable
data class MessageElement( data class Font(
@ProtoNumber(51) val json: JsonElement? = null, @ProtoNumber(9) val fontName: String? = null
)
@Serializable
data class JsonElement(
@ProtoNumber(1) val data: ByteArray? = null,
)
@Serializable
data class MessageHead(
@ProtoNumber(1) val peer: Long = Long.MIN_VALUE,
@ProtoNumber(2) val peerUid: String? = null,
@ProtoNumber(3) val flag: Int = Int.MIN_VALUE,
@ProtoNumber(5) val receiver: Long? = null,
@ProtoNumber(6) val receiverUid: String? = null,
)
@Serializable
data class MessageContentHead(
@ProtoNumber(1) val msgType: Int = Int.MIN_VALUE,
@ProtoNumber(2) val msgSubType: Int = Int.MIN_VALUE,
@ProtoNumber(4) val u1: Long = Long.MIN_VALUE,
@ProtoNumber(5) val msgSeq: Long = Long.MIN_VALUE,
@ProtoNumber(6) val msgTime: Long? = null,
@ProtoNumber(7) val u2: Int? = null,
@ProtoNumber(11) val u3: Long? = null,
@ProtoNumber(12) val msgRandom: Long = Long.MIN_VALUE,
@ProtoNumber(14) val u4: Long? = null,
@ProtoNumber(28) val u5: Long? = null,
) )

View File

@ -0,0 +1,11 @@
package protobuf.message
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class MessageBody(
@ProtoNumber(1) val rich: RichMessage? = null,
@ProtoNumber(2) val rawBuffer: ByteArray? = null,
@ProtoNumber(3) val MsgEncryptContent: ByteArray? = null
)

View File

@ -0,0 +1,31 @@
package protobuf.message
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class MessageContent(
@ProtoNumber(1) val msgType: Int = Int.MIN_VALUE,
@ProtoNumber(2) val msgSubType: Int? = null,
@ProtoNumber(3) val u1: Int? = null,
@ProtoNumber(4) val msgViaRandom: Long = Long.MIN_VALUE,
@ProtoNumber(5) val msgSeq_: Long? = null,
@ProtoNumber(6) val msgTime: Long? = null,
@ProtoNumber(7) val u2: Int? = null,
@ProtoNumber(8) val u6: Int? = null,
@ProtoNumber(9) val u7: Int? = null,
@ProtoNumber(11) val msgSeq: Long? = null,
@ProtoNumber(12) val msgRandom: Long = Long.MIN_VALUE,
@ProtoNumber(14) val u4: Long? = null,
@ProtoNumber(15) val forwardHead: ForwardHead? = null,
@ProtoNumber(28) val u5: Long? = null
)
@Serializable
data class ForwardHead(
@ProtoNumber(1) val u1: Int? = null,
@ProtoNumber(2) val u2: Int? = null,
@ProtoNumber(3) val u3: Int? = null,
@ProtoNumber(4) val ub641: String? = null,
@ProtoNumber(5) val Avatar: String? = null
)

View File

@ -0,0 +1,13 @@
package protobuf.message
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import protobuf.message.element.*
@Serializable
data class MessageElement(
@ProtoNumber(1) val text: TextElement? = null,
@ProtoNumber(2) val face: FaceElement? = null,
@ProtoNumber(51) val json: JsonElement? = null,
@ProtoNumber(53) val comm: CommonElement? = null,
)

View File

@ -0,0 +1,33 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.message
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class MessageHead(
@ProtoNumber(1) val peer: Long = Long.MIN_VALUE,
@ProtoNumber(2) val peerUid: String? = null,
@ProtoNumber(3) val flag: Int = Int.MIN_VALUE,
@ProtoNumber(4) val appId: Int = Int.MIN_VALUE,
@ProtoNumber(5) val receiver: Long? = null,
@ProtoNumber(6) val receiverUid: String? = null,
@ProtoNumber(7) val forward: MessageForward? = null,
@ProtoNumber(8) val groupInfo: GroupInfo? = null,
)
@Serializable
data class MessageForward(
@ProtoNumber(6) val friendName: String? = null,
@ProtoNumber(11) val u1: Int? = null,
)
@Serializable
data class GroupInfo(
@ProtoNumber(1) val groupCode: ULong = ULong.MIN_VALUE,
@ProtoNumber(4) val memberCard: String? = null,
@ProtoNumber(5) val u1: Int? = null,
@ProtoNumber(7) val groupName: String? = null,
@ProtoNumber(10) val u2: Int? = null,
)

View File

@ -0,0 +1,11 @@
package protobuf.message
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class NtMessage(
@ProtoNumber(1) val msgHead: MessageHead? = null,
@ProtoNumber(2) val contentHead: MessageContent? = null,
@ProtoNumber(3) val body: MessageBody? = null,
)

View File

@ -0,0 +1,11 @@
package protobuf.message.element
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class CommonElement(
@ProtoNumber(1) val type: Int? = null,
@ProtoNumber(2) val data: ByteArray? = null,
@ProtoNumber(3) val u1: Int? = null,
)

View File

@ -0,0 +1,9 @@
package protobuf.message.element
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class FaceElement(
@ProtoNumber(1) val id: Int? = null,
)

View File

@ -0,0 +1,9 @@
package protobuf.message.element
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class JsonElement(
@ProtoNumber(1) val data: ByteArray? = null,
)

View File

@ -0,0 +1,40 @@
package protobuf.message.element
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class TextElement(
@ProtoNumber(1) val text: String? = null,
@ProtoNumber(2) val link: String? = null,
@ProtoNumber(3) val attr6Buf: ByteArray? = null,
@ProtoNumber(4) val attr7Buf: ByteArray? = null,
@ProtoNumber(11) val buf: ByteArray? = null,
@ProtoNumber(12) val pbReserve: TextResvAttr? = null,
) {
companion object {
@Serializable
data class TextResvAttr(
@ProtoNumber(1) val wording: ByteArray? = null,
@ProtoNumber(2) val textAnalysisResult: Int? = null,
@ProtoNumber(3) val atType: Int? = null,
@ProtoNumber(4) val atMemberUin: Long? = null,
@ProtoNumber(5) val atMemberTinyid: Long? = null,
@ProtoNumber(6) val atChannelInfo: ExtChannelInfo? = null,
@ProtoNumber(7) val atRoleInfo: ExtRoleInfo? = null,
)
@Serializable
data class ExtChannelInfo(
@ProtoNumber(1) val guildId: Long? = null,
@ProtoNumber(2) val channelId: Long? = null,
)
@Serializable
data class ExtRoleInfo(
@ProtoNumber(1) val id: Long? = null,
@ProtoNumber(2) val info: ByteArray? = null,
@ProtoNumber(3) val flag: Int? = null,
)
}
}

View File

@ -0,0 +1,63 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.message.longmsg
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable
data class LongMsgSettings(
@ProtoNumber(1) val field1: Int? = null,
@ProtoNumber(2) val field2: Int? = null,
@ProtoNumber(3) val field3: Int? = null,
@ProtoNumber(4) val field4: Int? = null,
)
@Serializable
data class LongMsgUid(
@ProtoNumber(2) val uid: String? = null,
)
@Serializable
data class RecvLongMsgInfo(
@ProtoNumber(1) val uid: LongMsgUid? = null,
@ProtoNumber(2) val resId: String? = null,
@ProtoNumber(3) val u1: Int? = null,
)
@Serializable
data class SendLongMsgInfo(
@ProtoNumber(1) val type: Int? = null,
@ProtoNumber(2) val uid: LongMsgUid? = null,
@ProtoNumber(3) val groupUin: Int? = null,
@ProtoNumber(4) val payload: ByteArray? = null,
)
@Serializable
data class LongMsgReq(
@ProtoNumber(1) val recvInfo: RecvLongMsgInfo? = null,
@ProtoNumber(2) val sendInfo: SendLongMsgInfo? = null,
@ProtoNumber(15) val setting: LongMsgSettings? = null,
): Protobuf<LongMsgReq>
@Serializable
data class LongMsgRsp(
@ProtoNumber(1) val recvResult: RecvLongMsgResult? = null,
@ProtoNumber(2) val sendResult: SendLongMsgResult? = null,
@ProtoNumber(15) val setting: LongMsgSettings? = null
): Protobuf<LongMsgRsp> {
companion object {
@Serializable
data class SendLongMsgResult(
@ProtoNumber(3) val resId: String? = null,
)
@Serializable
data class RecvLongMsgResult(
@ProtoNumber(3) val resId: String? = null,
@ProtoNumber(4) val payload: ByteArray? = null,
)
}
}

View File

@ -0,0 +1,33 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.message.longmsg
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
import protobuf.message.MessageBody
import protobuf.message.MessageContent
import protobuf.message.MessageHead
@Serializable
data class PushMsgBody(
@ProtoNumber(1) val head: MessageHead? = null,
@ProtoNumber(2) val content: MessageContent? = null,
@ProtoNumber(3) val body: MessageBody? = null
)
@Serializable
data class LongMsgContent(
@ProtoNumber(1) val body: List<PushMsgBody>? = null
)
@Serializable
data class LongMsgAction(
@ProtoNumber(1) val command: String? = null,
@ProtoNumber(2) val data: LongMsgContent? = null
)
@Serializable
data class LongMsgPayload(
@ProtoNumber(2) val action: List<LongMsgAction>? = null
): Protobuf<LongMsgPayload>

View File

@ -0,0 +1,55 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.message.multimedia
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable
data class RichMediaForPicData(
@ProtoNumber(1) val info: MediaInfo?,
@ProtoNumber(2) val display: DisplayMediaInfo?,
): Protobuf<RichMediaForPicData> {
companion object {
@Serializable
data class MediaInfo(
@ProtoNumber(1) val picture: Picture? = null,
)
@Serializable
data class Picture(
@ProtoNumber(1) val info: PictureInfo? = null,
@ProtoNumber(2) val fileId: String? = null,
@ProtoNumber(4) val time: ULong? = null,
)
@Serializable
data class PictureInfo(
@ProtoNumber(2) val md5Hex: String? = null,
@ProtoNumber(3) val sha: String? = null,
@ProtoNumber(4) val name: String? = null,
@ProtoNumber(6) val width: Int? = null,
@ProtoNumber(7) val height: Int? = null,
)
}
}
@Serializable
data class DisplayMediaInfo(
@ProtoNumber(1) val show: Show? = null,
) {
companion object {
@Serializable
data class Show(
@ProtoNumber(2) val text: String? = null,
@ProtoNumber(12) val download: Download? = null
)
@Serializable
data class Download(
@ProtoNumber(30) val url: String? = null,
)
}
}

View File

@ -0,0 +1,104 @@
package protobuf.msg
import com.google.protobuf.Internal.EMPTY_BYTE_ARRAY
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class MsgBody(
@ProtoNumber(1) var richText: RichText,
//@ProtoNumber(2) var msgContent: ByteArray = EMPTY_BYTE_ARRAY,
//@/ProtoNumber(3) var msgEncryptContent: ByteArray = EMPTY_BYTE_ARRAY,
)
@Serializable
data class RichText(
//@ProtoNumber(1) var attr: Attr? = null,
@ProtoNumber(2) var elems: ArrayList<Elem>? = null,
//@ProtoNumber(3) var not_online_file: NotOnlineFile? = null,
//@ProtoNumber(4) var ptt: Ptt? = null,
//@ProtoNumber(5) var tmp_ptt: TmpPtt? = null,
//@ProtoNumber(6) var trans_211_tmp_msg: Trans211TmpMsg? = null,
)
@Serializable
data class Elem(
/*@ProtoNumber(1) var text: TextMsg? = null,
@ProtoNumber(2) var face: FaceMsg? = null,
@ProtoNumber(3) var online_image: OnlineImage? = null,
@ProtoNumber(4) var not_online_image: NotOnlineImage? = null,
@ProtoNumber(5) var trans_elem_info: TransElem? = null,
@ProtoNumber(6) var market_face: MarketFace? = null,
@ProtoNumber(7) var elem_flags: ElemFlags? = null,
@ProtoNumber(8) var customFace: CustomFace? = null,
@ProtoNumber(9) var elem_flags2: ElemFlags2? = null,
@ProtoNumber(10) var fun_face: FunFace? = null,
@ProtoNumber(11) var secret_file: SecretFileMsg? = null,
@ProtoNumber(12) var rich_msg: RichMsg? = null,
@ProtoNumber(13) var group_file: GroupFile? = null,
@ProtoNumber(14) var pub_group: PubGroup? = null,
@ProtoNumber(15) var market_trans: MarketTrans? = null,
@ProtoNumber(16) var extra_info: ExtraInfo? = null,
@ProtoNumber(17) var shake_window: ShakeWindow? = null,
@ProtoNumber(18) var pub_account: PubAccount? = null,
@ProtoNumber(19) var video_file: VideoFile? = null,
@ProtoNumber(20) var tips_info: TipsInfo? = null,
@ProtoNumber(21) var anon_group_msg: AnonymousGroupMsg? = null,
@ProtoNumber(22) var qq_live_old: QQLiveOld? = null,
@ProtoNumber(23) var life_online: LifeOnlineAccount? = null,
@ProtoNumber(24) var qqwallet_msg: QQWalletMsg? = null,
@ProtoNumber(25) var crm_elem: CrmElem? = null,
@ProtoNumber(26) var conference_tips_info: ConferenceTipsInfo? = null,
@ProtoNumber(27) var redbag_info: RedBagInfo? = null,
@ProtoNumber(28) var low_version_tips: LowVersionTips? = null,
@ProtoNumber(29) var bankcode_ctrl_info: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(30) var near_by_msg: NearByMessageType? = null,
@ProtoNumber(31) var custom_elem: CustomElem? = null,
@ProtoNumber(32) var location_info: LocationInfo? = null,
@ProtoNumber(33) var pub_acc_info: PubAccInfo? = null,
@ProtoNumber(34) var small_emoji: SmallEmoji? = null,
@ProtoNumber(35) var fsj_msg_elem: FSJMessageElem? = null,
@ProtoNumber(36) var ark_app: ArkAppElem? = null,
*/
@ProtoNumber(37) var generalFlags: GeneralFlags? = null,
/*
@ProtoNumber(38) var hc_flash_pic: CustomFace? = null,
@ProtoNumber(39) var deliver_gift_msg: DeliverGiftMsg? = null,
@ProtoNumber(40) var bitapp_msg: BitAppMsg? = null,
@ProtoNumber(41) var open_qq_data: OpenQQData? = null,
@ProtoNumber(42) var apollo_msg: ApolloActMsg? = null,
@ProtoNumber(43) var group_pub_acc_info: GroupPubAccountInfo? = null,
@ProtoNumber(44) var bless_msg: BlessingMessage? = null,
@ProtoNumber(45) var src_msg: SourceMsg? = null,
@ProtoNumber(46) var lola_msg: LolaMsg? = null,
@ProtoNumber(47) var group_business_msg: GroupBusinessMsg? = null,
@ProtoNumber(48) var msg_workflow_notify: WorkflowNotifyMsg? = null,
@ProtoNumber(49) var pat_elem: PatsElem? = null,
@ProtoNumber(50) var group_post_elem: GroupPostElem? = null,
@ProtoNumber(51) var light_app: LightAppElem? = null,
@ProtoNumber(52) var eim_info: EIMInfo? = null,
@ProtoNumber(53) var commonElem: CommonElem? = null,*/
)
@Serializable
data class GeneralFlags(
@ProtoNumber(1) var uint32_bubble_diy_text_id: UInt = 0u,
@ProtoNumber(2) var uint32_group_flag_new: UInt = 0u,
@ProtoNumber(3) var uint64_uin: ULong = 0u,
@ProtoNumber(4) var bytes_rp_id: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(5) var uint32_prp_fold: UInt = 0u,
@ProtoNumber(6) var long_text_flag: UInt = 0u,
@ProtoNumber(7) var long_text_resid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(8) var uint32_group_type: UInt = 0u,
@ProtoNumber(9) var uint32_to_uin_flag: UInt = 0u,
@ProtoNumber(10) var uint32_glamour_level: UInt = 0u,
@ProtoNumber(11) var uint32_member_level: UInt = 0u,
@ProtoNumber(12) var uint64_group_rank_seq: ULong = 0u,
@ProtoNumber(13) var uint32_olympic_torch: UInt = 0u,
@ProtoNumber(14) var babyq_guide_msg_cookie: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(15) var uin32_expert_flag: UInt = 0u,
@ProtoNumber(16) var uint32_bubble_sub_id: UInt = 0u,
@ProtoNumber(17) var pendantId: ULong = 0u,
@ProtoNumber(18) var bytes_rp_index: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(19) var reserve: ByteArray = EMPTY_BYTE_ARRAY,
)

View File

@ -0,0 +1,67 @@
package protobuf.msg
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable
class PbSendMsgReq(
@ProtoNumber(1) var routingHead: RoutingHead,
@ProtoNumber(2) var contentHead: ContentHead,
@ProtoNumber(3) var msgBody: MsgBody,
@ProtoNumber(4) var msgSeq: ULong = 0u,
@ProtoNumber(5) var msgRand: UInt = 0u,
//@ProtoNumber(6) var sync_cookie: ByteArray = EMPTY_BYTE_ARRAY,
//@ProtoNumber(7) var app_share: AppShareInfo? = null,
@ProtoNumber(8) var msgVia: UInt = 0u,
//@ProtoNumber(9) var data_statist: UInt = 0u,
//@ProtoNumber(10) var multi_msg_assist: MultiMsgAssist? = null,
//@ProtoNumber(11) var input_notify_info: PbInputNotifyInfo? = null,
//@ProtoNumber(12) var msgCtrl: MsgCtrl? = null,
//@ProtoNumber(13) var receipt_req: ReceiptReq? = null,
//@ProtoNumber(14) var multi_send_seq: UInt = 0u,
): Protobuf<PbSendMsgReq>
@Serializable
data class ContentHead(
@ProtoNumber(1) var pkg_num: UInt = 0u,
@ProtoNumber(2) var pkg_index: UInt = 0u,
@ProtoNumber(3) var div_seq: UInt = 0u,
@ProtoNumber(4) var auto_reply: UInt = 0u,
)
@Serializable
class RoutingHead(
@ProtoNumber(1) var c2c: C2C? = null,
@ProtoNumber(2) var grp: Grp? = null,
// @ProtoNumber(3) var grp_tmp: GrpTmp? = null,
//@ProtoNumber(4) var dis: Dis? = null,
// @ProtoNumber(5) var dis_tmp: DisTmp? = null,
// @ProtoNumber(6) var wpa_tmp: WPATmp? = null,
// @ProtoNumber(7) var secret_file: SecretFileHead? = null,
// @ProtoNumber(8) var public_plat: PublicPlat? = null,
/*@ProtoNumber(9) var trans_msg: TransMsg? = null,
@ProtoNumber(10) var address_list: AddressListTmp? = null,
@ProtoNumber(11) var rich_status_tmp: RichStatusTmp? = null,
@ProtoNumber(12) var trans_cmd: TransCmd? = null,
@ProtoNumber(13) var accost_tmp: AccostTmp? = null,
@ProtoNumber(14) var pub_group_tmp: PubGroupTmp? = null,
@ProtoNumber(15) var trans_0x211: Trans0x211? = null,
@ProtoNumber(16) var business_wpa_tmp: BusinessWPATmp? = null,
@ProtoNumber(17) var auth_tmp: AuthTmp? = null,
@ProtoNumber(18) var bsns_tmp: BsnsTmp? = null,
@ProtoNumber(19) var qq_querybusiness_tmp: QQQueryBusinessTmp? = null,
@ProtoNumber(20) var nearby_dating_tmp: NearByDatingTmp? = null,
@ProtoNumber(21) var nearby_assistant_tmp: NearByAssistantTmp? = null,
@ProtoNumber(22) var comm_tmp: CommTmp? = null,*/
)
@Serializable
class C2C(
@ProtoNumber(1) var to_uin: ULong = 0u,
)
@Serializable
class Grp(
@ProtoNumber(1) var groupCode: ULong = 0u,
)

View File

@ -2,11 +2,13 @@ package protobuf.oidb
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class TrpcOidb( data class TrpcOidb(
@ProtoNumber(1) val cmd: Int = Int.MIN_VALUE, @ProtoNumber(1) val cmd: Int = Int.MIN_VALUE,
@ProtoNumber(2) val service: Int = Int.MIN_VALUE, @ProtoNumber(2) val service: Int = Int.MIN_VALUE,
@ProtoNumber(4) val buffer: ByteArray, @ProtoNumber(4) val buffer: ByteArray,
//@ProtoNumber(11) val traceParams: Map<String, String> = mapOf(),
@ProtoNumber(12) val flag: Int = Int.MIN_VALUE, @ProtoNumber(12) val flag: Int = Int.MIN_VALUE,
) ): Protobuf<TrpcOidb>

View File

@ -0,0 +1,272 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.oidb.cmd0x11c5
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable
data class NtV2RichMediaReq(
@ProtoNumber(1) val head: MultiMediaReqHead,
@ProtoNumber(2) val upload: UploadReq? = null, // 100
@ProtoNumber(3) val download: DownloadReq? = null,
@ProtoNumber(4) val downloadRkey: DownloadRkeyReq? = null,
@ProtoNumber(5) val delete: DeleteReq? = null,
@ProtoNumber(6) val uploadCompleted: UploadCompletedReq? = null,
@ProtoNumber(7) val msgInfoAuth: MsgInfoAuthReq? = null,
@ProtoNumber(8) val uploadKeyRenewal: UploadKeyRenewalReq? = null,
@ProtoNumber(9) val downloadSafe: DownloadSafeReq? = null,
@ProtoNumber(99) val extension: ByteArray? = null,
): Protobuf<NtV2RichMediaReq>
@Serializable
data class DownloadSafeReq(
@ProtoNumber(1) val index: IndexNode,
)
@Serializable
data class UploadKeyRenewalReq(
@ProtoNumber(1) val oldUkey: String,
@ProtoNumber(2) val subType: UInt,
)
@Serializable
data class MsgInfoAuthReq(
@ProtoNumber(1) val msg: ByteArray,
@ProtoNumber(2) val authTime: ULong,
)
@Serializable
data class UploadCompletedReq(
@ProtoNumber(1) val srvSendMsg: Boolean,
@ProtoNumber(2) val clientRandomId: ULong,
@ProtoNumber(3) val msgInfo: MsgInfo,
@ProtoNumber(4) val clientSeq: UInt,
)
@Serializable
data class MsgInfo(
@ProtoNumber(1) val msgInfoBody: List<MsgInfoBody>,
@ProtoNumber(2) val extBizInfo: ExtBizInfo,
)
@Serializable
data class MsgInfoBody(
@ProtoNumber(1) val index: IndexNode? = null,
@ProtoNumber(2) val picture: PictureInfo? = null,
@ProtoNumber(3) val video: VideoInfo? = null,
@ProtoNumber(4) val audio: AudioInfo? = null,
@ProtoNumber(5) val fileExist: Boolean? = null,
@ProtoNumber(6) val hashSum: ByteArray? = null,
)
@Serializable
class VideoInfo
@Serializable
class AudioInfo
@Serializable
data class PictureInfo(
@ProtoNumber(1) val urlPath: String,
@ProtoNumber(2) val ext: PicUrlExtInfo? = null,
@ProtoNumber(3) val domain: String? = null
)
@Serializable
data class PicUrlExtInfo(
@ProtoNumber(1) val originalParameter: String? = null,
@ProtoNumber(2) val bigParameter: String? = null,
@ProtoNumber(3) val thumbParameter: String? = null
)
@Serializable
data class DeleteReq(
@ProtoNumber(1) val index: List<IndexNode>,
@ProtoNumber(2) val needRecallMsg: Boolean? = null,
@ProtoNumber(3) val msgSeq: ULong? = null,
@ProtoNumber(4) val msgRandom: ULong? = null,
@ProtoNumber(5) val msgTime: ULong? = null,
)
@Serializable
data class DownloadRkeyReq(
@ProtoNumber(1) val types: List<Int>
)
@Serializable
data class UploadReq(
@ProtoNumber(1) val uploadInfo: List<UploadInfo>,
@ProtoNumber(2) val tryFastUploadCompleted: Boolean? = null,
@ProtoNumber(3) val srvSendMsg: Boolean? = null,
@ProtoNumber(4) val clientRandomId: ULong = ULong.MIN_VALUE,
@ProtoNumber(5) val compatQMsgSceneType: UInt? = null,
@ProtoNumber(6) val extBizInfo: ExtBizInfo? = null,
@ProtoNumber(7) val clientSeq: UInt? = null,
@ProtoNumber(8) val noNeedCompatMsg: Boolean = false,
)
@Serializable
data class ExtBizInfo(
@ProtoNumber(1) val pic: PicExtBizInfo? = null,
@ProtoNumber(2) val video: VideoExtBizInfo? = null,
@ProtoNumber(3) val ptt: PttExtBizInfo? = null,
@ProtoNumber(10) val busiType: UInt,
)
@Serializable
data class PttExtBizInfo(
@ProtoNumber(1) val srcUin: ULong,
@ProtoNumber(2) val pttScene: UInt,
@ProtoNumber(3) val pttType: UInt,
@ProtoNumber(4) val changeVoice: UInt,
@ProtoNumber(5) val waveform: ByteArray? = null,
@ProtoNumber(6) val autoConvertText: UInt? = null,
@ProtoNumber(11) val bytesReserve: ByteArray? = null,
@ProtoNumber(12) val bytesPbReserve: ByteArray? = null,
@ProtoNumber(13) val bytesGeneralFlags: ByteArray? = null,
)
@Serializable
data class VideoExtBizInfo(
@ProtoNumber(1) val fromScene: UInt,
@ProtoNumber(2) val toScene: UInt,
@ProtoNumber(3) val bytesPbReserve: ByteArray,
)
@Serializable
data class PicExtBizInfo(
@ProtoNumber(1) val bizType: UInt,
@ProtoNumber(2) val textSummary: String,
@ProtoNumber(11) val bytesPbReserveC2c: ByteArray? = null,
@ProtoNumber(12) val bytesPbReserveTroop: ByteArray? = null,
@ProtoNumber(1001) val fromScene: UInt? = null,
@ProtoNumber(1002) val toScene: UInt? = null,
@ProtoNumber(1003) val oldFileId: UInt? = null,
)
@Serializable
data class UploadInfo(
@ProtoNumber(1) val fileInfo: FileInfo,
@ProtoNumber(2) val subFileType: UInt
)
@Serializable
data class FileInfo(
@ProtoNumber(1) val fileSize: ULong,
@ProtoNumber(2) val md5: String,
@ProtoNumber(3) val sha1: String,
@ProtoNumber(4) val name: String,
@ProtoNumber(5) val fileType: FileType,
@ProtoNumber(6) val width: UInt,
@ProtoNumber(7) val height: UInt,
@ProtoNumber(8) val time: UInt,
@ProtoNumber(9) val original: UInt,
)
@Serializable
data class FileType(
@ProtoNumber(1) val fileType: UInt = 0u,
@ProtoNumber(2) val picFormat: UInt = 0u,
@ProtoNumber(3) val videoFormat: UInt? = null,
@ProtoNumber(4) val voiceFormat: UInt? = null,
)
@Serializable
data class DownloadReq(
@ProtoNumber(1) val index: IndexNode,
@ProtoNumber(2) val ext: DownloadExt,
)
@Serializable
data class DownloadExt(
@ProtoNumber(1) val pic: PicDownloadExt? = null,
@ProtoNumber(2) val video: VideoDownloadExt,
@ProtoNumber(3) val voice: PttDownloadExt? = null,
)
@Serializable
class PicDownloadExt
@Serializable
class PttDownloadExt
@Serializable
data class VideoDownloadExt(
@ProtoNumber(1) val busiType: UInt?,
@ProtoNumber(2) val sceneType: UInt? = null,
@ProtoNumber(3) val subBusiType: UInt?,
@ProtoNumber(4) val msgCodecConfig: CodecConfigReq,
@ProtoNumber(5) val flag: UInt?,
)
@Serializable
data class CodecConfigReq(
@ProtoNumber(1) val platformChipinfo: String,
@ProtoNumber(2) val osVer: String,
@ProtoNumber(3) val deviceName: String,
)
@Serializable
data class IndexNode(
@ProtoNumber(1) val fileInfo: FileInfo,
@ProtoNumber(2) val fileUuid: String,
@ProtoNumber(3) val storeId: UInt, // 0为旧服务器 1为nt服务器
@ProtoNumber(4) val uploadTime: ULong,
@ProtoNumber(5) val ttl: ULong,
@ProtoNumber(6) val subType: UInt,
@ProtoNumber(7) val storeAppId: UInt? = null
)
@Serializable
data class MultiMediaReqHead(
@ProtoNumber(1) val commonHead: CommonHead,
@ProtoNumber(2) val sceneInfo: SceneInfo,
@ProtoNumber(3) val clientMeta: ClientMeta
)
@Serializable
data class ClientMeta(
@ProtoNumber(1) val agentType: UInt,
)
@Serializable
data class SceneInfo(
@ProtoNumber(101) val requestType: UInt,
@ProtoNumber(102) val businessType: UInt,
@ProtoNumber(103) val appType: UInt? = null,
@ProtoNumber(200) var sceneType: UInt? = null,
@ProtoNumber(201) var c2c: C2CUserInfo? = null,
@ProtoNumber(202) var grp: GroupUserInfo? = null,
@ProtoNumber(203) var channel: ChannelUserInfo? = null,
@ProtoNumber(205) val byteArr: ByteArray?= null
)
@Serializable
data class ChannelUserInfo(
@ProtoNumber(1) val guildId: ULong,
@ProtoNumber(2) val channelId: ULong,
@ProtoNumber(3) val channelType: UInt,
)
@Serializable
data class GroupUserInfo(
@ProtoNumber(1) val uin: ULong,
)
@Serializable
data class C2CUserInfo(
@ProtoNumber(1) val accountType: UInt,
@ProtoNumber(2) val uid: String,
@ProtoNumber(3) val byteArr: ByteArray? = null
)
@Serializable
data class CommonHead(
@ProtoNumber(1) val requestId: ULong,
@ProtoNumber(2) val cmd: UInt,
@ProtoNumber(3) val msg: String? = null
)

View File

@ -0,0 +1,139 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.oidb.cmd0x11c5
import com.google.protobuf.Internal.EMPTY_BYTE_ARRAY
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable
data class NtV2RichMediaRsp(
@ProtoNumber(1) val head: RspHead,
@ProtoNumber(2) val upload: UploadRsp?,
@ProtoNumber(3) val download: DownloadRsp?,
@ProtoNumber(4) val downloadRkeyRsp: DownloadRkeyRsp?,
@ProtoNumber(5) val delete: DeleteRsp?,
@ProtoNumber(6) val uploadCompleted: UploadCompletedRsp?,
@ProtoNumber(7) val msgInfoAuth: MsgInfoAuthRsp?,
@ProtoNumber(8) val uploadKeyRenew: UploadKeyRenewalRsp?,
@ProtoNumber(9) val downloadSafe: DownloadSafeRsp?,
@ProtoNumber(99) val extension: ByteArray? = null,
): Protobuf<NtV2RichMediaRsp>
@Serializable
class DownloadSafeRsp
@Serializable
data class UploadKeyRenewalRsp(
@ProtoNumber(1) val ukey: String,
@ProtoNumber(2) val ukeyTtlSec: ULong,
)
@Serializable
data class MsgInfoAuthRsp(
@ProtoNumber(1) val authCode: UInt = 0u,
@ProtoNumber(2) val msg: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(3) val resultTime: ULong = 0u,
)
@Serializable
data class UploadCompletedRsp(
@ProtoNumber(1) val msgSeq: ULong
)
@Serializable
class DeleteRsp
@Serializable
data class DownloadRkeyRsp(
@ProtoNumber(1) val rkeys: List<RKeyInfo>
)
@Serializable
data class RKeyInfo(
@ProtoNumber(1) val rkey: String,
@ProtoNumber(2) val rkeyTtlSec: ULong,
@ProtoNumber(3) val storeId: UInt = 0u,
@ProtoNumber(4) val rkeyCreateTime: UInt?,
@ProtoNumber(4) val type: UInt?,
)
@Serializable
data class DownloadRsp(
@ProtoNumber(1) val rkeyParam: String,
@ProtoNumber(2) val rkeyTtlSec: ULong,
@ProtoNumber(3) val downloadInfo: DownloadInfo?,
@ProtoNumber(4) val rkeyCreateTime: UInt?
)
@Serializable
data class DownloadInfo(
@ProtoNumber(1) val domain: String,
@ProtoNumber(2) val urlPath: String? = null,
@ProtoNumber(3) val httpsPort: Int = Int.MIN_VALUE,
@ProtoNumber(4) val ipv4: List<Ipv4>,
@ProtoNumber(5) val ipv6: List<Ipv6>,
@ProtoNumber(6) val picUrlExtInfo: PicUrlExtInfo?,
@ProtoNumber(7) val videoExtInfo: VideoExtInfo? = null,
)
@Serializable
data class VideoExtInfo(
@ProtoNumber(1) val videoCodecFormat: UInt,
)
@Serializable
data class UploadRsp(
@ProtoNumber(1) val ukey: String,
@ProtoNumber(2) val ukeyTtlSec: ULong,
@ProtoNumber(3) val ipv4: List<Ipv4>,
@ProtoNumber(4) val ipv6: List<Ipv6>,
@ProtoNumber(5) val msgSeq: ULong,
@ProtoNumber(6) val msgInfo: MsgInfo? = null,
@ProtoNumber(7) val ext: List<RichmediaStorageTransInfo>? = null,
@ProtoNumber(8) val compatQMsg: ByteArray? = null,
@ProtoNumber(10) val subFileInfos: List<SubFileInfo>? = null,
)
@Serializable
data class SubFileInfo(
@ProtoNumber(1) val subType: UInt,
@ProtoNumber(2) val ukey: String,
@ProtoNumber(3) val ukeyTTLSec: ULong,
@ProtoNumber(4) val ipv4: List<Ipv4>,
@ProtoNumber(5) val ipv6: List<Ipv6>,
)
@Serializable
data class RichmediaStorageTransInfo(
@ProtoNumber(1) val subType: UInt = UInt.MIN_VALUE,
@ProtoNumber(2) val extType: UInt = UInt.MIN_VALUE,
@ProtoNumber(3) val extValue: ByteArray? = null,
)
@Serializable
data class Ipv4(
@ProtoNumber(1) val outIp: Int = Int.MIN_VALUE,
@ProtoNumber(2) val outPort: Int = Int.MIN_VALUE,
@ProtoNumber(3) val inIp: Int = Int.MIN_VALUE,
@ProtoNumber(4) val inPort: Int = Int.MIN_VALUE,
@ProtoNumber(5) val ipType: Int = Int.MIN_VALUE,
)
@Serializable
data class Ipv6(
@ProtoNumber(1) val outIp: ByteArray? = null,
@ProtoNumber(2) val outPort: Int = Int.MIN_VALUE,
@ProtoNumber(3) val inIp: ByteArray? = null,
@ProtoNumber(4) val inPort: Int = Int.MIN_VALUE,
@ProtoNumber(5) val ipType: Int = Int.MIN_VALUE,
)
@Serializable
data class RspHead(
@ProtoNumber(1) val commonHead: CommonHead,
@ProtoNumber(2) val retCode: UInt = 0u,
@ProtoNumber(3) val msg: String
)

View File

@ -5,6 +5,7 @@ package protobuf.oidb.cmd0x6d7
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
import protobuf.group_file_common.FolderInfo import protobuf.group_file_common.FolderInfo
@Serializable @Serializable
@ -13,7 +14,7 @@ data class Oidb0x6d7ReqBody(
@ProtoNumber(2) val deleteFolder: DeleteFolderReq? = null, @ProtoNumber(2) val deleteFolder: DeleteFolderReq? = null,
@ProtoNumber(3) val moveFolder: MoveFolderReq? = null, @ProtoNumber(3) val moveFolder: MoveFolderReq? = null,
@ProtoNumber(4) val renameFolder: RenameFolderReq? = null, @ProtoNumber(4) val renameFolder: RenameFolderReq? = null,
) ): Protobuf<Oidb0x6d7ReqBody>
@Serializable @Serializable
data class CreateFolderReq( data class CreateFolderReq(
@ -53,7 +54,7 @@ data class Oidb0x6d7RespBody(
@ProtoNumber(2) val deleteFolder: DeleteFolderResp? = null, @ProtoNumber(2) val deleteFolder: DeleteFolderResp? = null,
@ProtoNumber(3) val moveFolder: MoveFolderResp? = null, @ProtoNumber(3) val moveFolder: MoveFolderResp? = null,
@ProtoNumber(4) val renameFolder: RenameFolderResp? = null, @ProtoNumber(4) val renameFolder: RenameFolderResp? = null,
) ): Protobuf<Oidb0x6d7RespBody>
@Serializable @Serializable
data class CreateFolderResp( data class CreateFolderResp(

View File

@ -2,6 +2,7 @@ package protobuf.oidb.cmd0x9082
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class Oidb0x9082( data class Oidb0x9082(
@ -11,4 +12,4 @@ data class Oidb0x9082(
@ProtoNumber(5) val flag: UInt = UInt.MIN_VALUE, @ProtoNumber(5) val flag: UInt = UInt.MIN_VALUE,
@ProtoNumber(6) val u1: UInt = UInt.MIN_VALUE, @ProtoNumber(6) val u1: UInt = UInt.MIN_VALUE,
@ProtoNumber(7) val u2: UInt = UInt.MIN_VALUE, @ProtoNumber(7) val u2: UInt = UInt.MIN_VALUE,
) ): Protobuf<Oidb0x9082>

View File

@ -2,11 +2,12 @@ package protobuf.oidb.cmd0xf16
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class Oidb0xf16( data class Oidb0xf16(
@ProtoNumber(1) var setGroupRemarkReq: SetGroupRemarkReq? = null, @ProtoNumber(1) var setGroupRemarkReq: SetGroupRemarkReq? = null,
) ): Protobuf<Oidb0xf16>
@Serializable @Serializable
data class SetGroupRemarkReq( data class SetGroupRemarkReq(

View File

@ -5,6 +5,7 @@ package protobuf.oidb.cmd0xf88
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class Oidb0xf88Req( data class Oidb0xf88Req(
@ -12,12 +13,12 @@ data class Oidb0xf88Req(
@ProtoNumber(2) val memberId: ULong, @ProtoNumber(2) val memberId: ULong,
@ProtoNumber(3) val tinyId: ULong, @ProtoNumber(3) val tinyId: ULong,
@ProtoNumber(4) val guildId: ULong, @ProtoNumber(4) val guildId: ULong,
) ): Protobuf<Oidb0xf88Req>
@Serializable @Serializable
data class Oidb0xf88Rsp( data class Oidb0xf88Rsp(
@ProtoNumber(1) val userInfo: GProUserInfo? @ProtoNumber(1) val userInfo: GProUserInfo?
) ): Protobuf<Oidb0xf88Rsp>
@Serializable @Serializable
data class GProUserInfo( data class GProUserInfo(

View File

@ -4,6 +4,7 @@ package protobuf.oidb.cmd0xfc2
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class Oidb0xfc2ReqBody( data class Oidb0xfc2ReqBody(
@ -16,7 +17,7 @@ data class Oidb0xfc2ReqBody(
@ProtoNumber(300) var msgApplyDownloadReq: Oidb0xfc2MsgApplyDownloadReq? = null, @ProtoNumber(300) var msgApplyDownloadReq: Oidb0xfc2MsgApplyDownloadReq? = null,
//@ProtoNumber(400) var msg_apply_preview_req: Any? = null, //@ProtoNumber(400) var msg_apply_preview_req: Any? = null,
//@ProtoNumber(500) var msg_apply_security_strike_req: Any? = null, //@ProtoNumber(500) var msg_apply_security_strike_req: Any? = null,
) ): Protobuf<Oidb0xfc2ReqBody>
@Serializable @Serializable
data class Oidb0xfc2RspBody( data class Oidb0xfc2RspBody(
@ -27,7 +28,7 @@ data class Oidb0xfc2RspBody(
@ProtoNumber(310) var msgApplyDownloadRsp: Oidb0xfc2MsgApplyDownloadRsp? = null, @ProtoNumber(310) var msgApplyDownloadRsp: Oidb0xfc2MsgApplyDownloadRsp? = null,
//@ProtoNumber(410) var msg_apply_preview_rsp: Any? = null, //@ProtoNumber(410) var msg_apply_preview_rsp: Any? = null,
//@ProtoNumber(510) var msg_apply_security_strike_rsp: Any? = null, //@ProtoNumber(510) var msg_apply_security_strike_rsp: Any? = null,
) ): Protobuf<Oidb0xfc2RspBody>
@Serializable @Serializable
data class Oidb0xfc2MsgApplyDownloadRsp( data class Oidb0xfc2MsgApplyDownloadRsp(

View File

@ -5,17 +5,18 @@ package protobuf.oidb.cmx0xf57
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class Oidb0xf57Req( data class Oidb0xf57Req(
@ProtoNumber(1) val filter: Oidb0xf57Filter, @ProtoNumber(1) val filter: Oidb0xf57Filter,
@ProtoNumber(2) val guildInfo: Oidb0xf57GuildInfo, @ProtoNumber(2) val guildInfo: Oidb0xf57GuildInfo,
) ): Protobuf<Oidb0xf57Req>
@Serializable @Serializable
data class Oidb0xf57Rsp( data class Oidb0xf57Rsp(
@ProtoNumber(1) val metaInfo: Oidb0xf57MetaInfo, @ProtoNumber(1) val metaInfo: Oidb0xf57MetaInfo,
) ): Protobuf<Oidb0xf57Rsp>
@Serializable @Serializable
data class Oidb0xf57MetaInfo( data class Oidb0xf57MetaInfo(

View File

@ -2,12 +2,13 @@ package protobuf.push
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class C2CCommonTipsEvent( data class C2CCommonTipsEvent(
@ProtoNumber(7) val params: List<PokeParam>? = null, @ProtoNumber(7) val params: List<PokeParam>? = null,
@ProtoNumber(8) val xmlTips: String? = null, @ProtoNumber(8) val xmlTips: String? = null,
) ): Protobuf<C2CCommonTipsEvent>
@Serializable @Serializable
data class PokeParam( data class PokeParam(

View File

@ -2,11 +2,12 @@ package protobuf.push
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class C2CRecallEvent( data class C2CRecallEvent(
@ProtoNumber(1) val head: C2CRecallHead? = null, @ProtoNumber(1) val head: C2CRecallHead? = null,
) ): Protobuf<C2CRecallEvent>
@Serializable @Serializable
data class C2CRecallHead( data class C2CRecallHead(

View File

@ -2,11 +2,12 @@ package protobuf.push
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class FriendApplyEvent( data class FriendApplyEvent(
@ProtoNumber(1) val head: FriendApplyHead? = null, @ProtoNumber(1) val head: FriendApplyHead? = null,
) ): Protobuf<FriendApplyEvent>
@Serializable @Serializable

View File

@ -2,10 +2,11 @@ package protobuf.push
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class GroupAdminChangeEvent( data class GroupAdminChangeEvent(
@ProtoNumber(1) val groupCode: Long, @ProtoNumber(1) val groupCode: Long,
@ProtoNumber(4) val operation: GroupAdminChangedOperation? = null @ProtoNumber(4) val operation: GroupAdminChangedOperation? = null
) ): Protobuf<GroupAdminChangeEvent>

View File

@ -2,10 +2,11 @@ package protobuf.push
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class GroupApplyEvent( data class GroupApplyEvent(
@ProtoNumber(1) val groupCode: Long = Long.MIN_VALUE, @ProtoNumber(1) val groupCode: Long = Long.MIN_VALUE,
@ProtoNumber(3) val applierUid: String = "", @ProtoNumber(3) val applierUid: String = "",
@ProtoNumber(5) val applyMsg: String? = null, @ProtoNumber(5) val applyMsg: String? = null,
) ): Protobuf<GroupApplyEvent>

View File

@ -2,13 +2,14 @@ package protobuf.push
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class GroupBanEvent( data class GroupBanEvent(
@ProtoNumber(1) val groupCode: ULong = ULong.MIN_VALUE, @ProtoNumber(1) val groupCode: ULong = ULong.MIN_VALUE,
@ProtoNumber(4) val operatorUid: String = "", @ProtoNumber(4) val operatorUid: String = "",
@ProtoNumber(5) val target: GroupBanTarget? = null, @ProtoNumber(5) val target: GroupBanTarget? = null,
) ): Protobuf<GroupBanEvent>
@Serializable @Serializable
data class GroupBanTarget( data class GroupBanTarget(

View File

@ -2,6 +2,7 @@ package protobuf.push
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class GroupCommonTipsEvent( data class GroupCommonTipsEvent(
@ -11,7 +12,7 @@ data class GroupCommonTipsEvent(
@ProtoNumber(26) val baseTips: List<GroupBaseTips>? = null, @ProtoNumber(26) val baseTips: List<GroupBaseTips>? = null,
@ProtoNumber(33) val essenceMsgInfo: List<EssenceMsgInfo>? = null, @ProtoNumber(33) val essenceMsgInfo: List<EssenceMsgInfo>? = null,
@ProtoNumber(37) val msgSeq: ULong = ULong.MIN_VALUE, @ProtoNumber(37) val msgSeq: ULong = ULong.MIN_VALUE,
) ): Protobuf<GroupCommonTipsEvent>
@Serializable @Serializable
data class EssenceMsgInfo( data class EssenceMsgInfo(

View File

@ -2,9 +2,10 @@ package protobuf.push
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class GroupInviteEvent( data class GroupInviteEvent(
@ProtoNumber(1) val groupCode: Long, @ProtoNumber(1) val groupCode: Long,
@ProtoNumber(5) val inviterUid: String, @ProtoNumber(5) val inviterUid: String,
) ): Protobuf<GroupInviteEvent>

View File

@ -2,11 +2,12 @@ package protobuf.push
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class GroupInvitedApplyEvent( data class GroupInvitedApplyEvent(
@ProtoNumber(2) val applyInfo: GroupInvitedApplyInfo? = null, @ProtoNumber(2) val applyInfo: GroupInvitedApplyInfo? = null,
) ): Protobuf<GroupInvitedApplyEvent>
@Serializable @Serializable
data class GroupInvitedApplyInfo( data class GroupInvitedApplyInfo(

View File

@ -2,6 +2,7 @@ package protobuf.push
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class GroupListChangeEvent( data class GroupListChangeEvent(
@ -9,4 +10,4 @@ data class GroupListChangeEvent(
@ProtoNumber(3) val memberUid: String = "", @ProtoNumber(3) val memberUid: String = "",
@ProtoNumber(4) val type: Int = Int.MIN_VALUE, @ProtoNumber(4) val type: Int = Int.MIN_VALUE,
@ProtoNumber(5) val operatorUid: String = "", @ProtoNumber(5) val operatorUid: String = "",
) ): Protobuf<GroupListChangeEvent>

View File

@ -2,13 +2,14 @@ package protobuf.push
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import protobuf.message.MessageBody import moe.fuqiuluo.symbols.Protobuf
import protobuf.message.NtMessage
@Serializable @Serializable
data class MessagePush( data class MessagePush(
@ProtoNumber(1) val msgBody: MessageBody? = null, @ProtoNumber(1) val msgBody: NtMessage? = null,
@ProtoNumber(4) val clientInfo: MessagePushClientInfo? = null, @ProtoNumber(4) val clientInfo: MessagePushClientInfo? = null,
) ): Protobuf<MessagePush>
@Serializable @Serializable
data class MessagePushClientInfo( data class MessagePushClientInfo(

View File

@ -5,6 +5,7 @@ package protobuf.qweb
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class QWebReq( data class QWebReq(
@ -19,7 +20,7 @@ data class QWebReq(
//@ProtoNumber(9) var Crypto: Any? = null, //@ProtoNumber(9) var Crypto: Any? = null,
@ProtoNumber(10) var extinfo: List<QWebExtInfo>? = null, @ProtoNumber(10) var extinfo: List<QWebExtInfo>? = null,
//@ProtoNumber(11) var contentType: Any? = null, //@ProtoNumber(11) var contentType: Any? = null,
) ): Protobuf<QWebReq>
@Serializable @Serializable
data class QWebExtInfo( data class QWebExtInfo(
@ -34,4 +35,4 @@ data class QWebRsp(
//@ProtoNumber(3) var errMsg: String? = null, //@ProtoNumber(3) var errMsg: String? = null,
@ProtoNumber(4) var buffer: ByteArray? = null, @ProtoNumber(4) var buffer: ByteArray? = null,
//@ProtoNumber(5) var Extinfo: List<QWebExtInfo>? = null, //@ProtoNumber(5) var Extinfo: List<QWebExtInfo>? = null,
) ): Protobuf<QWebRsp>

View File

@ -0,0 +1,19 @@
package com.tencent.guild.api.transfile;
import androidx.annotation.Nullable;
import com.tencent.mobileqq.qroute.QRouteApi;
import com.tencent.qqnt.kernel.nativeinterface.BigDataTicket;
public interface IGuildTransFileApi extends QRouteApi {
//@Nullable
//ArrayList<ServerAddress> getBigDataIpList(boolean z, @Nullable IpType ipType);
@Nullable
BigDataTicket getBigDataTicket();
//@Nullable
//ArrayList<ServerAddress> getIpDirectList(@Nullable String str, @Nullable IpType ipType);
void pullConfigIfNeed();
}

View File

@ -0,0 +1,26 @@
package com.tencent.libra.download;
import androidx.annotation.NonNull;
import com.tencent.libra.request.Option;
public interface ILibraDownloader {
class PicDownLoadListener {
Option mOption;
public PicDownLoadListener(@NonNull Option option) {
this.mOption = option;
}
public void onResult(boolean success, int code) {
}
}
boolean canDownload(Option option);
void cancel(Option option);
void downLoad(Option option, PicDownLoadListener picDownLoadListener);
boolean needDownloadOnWorkThread();
}

View File

@ -0,0 +1,4 @@
package com.tencent.libra.request;
public class Option {
}

View File

@ -0,0 +1,29 @@
package com.tencent.mobileqq.perf.block;
import android.os.Bundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import epic.EIPCClient;
import epic.EIPCResult;
import kotlin.Metadata;
import kotlin.jvm.JvmStatic;
public final class BinderMethodProxy {
@NotNull
public static final BinderMethodProxy INSTANCE;
static {
INSTANCE = new BinderMethodProxy();
}
@JvmStatic
public static EIPCResult callServer(@NotNull EIPCClient client, @Nullable String module, @Nullable String action, @Nullable Bundle bundle) {
//MainBlockMethodMonitor.onMethodStart();
//EIPCResult callServer = client.callServer(str, str2, bundle);
//MainBlockMethodMonitor.onMethodEnd();
//return callServer;
return null;
}
}

View File

@ -0,0 +1,13 @@
package com.tencent.mobileqq.qipc;
import epic.EIPCClient;
public class QIPCClientHelper {
public static synchronized QIPCClientHelper getInstance() {
return null;
}
public EIPCClient getClient() {
return null;
}
}

View File

@ -0,0 +1,7 @@
package com.tencent.mobileqq.qmmkv;
public class MMKVOptionEntity {
public String decodeString(String str, String str2) {
return "";
}
}

View File

@ -0,0 +1,9 @@
package com.tencent.mobileqq.qmmkv;
import android.content.Context;
public class QMMKV {
public static MMKVOptionEntity from(Context context, String str) {
return null;
}
}

View File

@ -0,0 +1,11 @@
package com.tencent.qqnt.aio.api;
import com.tencent.libra.download.ILibraDownloader;
import com.tencent.mobileqq.qroute.QRouteApi;
import org.jetbrains.annotations.NotNull;
public interface IAIOPicDownloaderProvider extends QRouteApi {
@NotNull
ILibraDownloader provideDownloader();
}

View File

@ -1,7 +1,9 @@
package com.tencent.qqnt.kernel.api.impl; package com.tencent.qqnt.kernel.api.impl;
import com.tencent.qqnt.kernel.nativeinterface.IGetTempChatInfoCallback;
import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgListener; import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgListener;
import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback; import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback;
import com.tencent.qqnt.kernel.nativeinterface.RichMediaElementGetReq;
import com.tencent.qqnt.kernel.nativeinterface.RichMediaFilePathInfo; import com.tencent.qqnt.kernel.nativeinterface.RichMediaFilePathInfo;
import com.tencent.qqnt.kernel.nativeinterface.TempChatPrepareInfo; import com.tencent.qqnt.kernel.nativeinterface.TempChatPrepareInfo;
@ -9,6 +11,10 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public class MsgService { public class MsgService {
void getRichMediaElement(@NotNull RichMediaElementGetReq req) {
}
public void addMsgListener(IKernelMsgListener listener) { public void addMsgListener(IKernelMsgListener listener) {
} }
@ -24,4 +30,8 @@ public class MsgService {
public void prepareTempChat(TempChatPrepareInfo tempChatPrepareInfo, IOperateCallback cb) { public void prepareTempChat(TempChatPrepareInfo tempChatPrepareInfo, IOperateCallback cb) {
} }
public void getTempChatInfo(int chatType, @Nullable String uid, @Nullable IGetTempChatInfoCallback cb) {
}
} }

View File

@ -0,0 +1,30 @@
package com.tencent.qqnt.kernel.nativeinterface;
public final class BigDataTicket {
public String sessionKey;
public String sessionSig;
public BigDataTicket() {
this.sessionSig = "";
this.sessionKey = "";
}
public String getSessionKey() {
return this.sessionKey;
}
public String getSessionSig() {
return this.sessionSig;
}
public String toString() {
return "BigDataTicket{sessionSig=" + this.sessionSig + ",sessionKey=" + this.sessionKey + ",}";
}
public BigDataTicket(String str, String str2) {
this.sessionSig = "";
this.sessionKey = "";
this.sessionSig = str;
this.sessionKey = str2;
}
}

View File

@ -0,0 +1,5 @@
package com.tencent.qqnt.kernel.nativeinterface;
public interface IGetTempChatInfoCallback {
void onResult(int code, String msg, TempChatInfo info);
}

View File

@ -79,6 +79,10 @@ public final class InlineKeyboardButton {
return "InlineKeyboardButton{id=" + this.id + ",label=" + this.label + ",visitedLabel=" + this.visitedLabel + ",style=" + this.style + ",type=" + this.type + ",clickLimit=" + this.clickLimit + ",unsupportTips=" + this.unsupportTips + ",data=" + this.data + ",atBotShowChannelList=" + this.atBotShowChannelList + ",permissionType=" + this.permissionType + ",specifyRoleIds=" + this.specifyRoleIds + ",specifyTinyids=" + this.specifyTinyids + ",}"; return "InlineKeyboardButton{id=" + this.id + ",label=" + this.label + ",visitedLabel=" + this.visitedLabel + ",style=" + this.style + ",type=" + this.type + ",clickLimit=" + this.clickLimit + ",unsupportTips=" + this.unsupportTips + ",data=" + this.data + ",atBotShowChannelList=" + this.atBotShowChannelList + ",permissionType=" + this.permissionType + ",specifyRoleIds=" + this.specifyRoleIds + ",specifyTinyids=" + this.specifyTinyids + ",}";
} }
public InlineKeyboardButton(String str, String str2, String str3, int i, int i2, int i3, String str4, String str5, boolean z, int i4, ArrayList<String> arrayList, ArrayList<String> arrayList2, boolean z2, int i5, boolean z3, ArrayList<SubscribeMsgTemplateID> arrayList3) {
}
public InlineKeyboardButton(String str, String str2, String str3, int i2, int i3, int i4, String str4, String str5, boolean z, int i5, ArrayList<String> arrayList, ArrayList<String> arrayList2) { public InlineKeyboardButton(String str, String str2, String str3, int i2, int i3, int i4, String str4, String str5, boolean z, int i5, ArrayList<String> arrayList, ArrayList<String> arrayList2) {
this.id = ""; this.id = "";
this.label = ""; this.label = "";

View File

@ -264,6 +264,10 @@ public final class PicElement implements IKernelModel {
this.transferStatus = num; this.transferStatus = num;
} }
public int getStoreID() {
return 0;
}
public String toString() { public String toString() {
return "PicElement{picSubType=" + this.picSubType + ",fileName=" + this.fileName + ",fileSize=" + this.fileSize + ",picWidth=" + this.picWidth + ",picHeight=" + this.picHeight + ",original=" + this.original + ",md5HexStr=" + this.md5HexStr + ",sourcePath=" + this.sourcePath + ",thumbPath=" + this.thumbPath + ",transferStatus=" + this.transferStatus + ",progress=" + this.progress + ",picType=" + this.picType + ",invalidState=" + this.invalidState + ",fileUuid=" + this.fileUuid + ",fileSubId=" + this.fileSubId + ",thumbFileSize=" + this.thumbFileSize + ",fileBizId=" + this.fileBizId + ",downloadIndex=" + this.downloadIndex + ",summary=" + this.summary + ",emojiFrom=" + this.emojiFrom + ",emojiWebUrl=" + this.emojiWebUrl + ",emojiAd=" + this.emojiAd + ",emojiMall=" + this.emojiMall + ",emojiZplan=" + this.emojiZplan + ",originImageMd5=" + this.originImageMd5 + ",originImageUrl=" + this.originImageUrl + ",importRichMediaContext=" + this.importRichMediaContext + ",isFlashPic=" + this.isFlashPic + ",}"; return "PicElement{picSubType=" + this.picSubType + ",fileName=" + this.fileName + ",fileSize=" + this.fileSize + ",picWidth=" + this.picWidth + ",picHeight=" + this.picHeight + ",original=" + this.original + ",md5HexStr=" + this.md5HexStr + ",sourcePath=" + this.sourcePath + ",thumbPath=" + this.thumbPath + ",transferStatus=" + this.transferStatus + ",progress=" + this.progress + ",picType=" + this.picType + ",invalidState=" + this.invalidState + ",fileUuid=" + this.fileUuid + ",fileSubId=" + this.fileSubId + ",thumbFileSize=" + this.thumbFileSize + ",fileBizId=" + this.fileBizId + ",downloadIndex=" + this.downloadIndex + ",summary=" + this.summary + ",emojiFrom=" + this.emojiFrom + ",emojiWebUrl=" + this.emojiWebUrl + ",emojiAd=" + this.emojiAd + ",emojiMall=" + this.emojiMall + ",emojiZplan=" + this.emojiZplan + ",originImageMd5=" + this.originImageMd5 + ",originImageUrl=" + this.originImageUrl + ",importRichMediaContext=" + this.importRichMediaContext + ",isFlashPic=" + this.isFlashPic + ",}";
} }

View File

@ -0,0 +1,118 @@
package com.tencent.qqnt.kernel.nativeinterface;
public final class RichMediaElementGetReq implements IKernelModel {
public int chatType;
public int downSourceType;
public int downloadType;
public long elementId;
public long fileModelId;
public String filePath;
public long msgId;
public String peerUid;
public int thumbSize;
public int triggerType;
public RichMediaElementGetReq() {
this.peerUid = "";
this.filePath = "";
}
public int getChatType() {
return this.chatType;
}
public int getDownSourceType() {
return this.downSourceType;
}
public int getDownloadType() {
return this.downloadType;
}
public long getElementId() {
return this.elementId;
}
public long getFileModelId() {
return this.fileModelId;
}
public String getFilePath() {
return this.filePath;
}
public long getMsgId() {
return this.msgId;
}
public String getPeerUid() {
return this.peerUid;
}
public int getThumbSize() {
return this.thumbSize;
}
public int getTriggerType() {
return this.triggerType;
}
public void setChatType(int i2) {
this.chatType = i2;
}
public void setDownSourceType(int i2) {
this.downSourceType = i2;
}
public void setDownloadType(int i2) {
this.downloadType = i2;
}
public void setElementId(long j2) {
this.elementId = j2;
}
public void setFileModelId(long j2) {
this.fileModelId = j2;
}
public void setFilePath(String str) {
this.filePath = str;
}
public void setMsgId(long j2) {
this.msgId = j2;
}
public void setPeerUid(String str) {
this.peerUid = str;
}
public void setThumbSize(int i2) {
this.thumbSize = i2;
}
public void setTriggerType(int i2) {
this.triggerType = i2;
}
public String toString() {
return "RichMediaElementGetReq{msgId=" + this.msgId + ",peerUid=" + this.peerUid + ",chatType=" + this.chatType + ",elementId=" + this.elementId + ",downloadType=" + this.downloadType + ",thumbSize=" + this.thumbSize + ",filePath=" + this.filePath + ",fileModelId=" + this.fileModelId + ",downSourceType=" + this.downSourceType + ",triggerType=" + this.triggerType + ",}";
}
public RichMediaElementGetReq(long j2, String str, int i2, long j3, int i3, int i4, String str2, long j4, int i5, int i6) {
this.peerUid = "";
this.filePath = "";
this.msgId = j2;
this.peerUid = str;
this.chatType = i2;
this.elementId = j3;
this.downloadType = i3;
this.thumbSize = i4;
this.filePath = str2;
this.fileModelId = j4;
this.downSourceType = i5;
this.triggerType = i6;
}
}

View File

@ -0,0 +1,4 @@
package com.tencent.qqnt.kernel.nativeinterface;
public class SubscribeMsgTemplateID {
}

View File

@ -0,0 +1,4 @@
package epic;
public class EIPCClient {
}

View File

@ -0,0 +1,11 @@
package epic;
import android.os.Bundle;
public class EIPCResult {
public Bundle data;
public boolean isSuccess() {
return false;
}
}

View File

@ -31,10 +31,24 @@ public class oidb_cmd0xb77 {
// public final PBUInt32Field service_id = PBField.initUInt32(0); // public final PBUInt32Field service_id = PBField.initUInt32(0);
// public final PBStringField xml = PBField.initString(""); // public final PBStringField xml = PBField.initString("");
//}; //};
//public oidb_cmd0xb77$MiniAppMsgBody mini_app_msg_body = new oidb_cmd0xb77$MiniAppMsgBody(); public MiniAppMsgBody mini_app_msg_body = new MiniAppMsgBody();
public final PBUInt64Field recv_guild_id = PBField.initUInt64(0); public final PBUInt64Field recv_guild_id = PBField.initUInt64(0);
} }
public static class MiniAppMsgBody extends MessageMicro<MiniAppMsgBody> {
//static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(
// new int[]{8, 18, 26, 32, 42, 50, 82}, new String[]{"mini_app_appid", "mini_app_path", "web_page_url", "mini_app_type", "title", "desc", "json_str"}
//, new Object[]{0L, "", "", 0, "", "", ""}, oidb_cmd0xb77$MiniAppMsgBody.class);
public final PBUInt64Field mini_app_appid = PBField.initUInt64(0);
public final PBStringField mini_app_path = PBField.initString("");
public final PBStringField web_page_url = PBField.initString("");
public final PBUInt32Field mini_app_type = PBField.initUInt32(0);
public final PBStringField title = PBField.initString("");
public final PBStringField desc = PBField.initString("");
public final PBStringField json_str = PBField.initString("");
}
public static class ArkMsgBody extends MessageMicro<ArkMsgBody> { public static class ArkMsgBody extends MessageMicro<ArkMsgBody> {
public final PBStringField app = PBField.initString(""); public final PBStringField app = PBField.initString("");
public final PBStringField view = PBField.initString(""); public final PBStringField view = PBField.initString("");

View File

@ -11,14 +11,15 @@ import io.ktor.utils.io.core.BytePacketBuilder
import io.ktor.utils.io.core.readBytes import io.ktor.utils.io.core.readBytes
import io.ktor.utils.io.core.writeFully import io.ktor.utils.io.core.writeFully
import io.ktor.utils.io.core.writeInt import io.ktor.utils.io.core.writeInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.encodeToByteArray import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.utils.DeflateTools import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.shamrock.utils.PlatformUtils import moe.fuqiuluo.shamrock.utils.PlatformUtils
@ -27,11 +28,13 @@ import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver
import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest
import protobuf.oidb.TrpcOidb import protobuf.oidb.TrpcOidb
import mqq.app.MobileQQ import mqq.app.MobileQQ
import protobuf.auto.toByteArray
import tencent.im.oidb.oidb_sso import tencent.im.oidb.oidb_sso
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume import kotlin.coroutines.resume
internal abstract class BaseSvc { internal abstract class BaseSvc {
companion object { companion object Default: CoroutineScope {
val currentUin: String val currentUin: String
get() = app.currentAccountUin get() = app.currentAccountUin
@ -46,7 +49,7 @@ internal abstract class BaseSvc {
val seq = MsfCore.getNextSeq() val seq = MsfCore.getNextSeq()
val buffer = withTimeoutOrNull(timeout) { val buffer = withTimeoutOrNull(timeout) {
suspendCancellableCoroutine { continuation -> suspendCancellableCoroutine { continuation ->
GlobalScope.launch(Dispatchers.Default) { launch(Dispatchers.Default) {
DynamicReceiver.register(IPCRequest(cmd, seq) { DynamicReceiver.register(IPCRequest(cmd, seq) {
val buffer = it.getByteArrayExtra("buffer")!! val buffer = it.getByteArrayExtra("buffer")!!
continuation.resume(buffer) continuation.resume(buffer)
@ -75,7 +78,7 @@ internal abstract class BaseSvc {
val seq = MsfCore.getNextSeq() val seq = MsfCore.getNextSeq()
val buffer = withTimeoutOrNull<ByteArray?>(timeout) { val buffer = withTimeoutOrNull<ByteArray?>(timeout) {
suspendCancellableCoroutine { continuation -> suspendCancellableCoroutine { continuation ->
GlobalScope.launch(Dispatchers.Default) { launch(Dispatchers.Default) {
DynamicReceiver.register(IPCRequest(cmd, seq) { DynamicReceiver.register(IPCRequest(cmd, seq) {
val buffer = it.getByteArrayExtra("buffer")!! val buffer = it.getByteArrayExtra("buffer")!!
continuation.resume(buffer) continuation.resume(buffer)
@ -125,9 +128,9 @@ internal abstract class BaseSvc {
cmd = cmdId, cmd = cmdId,
service = serviceId, service = serviceId,
buffer = buffer, buffer = buffer,
flag = 0 flag = 1
) )
to.putWupBuffer(ProtoBuf.encodeToByteArray(oidb)) to.putWupBuffer(oidb.toByteArray())
to.addAttribute("req_pb_protocol_flag", true) to.addAttribute("req_pb_protocol_flag", true)
if (seq != -1) { if (seq != -1) {
@ -143,6 +146,11 @@ internal abstract class BaseSvc {
toServiceMsg.addAttribute("shamrock_seq", seq) toServiceMsg.addAttribute("shamrock_seq", seq)
app.sendToService(toServiceMsg) app.sendToService(toServiceMsg)
} }
@OptIn(ExperimentalCoroutinesApi::class)
override val coroutineContext: CoroutineContext by lazy {
Dispatchers.IO.limitedParallelism(12)
}
} }
protected fun send(toServiceMsg: ToServiceMsg) { protected fun send(toServiceMsg: ToServiceMsg) {
@ -153,7 +161,7 @@ internal abstract class BaseSvc {
val seq = MsfCore.getNextSeq() val seq = MsfCore.getNextSeq()
val buffer = withTimeoutOrNull<ByteArray?>(timeout) { val buffer = withTimeoutOrNull<ByteArray?>(timeout) {
suspendCancellableCoroutine { continuation -> suspendCancellableCoroutine { continuation ->
GlobalScope.launch(Dispatchers.Default) { launch(Dispatchers.Default) {
DynamicReceiver.register(IPCRequest(toServiceMsg.serviceCmd, seq) { DynamicReceiver.register(IPCRequest(toServiceMsg.serviceCmd, seq) {
val buffer = it.getByteArrayExtra("buffer")!! val buffer = it.getByteArrayExtra("buffer")!!
continuation.resume(buffer) continuation.resume(buffer)

View File

@ -1,21 +1,20 @@
package moe.fuqiuluo.qqinterface.servlet package moe.fuqiuluo.qqinterface.servlet
import kotlinx.serialization.encodeToByteArray import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf import protobuf.auto.toByteArray
import protobuf.oidb.cmd0x9082.Oidb0x9082 import protobuf.oidb.cmd0x9082.Oidb0x9082
internal object ChatSvc: BaseSvc() { internal object ChatSvc: BaseSvc() {
fun setGroupMessageCommentFace(peer: Long, msgSeq: ULong, faceIndex: String, isSet: Boolean) { fun setGroupMessageCommentFace(peer: Long, msgSeq: ULong, faceIndex: String, isSet: Boolean) {
val serviceId = if (isSet) 1 else 2 val serviceId = if (isSet) 1 else 2
sendOidb("OidbSvcTrpcTcp.0x9082_$serviceId", 36994, serviceId, ProtoBuf.encodeToByteArray( sendOidb("OidbSvcTrpcTcp.0x9082_$serviceId", 36994, serviceId, Oidb0x9082(
Oidb0x9082(
peer = peer.toULong(), peer = peer.toULong(),
msgSeq = msgSeq, msgSeq = msgSeq,
faceIndex = faceIndex, faceIndex = faceIndex,
flag = 1u, flag = 1u,
u1 = 0u, u1 = 0u,
u2 = 0u u2 = 0u
) ).toByteArray())
))
} }
} }

View File

@ -1,9 +1,6 @@
package moe.fuqiuluo.qqinterface.servlet package moe.fuqiuluo.qqinterface.servlet
import com.tencent.mobileqq.pb.ByteStringMicro import com.tencent.mobileqq.pb.ByteStringMicro
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import moe.fuqiuluo.qqinterface.servlet.structures.* import moe.fuqiuluo.qqinterface.servlet.structures.*
import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
@ -12,6 +9,8 @@ import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.tools.toHexString import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.symbols.decode
import moe.fuqiuluo.symbols.decodeProtobuf
import protobuf.oidb.cmd0x6d7.CreateFolderReq import protobuf.oidb.cmd0x6d7.CreateFolderReq
import protobuf.oidb.cmd0x6d7.DeleteFolderReq import protobuf.oidb.cmd0x6d7.DeleteFolderReq
import protobuf.oidb.cmd0x6d7.MoveFolderReq import protobuf.oidb.cmd0x6d7.MoveFolderReq
@ -22,24 +21,25 @@ import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
import tencent.im.oidb.cmd0x6d8.oidb_0x6d8 import tencent.im.oidb.cmd0x6d8.oidb_0x6d8
import tencent.im.oidb.oidb_sso import tencent.im.oidb.oidb_sso
import protobuf.group_file_common.FolderInfo as GroupFileCommonFolderInfo import protobuf.group_file_common.FolderInfo as GroupFileCommonFolderInfo
import protobuf.auto.toByteArray
internal object FileSvc: BaseSvc() { internal object FileSvc: BaseSvc() {
suspend fun createFileFolder(groupId: String, folderName: String, parentFolderId: String = "/"): Result<GroupFileCommonFolderInfo> { suspend fun createFileFolder(groupId: String, folderName: String, parentFolderId: String = "/"): Result<GroupFileCommonFolderInfo> {
val data = ProtoBuf.encodeToByteArray( val data = Oidb0x6d7ReqBody(
Oidb0x6d7ReqBody(
createFolder = CreateFolderReq( createFolder = CreateFolderReq(
groupCode = groupId.toULong(), groupCode = groupId.toULong(),
appId = 3u, appId = 3u,
parentFolderId = parentFolderId, parentFolderId = parentFolderId,
folderName = folderName folderName = folderName
) )
) ).toByteArray()
)
val resultBuffer = sendOidbAW("OidbSvc.0x6d7_0", 1751, 0, data) val resultBuffer = sendOidbAW("OidbSvc.0x6d7_0", 1751, 0, data)
?: return Result.failure(Exception("unable to fetch result")) ?: return Result.failure(Exception("unable to fetch result"))
val oidbPkg = oidb_sso.OIDBSSOPkg() val oidbPkg = oidb_sso.OIDBSSOPkg()
oidbPkg.mergeFrom(resultBuffer.slice(4)) oidbPkg.mergeFrom(resultBuffer.slice(4))
val rsp = ProtoBuf.decodeFromByteArray<Oidb0x6d7RespBody>(oidbPkg.bytes_bodybuffer.get().toByteArray()) val rsp = oidbPkg.bytes_bodybuffer.get()
.toByteArray()
.decodeProtobuf<Oidb0x6d7RespBody>()
if (rsp.createFolder?.retCode != 0) { if (rsp.createFolder?.retCode != 0) {
return Result.failure(Exception("unable to create folder: ${rsp.createFolder?.retCode}")) return Result.failure(Exception("unable to create folder: ${rsp.createFolder?.retCode}"))
} }
@ -47,52 +47,46 @@ internal object FileSvc: BaseSvc() {
} }
suspend fun deleteGroupFolder(groupId: String, folderUid: String): Boolean { suspend fun deleteGroupFolder(groupId: String, folderUid: String): Boolean {
val buffer = sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, ProtoBuf.encodeToByteArray( val buffer = sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody(
Oidb0x6d7ReqBody(
deleteFolder = DeleteFolderReq( deleteFolder = DeleteFolderReq(
groupCode = groupId.toULong(), groupCode = groupId.toULong(),
appId = 3u, appId = 3u,
folderId = folderUid folderId = folderUid
) )
) ).toByteArray()) ?: return false
)) ?: return false
val oidbPkg = oidb_sso.OIDBSSOPkg() val oidbPkg = oidb_sso.OIDBSSOPkg()
oidbPkg.mergeFrom(buffer.slice(4)) oidbPkg.mergeFrom(buffer.slice(4))
val rsp = ProtoBuf.decodeFromByteArray<Oidb0x6d7RespBody>(oidbPkg.bytes_bodybuffer.get().toByteArray()) val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
return rsp.deleteFolder?.retCode == 0 return rsp.deleteFolder?.retCode == 0
} }
suspend fun moveGroupFolder(groupId: String, folderUid: String, newParentFolderUid: String): Boolean { suspend fun moveGroupFolder(groupId: String, folderUid: String, newParentFolderUid: String): Boolean {
val buffer = sendOidbAW("OidbSvc.0x6d7_2", 1751, 2, ProtoBuf.encodeToByteArray( val buffer = sendOidbAW("OidbSvc.0x6d7_2", 1751, 2, Oidb0x6d7ReqBody(
Oidb0x6d7ReqBody(
moveFolder = MoveFolderReq( moveFolder = MoveFolderReq(
groupCode = groupId.toULong(), groupCode = groupId.toULong(),
appId = 3u, appId = 3u,
folderId = folderUid, folderId = folderUid,
parentFolderId = "/" parentFolderId = "/"
) )
) ).toByteArray()) ?: return false
)) ?: return false
val oidbPkg = oidb_sso.OIDBSSOPkg() val oidbPkg = oidb_sso.OIDBSSOPkg()
oidbPkg.mergeFrom(buffer.slice(4)) oidbPkg.mergeFrom(buffer.slice(4))
val rsp = ProtoBuf.decodeFromByteArray<Oidb0x6d7RespBody>(oidbPkg.bytes_bodybuffer.get().toByteArray()) val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
return rsp.moveFolder?.retCode == 0 return rsp.moveFolder?.retCode == 0
} }
suspend fun renameFolder(groupId: String, folderUid: String, name: String): Boolean { suspend fun renameFolder(groupId: String, folderUid: String, name: String): Boolean {
val buffer = sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, ProtoBuf.encodeToByteArray( val buffer = sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody(
Oidb0x6d7ReqBody(
renameFolder = RenameFolderReq( renameFolder = RenameFolderReq(
groupCode = groupId.toULong(), groupCode = groupId.toULong(),
appId = 3u, appId = 3u,
folderId = folderUid, folderId = folderUid,
folderName = name folderName = name
) )
) ).toByteArray()) ?: return false
)) ?: return false
val oidbPkg = oidb_sso.OIDBSSOPkg() val oidbPkg = oidb_sso.OIDBSSOPkg()
oidbPkg.mergeFrom(buffer.slice(4)) oidbPkg.mergeFrom(buffer.slice(4))
val rsp = ProtoBuf.decodeFromByteArray<Oidb0x6d7RespBody>(oidbPkg.bytes_bodybuffer.get().toByteArray()) val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
return rsp.renameFolder?.retCode == 0 return rsp.renameFolder?.retCode == 0
} }

View File

@ -7,14 +7,10 @@ import com.tencent.qqnt.kernel.nativeinterface.GProGuildRole
import com.tencent.qqnt.kernel.nativeinterface.GProRoleCreateInfo import com.tencent.qqnt.kernel.nativeinterface.GProRoleCreateInfo
import com.tencent.qqnt.kernel.nativeinterface.GProRoleMemberList import com.tencent.qqnt.kernel.nativeinterface.GProRoleMemberList
import com.tencent.qqnt.kernel.nativeinterface.GProRolePermission import com.tencent.qqnt.kernel.nativeinterface.GProRolePermission
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import moe.fuqiuluo.qqinterface.servlet.structures.GProChannelInfo import moe.fuqiuluo.qqinterface.servlet.structures.GProChannelInfo
import moe.fuqiuluo.qqinterface.servlet.structures.GetGuildMemberListNextToken import moe.fuqiuluo.qqinterface.servlet.structures.GetGuildMemberListNextToken
import moe.fuqiuluo.qqinterface.servlet.structures.GuildInfo import moe.fuqiuluo.qqinterface.servlet.structures.GuildInfo
@ -24,10 +20,11 @@ import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.shamrock.utils.PlatformUtils import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import moe.fuqiuluo.symbols.decode
import moe.fuqiuluo.symbols.decodeProtobuf
import protobuf.auto.toByteArray
import protobuf.guild.GetGuildFeedsReq import protobuf.guild.GetGuildFeedsReq
import protobuf.guild.GetGuildFeedsRsp import protobuf.guild.GetGuildFeedsRsp
import protobuf.oidb.cmd0xf88.GProFilter import protobuf.oidb.cmd0xf88.GProFilter
@ -54,33 +51,31 @@ internal object GProSvc: BaseSvc() {
} }
suspend fun getGuildInfo(guildId: ULong): Result<Oidb0xf57MetaInfo> { suspend fun getGuildInfo(guildId: ULong): Result<Oidb0xf57MetaInfo> {
val respBuffer = sendOidbAW("OidbSvcTrpcTcp.0xf57_9", 0xf57, 9, ProtoBuf.encodeToByteArray( val respBuffer = sendOidbAW("OidbSvcTrpcTcp.0xf57_9", 0xf57, 9, Oidb0xf57Req(
Oidb0xf57Req(
filter = Oidb0xf57Filter( filter = Oidb0xf57Filter(
u1 = Oidb0xf57U1(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u), u1 = Oidb0xf57U1(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u),
u2 = Oidb0xf57U2(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u) u2 = Oidb0xf57U2(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u)
), ),
guildInfo = Oidb0xf57GuildInfo(guildId = guildId) guildInfo = Oidb0xf57GuildInfo(guildId = guildId)
) ).toByteArray())
))
val body = oidb_sso.OIDBSSOPkg() val body = oidb_sso.OIDBSSOPkg()
if (respBuffer == null) { if (respBuffer == null) {
return Result.failure(Exception("unable to send packet")) return Result.failure(Exception("unable to send packet"))
} }
body.mergeFrom(respBuffer.slice(4)) body.mergeFrom(respBuffer.slice(4))
return runCatching { return runCatching {
ProtoBuf.decodeFromByteArray<Oidb0xf57Rsp>( body.bytes_bodybuffer.get()
body.bytes_bodybuffer.get().toByteArray() .toByteArray()
).metaInfo .decodeProtobuf<Oidb0xf57Rsp>().metaInfo
} }
} }
suspend fun getGuildFeeds(guildId: ULong, channelId: ULong, startIndex: Int): Result<GetGuildFeedsRsp> { suspend fun getGuildFeeds(guildId: ULong, channelId: ULong, startIndex: Int): Result<GetGuildFeedsRsp> {
val buffer = sendBufferAW("QChannelSvr.trpc.qchannel.commreader.ComReader.GetGuildFeeds", true, ProtoBuf.encodeToByteArray(QWebReq( val buffer = sendBufferAW("QChannelSvr.trpc.qchannel.commreader.ComReader.GetGuildFeeds", true, QWebReq(
seq = 10, seq = 10,
qua = PlatformUtils.getQUA(), qua = PlatformUtils.getQUA(),
deviceInfo = "i=&imsi=&mac=02:00:00:00:00:00&m=Shamrock&o=114514&a=1919810&sd=0&c64=1&sc=1&p=8000*8000&aid=123456789012345678901234567890abcdef&f=Tencent&mm=5610&cf=1726&cc=8&qimei=&qimei36=&sharpP=1&n=nether_world&support_xsj_live=false&client_mod=concise&timezone=America/La_Paz&material_sdk_version=&vh265=&refreshrate=10086&hwlevel=9&suphdr=1&is_teenager_mod=8&liveH265=&bmst=5&AV1=0", deviceInfo = "i=&imsi=&mac=02:00:00:00:00:00&m=Shamrock&o=114514&a=1919810&sd=0&c64=1&sc=1&p=8000*8000&aid=123456789012345678901234567890abcdef&f=Tencent&mm=5610&cf=1726&cc=8&qimei=&qimei36=&sharpP=1&n=nether_world&support_xsj_live=false&client_mod=concise&timezone=America/La_Paz&material_sdk_version=&vh265=&refreshrate=10086&hwlevel=9&suphdr=1&is_teenager_mod=8&liveH265=&bmst=5&AV1=0",
buffer = ProtoBuf.encodeToByteArray(GetGuildFeedsReq( buffer = GetGuildFeedsReq(
count = 12, count = 12,
from = startIndex, from = startIndex,
feedAttchInfo = EMPTY_BYTE_ARRAY, feedAttchInfo = EMPTY_BYTE_ARRAY,
@ -89,18 +84,18 @@ internal object GProSvc: BaseSvc() {
u7 = 0, u7 = 0,
u8 = 1, u8 = 1,
u9 = EMPTY_BYTE_ARRAY u9 = EMPTY_BYTE_ARRAY
)), ).toByteArray(),
traceId = app.account + "_0_0", traceId = app.account + "_0_0",
extinfo = listOf( extinfo = listOf(
QWebExtInfo("fc-appid", "96"), QWebExtInfo("fc-appid", "96"),
QWebExtInfo("environment_id", "shamrock"), QWebExtInfo("environment_id", "shamrock"),
QWebExtInfo("tiny_id", getSelfTinyId().toString()), QWebExtInfo("tiny_id", getSelfTinyId().toString()),
) )
))) ?: return Result.failure(Exception("unable to send packet")) ).toByteArray()) ?: return Result.failure(Exception("unable to send packet"))
val webRsp = ProtoBuf.decodeFromByteArray<QWebRsp>(buffer.slice(4)) val webRsp = buffer.slice(4).decodeProtobuf<QWebRsp>()
if(webRsp.buffer == null) return Result.failure(Exception("server error")) if(webRsp.buffer == null) return Result.failure(Exception("server error"))
val wupBuffer = webRsp.buffer!! val wupBuffer = webRsp.buffer!!
val feeds = ProtoBuf.decodeFromByteArray<GetGuildFeedsRsp>(wupBuffer) val feeds = wupBuffer.decodeProtobuf<GetGuildFeedsRsp>()
return Result.success(feeds) return Result.success(feeds)
} }
@ -188,23 +183,19 @@ internal object GProSvc: BaseSvc() {
guildId: ULong, guildId: ULong,
memberTinyId: ULong memberTinyId: ULong
): Result<GProUserInfo> { ): Result<GProUserInfo> {
val respBuffer = sendOidbAW("OidbSvcTrpcTcp.0xf88_1", 0xf88, 1, ProtoBuf.encodeToByteArray( val respBuffer = sendOidbAW("OidbSvcTrpcTcp.0xf88_1", 0xf88, 1, Oidb0xf88Req(
Oidb0xf88Req(
filter = GProFilter(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u), filter = GProFilter(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u),
memberId = 0uL, memberId = 0uL,
tinyId = memberTinyId, tinyId = memberTinyId,
guildId = guildId guildId = guildId
) ).toByteArray())
))
val body = oidb_sso.OIDBSSOPkg() val body = oidb_sso.OIDBSSOPkg()
if (respBuffer == null) { if (respBuffer == null) {
return Result.failure(Exception("unable to send packet")) return Result.failure(Exception("unable to send packet"))
} }
body.mergeFrom(respBuffer.slice(4)) body.mergeFrom(respBuffer.slice(4))
return runCatching { return runCatching {
ProtoBuf.decodeFromByteArray<Oidb0xf88Rsp>( body.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0xf88Rsp>().userInfo!!
body.bytes_bodybuffer.get().toByteArray()
).userInfo!!
} }
} }

View File

@ -46,7 +46,7 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.protobuf.ProtoBuf
import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getLongUin import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getLongUin
import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getUin import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getUin
import moe.fuqiuluo.qqinterface.servlet.structures.GroupAtAllRemainInfo import moe.fuqiuluo.qqinterface.servlet.structures.GroupAtAllRemainInfo
@ -59,6 +59,7 @@ import moe.fuqiuluo.shamrock.remote.service.data.EssenceMessage
import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncement import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncement
import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncementMessage import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncementMessage
import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncementMessageImage import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncementMessageImage
import moe.fuqiuluo.shamrock.remote.service.data.push.MemberRole
import moe.fuqiuluo.shamrock.tools.EmptyJsonArray import moe.fuqiuluo.shamrock.tools.EmptyJsonArray
import moe.fuqiuluo.shamrock.tools.GlobalClient import moe.fuqiuluo.shamrock.tools.GlobalClient
import moe.fuqiuluo.shamrock.tools.asInt import moe.fuqiuluo.shamrock.tools.asInt
@ -77,6 +78,7 @@ import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import protobuf.oidb.cmd0xf16.Oidb0xf16 import protobuf.oidb.cmd0xf16.Oidb0xf16
import protobuf.oidb.cmd0xf16.SetGroupRemarkReq import protobuf.oidb.cmd0xf16.SetGroupRemarkReq
import mqq.app.MobileQQ import mqq.app.MobileQQ
import protobuf.auto.toByteArray
import tencent.im.group.group_member_info import tencent.im.group.group_member_info
import tencent.im.oidb.cmd0x88d.oidb_0x88d import tencent.im.oidb.cmd0x88d.oidb_0x88d
import tencent.im.oidb.cmd0x899.oidb_0x899 import tencent.im.oidb.cmd0x899.oidb_0x899
@ -96,6 +98,8 @@ import java.nio.ByteBuffer
import kotlin.coroutines.resume import kotlin.coroutines.resume
internal object GroupSvc: BaseSvc() { internal object GroupSvc: BaseSvc() {
private const val GET_MEMBER_ROLE_BY_NT = false
private val RefreshTroopMemberInfoLock by lazy { private val RefreshTroopMemberInfoLock by lazy {
Mutex() Mutex()
} }
@ -271,15 +275,13 @@ internal object GroupSvc: BaseSvc() {
} }
fun modifyGroupRemark(groupId: Long, remark: String): Boolean { fun modifyGroupRemark(groupId: Long, remark: String): Boolean {
sendOidb("OidbSvc.0xf16_1", 3862, 1, ProtoBuf.encodeToByteArray( sendOidb("OidbSvc.0xf16_1", 3862, 1, Oidb0xf16(
Oidb0xf16(
setGroupRemarkReq = SetGroupRemarkReq( setGroupRemarkReq = SetGroupRemarkReq(
groupCode = groupId.toULong(), groupCode = groupId.toULong(),
groupUin = groupCode2GroupUin(groupId).toULong(), groupUin = groupCode2GroupUin(groupId).toULong(),
groupRemark = remark groupRemark = remark
) )
) ).toByteArray())
))
return true return true
} }
@ -394,6 +396,27 @@ internal object GroupSvc: BaseSvc() {
.filter { it != 0L } .filter { it != 0L }
} }
suspend fun getMemberRole(groupId: Long, memberUin: Long): MemberRole {
if (!GET_MEMBER_ROLE_BY_NT) {
return when (memberUin) {
getOwner(groupId.toString()) -> MemberRole.Owner
in getAdminList(groupId.toString()) -> MemberRole.Admin
else -> MemberRole.Member
}
}
return when(getTroopMemberInfoByUinViaNt(groupId.toString(), memberUin, 3000).getOrNull()?.role) {
com.tencent.qqnt.kernel.nativeinterface.MemberRole.STRANGER -> MemberRole.Stranger
com.tencent.qqnt.kernel.nativeinterface.MemberRole.MEMBER -> MemberRole.Member
com.tencent.qqnt.kernel.nativeinterface.MemberRole.ADMIN -> MemberRole.Admin
com.tencent.qqnt.kernel.nativeinterface.MemberRole.OWNER -> MemberRole.Owner
com.tencent.qqnt.kernel.nativeinterface.MemberRole.UNSPECIFIED, null -> when (memberUin) {
getOwner(groupId.toString()) -> MemberRole.Owner
in getAdminList(groupId.toString()) -> MemberRole.Admin
else -> MemberRole.Member
}
}
}
fun getOwner(groupId: String): Long { fun getOwner(groupId: String): Long {
val groupInfo = getGroupInfo(groupId) val groupInfo = getGroupInfo(groupId)
return groupInfo.troopowneruin?.toLong() ?: 0 return groupInfo.troopowneruin?.toLong() ?: 0
@ -566,24 +589,53 @@ internal object GroupSvc: BaseSvc() {
} }
} }
private suspend fun getTroopMemberInfoByUinViaNt(groupId: String, qq: Long): Result<MemberInfo> { suspend fun getTroopMemberInfoByUinV2(
val kernelService = NTServiceFetcher.kernelService groupId: String,
val sessionService = kernelService.wrapperSession uin: String,
val groupService = sessionService.groupService refresh: Boolean = false
val info = suspendCancellableCoroutine { ): Result<TroopMemberInfo> {
groupService.getTransferableMemberInfo(groupId.toLong()) { code, _, data -> val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
if (code != 0) { var info = service.getTroopMember(groupId, uin)
it.resume(null) if (refresh || !service.isMemberInCache(groupId, uin) || info == null || info.troopnick == null) {
return@getTransferableMemberInfo info = requestTroopMemberInfo(service, groupId.toLong(), uin.toLong(), timeout = 2000).getOrNull()
}
if (info == null) {
info = getTroopMemberInfoByUinViaNt(groupId, uin.toLong(), timeout = 2000L).getOrNull()?.let {
TroopMemberInfo().apply {
troopnick = it.cardName
friendnick = it.nick
} }
data.forEach { (_, info) -> }
if (info.uin == qq) { }
it.resume(info) try {
return@forEach if (info != null && (info.alias == null || info.alias.isBlank())) {
val req = group_member_info.ReqBody()
req.uint64_group_code.set(groupId.toLong())
req.uint64_uin.set(uin.toLong())
req.bool_new_client.set(true)
req.uint32_client_type.set(1)
req.uint32_rich_card_name_ver.set(1)
val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray(), timeout = 2000)
if (respBuffer != null) {
val rsp = group_member_info.RspBody()
rsp.mergeFrom(respBuffer.slice(4))
if (rsp.msg_meminfo.str_location.has()) {
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
}
if (rsp.msg_meminfo.uint32_age.has()) {
info.age = rsp.msg_meminfo.uint32_age.get().toByte()
}
if (rsp.msg_meminfo.bytes_group_honor.has()) {
val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray()
val honor = troop_honor.GroupUserCardHonor()
honor.mergeFrom(honorBytes)
info.level = honor.level.get()
// 10315: medal_id not real group level
} }
} }
it.resume(null)
} }
} catch (err: Throwable) {
LogCenter.log(err.stackTraceToString(), Level.WARN)
} }
return if (info != null) { return if (info != null) {
Result.success(info) Result.success(info)
@ -592,6 +644,40 @@ internal object GroupSvc: BaseSvc() {
} }
} }
suspend fun getTroopMemberInfoByUinViaNt(
groupId: String,
qq: Long,
timeout: Long = 5000L
): Result<MemberInfo> {
return runCatching {
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
val groupService = sessionService.groupService
val info = withTimeoutOrNull(timeout) {
suspendCancellableCoroutine {
groupService.getTransferableMemberInfo(groupId.toLong()) { code, _, data ->
if (code != 0) {
it.resume(null)
return@getTransferableMemberInfo
}
data.forEach { (_, info) ->
if (info.uin == qq) {
it.resume(info)
return@forEach
}
}
it.resume(null)
}
}
}
return if (info != null) {
Result.success(info)
} else {
Result.failure(Exception("获取群成员信息失败"))
}
}
}
suspend fun getTroopMemberInfoByUid(groupId: Long, uid: String): Result<MemberInfo> { suspend fun getTroopMemberInfoByUid(groupId: Long, uid: String): Result<MemberInfo> {
val kernelService = NTServiceFetcher.kernelService val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession val sessionService = kernelService.wrapperSession
@ -748,7 +834,7 @@ internal object GroupSvc: BaseSvc() {
} }
} }
private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: Long, memberUin: Long): Result<TroopMemberInfo> { private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: Long, memberUin: Long, timeout: Long = 10_000): Result<TroopMemberInfo> {
val info = RefreshTroopMemberInfoLock.withLock { val info = RefreshTroopMemberInfoLock.withLock {
val groupIdStr = groupId.toString() val groupIdStr = groupId.toString()
val memberUinStr = memberUin.toString() val memberUinStr = memberUin.toString()
@ -758,7 +844,7 @@ internal object GroupSvc: BaseSvc() {
requestMemberInfoV2(groupId, memberUin) requestMemberInfoV2(groupId, memberUin)
requestMemberInfo(groupId, memberUin) requestMemberInfo(groupId, memberUin)
withTimeoutOrNull(10000) { withTimeoutOrNull(timeout) {
while (!service.isMemberInCache(groupIdStr, memberUinStr)) { while (!service.isMemberInCache(groupIdStr, memberUinStr)) {
delay(200) delay(200)
} }

View File

@ -5,33 +5,35 @@ package moe.fuqiuluo.qqinterface.servlet
import com.tencent.mobileqq.qroute.QRoute import com.tencent.mobileqq.qroute.QRoute
import com.tencent.mobileqq.troop.api.ITroopMemberNameService import com.tencent.mobileqq.troop.api.ITroopMemberNameService
import com.tencent.qqnt.kernel.api.IKernelService import com.tencent.qqnt.kernel.api.IKernelService
import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback import com.tencent.qqnt.kernel.nativeinterface.*
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.kernel.nativeinterface.TempChatGameSession
import com.tencent.qqnt.kernel.nativeinterface.TempChatPrepareInfo
import com.tencent.qqnt.msg.api.IMsgService import com.tencent.qqnt.msg.api.IMsgService
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonArray
import moe.fuqiuluo.qqinterface.servlet.msg.messageelement.toSegments
import moe.fuqiuluo.qqinterface.servlet.msg.toListMap
import moe.fuqiuluo.shamrock.helper.ContactHelper import moe.fuqiuluo.shamrock.helper.ContactHelper
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.MessageHelper import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.helper.SendMsgException import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail
import moe.fuqiuluo.shamrock.remote.service.data.MessageSender
import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY import moe.fuqiuluo.shamrock.tools.*
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import moe.fuqiuluo.shamrock.xposed.helper.msgService import moe.fuqiuluo.shamrock.xposed.helper.msgService
import java.util.UUID import moe.fuqiuluo.symbols.decode
import moe.fuqiuluo.symbols.decodeProtobuf
import protobuf.auto.toByteArray
import protobuf.message.longmsg.*
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
internal object MsgSvc: BaseSvc() { internal object MsgSvc : BaseSvc() {
suspend fun prepareTempChatFromGroup( suspend fun prepareTempChatFromGroup(
groupId: String, groupId: String,
peerId: String peerId: String
@ -39,13 +41,19 @@ internal object MsgSvc: BaseSvc() {
LogCenter.log("主动临时消息,创建临时会话。", Level.INFO) LogCenter.log("主动临时消息,创建临时会话。", Level.INFO)
val msgService = app.getRuntimeService(IKernelService::class.java, "all").msgService val msgService = app.getRuntimeService(IKernelService::class.java, "all").msgService
?: return Result.failure(Exception("获取消息服务失败")) ?: return Result.failure(Exception("获取消息服务失败"))
msgService.prepareTempChat(TempChatPrepareInfo( msgService.prepareTempChat(
MsgConstant.KCHATTYPETEMPC2CFROMGROUP, TempChatPrepareInfo(
ContactHelper.getUidByUinAsync(peerId = peerId.toLong()), MsgConstant.KCHATTYPETEMPC2CFROMGROUP,
app.getRuntimeService(ITroopMemberNameService::class.java, "all") ContactHelper.getUidByUinAsync(peerId = peerId.toLong()),
.getTroopMemberNameRemarkFirst(groupId, peerId), app.getRuntimeService(ITroopMemberNameService::class.java, "all")
groupId, EMPTY_BYTE_ARRAY, app.currentUid, "", TempChatGameSession() .getTroopMemberNameRemarkFirst(groupId, peerId),
)) { code, reason -> groupId,
EMPTY_BYTE_ARRAY,
app.currentUid,
"",
TempChatGameSession()
)
) { code, reason ->
if (code != 0) { if (code != 0) {
LogCenter.log("临时会话创建失败: $code, $reason", Level.ERROR) LogCenter.log("临时会话创建失败: $code, $reason", Level.ERROR)
} }
@ -53,6 +61,24 @@ internal object MsgSvc: BaseSvc() {
return Result.success(Unit) return Result.success(Unit)
} }
suspend fun getTempChatInfo(chatType: Int, uid: String): Result<TempChatInfo> {
val msgService = app.getRuntimeService(IKernelService::class.java, "all").msgService
?: return Result.failure(Exception("获取消息服务失败"))
val info: TempChatInfo = withTimeoutOrNull(5000) {
suspendCancellableCoroutine {
msgService.getTempChatInfo(chatType, uid) { code, msg, tempChatInfo ->
if (code == 0) {
it.resume(tempChatInfo)
} else {
LogCenter.log("获取临时会话信息失败: $code:$msg", Level.ERROR)
it.resume(null)
}
}
}
} ?: return Result.failure(Exception("获取临时会话信息失败"))
return Result.success(info)
}
/** /**
* 正常获取 * 正常获取
*/ */
@ -61,7 +87,7 @@ internal object MsgSvc: BaseSvc() {
?: return Result.failure(Exception("没有对应消息映射,消息获取失败")) ?: return Result.failure(Exception("没有对应消息映射,消息获取失败"))
val peerId = mapping.peerId val peerId = mapping.peerId
val contact = MessageHelper.generateContact(mapping.chatType, peerId, mapping.subPeerId ?: "") val contact = MessageHelper.generateContact(mapping.chatType, peerId, mapping.subPeerId)
val msg = withTimeoutOrNull(5000) { val msg = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java) val service = QRoute.api(IMsgService::class.java)
@ -152,7 +178,7 @@ internal object MsgSvc: BaseSvc() {
val mapping = MessageHelper.getMsgMappingByHash(msgHash) val mapping = MessageHelper.getMsgMappingByHash(msgHash)
?: return -1 to "无法找到消息映射" ?: return -1 to "无法找到消息映射"
val contact = MessageHelper.generateContact(mapping.chatType, mapping.peerId, mapping.subPeerId ?: "") val contact = MessageHelper.generateContact(mapping.chatType, mapping.peerId, mapping.subPeerId)
return suspendCancellableCoroutine { continuation -> return suspendCancellableCoroutine { continuation ->
msgService.recallMsg(contact, arrayListOf(mapping.qqMsgId)) { code, why -> msgService.recallMsg(contact, arrayListOf(mapping.qqMsgId)) { code, why ->
@ -182,9 +208,10 @@ internal object MsgSvc: BaseSvc() {
} }
} }
} }
val result = MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, fromId, MessageCallback(peedId, 0)) val result =
result.onFailure { MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, fromId, MessageCallback(peedId, 0))
LogCenter.log("sendToAio: " + it.stackTraceToString(), Level.ERROR) if (result.isFailure) {
LogCenter.log("sendToAio: " + result.exceptionOrNull()?.stackTraceToString(), Level.ERROR)
return result return result
} }
val sendResult = result.getOrThrow() val sendResult = result.getOrThrow()
@ -198,48 +225,109 @@ internal object MsgSvc: BaseSvc() {
} }
} }
suspend fun getMultiMsg(resId: String): Result<List<MsgRecord>> { suspend fun sendMultiMsg(
val kernelService = NTServiceFetcher.kernelService uid: String,
val sessionService = kernelService.wrapperSession groupUin: String?,
val msgService = sessionService.msgService messages: List<PushMsgBody>,
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin()) ): Result<String> {
val payload = LongMsgPayload(
action = listOf(
LongMsgAction(
command = "MultiMsg",
data = LongMsgContent(
body = messages
)
)
)
)
LogCenter.log(payload.toByteArray().toHexString(), Level.DEBUG)
val content = "{\"app\":\"com.tencent.multimsg\",\"config\":{\"autosize\":1,\"forward\":1,\"round\":1,\"type\":\"normal\",\"width\":300},\"desc\":\"[聊天记录]\",\"extra\":\"\",\"meta\":{\"detail\":{\"news\":[{\"text\":\"Shamrock: 这是条假消息!\"}],\"resid\":\"$resId\",\"source\":\"聊天记录\",\"summary\":\"转发消息\",\"uniseq\":\"${UUID.randomUUID()}\"}},\"prompt\":\"[聊天记录]\",\"ver\":\"0.0.0.5\",\"view\":\"contact\"}" val req = LongMsgReq(
val msgId = PacketSvc.fakeSelfRecvJsonMsg(msgService, content) sendInfo = SendLongMsgInfo(
if (msgId < 0) { type = if (groupUin == null) 1 else 3,
return Result.failure(Exception("获取合并转发消息ID失败")) uid = LongMsgUid(groupUin ?: uid),
} groupUin = groupUin?.toInt(),
val msgList = withTimeoutOrNull(5000L) { payload = DeflateTools.gzip(payload.toByteArray())
suspendCancellableCoroutine<ArrayList<MsgRecord>> { ),
val job = GlobalScope.launch { setting = LongMsgSettings(
var hasResult = false field1 = 4,
while (!hasResult) { field2 = 2,
msgService.getMultiMsg(contact, msgId, msgId) { code, why, msgList -> field3 = 9,
if (code == 0) { field4 = 0
it.resume(msgList) )
hasResult = true )
} else { val buffer = sendBufferAW(
LogCenter.log("获取合并转发消息失败: $code($why): $msgId", Level.ERROR) "trpc.group.long_msg_interface.MsgService.SsoSendLongMsg",
} true,
} req.toByteArray()
delay(200) ) ?: return Result.failure(Exception("unable to upload multi message"))
} val rsp = buffer.slice(4).decodeProtobuf<LongMsgRsp>()
} return rsp.sendResult?.resId?.let { Result.success(it) }
it.invokeOnCancellation { ?: Result.failure(Exception("unable to upload multi message"))
job.cancel() }
suspend fun getMultiMsg(resId: String): Result<List<MessageDetail>> {
val req = LongMsgReq(
recvInfo = RecvLongMsgInfo(
uid = LongMsgUid(TicketSvc.getUid()),
resId = resId,
u1 = 3
),
setting = LongMsgSettings(
field1 = 2,
field2 = 2,
field3 = 9,
field4 = 0
)
)
val buffer = sendBufferAW(
"trpc.group.long_msg_interface.MsgService.SsoRecvLongMsg",
true,
req.toByteArray()
) ?: return Result.failure(Exception("unable to get multi message"))
val rsp = buffer.slice(4).decodeProtobuf<LongMsgRsp>()
val zippedPayload = DeflateTools.ungzip(
rsp.recvResult?.payload ?: return Result.failure(Exception("unable to get multi message"))
)
LogCenter.log(zippedPayload.toHexString(), Level.DEBUG)
val payload = zippedPayload.decodeProtobuf<LongMsgPayload>()
payload.action?.forEach {
if (it.command == "MultiMsg") {
return Result.success(it.data?.body?.map { msg ->
val chatType =
if (msg.content!!.msgType == 82) MsgConstant.KCHATTYPEGROUP else MsgConstant.KCHATTYPEC2C
MessageDetail(
time = msg.content?.msgTime?.toInt() ?: 0,
msgType = MessageHelper.obtainDetailTypeByMsgType(chatType),
msgId = 0, // MessageHelper.generateMsgIdHash(chatType, msg.content!!.msgViaRandom), msgViaRandom 为空
realId = msg.content!!.msgSeq?.toInt() ?: 0,
sender = MessageSender(
msg.head?.peer ?: 0,
msg.head?.groupInfo?.memberCard?.ifEmpty { msg.head?.forward?.friendName }
?: msg.head?.forward?.friendName ?: "",
"unknown",
0,
msg.head?.peerUid ?: "",
msg.head?.peerUid ?: ""
),
message = msg.body?.rich?.elements?.toSegments(chatType, msg.head?.peer.toString(), "0")
?.toListMap() ?: emptyList(),
peerId = msg.head?.peer ?: 0,
groupId = if (chatType == MsgConstant.KCHATTYPEGROUP) msg.head?.groupInfo?.groupCode?.toLong()
?: 0 else 0,
targetId = if (chatType != MsgConstant.KCHATTYPEGROUP) msg.head?.peer ?: 0 else 0
)
} }
?: return Result.failure(Exception("Msg is empty")))
} }
} ?: return Result.failure(Exception("获取合并转发消息失败")) }
return Result.failure(Exception("Can't find msg"))
//msgService.deleteMsg(contact, arrayListOf(msgId), null)
return Result.success(msgList)
} }
class MessageCallback( class MessageCallback(
private val peerId: String, private val peerId: String,
var msgHash: Int var msgHash: Int
): IOperateCallback { ) : IOperateCallback {
override fun onResult(code: Int, reason: String?) { override fun onResult(code: Int, reason: String?) {
if (code != 0 && msgHash != 0) { if (code != 0 && msgHash != 0) {
MessageHelper.removeMsgByHashCode(msgHash) MessageHelper.removeMsgByHashCode(msgHash)

View File

@ -10,20 +10,21 @@ import io.ktor.utils.io.core.writeInt
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.encodeToByteArray import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import moe.fuqiuluo.shamrock.remote.action.handlers.GetHistoryMsg import moe.fuqiuluo.shamrock.remote.action.handlers.GetHistoryMsg
import moe.fuqiuluo.shamrock.remote.service.listener.AioListener import moe.fuqiuluo.shamrock.remote.service.listener.AioListener
import moe.fuqiuluo.shamrock.tools.broadcast import moe.fuqiuluo.shamrock.tools.broadcast
import moe.fuqiuluo.shamrock.utils.DeflateTools import moe.fuqiuluo.shamrock.utils.DeflateTools
import protobuf.message.JsonElement import protobuf.message.element.JsonElement
import protobuf.message.MessageBody import protobuf.message.NtMessage
import protobuf.message.MessageContentHead import protobuf.message.MessageContent
import protobuf.message.MessageElement import protobuf.message.MessageElement
import protobuf.message.MessageElementList
import protobuf.message.MessageHead
import protobuf.message.RichMessage import protobuf.message.RichMessage
import protobuf.message.MessageHead
import protobuf.message.MessageBody
import protobuf.push.MessagePush import protobuf.push.MessagePush
import mqq.app.MobileQQ import mqq.app.MobileQQ
import protobuf.auto.toByteArray
import kotlin.coroutines.resume import kotlin.coroutines.resume
internal object PacketSvc: BaseSvc() { internal object PacketSvc: BaseSvc() {
@ -51,7 +52,7 @@ internal object PacketSvc: BaseSvc() {
val msgSeq = (latestMsg?.msgSeq ?: 0) + 1 val msgSeq = (latestMsg?.msgSeq ?: 0) + 1
val msgPush = MessagePush( val msgPush = MessagePush(
msgBody = MessageBody( msgBody = NtMessage(
msgHead = MessageHead( msgHead = MessageHead(
peer = app.longAccountUin, peer = app.longAccountUin,
peerUid = app.currentUid, peerUid = app.currentUid,
@ -59,28 +60,33 @@ internal object PacketSvc: BaseSvc() {
receiver = app.longAccountUin, receiver = app.longAccountUin,
receiverUid = app.currentUid receiverUid = app.currentUid
), ),
contentHead = MessageContentHead( contentHead = MessageContent(
msgType = 166, msgType = 166,
msgSubType = 11, msgSubType = 11,
msgSeq = msgSeq, msgSeq = msgSeq,
u1 = msgSeq, msgViaRandom = msgSeq,
msgTime = System.currentTimeMillis() / 1000, msgTime = System.currentTimeMillis() / 1000,
u2 = 1, u2 = 1,
u3 = msgSeq, msgSeq_ = msgSeq,
msgRandom = msgService.getMsgUniqueId(System.currentTimeMillis()), msgRandom = msgService.getMsgUniqueId(System.currentTimeMillis()),
u4 = msgSeq - 2, u4 = msgSeq - 2,
u5 = msgSeq u5 = msgSeq
), ),
richMsg = RichMessage(MessageElementList(builder())) body = MessageBody(RichMessage(
elements = builder()
))
) )
) )
fakeReceive("trpc.msg.olpush.OlPushService.MsgPush", 10000, ProtoBuf.encodeToByteArray(msgPush)) fakeReceive("trpc.msg.olpush.OlPushService.MsgPush", 10000, msgPush.toByteArray())
return withTimeoutOrNull(5000L) { return withTimeoutOrNull(5000L) {
suspendCancellableCoroutine { suspendCancellableCoroutine {
AioListener.messageLessListenerMap[msgSeq] = { AioListener.registerTemporaryMsgListener(msgSeq) {
it.resume(this.msgId) it.resume(this.msgId)
} }
it.invokeOnCancellation {
AioListener.unregisterTemporaryMsgListener(msgSeq)
}
} }
} ?: -1L } ?: -1L
} }

View File

@ -15,7 +15,7 @@ import kotlinx.io.core.readBytes
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.encodeToByteArray import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.hex2ByteArray import moe.fuqiuluo.shamrock.tools.hex2ByteArray
@ -39,6 +39,7 @@ import mqq.manager.TicketManager
import oicq.wlogin_sdk.request.Ticket import oicq.wlogin_sdk.request.Ticket
import oicq.wlogin_sdk.request.WtTicketPromise import oicq.wlogin_sdk.request.WtTicketPromise
import oicq.wlogin_sdk.tools.ErrMsg import oicq.wlogin_sdk.tools.ErrMsg
import protobuf.auto.toByteArray
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -329,9 +330,9 @@ internal object QFavSvc: BaseSvc() {
} }
val pSKey = getWeiYunPSKey() val pSKey = getWeiYunPSKey()
httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST
httpNetReq.mSendData = DeflateTools.gzip(packData(packHead(cmd, pSKey), ProtoBuf.encodeToByteArray( httpNetReq.mSendData = DeflateTools.gzip(packData(packHead(cmd, pSKey), WeiyunComm(
WeiyunComm(req = req) req = req
))) ).toByteArray()))
httpNetReq.mOutStream = outputStream httpNetReq.mOutStream = outputStream
httpNetReq.mStartDownOffset = 0L httpNetReq.mStartDownOffset = 0L
httpNetReq.mReqProperties["Shamrock"] = "true" httpNetReq.mReqProperties["Shamrock"] = "true"
@ -351,8 +352,7 @@ internal object QFavSvc: BaseSvc() {
} }
private fun packHead(cmd: Int, pskey: String): ByteArray { private fun packHead(cmd: Int, pskey: String): ByteArray {
return ProtoBuf.encodeToByteArray( return WeiyunMsgHead(
WeiyunMsgHead(
uin = app.longAccountUin.toULong(), uin = app.longAccountUin.toULong(),
seq = seq++.toUInt(), seq = seq++.toUInt(),
type = 1u, type = 1u,
@ -364,8 +364,7 @@ internal object QFavSvc: BaseSvc() {
key = pskey.toByteArray(), key = pskey.toByteArray(),
majorVersion = MAJOR_VERSION.toUInt(), majorVersion = MAJOR_VERSION.toUInt(),
minorVersion = MINOR_VERSION.toUInt(), minorVersion = MINOR_VERSION.toUInt(),
) ).toByteArray()
)
} }
private fun packData(head: ByteArray, body: ByteArray): ByteArray { private fun packData(head: ByteArray, body: ByteArray): ByteArray {

View File

@ -13,8 +13,11 @@ import tencent.im.oidb.oidb_sso
internal object TicketSvc: BaseSvc() { internal object TicketSvc: BaseSvc() {
object SigType { object SigType {
const val WLOGIN_A2 = 64
const val WLOGIN_A5 = 2 const val WLOGIN_A5 = 2
const val WLOGIN_RESERVED = 16
const val WLOGIN_STWEB = 32 // TLV 103
const val WLOGIN_A2 = 64
const val WLOGIN_ST = 128
const val WLOGIN_AQSIG = 2097152 const val WLOGIN_AQSIG = 2097152
const val WLOGIN_D2 = 262144 const val WLOGIN_D2 = 262144
const val WLOGIN_DA2 = 33554432 const val WLOGIN_DA2 = 33554432
@ -26,14 +29,17 @@ internal object TicketSvc: BaseSvc() {
const val WLOGIN_PSKEY = 1048576 const val WLOGIN_PSKEY = 1048576
const val WLOGIN_PT4Token = 134217728 const val WLOGIN_PT4Token = 134217728
const val WLOGIN_QRPUSH = 67108864 const val WLOGIN_QRPUSH = 67108864
const val WLOGIN_RESERVED = 16
const val WLOGIN_SID = 524288 const val WLOGIN_SID = 524288
const val WLOGIN_SIG64 = 8192 const val WLOGIN_SIG64 = 8192
const val WLOGIN_SKEY = 4096 const val WLOGIN_SKEY = 4096
const val WLOGIN_ST = 128
const val WLOGIN_STWEB = 32 // TLV 103
const val WLOGIN_TOKEN = 32768 const val WLOGIN_TOKEN = 32768
const val WLOGIN_VKEY = 131072 const val WLOGIN_VKEY = 131072
val ALL_TICKET = arrayOf(
WLOGIN_A5, WLOGIN_RESERVED, WLOGIN_STWEB, WLOGIN_A2, WLOGIN_ST, WLOGIN_AQSIG, WLOGIN_D2, WLOGIN_DA2,
WLOGIN_LHSIG, WLOGIN_LSKEY, WLOGIN_OPENKEY, WLOGIN_PAYTOKEN, WLOGIN_PF, WLOGIN_PSKEY, WLOGIN_PT4Token,
WLOGIN_QRPUSH, WLOGIN_SID, WLOGIN_SIG64, WLOGIN_SKEY, WLOGIN_TOKEN, WLOGIN_VKEY
)
} }
fun getUin(): String { fun getUin(): String {
@ -44,6 +50,14 @@ internal object TicketSvc: BaseSvc() {
return app.longAccountUin return app.longAccountUin
} }
fun getUid(): String {
return app.currentUid.ifBlank { "u_" }
}
fun getNickname(): String {
return app.currentNickname
}
fun getCookie(): String { fun getCookie(): String {
val uin = getUin() val uin = getUin()
val skey = getRealSkey(uin) val skey = getRealSkey(uin)

View File

@ -0,0 +1,37 @@
package moe.fuqiuluo.qqinterface.servlet.ark
sealed class ArkAppInfo(
val appId: Long,
val version: String,
val packageName: String,
val signature: String,
val miniAppId: Long = 0
) {
data object QQMusic: ArkAppInfo(
appId = 100497308,
version = "0.0.0",
packageName = "com.tencent.qqmusic",
signature = "cbd27cd7c861227d013a25b2d10f0799"
)
data object NetEaseMusic: ArkAppInfo(
appId = 100495085,
version = "0.0.0",
packageName = "com.netease.cloudmusic",
signature = "da6b069da1e2982db3e386233f68d76d"
)
data object DanMaKu: ArkAppInfo(
appId = 100951776,
version = "0.0.0",
packageName = "tv.danmaku.bili",
signature = "7194d531cbe7960a22007b9f6bdaa38b",
miniAppId = 1109937557
)
data object Docs: ArkAppInfo(
appId = 0,
version = "0.0.0",
packageName = "",
signature = "f3da3147654d9a21f3237b88f20dce9c",
miniAppId = 1108338344
)
}

View File

@ -1,18 +1,16 @@
package moe.fuqiuluo.qqinterface.servlet.ark package moe.fuqiuluo.qqinterface.servlet.ark
import com.tencent.mobileqq.pb.ByteStringMicro
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.qqinterface.servlet.BaseSvc
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.remote.service.listener.AioListener
import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77 import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77
import kotlin.coroutines.resume
sealed class ArkAppInfo( import kotlin.time.Duration.Companion.seconds
val appId: Long,
val version: String,
val packageName: String,
val signature: String
) {
object QQMusic: ArkAppInfo(100497308, "0.0.0", "com.tencent.qqmusic", "cbd27cd7c861227d013a25b2d10f0799")
object NeteaseMusic: ArkAppInfo(100495085, "0.0.0", "com.netease.cloudmusic", "da6b069da1e2982db3e386233f68d76d")
}
internal object ArkMsgSvc: BaseSvc() { internal object ArkMsgSvc: BaseSvc() {
fun tryShareMusic( fun tryShareMusic(
@ -54,4 +52,50 @@ internal object ArkMsgSvc: BaseSvc() {
} }
sendOidb("OidbSvc.0xb77_9", 0xb77, 9, req.toByteArray()) sendOidb("OidbSvc.0xb77_9", 0xb77, 9, req.toByteArray())
} }
/*
suspend fun tryShareJsonMessage(
jsonString: String,
arkAppInfo: ArkAppInfo = ArkAppInfo.DanMaKu,
): Result<String> {
val msgSeq = MessageHelper.generateMsgId(MsgConstant.KCHATTYPEC2C).qqMsgId
val req = oidb_cmd0xb77.ReqBody()
req.appid.set(arkAppInfo.appId)
req.app_type.set(1)
req.msg_style.set(10)
req.client_info.set(oidb_cmd0xb77.ClientInfo().also {
it.platform.set(1)
it.sdk_version.set(arkAppInfo.version)
it.android_package_name.set(arkAppInfo.packageName)
it.android_signature.set(arkAppInfo.signature)
})
req.ext_info.set(oidb_cmd0xb77.ExtInfo().also {
it.tag_name.set(ByteStringMicro.copyFromUtf8("shamrock"))
it.msg_seq.set(msgSeq)
})
req.send_type.set(0)
req.recv_uin.set(TicketSvc.getLongUin())
req.mini_app_msg_body.set(oidb_cmd0xb77.MiniAppMsgBody().also {
it.mini_app_appid.set(arkAppInfo.miniAppId)
it.mini_app_path.set("pages")
it.web_page_url.set("https://im.qq.com/index/")
it.title.set("title")
it.desc.set("desc")
it.json_str.set(jsonString)
})
sendOidb("OidbSvc.0xb77_9", 0xb77, 9, req.toByteArray())
val signedJson: String = withTimeoutOrNull(5.seconds) {
suspendCancellableCoroutine {
AioListener.registerTemporaryMsgListener(msgSeq) {
it.resume(elements.first {
it.elementType == MsgConstant.KELEMTYPEARKSTRUCT
}.arkElement.bytesData)
}
it.invokeOnCancellation {
AioListener.unregisterTemporaryMsgListener(msgSeq)
}
}
} ?: return Result.failure(Exception("unable to sign json"))
return Result.success(signedJson)
}*/
} }

View File

@ -0,0 +1,9 @@
package moe.fuqiuluo.qqinterface.servlet.ark
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
internal object LightAppSvc: BaseSvc() {
suspend fun adaptShare() {
}
}

View File

@ -0,0 +1,10 @@
package moe.fuqiuluo.qqinterface.servlet.ark
import kotlinx.serialization.Serializable
@Serializable
internal data class Region(
val adcode: Int,
val province: String?,
val city: String?
)

View File

@ -15,13 +15,6 @@ import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.* import moe.fuqiuluo.shamrock.tools.*
import java.lang.Exception import java.lang.Exception
@Serializable
internal data class Region(
val adcode: Int,
val province: String?,
val city: String?
)
internal object WeatherSvc { internal object WeatherSvc {
suspend fun fetchWeatherCard(code: Int): Result<JsonObject> { suspend fun fetchWeatherCard(code: Int): Result<JsonObject> {
val cookie = TicketSvc.getCookie("mp.qq.com") val cookie = TicketSvc.getCookie("mp.qq.com")

View File

@ -0,0 +1,34 @@
package moe.fuqiuluo.qqinterface.servlet.msg
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import moe.fuqiuluo.shamrock.tools.json
internal data class MessageSegment(
val type: String,
val data: Map<String, Any> = emptyMap()
) {
fun toJson(): JsonObject {
return hashMapOf(
"type" to type.json,
"data" to data.json
).json
}
}
internal fun List<MessageSegment>.toJson(): JsonArray {
return this.map {
it.toJson()
}.json
}
internal fun List<MessageSegment>.toListMap(): List<Map<String, JsonElement>> {
return this.map {
hashMapOf(
"type" to it.type.json,
"data" to it.data.json
).json
}
}

View File

@ -1,132 +0,0 @@
package moe.fuqiuluo.qqinterface.servlet.msg.convert
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.msg.convert.MessageElemConverter.*
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.tools.json
internal typealias MessageSegmentList = ArrayList<MessageSegment>
internal data class MessageSegment(
val type: String,
val data: Map<String, Any> = emptyMap()
) {
fun toJson(): Map<String, JsonElement> {
return hashMapOf(
"type" to type.json,
"data" to data.json
)
}
}
internal suspend fun MsgRecord.toSegments(): ArrayList<MessageSegment> {
return MessageConvert.convertMessageRecordToMsgSegment(this)
}
internal suspend fun MsgRecord.toCQCode(): String {
return MessageConvert.convertMessageRecordToCQCode(this)
}
internal suspend fun List<MsgElement>.toSegments(chatType: Int, peerId: String, subPeer: String): MessageSegmentList {
return MessageConvert.convertMessageElementsToMsgSegment(chatType, this, peerId, subPeer)
}
internal suspend fun List<MsgElement>.toCQCode(chatType: Int, peerId: String, subPeer: String): String {
return MessageConvert.convertMsgElementsToCQCode(this, chatType, peerId, subPeer)
}
internal object MessageConvert {
private val convertMap by lazy {
mutableMapOf<Int, IMessageConvert>(
MsgConstant.KELEMTYPETEXT to TextConverter,
MsgConstant.KELEMTYPEFACE to FaceConverter,
MsgConstant.KELEMTYPEPIC to ImageConverter,
MsgConstant.KELEMTYPEPTT to VoiceConverter,
MsgConstant.KELEMTYPEVIDEO to VideoConverter,
MsgConstant.KELEMTYPEMARKETFACE to MarketFaceConverter,
MsgConstant.KELEMTYPEARKSTRUCT to StructJsonConverter,
MsgConstant.KELEMTYPEREPLY to ReplyConverter,
MsgConstant.KELEMTYPEGRAYTIP to GrayTipsConverter,
MsgConstant.KELEMTYPEFILE to FileConverter,
MsgConstant.KELEMTYPEMARKDOWN to MarkdownConverter,
//MsgConstant.KELEMTYPEMULTIFORWARD to XmlMultiMsgConverter,
//MsgConstant.KELEMTYPESTRUCTLONGMSG to XmlLongMsgConverter,
MsgConstant.KELEMTYPEFACEBUBBLE to BubbleFaceConverter,
)
}
suspend fun convertMessageElementsToMsgSegment(
chatType: Int,
elements: List<MsgElement>,
peerId: String,
subPeer: String
): ArrayList<MessageSegment> {
val messageData = arrayListOf<MessageSegment>()
elements.forEach { msg ->
kotlin.runCatching {
val elementId = msg.elementType
val converter = convertMap[elementId]
converter?.convert(chatType, peerId, subPeer, msg)
?: throw UnsupportedOperationException("不支持的消息element类型$elementId")
}.onSuccess {
messageData.add(it)
}.onFailure {
if (it is UnknownError) {
// 不处理的消息类型抛出unknown error
} else {
LogCenter.log("消息element转换错误$it, elementType: ${msg.elementType}", Level.WARN)
}
}
}
return messageData
}
suspend fun convertMessageRecordToMsgSegment(record: MsgRecord, chatType: Int = record.chatType): ArrayList<MessageSegment> {
val peerId = when(chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString()
}
return convertMessageElementsToMsgSegment(chatType, record.elements, peerId, record.channelId ?: peerId)
}
suspend fun convertMsgElementsToCQCode(
elements: List<MsgElement>,
chatType: Int,
peerId: String,
subPeer: String
): String {
if(elements.isEmpty()) {
return ""
}
val msgList = convertMessageElementsToMsgSegment(chatType, elements, peerId, subPeer).map {
it.toJson()
}
return MessageHelper.encodeCQCode(msgList)
}
suspend fun convertMessageRecordToCQCode(record: MsgRecord, chatType: Int = record.chatType): String {
val peerId = when(chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString()
}
return MessageHelper.encodeCQCode(
convertMessageElementsToMsgSegment(
chatType,
record.elements,
peerId,
record.channelId ?: peerId
).map { it.toJson() }
)
}
}
internal fun interface IMessageConvert {
suspend fun convert(chatType: Int, peerId: String, subPeer: String, element: MsgElement): MessageSegment
}

View File

@ -1,493 +0,0 @@
package moe.fuqiuluo.qqinterface.servlet.msg.convert
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
import moe.fuqiuluo.shamrock.helper.ContactHelper
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.helper.db.ImageDB
import moe.fuqiuluo.shamrock.helper.db.ImageMapping
import moe.fuqiuluo.shamrock.helper.db.MessageDB
import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.json
internal sealed class MessageElemConverter: IMessageConvert {
/**
* 文本 / 艾特 消息转换消息段
*/
data object TextConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val text = element.textElement
return if (text.atType != MsgConstant.ATTYPEUNKNOWN) {
MessageSegment(
type = "at",
data = hashMapOf(
"qq" to ContactHelper.getUinByUidAsync(text.atNtUid),
)
)
} else {
MessageSegment(
type = "text",
data = hashMapOf(
"text" to text.content
)
)
}
}
}
/**
* 小表情 / 戳一戳 消息转换消息段
*/
data object FaceConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val face = element.faceElement
if (face.faceType == 5) {
return MessageSegment(
type = "poke",
data = hashMapOf(
"type" to face.pokeType,
"id" to face.vaspokeId,
"strength" to face.pokeStrength
)
)
}
when (face.faceIndex) {
114 -> {
return MessageSegment(
type = "basketball",
data = hashMapOf(
"id" to face.resultId.ifEmpty { "0" }.toInt(),
)
)
}
358 -> {
if (face.sourceType == 1) return MessageSegment("new_dice")
return MessageSegment(
type = "new_dice",
data = hashMapOf(
"id" to face.resultId.ifEmpty { "0" }.toInt()
)
)
}
359 -> {
if (face.resultId.isEmpty()) return MessageSegment("new_rps")
return MessageSegment(
type = "new_rps",
data = hashMapOf(
"id" to face.resultId.ifEmpty { "0" }.toInt()
)
)
}
394 -> {
//LogCenter.log(face.toString())
return MessageSegment(
type = "face",
data = hashMapOf(
"id" to face.faceIndex,
"big" to (face.faceType == 3),
"result" to (face.resultId ?: "1")
)
)
}
else -> return MessageSegment(
type = "face",
data = hashMapOf(
"id" to face.faceIndex,
"big" to (face.faceType == 3)
)
)
}
}
}
/**
* 图片消息转换消息段
*/
data object ImageConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val image = element.picElement
val md5 = image.md5HexStr ?: image.fileName
.replace("{", "")
.replace("}", "")
.replace("-", "").split(".")[0]
ImageDB.getInstance().imageMappingDao().insert(
ImageMapping(md5.uppercase(), chatType, image.fileSize)
)
return MessageSegment(
type = "image",
data = hashMapOf(
"file" to md5,
"url" to when(chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(md5)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(md5)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(md5)
else -> unknownChatType(chatType)
},
"subType" to image.picSubType,
"type" to if (image.isFlashPic == true) "flash" else if(image.original) "original" else "show"
)
)
}
}
/**
* 语音消息转换消息段
*/
data object VoiceConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val record = element.pttElement
val md5 = if (record.fileName.startsWith("silk"))
record.fileName.substring(5)
else record.md5HexStr
return MessageSegment(
type = "record",
data = hashMapOf(
"file" to md5,
"url" to when(chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPttDownUrl("0", record.md5HexStr, record.fileUuid)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", record.fileUuid)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl("0", record.md5HexStr, record.fileUuid)
else -> unknownChatType(chatType)
}
).also {
if(record.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE) {
it["magic"] = "1".json
}
if ((it["url"] as String).isBlank()) {
it.remove("url")
}
}
)
}
}
/**
* 视频消息转换消息段
*/
data object VideoConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val video = element.videoElement
val md5 = video.fileName.split(".")[0]
return MessageSegment(
type = "video",
data = hashMapOf(
"file" to video.fileName,
"url" to when(chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl(peerId, md5, video.fileUuid)
else -> unknownChatType(chatType)
}
).also {
if ((it["url"] as String).isBlank())
it.remove("url")
}
)
}
}
/**
* 商城大表情消息转换消息段
*/
data object MarketFaceConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val face = element.marketFaceElement
return when (face.emojiId.lowercase()) {
"4823d3adb15df08014ce5d6796b76ee1" -> MessageSegment("dice")
"83c8a293ae65ca140f348120a77448ee" -> MessageSegment("rps")
else -> MessageSegment(
type = "mface",
data = hashMapOf(
"id" to face.emojiId
)
)
}
}
}
/**
* JSON消息转消息段
*/
data object StructJsonConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val data = element.arkElement.bytesData.asJsonObject
return when (data["app"].asString) {
"com.tencent.multimsg" -> {
val info = data["meta"].asJsonObject["detail"].asJsonObject
MessageSegment(
type = "forward",
data = mapOf(
"id" to info["resid"].asString
)
)
}
"com.tencent.troopsharecard" -> {
val info = data["meta"].asJsonObject["contact"].asJsonObject
MessageSegment(
type = "contact",
data = hashMapOf(
"type" to "group",
"id" to info["jumpUrl"].asString.split("group_code=")[1]
)
)
}
"com.tencent.contact.lua" -> {
val info = data["meta"].asJsonObject["contact"].asJsonObject
MessageSegment(
type = "contact",
data = hashMapOf(
"type" to "private",
"id" to info["jumpUrl"].asString.split("uin=")[1]
)
)
}
"com.tencent.map" -> {
val info = data["meta"].asJsonObject["Location.Search"].asJsonObject
MessageSegment(
type = "location",
data = hashMapOf(
"lat" to info["lat"].asString,
"lon" to info["lng"].asString,
"content" to info["address"].asString,
"title" to info["name"].asString
)
)
}
else -> MessageSegment(
type = "json",
data = mapOf(
"data" to element.arkElement.bytesData.asJsonObject.toString()
)
)
}
}
}
/**
* 回复消息转消息段
*/
data object ReplyConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val reply = element.replyElement
val msgId = reply.replayMsgId
val msgHash = if (msgId != 0L) {
MessageHelper.generateMsgIdHash(chatType, msgId)
} else {
MessageDB.getInstance().messageMappingDao()
.queryByMsgSeq(chatType, peerId, reply.replayMsgSeq?.toInt() ?: 0)?.msgHashId
?:
kotlin.run {
LogCenter.log("消息映射关系未找到: Message($reply)", Level.WARN)
MessageHelper.generateMsgIdHash(chatType, reply.sourceMsgIdInRecords)
}
}
return MessageSegment(
type = "reply",
data = mapOf(
"id" to msgHash
)
)
}
}
/**
* 灰色提示条消息过滤
*/
data object GrayTipsConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val tip = element.grayTipElement
when(tip.subElementType) {
MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> {
val notify = tip.jsonGrayTipElement
when(notify.busiId) {
/* 新人入群 */ 17L, /* 群戳一戳 */1061L,
/* 群撤回 */1014L, /* 群设精消息 */2401L,
/* 群头衔 */2407L -> {}
else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN)
}
}
MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> {
val notify = tip.xmlElement
when(notify.busiId) {
/* 群戳一戳 */1061L, /* 群打卡 */1068L -> {}
else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN)
}
}
else -> LogCenter.log("不支持的提示类型: ${tip.subElementType}", Level.WARN)
}
// 提示类消息这里提供的是一个xml不具备解析通用性
// 在这里不推送
throw UnknownError()
}
}
/**
* 文件消息转换消息段
*/
data object FileConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val fileMsg = element.fileElement
val fileName = fileMsg.fileName
val fileSize = fileMsg.fileSize
val expireTime = fileMsg.expireTime ?: 0
val fileId = fileMsg.fileUuid
val bizId = fileMsg.fileBizId ?: 0
val fileSubId = fileMsg.fileSubId ?: ""
val url = when (chatType) {
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(peerId, subPeer, fileId, bizId)
else -> RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId)
}
return MessageSegment(
type = "file",
data = mapOf(
"name" to fileName,
"size" to fileSize,
"expire" to expireTime,
"id" to fileId,
"url" to url,
"biz" to bizId,
"sub" to fileSubId
)
)
}
}
/**
* 老板QQ的合并转发信息
*/
data object XmlMultiMsgConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val multiMsg = element.multiForwardMsgElement
return MessageSegment(
type = "forward",
data = mapOf(
"id" to multiMsg.resId
)
)
}
}
data object XmlLongMsgConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val longMsg = element.structLongMsgElement
return MessageSegment(
type = "forward",
data = mapOf(
"id" to longMsg.resId
)
)
}
}
data object MarkdownConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val markdown = element.markdownElement
return MessageSegment(
type = "markdown",
data = mapOf(
"content" to markdown.content
)
)
}
}
data object BubbleFaceConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val bubbleElement = element.faceBubbleElement
return MessageSegment(
type = "bubble_face",
data = mapOf(
"id" to bubbleElement.yellowFaceInfo.index,
"count" to (bubbleElement.faceCount ?: 1),
)
)
}
}
protected fun unknownChatType(chatType: Int) {
throw UnsupportedOperationException("Not supported chat type: $chatType")
}
}

View File

@ -0,0 +1,616 @@
package moe.fuqiuluo.qqinterface.servlet.msg.messageelement
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import moe.fuqiuluo.qqinterface.servlet.msg.MessageSegment
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asString
import protobuf.message.MessageElement
internal suspend fun List<MessageElement>.toSegments(
chatType: Int,
peerId: String,
subPeer: String
): List<MessageSegment> {
val messageData = arrayListOf<MessageSegment>()
this.forEach { msg ->
kotlin.runCatching {
val elementType = if (msg.text != null) {
1
} else if (msg.face != null) {
2
} else if (msg.json != null) {
51
} else if (msg.comm != null) {
53
} else
throw UnsupportedOperationException("不支持的消息element类型$msg")
val converter = MessageElementConverter[elementType]
converter?.invoke(chatType, peerId, subPeer, msg)
?: throw UnsupportedOperationException("不支持的消息element类型$elementType")
}.onSuccess {
messageData.add(it)
}.onFailure {
if (it is UnknownError) {
// 不处理的消息类型抛出unknown error
} else {
LogCenter.log("消息element转换错误$it", Level.WARN)
}
}
}
return messageData
}
internal typealias IMessageElementConverter = suspend (Int, String, String, MessageElement) -> MessageSegment
internal object MessageElementConverter {
private val convertMap = hashMapOf(
1 to MessageElementConverter::convertTextElem,
// MsgConstant.KELEMTYPEFACE to MessageElementConverter::convertFaceElem,
// MsgConstant.KELEMTYPEPIC to MessageElementConverter::convertImageElem,
// MsgConstant.KELEMTYPEPTT to MessageElementConverter::convertVoiceElem,
// MsgConstant.KELEMTYPEVIDEO to MessageElementConverter::convertVideoElem,
// MsgConstant.KELEMTYPEMARKETFACE to MessageElementConverter::convertMarketFaceElem,
51 to MessageElementConverter::convertStructJsonElem,
// MsgConstant.KELEMTYPEREPLY to MessageElementConverter::convertReplyElem,
// MsgConstant.KELEMTYPEGRAYTIP to MessageElementConverter::convertGrayTipsElem,
// MsgConstant.KELEMTYPEFILE to MessageElementConverter::convertFileElem,
// MsgConstant.KELEMTYPEMARKDOWN to MessageElementConverter::convertMarkdownElem,
// //MsgConstant.KELEMTYPEMULTIFORWARD to MessageElementConverter::convertXmlMultiMsgElem,
// //MsgConstant.KELEMTYPESTRUCTLONGMSG to MessageElementConverter::convertXmlLongMsgElem,
// MsgConstant.KELEMTYPEFACEBUBBLE to MessageElementConverter::convertBubbleFaceElem,
// MsgConstant.KELEMTYPEINLINEKEYBOARD to MessageElementConverter::convertInlineKeyboardElem,
)
operator fun get(type: Int): IMessageElementConverter? = convertMap[type]
/**
* 文本 / 艾特 消息转换消息段
*/
private suspend fun convertTextElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MessageElement
): MessageSegment {
val text = element.text!!
if (text.attr6Buf != null) {
val at = ByteReadPacket(text.attr6Buf!!)
at.discardExact(7)
val uin = at.readUInt()
return MessageSegment(
type = "at",
data = hashMapOf(
"qq" to uin
)
)
} else if (text.pbReserve != null) {
val resv = text.pbReserve!!
return MessageSegment(
type = "at",
data = hashMapOf(
"qq" to when (resv.atType) {
2 -> resv.atMemberTinyid!!
4 -> resv.atChannelInfo!!.channelId!!
else -> throw UnsupportedOperationException("Unknown at type: ${resv.atType}")
}
)
)
} else {
return MessageSegment(
type = "text",
data = hashMapOf(
"text" to text.text!!
)
)
}
}
// /**
// * 小表情 / 戳一戳 消息转换消息段
// */
// private suspend fun convertFaceElem(
// chatType: Int,
// peerId: String,
// subPeer: String,
// element: MessageElement
// ): MessageSegment {
// val face = element.faceElement
//
// if (face.faceType == 5) {
// return MessageSegment(
// type = "poke",
// data = hashMapOf(
// "type" to face.pokeType,
// "id" to face.vaspokeId,
// "strength" to face.pokeStrength
// )
// )
// }
// when (face.faceIndex) {
// 114 -> {
// return MessageSegment(
// type = "basketball",
// data = hashMapOf(
// "id" to face.resultId.ifEmpty { "0" }.toInt(),
// )
// )
// }
//
// 358 -> {
// if (face.sourceType == 1) return MessageSegment("new_dice")
// return MessageSegment(
// type = "new_dice",
// data = hashMapOf(
// "id" to face.resultId.ifEmpty { "0" }.toInt()
// )
// )
// }
//
// 359 -> {
// if (face.resultId.isEmpty()) return MessageSegment("new_rps")
// return MessageSegment(
// type = "new_rps",
// data = hashMapOf(
// "id" to face.resultId.ifEmpty { "0" }.toInt()
// )
// )
// }
//
// 394 -> {
// //LogCenter.log(face.toString())
// return MessageSegment(
// type = "face",
// data = hashMapOf(
// "id" to face.faceIndex,
// "big" to (face.faceType == 3),
// "result" to (face.resultId ?: "1")
// )
// )
// }
//
// else -> return MessageSegment(
// type = "face",
// data = hashMapOf(
// "id" to face.faceIndex,
// "big" to (face.faceType == 3)
// )
// )
// }
// }
//
// /**
// * 图片消息转换消息段
// */
// private suspend fun convertImageElem(
// chatType: Int,
// peerId: String,
// subPeer: String,
// element: MessageElement
// ): MessageSegment {
// val image = element.picElement
// val md5 = image.md5HexStr ?: image.fileName
// .replace("{", "")
// .replace("}", "")
// .replace("-", "").split(".")[0]
//
// ImageDB.getInstance().imageMappingDao().insert(
// ImageMapping(md5.uppercase(), chatType, image.fileSize)
// )
//
// //LogCenter.log(image.toString())
//
// val originalUrl = image.originImageUrl ?: ""
// //LogCenter.log({ "receive image: $image" }, Level.DEBUG)
//
// return MessageSegment(
// type = "image",
// data = hashMapOf(
// "file" to md5,
// "url" to when (chatType) {
// MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
// originalUrl,
// md5
// )
//
// MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(originalUrl, md5)
// MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(originalUrl, md5)
// else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
// },
// "subType" to image.picSubType,
// "type" to if (image.isFlashPic == true) "flash" else if (image.original) "original" else "show"
// )
// )
// }
//
// /**
// * 语音消息转换消息段
// */
// private suspend fun convertVoiceElem(
// chatType: Int,
// peerId: String,
// subPeer: String,
// element: MessageElement
// ): MessageSegment {
// val record = element.pttElement
//
// val md5 = if (record.fileName.startsWith("silk"))
// record.fileName.substring(5)
// else record.md5HexStr
//
// return MessageSegment(
// type = "record",
// data = hashMapOf(
// "file" to md5,
// "url" to when (chatType) {
// MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPttDownUrl(
// "0",
// record.md5HexStr,
// record.fileUuid
// )
//
// MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", record.fileUuid)
// MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl(
// "0",
// record.md5HexStr,
// record.fileUuid
// )
//
// else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
// }
// ).also {
// if (record.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE) {
// it["magic"] = "1"
// }
// if ((it["url"] as String).isBlank()) {
// it.remove("url")
// }
// }
// )
// }
//
// /**
// * 视频消息转换消息段
// */
// private suspend fun convertVideoElem(
// chatType: Int,
// peerId: String,
// subPeer: String,
// element: MessageElement
// ): MessageSegment {
// val video = element.videoElement
// val md5 = if (video.fileName.contains("/")) {
// video.videoMd5.takeIf {
// !it.isNullOrEmpty()
// }?.hex2ByteArray() ?: video.fileName.split("/").let {
// it[it.size - 2].hex2ByteArray()
// }
// } else video.fileName.split(".")[0].hex2ByteArray()
//
// //LogCenter.log({ "receive video msg: $video" }, Level.DEBUG)
//
// return MessageSegment(
// type = "video",
// data = hashMapOf(
// "file" to video.fileName,
// "url" to when (chatType) {
// MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
// MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
// MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
// else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
// }
// ).also {
// if ((it["url"] as String).isBlank())
// it.remove("url")
// }
// )
// }
//
// /**
// * 商城大表情消息转换消息段
// */
// private suspend fun convertMarketFaceElem(
// chatType: Int,
// peerId: String,
// subPeer: String,
// element: MessageElement
// ): MessageSegment {
// val face = element.marketFaceElement
// return when (face.emojiId.lowercase()) {
// "4823d3adb15df08014ce5d6796b76ee1" -> MessageSegment("dice")
// "83c8a293ae65ca140f348120a77448ee" -> MessageSegment("rps")
// else -> MessageSegment(
// type = "mface",
// data = hashMapOf(
// "id" to face.emojiId
// )
// )
// }
// }
//
/**
* JSON消息转消息段
*/
private suspend fun convertStructJsonElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MessageElement
): MessageSegment {
val data = element.json!!.data!!
val jsonStr =
(if (data[0].toInt() == 1) DeflateTools.uncompress(data.sliceArray(1 until data.size)) else data.sliceArray(1 until data.size)).toString()
val json = jsonStr.asJsonObject
return when (json["app"].asString) {
"com.tencent.multimsg" -> {
val info = json["meta"].asJsonObject["detail"].asJsonObject
MessageSegment(
type = "forward",
data = mapOf(
"id" to info["resid"].asString
)
)
}
"com.tencent.troopsharecard" -> {
val info = json["meta"].asJsonObject["contact"].asJsonObject
MessageSegment(
type = "contact",
data = hashMapOf(
"type" to "group",
"id" to info["jumpUrl"].asString.split("group_code=")[1]
)
)
}
"com.tencent.contact.lua" -> {
val info = json["meta"].asJsonObject["contact"].asJsonObject
MessageSegment(
type = "contact",
data = hashMapOf(
"type" to "private",
"id" to info["jumpUrl"].asString.split("uin=")[1]
)
)
}
"com.tencent.map" -> {
val info = json["meta"].asJsonObject["Location.Search"].asJsonObject
MessageSegment(
type = "location",
data = hashMapOf(
"lat" to info["lat"].asString,
"lon" to info["lng"].asString,
"content" to info["address"].asString,
"title" to info["name"].asString
)
)
}
else -> MessageSegment(
type = "json",
data = mapOf(
"data" to jsonStr
)
)
}
}
// /**
// * 回复消息转消息段
// */
// private suspend fun convertReplyElem(
// chatType: Int,
// peerId: String,
// subPeer: String,
// element: MessageElement
// ): MessageSegment {
// val reply = element.replyElement
// val msgId = reply.replayMsgId
// val msgHash = if (msgId != 0L) {
// MessageHelper.generateMsgIdHash(chatType, msgId)
// } else {
// MessageDB.getInstance().messageMappingDao()
// .queryByMsgSeq(chatType, peerId, reply.replayMsgSeq?.toInt() ?: 0)?.msgHashId
// ?: kotlin.run {
// LogCenter.log("消息映射关系未找到: Message($reply)", Level.WARN)
// MessageHelper.generateMsgIdHash(chatType, reply.sourceMsgIdInRecords)
// }
// }
//
// return MessageSegment(
// type = "reply",
// data = mapOf(
// "id" to msgHash
// )
// )
// }
//
// /**
// * 灰色提示条消息过滤
// */
// private suspend fun convertGrayTipsElem(
// chatType: Int,
// peerId: String,
// subPeer: String,
// element: MessageElement
// ): MessageSegment {
// val tip = element.grayTipElement
// when (tip.subElementType) {
// MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> {
// val notify = tip.jsonGrayTipElement
// when (notify.busiId) {
// /* 新人入群 */ 17L, /* 群戳一戳 */1061L,
// /* 群撤回 */1014L, /* 群设精消息 */2401L,
// /* 群头衔 */2407L -> {
// }
//
// else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN)
// }
// }
//
// MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> {
// val notify = tip.xmlElement
// when (notify.busiId) {
// /* 群戳一戳 */1061L, /* 群打卡 */1068L -> {}
// else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN)
// }
// }
//
// else -> LogCenter.log("不支持的提示类型: ${tip.subElementType}", Level.WARN)
// }
// // 提示类消息这里提供的是一个xml不具备解析通用性
// // 在这里不推送
// throw UnknownError()
// }
//
// /**
// * 文件消息转换消息段
// */
// private suspend fun convertFileElem(
// chatType: Int,
// peerId: String,
// subPeer: String,
// element: MessageElement
// ): MessageSegment {
// val fileMsg = element.fileElement
// val fileName = fileMsg.fileName
// val fileSize = fileMsg.fileSize
// val expireTime = fileMsg.expireTime ?: 0
// val fileId = fileMsg.fileUuid
// val bizId = fileMsg.fileBizId ?: 0
// val fileSubId = fileMsg.fileSubId ?: ""
// val url = when (chatType) {
// MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
// MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(peerId, subPeer, fileId, bizId)
// else -> RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId)
// }
//
// return MessageSegment(
// type = "file",
// data = mapOf(
// "name" to fileName,
// "size" to fileSize,
// "expire" to expireTime,
// "id" to fileId,
// "url" to url,
// "biz" to bizId,
// "sub" to fileSubId
// )
// )
// }
//
// /**
// * 老板QQ的合并转发信息
// */
// private suspend fun convertXmlMultiMsgElem(
// chatType: Int,
// peerId: String,
// subPeer: String,
// element: MessageElement
// ): MessageSegment {
// val multiMsg = element.multiForwardMessageElement
// return MessageSegment(
// type = "forward",
// data = mapOf(
// "id" to multiMsg.resId
// )
// )
// }
//
// private suspend fun convertXmlLongMsgElem(
// chatType: Int,
// peerId: String,
// subPeer: String,
// element: MessageElement
// ): MessageSegment {
// val longMsg = element.structLongMessageElement
// return MessageSegment(
// type = "forward",
// data = mapOf(
// "id" to longMsg.resId
// )
// )
// }
//
// private suspend fun convertMarkdownElem(
// chatType: Int,
// peerId: String,
// subPeer: String,
// element: MessageElement
// ): MessageSegment {
// val markdown = element.markdownElement
// return MessageSegment(
// type = "markdown",
// data = mapOf(
// "content" to markdown.content
// )
// )
// }
//
// private suspend fun convertBubbleFaceElem(
// chatType: Int,
// peerId: String,
// subPeer: String,
// element: MessageElement
// ): MessageSegment {
// val bubbleElement = element.faceBubbleElement
// return MessageSegment(
// type = "bubble_face",
// data = mapOf(
// "id" to bubbleElement.yellowFaceInfo.index,
// "count" to (bubbleElement.faceCount ?: 1),
// )
// )
// }
//
// private suspend fun convertInlineKeyboardElem(
// chatType: Int,
// peerId: String,
// subPeer: String,
// element: MessageElement
// ): MessageSegment {
// val keyboard = element.inlineKeyboardElement
// return MessageSegment(
// type = "inline_keyboard",
// data = mapOf(
// "data" to buildJsonObject {
// putJsonArray("rows") {
// keyboard.rows.forEach { row ->
// add(buildJsonObject row@{
// putJsonArray("buttons") {
// row.buttons.forEach { button ->
// add(buildJsonObject {
// put("id", button.id ?: "")
// put("label", button.label ?: "")
// put("visited_label", button.visitedLabel ?: "")
// put("style", button.style)
// put("type", button.type)
// put("click_limit", button.clickLimit)
// put("unsupport_tips", button.unsupportTips ?: "")
// put("data", button.data)
// put("at_bot_show_channel_list", button.atBotShowChannelList)
// put("permission_type", button.permissionType)
// putJsonArray("specify_role_ids") {
// button.specifyRoleIds?.forEach { add(it) }
// }
// putJsonArray("specify_tinyids") {
// button.specifyTinyids?.forEach { add(it) }
// }
// })
// }
// }
// })
// }
// }
// put("bot_appid", keyboard.botAppid)
// }.toString()
// )
// )
// }
}

View File

@ -0,0 +1,183 @@
package moe.fuqiuluo.qqinterface.servlet.msg.messageelement
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import kotlinx.serialization.json.JsonObject
import moe.fuqiuluo.qqinterface.servlet.GProSvc
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
import moe.fuqiuluo.shamrock.helper.*
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.ParamsException
import moe.fuqiuluo.shamrock.tools.*
import moe.fuqiuluo.shamrock.utils.DeflateTools
import protobuf.message.MessageElement
import protobuf.message.element.FaceElement
import protobuf.message.element.JsonElement
import protobuf.message.element.TextElement
import java.nio.ByteBuffer
internal typealias IMessageElementMaker = suspend (Int, Long, String, JsonObject) -> Result<MessageElement>
internal object MessageElementMaker {
private val makerArray = hashMapOf(
"text" to MessageElementMaker::createTextElem,
"face" to MessageElementMaker::createFaceElem,
// "pic" to MessageElementMaker::createImageElem,
// "image" to MessageElementMaker::createImageElem,
// "voice" to MessageElementMaker::createRecordElem,
// "record" to MessageElementMaker::createRecordElem,
"at" to MessageElementMaker::createAtElem,
// "video" to MessageElementMaker::createVideoElem,
// "markdown" to MessageElementMaker::createMarkdownElem,
// "dice" to MessageElementMaker::createDiceElem,
// "rps" to MessageElementMaker::createRpsElem,
// "poke" to MessageElementMaker::createPokeElem,
// "anonymous" to MessageElementMaker::createAnonymousElem,
// "share" to MessageElementMaker::createShareElem,
// "contact" to MessageElementMaker::createContactElem,
// "location" to MessageElementMaker::createLocationElem,
// "music" to MessageElementMaker::createMusicElem,
// "reply" to MessageElementMaker::createReplyElem,
// "touch" to MessageElementMaker::createTouchElem,
// "weather" to MessageElementMaker::createWeatherElem,
"json" to MessageElementMaker::createJsonElem,
//"new_dice" to MessageElementMaker::createNewDiceElem,
//"new_rps" to MessageElementMaker::createNewRpsElem,
//"basketball" to MessageElementMaker::createBasketballElem,
//"node" to MessageMaker::createNodeElem,
//"multi_msg" to MessageMaker::createLongMsgStruct,
//"bubble_face" to MessageElementMaker::createBubbleFaceElem,
)
operator fun get(type: String): IMessageElementMaker? = makerArray[type]
private suspend fun createTextElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MessageElement> {
data.checkAndThrow("text")
val elem = MessageElement(
text = TextElement(data["text"].asString)
)
return Result.success(elem)
}
private suspend fun createFaceElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MessageElement> {
data.checkAndThrow("id")
val elem = MessageElement(
face = FaceElement(data["id"].asInt)
)
return Result.success(elem)
}
private suspend fun createAtElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MessageElement> {
return if (chatType == MsgConstant.KCHATTYPEGROUP) {
data.checkAndThrow("qq")
val qq: Long
val type: Int
lateinit var display: String
when (val qqStr = data["qq"].asString) {
"0", "all" -> {
qq = 0
type = 1
display = "@全体成员"
}
"online" -> {
qq = 0
type = 64
display = "@在线成员"
}
else -> {
qq = qqStr.toLong()
type = 0
display =
"@" + (data["name"].asStringOrNull ?: GroupSvc.getTroopMemberInfoByUinV2(peerId, qqStr, true)
.onSuccess {
it.troopnick
.ifEmpty { it.friendnick }
.ifEmpty { qqStr }
}.onFailure {
LogCenter.log("无法获取群成员信息: $qqStr", Level.ERROR)
})
}
}
val attr6: ByteBuffer = ByteBuffer.allocate(6)
attr6.put(byteArrayOf(0, 1, 0, 0, 0))
attr6.putChar(display.length.toChar())
attr6.putChar(type.toChar())
attr6.putBuf32Long(qq)
attr6.put(byteArrayOf(0, 0))
val elem = MessageElement(
text = TextElement(text = display, attr6Buf = attr6.array())
)
Result.success(elem)
} else if (chatType == MsgConstant.KCHATTYPEGUILD) {
data.checkAndThrow("qq")
val qq: Long
val type: Int
lateinit var display: String
when (val qqStr = data["qq"].asString) {
"0", "all" -> {
type = 2
display = "@全体成员"
}
else -> {
qq = qqStr.toLong()
type = 2
display =
"@" + (data["name"].asStringOrNull ?: GProSvc.getUserGuildInfo(0UL, 0UL)
.onSuccess {
it.nickName.ifNullOrEmpty(qqStr)
}.onFailure {
LogCenter.log("无法获取频道组成员信息: $qqStr", Level.ERROR)
})
}
}
val elem = MessageElement(
text = TextElement(text = display, pbReserve = TextElement.Companion.TextResvAttr(atType = type))
)
Result.success(elem)
} else Result.failure(ActionMsgException)
}
private suspend fun createJsonElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MessageElement> {
data.checkAndThrow("data")
val elem = MessageElement(
json = JsonElement(
data = DeflateTools.compress(data.toString().toByteArray())
)
)
return Result.success(elem)
}
private fun JsonObject.checkAndThrow(vararg key: String) {
key.forEach {
if (!containsKey(it)) throw ParamsException(it)
}
}
}

View File

@ -0,0 +1,629 @@
package moe.fuqiuluo.qqinterface.servlet.msg.msgelement
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import kotlinx.serialization.json.*
import moe.fuqiuluo.qqinterface.servlet.msg.MessageSegment
import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
import moe.fuqiuluo.shamrock.helper.ContactHelper
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.helper.db.ImageDB
import moe.fuqiuluo.shamrock.helper.db.ImageMapping
import moe.fuqiuluo.shamrock.helper.db.MessageDB
import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
internal suspend fun List<MsgElement>.toSegments(chatType: Int, peerId: String, subPeer: String): List<MessageSegment> {
val messageData = arrayListOf<MessageSegment>()
this.forEach { msg ->
kotlin.runCatching {
val converter = MsgElementConverter[msg.elementType]
converter?.invoke(chatType, peerId, subPeer, msg)
?: throw UnsupportedOperationException("不支持的消息element类型${msg.elementType}")
}.onSuccess {
messageData.add(it)
}.onFailure {
if (it is UnknownError) {
// 不处理的消息类型抛出unknown error
} else {
LogCenter.log("消息element转换错误$it, elementType: ${msg.elementType}", Level.WARN)
}
}
}
return messageData
}
internal suspend fun List<MsgElement>.toCQCode(chatType: Int, peerId: String, subPeer: String): String {
if (this.isEmpty()) {
return ""
}
return MessageHelper.nativeEncodeCQCode(this.toSegments(chatType, peerId, subPeer).map {
val params = hashMapOf<String, String>()
params["_type"] = it.type
it.data.forEach { (key, value) ->
params[key] = value.toString()
}
params
})
}
internal typealias IMsgElementConverter = suspend (Int, String, String, MsgElement) -> MessageSegment
internal object MsgElementConverter {
private val convertMap = hashMapOf(
MsgConstant.KELEMTYPETEXT to MsgElementConverter::convertTextElem,
MsgConstant.KELEMTYPEFACE to MsgElementConverter::convertFaceElem,
MsgConstant.KELEMTYPEPIC to MsgElementConverter::convertImageElem,
MsgConstant.KELEMTYPEPTT to MsgElementConverter::convertVoiceElem,
MsgConstant.KELEMTYPEVIDEO to MsgElementConverter::convertVideoElem,
MsgConstant.KELEMTYPEMARKETFACE to MsgElementConverter::convertMarketFaceElem,
MsgConstant.KELEMTYPEARKSTRUCT to MsgElementConverter::convertStructJsonElem,
MsgConstant.KELEMTYPEREPLY to MsgElementConverter::convertReplyElem,
MsgConstant.KELEMTYPEGRAYTIP to MsgElementConverter::convertGrayTipsElem,
MsgConstant.KELEMTYPEFILE to MsgElementConverter::convertFileElem,
MsgConstant.KELEMTYPEMARKDOWN to MsgElementConverter::convertMarkdownElem,
//MsgConstant.KELEMTYPEMULTIFORWARD to MsgElementConverter::convertXmlMultiMsgElem,
//MsgConstant.KELEMTYPESTRUCTLONGMSG to MsgElementConverter::convertXmlLongMsgElem,
MsgConstant.KELEMTYPEFACEBUBBLE to MsgElementConverter::convertBubbleFaceElem,
MsgConstant.KELEMTYPEINLINEKEYBOARD to MsgElementConverter::convertInlineKeyboardElem
)
operator fun get(type: Int): IMsgElementConverter? = convertMap[type]
/**
* 文本 / 艾特 消息转换消息段
*/
private suspend fun convertTextElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val text = element.textElement
return if (text.atType != MsgConstant.ATTYPEUNKNOWN) {
MessageSegment(
type = "at",
data = hashMapOf(
"qq" to ContactHelper.getUinByUidAsync(text.atNtUid),
)
)
} else {
MessageSegment(
type = "text",
data = hashMapOf(
"text" to text.content
)
)
}
}
/**
* 小表情 / 戳一戳 消息转换消息段
*/
private suspend fun convertFaceElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val face = element.faceElement
if (face.faceType == 5) {
return MessageSegment(
type = "poke",
data = hashMapOf(
"type" to face.pokeType,
"id" to face.vaspokeId,
"strength" to face.pokeStrength
)
)
}
when (face.faceIndex) {
114 -> {
return MessageSegment(
type = "basketball",
data = hashMapOf(
"id" to face.resultId.ifEmpty { "0" }.toInt(),
)
)
}
358 -> {
if (face.sourceType == 1) return MessageSegment("new_dice")
return MessageSegment(
type = "new_dice",
data = hashMapOf(
"id" to face.resultId.ifEmpty { "0" }.toInt()
)
)
}
359 -> {
if (face.resultId.isEmpty()) return MessageSegment("new_rps")
return MessageSegment(
type = "new_rps",
data = hashMapOf(
"id" to face.resultId.ifEmpty { "0" }.toInt()
)
)
}
394 -> {
//LogCenter.log(face.toString())
return MessageSegment(
type = "face",
data = hashMapOf(
"id" to face.faceIndex,
"big" to (face.faceType == 3),
"result" to (face.resultId ?: "1")
)
)
}
else -> return MessageSegment(
type = "face",
data = hashMapOf(
"id" to face.faceIndex,
"big" to (face.faceType == 3)
)
)
}
}
/**
* 图片消息转换消息段
*/
private suspend fun convertImageElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val image = element.picElement
val md5 = (image.md5HexStr ?: image.fileName
.replace("{", "")
.replace("}", "")
.replace("-", "").split(".")[0])
.uppercase()
ImageDB.getInstance().imageMappingDao().insert(
ImageMapping(md5, chatType, image.fileSize)
)
//LogCenter.log(image.toString())
val originalUrl = image.originImageUrl ?: ""
LogCenter.log({ "receive image: $image" }, Level.DEBUG)
return MessageSegment(
type = "image",
data = hashMapOf(
"file" to md5,
"url" to when (chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = peerId
)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = peerId,
)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = peerId,
subPeer = subPeer
)
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
},
"subType" to image.picSubType,
"type" to if (image.isFlashPic == true) "flash" else if (image.original) "original" else "show"
)
)
}
/**
* 语音消息转换消息段
*/
private suspend fun convertVoiceElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val record = element.pttElement
val md5 = if (record.fileName.startsWith("silk"))
record.fileName.substring(5)
else record.md5HexStr
return MessageSegment(
type = "record",
data = hashMapOf(
"file" to md5,
"url" to when (chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPttDownUrl(
"0",
record.md5HexStr,
record.fileUuid
)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", record.fileUuid)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl(
"0",
record.md5HexStr,
record.fileUuid
)
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
}
).also {
if (record.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE) {
it["magic"] = "1"
}
if ((it["url"] as String).isBlank()) {
it.remove("url")
}
}
)
}
/**
* 视频消息转换消息段
*/
private suspend fun convertVideoElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val video = element.videoElement
val md5 = if (video.fileName.contains("/")) {
video.videoMd5.takeIf {
!it.isNullOrEmpty()
}?.hex2ByteArray() ?: video.fileName.split("/").let {
it[it.size - 2].hex2ByteArray()
}
} else video.fileName.split(".")[0].hex2ByteArray()
//LogCenter.log({ "receive video msg: $video" }, Level.DEBUG)
return MessageSegment(
type = "video",
data = hashMapOf(
"file" to video.fileName,
"url" to when (chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
}
).also {
if ((it["url"] as String).isBlank())
it.remove("url")
}
)
}
/**
* 商城大表情消息转换消息段
*/
private suspend fun convertMarketFaceElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val face = element.marketFaceElement
return when (face.emojiId.lowercase()) {
"4823d3adb15df08014ce5d6796b76ee1" -> MessageSegment("dice")
"83c8a293ae65ca140f348120a77448ee" -> MessageSegment("rps")
else -> MessageSegment(
type = "mface",
data = hashMapOf(
"id" to face.emojiId
)
)
}
}
/**
* JSON消息转消息段
*/
private suspend fun convertStructJsonElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val data = element.arkElement.bytesData.asJsonObject
return when (data["app"].asString) {
"com.tencent.multimsg" -> {
val info = data["meta"].asJsonObject["detail"].asJsonObject
MessageSegment(
type = "forward",
data = mapOf(
"id" to info["resid"].asString
)
)
}
"com.tencent.troopsharecard" -> {
val info = data["meta"].asJsonObject["contact"].asJsonObject
MessageSegment(
type = "contact",
data = hashMapOf(
"type" to "group",
"id" to info["jumpUrl"].asString.split("group_code=")[1]
)
)
}
"com.tencent.contact.lua" -> {
val info = data["meta"].asJsonObject["contact"].asJsonObject
MessageSegment(
type = "contact",
data = hashMapOf(
"type" to "private",
"id" to info["jumpUrl"].asString.split("uin=")[1]
)
)
}
"com.tencent.map" -> {
val info = data["meta"].asJsonObject["Location.Search"].asJsonObject
MessageSegment(
type = "location",
data = hashMapOf(
"lat" to info["lat"].asString,
"lon" to info["lng"].asString,
"content" to info["address"].asString,
"title" to info["name"].asString
)
)
}
else -> MessageSegment(
type = "json",
data = mapOf(
"data" to element.arkElement.bytesData.asJsonObject.toString()
)
)
}
}
/**
* 回复消息转消息段
*/
private suspend fun convertReplyElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val reply = element.replyElement
val msgId = reply.replayMsgId
val msgHash = if (msgId != 0L) {
MessageHelper.generateMsgIdHash(chatType, msgId)
} else {
MessageDB.getInstance().messageMappingDao()
.queryByMsgSeq(chatType, peerId, reply.replayMsgSeq?.toInt() ?: 0)?.msgHashId
?: kotlin.run {
LogCenter.log("消息映射关系未找到: Message($reply)", Level.WARN)
MessageHelper.generateMsgIdHash(chatType, reply.sourceMsgIdInRecords)
}
}
return MessageSegment(
type = "reply",
data = mapOf(
"id" to msgHash
)
)
}
/**
* 灰色提示条消息过滤
*/
private suspend fun convertGrayTipsElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val tip = element.grayTipElement
when (tip.subElementType) {
MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> {
val notify = tip.jsonGrayTipElement
when (notify.busiId) {
/* 新人入群 */ 17L, /* 群戳一戳 */1061L,
/* 群撤回 */1014L, /* 群设精消息 */2401L,
/* 群头衔 */2407L -> {
}
else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN)
}
}
MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> {
val notify = tip.xmlElement
when (notify.busiId) {
/* 群戳一戳 */1061L, /* 群打卡 */1068L -> {}
else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN)
}
}
else -> LogCenter.log("不支持的提示类型: ${tip.subElementType}", Level.WARN)
}
// 提示类消息这里提供的是一个xml不具备解析通用性
// 在这里不推送
throw UnknownError()
}
/**
* 文件消息转换消息段
*/
private suspend fun convertFileElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val fileMsg = element.fileElement
val fileName = fileMsg.fileName
val fileSize = fileMsg.fileSize
val expireTime = fileMsg.expireTime ?: 0
val fileId = fileMsg.fileUuid
val bizId = fileMsg.fileBizId ?: 0
val fileSubId = fileMsg.fileSubId ?: ""
val url = when (chatType) {
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(peerId, subPeer, fileId, bizId)
else -> RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId)
}
return MessageSegment(
type = "file",
data = mapOf(
"name" to fileName,
"size" to fileSize,
"expire" to expireTime,
"id" to fileId,
"url" to url,
"biz" to bizId,
"sub" to fileSubId
)
)
}
/**
* 老板QQ的合并转发信息
*/
private suspend fun convertXmlMultiMsgElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val multiMsg = element.multiForwardMsgElement
return MessageSegment(
type = "forward",
data = mapOf(
"id" to multiMsg.resId
)
)
}
private suspend fun convertXmlLongMsgElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val longMsg = element.structLongMsgElement
return MessageSegment(
type = "forward",
data = mapOf(
"id" to longMsg.resId
)
)
}
private suspend fun convertMarkdownElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val markdown = element.markdownElement
return MessageSegment(
type = "markdown",
data = mapOf(
"content" to markdown.content
)
)
}
private suspend fun convertBubbleFaceElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val bubbleElement = element.faceBubbleElement
return MessageSegment(
type = "bubble_face",
data = mapOf(
"id" to bubbleElement.yellowFaceInfo.index,
"count" to (bubbleElement.faceCount ?: 1),
)
)
}
private suspend fun convertInlineKeyboardElem(
chatType: Int,
peerId: String,
subPeer: String,
element: MsgElement
): MessageSegment {
val keyboard = element.inlineKeyboardElement
return MessageSegment(
type = "inline_keyboard",
data = mapOf(
"data" to buildJsonObject {
putJsonArray("rows") {
keyboard.rows.forEach { row ->
add(buildJsonObject row@{
putJsonArray("buttons") {
row.buttons.forEach { button ->
add(buildJsonObject {
put("id", button.id ?: "")
put("label", button.label ?: "")
put("visited_label", button.visitedLabel ?: "")
put("style", button.style)
put("type", button.type)
put("click_limit", button.clickLimit)
put("unsupport_tips", button.unsupportTips ?: "")
put("data", button.data)
put("at_bot_show_channel_list", button.atBotShowChannelList)
put("permission_type", button.permissionType)
putJsonArray("specify_role_ids") {
button.specifyRoleIds?.forEach { add(it) }
}
putJsonArray("specify_tinyids") {
button.specifyTinyids?.forEach { add(it) }
}
})
}
}
})
}
}
put("bot_appid", keyboard.botAppid)
}.toString()
)
)
}
}

View File

@ -1,4 +1,4 @@
package moe.fuqiuluo.qqinterface.servlet.msg package moe.fuqiuluo.qqinterface.servlet.msg.msgelement
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import androidx.exifinterface.media.ExifInterface import androidx.exifinterface.media.ExifInterface
@ -8,22 +8,7 @@ import com.tencent.mobileqq.pb.ByteStringMicro
import com.tencent.mobileqq.qroute.QRoute import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qphone.base.remote.ToServiceMsg import com.tencent.qphone.base.remote.ToServiceMsg
import com.tencent.qqnt.aio.adapter.api.IAIOPttApi import com.tencent.qqnt.aio.adapter.api.IAIOPttApi
import com.tencent.qqnt.kernel.nativeinterface.ArkElement import com.tencent.qqnt.kernel.nativeinterface.*
import com.tencent.qqnt.kernel.nativeinterface.FaceBubbleElement
import com.tencent.qqnt.kernel.nativeinterface.FaceElement
import com.tencent.qqnt.kernel.nativeinterface.MarkdownElement
import com.tencent.qqnt.kernel.nativeinterface.MarketFaceElement
import com.tencent.qqnt.kernel.nativeinterface.MarketFaceSupportSize
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.PicElement
import com.tencent.qqnt.kernel.nativeinterface.PttElement
import com.tencent.qqnt.kernel.nativeinterface.QQNTWrapperUtil
import com.tencent.qqnt.kernel.nativeinterface.ReplyElement
import com.tencent.qqnt.kernel.nativeinterface.RichMediaFilePathInfo
import com.tencent.qqnt.kernel.nativeinterface.SmallYellowFaceInfo
import com.tencent.qqnt.kernel.nativeinterface.TextElement
import com.tencent.qqnt.kernel.nativeinterface.VideoElement
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
@ -34,6 +19,7 @@ import moe.fuqiuluo.qqinterface.servlet.LbsSvc
import moe.fuqiuluo.qqinterface.servlet.ark.ArkAppInfo import moe.fuqiuluo.qqinterface.servlet.ark.ArkAppInfo
import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc
import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc
import moe.fuqiuluo.qqinterface.servlet.transfile.*
import moe.fuqiuluo.qqinterface.servlet.transfile.FileTransfer import moe.fuqiuluo.qqinterface.servlet.transfile.FileTransfer
import moe.fuqiuluo.qqinterface.servlet.transfile.PictureResource import moe.fuqiuluo.qqinterface.servlet.transfile.PictureResource
import moe.fuqiuluo.qqinterface.servlet.transfile.Private import moe.fuqiuluo.qqinterface.servlet.transfile.Private
@ -41,8 +27,6 @@ import moe.fuqiuluo.qqinterface.servlet.transfile.Transfer
import moe.fuqiuluo.qqinterface.servlet.transfile.Troop import moe.fuqiuluo.qqinterface.servlet.transfile.Troop
import moe.fuqiuluo.qqinterface.servlet.transfile.VideoResource import moe.fuqiuluo.qqinterface.servlet.transfile.VideoResource
import moe.fuqiuluo.qqinterface.servlet.transfile.VoiceResource import moe.fuqiuluo.qqinterface.servlet.transfile.VoiceResource
import moe.fuqiuluo.qqinterface.servlet.transfile.trans
import moe.fuqiuluo.qqinterface.servlet.transfile.with
import moe.fuqiuluo.shamrock.helper.ActionMsgException import moe.fuqiuluo.shamrock.helper.ActionMsgException
import moe.fuqiuluo.shamrock.helper.ContactHelper import moe.fuqiuluo.shamrock.helper.ContactHelper
import moe.fuqiuluo.shamrock.helper.IllegalParamsException import moe.fuqiuluo.shamrock.helper.IllegalParamsException
@ -53,14 +37,7 @@ import moe.fuqiuluo.shamrock.helper.LogicException
import moe.fuqiuluo.shamrock.helper.MessageHelper import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.helper.MusicHelper import moe.fuqiuluo.shamrock.helper.MusicHelper
import moe.fuqiuluo.shamrock.helper.ParamsException import moe.fuqiuluo.shamrock.helper.ParamsException
import moe.fuqiuluo.shamrock.tools.asBooleanOrNull import moe.fuqiuluo.shamrock.tools.*
import moe.fuqiuluo.shamrock.tools.asInt
import moe.fuqiuluo.shamrock.tools.asIntOrNull
import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asLong
import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.asStringOrNull
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
import moe.fuqiuluo.shamrock.utils.AudioUtils import moe.fuqiuluo.shamrock.utils.AudioUtils
import moe.fuqiuluo.shamrock.utils.FileUtils import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.shamrock.utils.MediaType import moe.fuqiuluo.shamrock.utils.MediaType
@ -77,40 +54,101 @@ import kotlin.math.roundToInt
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.nextInt import kotlin.random.nextInt
internal typealias IMaker = suspend (Int, Long, String, JsonObject) -> Result<MsgElement> internal typealias IMsgElementMaker = suspend (Int, Long, String, JsonObject) -> Result<MsgElement>
internal object MessageMaker { internal object MsgElementMaker {
private val makerArray = mutableMapOf( private val makerMap = hashMapOf(
"text" to MessageMaker::createTextElem, "text" to MsgElementMaker::createTextElem,
"face" to MessageMaker::createFaceElem, "face" to MsgElementMaker::createFaceElem,
"pic" to MessageMaker::createImageElem, "pic" to MsgElementMaker::createImageElem,
"image" to MessageMaker::createImageElem, "image" to MsgElementMaker::createImageElem,
"voice" to MessageMaker::createRecordElem, "voice" to MsgElementMaker::createRecordElem,
"record" to MessageMaker::createRecordElem, "record" to MsgElementMaker::createRecordElem,
"at" to MessageMaker::createAtElem, "at" to MsgElementMaker::createAtElem,
"video" to MessageMaker::createVideoElem, "video" to MsgElementMaker::createVideoElem,
"markdown" to MessageMaker::createMarkdownElem, "markdown" to MsgElementMaker::createMarkdownElem,
"dice" to MessageMaker::createDiceElem, "dice" to MsgElementMaker::createDiceElem,
"rps" to MessageMaker::createRpsElem, "rps" to MsgElementMaker::createRpsElem,
"poke" to MessageMaker::createPokeElem, "poke" to MsgElementMaker::createPokeElem,
"anonymous" to MessageMaker::createAnonymousElem, "anonymous" to MsgElementMaker::createAnonymousElem,
"share" to MessageMaker::createShareElem, "share" to MsgElementMaker::createShareElem,
"contact" to MessageMaker::createContactElem, "contact" to MsgElementMaker::createContactElem,
"location" to MessageMaker::createLocationElem, "location" to MsgElementMaker::createLocationElem,
"music" to MessageMaker::createMusicElem, "music" to MsgElementMaker::createMusicElem,
"reply" to MessageMaker::createReplyElem, "reply" to MsgElementMaker::createReplyElem,
"touch" to MessageMaker::createTouchElem, "touch" to MsgElementMaker::createTouchElem,
"weather" to MessageMaker::createWeatherElem, "weather" to MsgElementMaker::createWeatherElem,
"json" to MessageMaker::createJsonElem, "json" to MsgElementMaker::createJsonElem,
"new_dice" to MessageMaker::createNewDiceElem, "new_dice" to MsgElementMaker::createNewDiceElem,
"new_rps" to MessageMaker::createNewRpsElem, "new_rps" to MsgElementMaker::createNewRpsElem,
"basketball" to MessageMaker::createBasketballElem, "basketball" to MsgElementMaker::createBasketballElem,
//"node" to MessageMaker::createNodeElem, //"node" to MessageMaker::createNodeElem,
//"multi_msg" to MessageMaker::createLongMsgStruct, //"multi_msg" to MessageMaker::createLongMsgStruct,
"bubble_face" to MessageMaker::createBubbleFaceElem, "bubble_face" to MsgElementMaker::createBubbleFaceElem,
"inline_keyboard" to MsgElementMaker::createInlineKeywordElem
) )
private suspend fun createBubbleFaceElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { operator fun get(type: String): IMsgElementMaker? = makerMap[type]
private suspend fun createInlineKeywordElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> {
fun tryNewKeyboardButton(btn: JsonObject): InlineKeyboardButton {
return runCatching {
InlineKeyboardButton(
btn["id"].asString,
btn["label"].asString,
btn["visited_label"].asString,
btn["style"].asInt,
btn["type"].asInt,
btn["click_limit"].asInt,
btn["unsupport_tips"].asString,
btn["data"].asString,
btn["at_bot_show_channel_list"].asBoolean,
btn["permission_type"].asInt,
ArrayList(btn["specify_role_ids"].asJsonArray.map { it.asString }),
ArrayList(btn["specify_tinyids"].asJsonArray.map { it.asString }),
false, 0, false, arrayListOf()
)
}.getOrElse {
InlineKeyboardButton(
btn["id"].asString,
btn["label"].asString,
btn["visited_label"].asString,
btn["style"].asInt,
btn["type"].asInt,
btn["click_limit"].asInt,
btn["unsupport_tips"].asString,
btn["data"].asString,
btn["at_bot_show_channel_list"].asBoolean,
btn["permission_type"].asInt,
ArrayList(btn["specify_role_ids"].asJsonArray.map { it.asString }),
ArrayList(btn["specify_tinyids"].asJsonArray.map { it.asString }),
)
}
}
val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEINLINEKEYBOARD
val rows = arrayListOf<InlineKeyboardRow>()
val keyboard = Json.parseToJsonElement(data["data"].asString).asJsonObject
keyboard["rows"].asJsonArray.forEach {
val row = it.asJsonObject
val buttons = arrayListOf<InlineKeyboardButton>()
row["buttons"].asJsonArray.forEach { button ->
val btn = button.asJsonObject
buttons.add(tryNewKeyboardButton(btn))
}
rows.add(InlineKeyboardRow(buttons))
}
elem.inlineKeyboardElement = InlineKeyboardElement(rows, keyboard["bot_appid"].asLong)
return Result.success(elem)
}
private suspend fun createBubbleFaceElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("id", "count") data.checkAndThrow("id", "count")
val faceId = data["id"].asInt val faceId = data["id"].asInt
val local = QQSysFaceUtil.convertToLocal(faceId) val local = QQSysFaceUtil.convertToLocal(faceId)
@ -143,21 +181,13 @@ internal object MessageMaker {
// SendForwardMessage(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin(), data["content"].asJsonArray) // SendForwardMessage(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin(), data["content"].asJsonArray)
// //
// } // }
/**\
* msgElement.setFaceElement(new FaceElement()); private suspend fun createBasketballElem(
* msgElement.getFaceElement().setFaceIndex(114); chatType: Int,
* msgElement.getFaceElement().setFaceText("/篮球"); msgId: Long,
* msgElement.getFaceElement().setFaceType(3); peerId: String,
* msgElement.getFaceElement().setPackId("1"); data: JsonObject
* msgElement.getFaceElement().setStickerId("13"); ): Result<MsgElement> {
* msgElement.getFaceElement().setRandomType(1);
* msgElement.getFaceElement().setImageType(1);
* msgElement.getFaceElement().setStickerType(2);
* msgElement.getFaceElement().setSourceType(1);
* msgElement.getFaceElement().setSurpriseId("");
* msgElement.getFaceElement().setResultId(String.valueOf(new Random().nextInt(5) + 1));
*/
private suspend fun createBasketballElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> {
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEFACE elem.elementType = MsgConstant.KELEMTYPEFACE
val face = FaceElement() val face = FaceElement()
@ -168,14 +198,19 @@ internal object MessageMaker {
face.stickerId = "13" face.stickerId = "13"
face.sourceType = 1 face.sourceType = 1
face.stickerType = 2 face.stickerType = 2
face.resultId = Random.nextInt(1 .. 5).toString() face.resultId = Random.nextInt(1..5).toString()
face.surpriseId = "" face.surpriseId = ""
face.randomType = 1 face.randomType = 1
elem.faceElement = face elem.faceElement = face
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createNewRpsElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createNewRpsElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEFACE elem.elementType = MsgConstant.KELEMTYPEFACE
val face = FaceElement() val face = FaceElement()
@ -193,7 +228,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createNewDiceElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createNewDiceElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEFACE elem.elementType = MsgConstant.KELEMTYPEFACE
val face = FaceElement() val face = FaceElement()
@ -311,7 +351,7 @@ internal object MessageMaker {
LogCenter.log("无法获取被回复消息", Level.ERROR) LogCenter.log("无法获取被回复消息", Level.ERROR)
} }
if(data.containsKey("text")) { if (data.containsKey("text")) {
data.checkAndThrow("qq", "time", "seq") data.checkAndThrow("qq", "time", "seq")
reply.replayMsgSeq = data["seq"].asLong reply.replayMsgSeq = data["seq"].asLong
reply.sourceMsgText = data["text"].asString reply.sourceMsgText = data["text"].asString
@ -330,21 +370,23 @@ internal object MessageMaker {
): Result<MsgElement> { ): Result<MsgElement> {
data.checkAndThrow("type") data.checkAndThrow("type")
when(val type = data["type"].asString) { when (val type = data["type"].asString) {
"qq" -> { "qq" -> {
data.checkAndThrow("id") data.checkAndThrow("id")
val id = data["id"].asString val id = data["id"].asString
if(!MusicHelper.tryShareQQMusicById(chatType, peerId.toLong(), msgId, id)) { if (!MusicHelper.tryShareQQMusicById(chatType, peerId.toLong(), msgId, id)) {
LogCenter.log("无法发送QQ音乐分享", Level.ERROR) LogCenter.log("无法发送QQ音乐分享", Level.ERROR)
} }
} }
"163" -> { "163" -> {
data.checkAndThrow("id") data.checkAndThrow("id")
val id = data["id"].asString val id = data["id"].asString
if(!MusicHelper.tryShare163MusicById(chatType, peerId.toLong(), msgId, id)) { if (!MusicHelper.tryShare163MusicById(chatType, peerId.toLong(), msgId, id)) {
LogCenter.log("无法发送网易云音乐分享", Level.ERROR) LogCenter.log("无法发送网易云音乐分享", Level.ERROR)
} }
} }
"custom" -> { "custom" -> {
data.checkAndThrow("url", "audio", "title") data.checkAndThrow("url", "audio", "title")
ArkMsgSvc.tryShareMusic( ArkMsgSvc.tryShareMusic(
@ -359,13 +401,19 @@ internal object MessageMaker {
data["audio"].asString data["audio"].asString
) )
} }
else -> LogCenter.log("不支持的音乐分享类型: $type", Level.ERROR) else -> LogCenter.log("不支持的音乐分享类型: $type", Level.ERROR)
} }
return Result.failure(ActionMsgException) return Result.failure(ActionMsgException)
} }
private suspend fun createLocationElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createLocationElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("lat", "lon") data.checkAndThrow("lat", "lon")
val lat = data["lat"].asString.toDouble() val lat = data["lat"].asString.toDouble()
@ -378,7 +426,12 @@ internal object MessageMaker {
return Result.failure(ActionMsgException) return Result.failure(ActionMsgException)
} }
private suspend fun createContactElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createContactElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("id") data.checkAndThrow("id")
val type = data["type"].asStringOrNull ?: data["kind"].asStringOrNull val type = data["type"].asStringOrNull ?: data["kind"].asStringOrNull
val id = data["id"].asString val id = data["id"].asString
@ -389,10 +442,12 @@ internal object MessageMaker {
val ark = ArkElement(CardSvc.getSharePrivateArkMsg(id.toLong()), null, null) val ark = ArkElement(CardSvc.getSharePrivateArkMsg(id.toLong()), null, null)
elem.arkElement = ark elem.arkElement = ark
} }
"group" -> { "group" -> {
val ark = ArkElement(GroupSvc.getShareTroopArkMsg(id.toLong()), null, null) val ark = ArkElement(GroupSvc.getShareTroopArkMsg(id.toLong()), null, null)
elem.arkElement = ark elem.arkElement = ark
} }
else -> throw IllegalParamsException("type") else -> throw IllegalParamsException("type")
} }
@ -401,7 +456,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createShareElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createShareElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("title", "url") data.checkAndThrow("title", "url")
val url = data["url"].asString val url = data["url"].asString
@ -466,11 +526,21 @@ internal object MessageMaker {
return Result.failure(ActionMsgException) return Result.failure(ActionMsgException)
} }
private suspend fun createAnonymousElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createAnonymousElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
return Result.failure(ActionMsgException) return Result.failure(ActionMsgException)
} }
private suspend fun createPokeElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createPokeElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("type", "id") data.checkAndThrow("type", "id")
val elem = MsgElement() val elem = MsgElement()
val face = FaceElement() val face = FaceElement()
@ -485,8 +555,8 @@ internal object MessageMaker {
face.vaspokeName = "" face.vaspokeName = ""
face.vaspokeMinver = "" face.vaspokeMinver = ""
face.pokeStrength = (data["strength"].asIntOrNull ?: data["cnt"].asIntOrNull face.pokeStrength = (data["strength"].asIntOrNull ?: data["cnt"].asIntOrNull
?: data["count"].asIntOrNull ?: data["time"].asIntOrNull ?: 0).also { ?: data["count"].asIntOrNull ?: data["time"].asIntOrNull ?: 0).also {
if(it < 0 || it > 3) throw IllegalParamsException("strength") if (it < 0 || it > 3) throw IllegalParamsException("strength")
} }
face.msgType = 0 face.msgType = 0
face.faceBubbleCount = 0 face.faceBubbleCount = 0
@ -497,7 +567,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createFaceElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createFaceElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("id") data.checkAndThrow("id")
val serverId = data["id"].asInt val serverId = data["id"].asInt
@ -516,15 +591,15 @@ internal object MessageMaker {
face.faceIndex = serverId face.faceIndex = serverId
face.faceText = QQSysFaceUtil.getFaceDescription(QQSysFaceUtil.convertToLocal(serverId)) face.faceText = QQSysFaceUtil.getFaceDescription(QQSysFaceUtil.convertToLocal(serverId))
if (serverId == 394) { if (serverId == 394) {
face.stickerId = 40.toString() face.stickerId = "40"
face.packId = "1" face.packId = "1"
face.sourceType = 1 face.sourceType = 1
face.stickerType = 3 face.stickerType = 3
face.randomType = 1 face.randomType = 1
face.resultId = data["result"].asStringOrNull ?: Random.nextInt(1 .. 5).toString() face.resultId = data["result"].asStringOrNull ?: Random.nextInt(1..5).toString()
} else if (big) { } else if (big) {
face.imageType = 0 face.imageType = 0
face.stickerId = 30.toString() face.stickerId = "30"
face.packId = "1" face.packId = "1"
face.sourceType = 1 face.sourceType = 1
face.stickerType = 1 face.stickerType = 1
@ -538,7 +613,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createRpsElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createRpsElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEMARKETFACE elem.elementType = MsgConstant.KELEMTYPEMARKETFACE
val market = MarketFaceElement( val market = MarketFaceElement(
@ -553,7 +633,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createDiceElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createDiceElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEMARKETFACE elem.elementType = MsgConstant.KELEMTYPEMARKETFACE
val market = MarketFaceElement( val market = MarketFaceElement(
@ -568,22 +653,32 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createMarkdownElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createMarkdownElem(
data.checkAndThrow("text") chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("content")
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEMARKDOWN elem.elementType = MsgConstant.KELEMTYPEMARKDOWN
val markdown = MarkdownElement(data["text"].asString) val markdown = MarkdownElement(data["content"].asString)
elem.markdownElement = markdown elem.markdownElement = markdown
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createVideoElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createVideoElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("file") data.checkAndThrow("file")
val file = data["file"].asString.let { val file = data["file"].asString.let {
val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase() val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase()
var file = if (md5.length == 32) { var file = if (md5.length == 32) {
FileUtils.getFile(it) FileUtils.getFileByMd5(it)
} else { } else {
FileUtils.parseAndSave(it) FileUtils.parseAndSave(it)
} }
@ -613,7 +708,8 @@ internal object MessageMaker {
) )
if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize( if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize(
originalPath originalPath
) != file.length()) { ) != file.length()
) {
QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath) QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath)
AudioUtils.obtainVideoCover(file.absolutePath, thumbPath!!) AudioUtils.obtainVideoCover(file.absolutePath, thumbPath!!)
} }
@ -652,35 +748,43 @@ internal object MessageMaker {
val qq = data["qq"].asString val qq = data["qq"].asString
val at = TextElement() val at = TextElement()
when(qq) { when (qq) {
"0", "all" -> { "0", "all" -> {
at.content = "@全体成员" at.content = "@全体成员"
at.atType = MsgConstant.ATTYPEALL at.atType = MsgConstant.ATTYPEALL
at.atNtUid = "0" at.atNtUid = "0"
} }
"online" -> {
at.content = "@在线成员"
at.atType = MsgConstant.ATTYPEONLINE
at.atNtUid = "0"
}
"admin" -> { "admin" -> {
at.content = "@管理员" at.content = "@管理员"
at.atRoleId = 1 at.atRoleId = 1
at.atType = MsgConstant.ATTYPEROLE at.atType = MsgConstant.ATTYPEROLE
at.atNtUid = "0" at.atNtUid = "0"
} }
"online" -> {
at.content = "@在线成员"
at.atType = MsgConstant.ATTYPEONLINE
at.atNtUid = "0"
}
else -> { else -> {
val info = GroupSvc.getTroopMemberInfoByUin(peerId, qq, true).onFailure { val name = data["name"].asStringOrNull
LogCenter.log("无法获取群成员信息: $qq", Level.ERROR) if (name == null) {
}.getOrNull() val info = GroupSvc.getTroopMemberInfoByUinV2(peerId, qq, true).onFailure {
if (info != null) { LogCenter.log("无法获取群成员信息: $qq", Level.ERROR)
at.content = "@${ }.getOrNull()
info.troopnick if (info != null) {
.ifNullOrEmpty(info.friendnick) at.content = "@${
.ifNullOrEmpty(qq) info.troopnick
}" .ifNullOrEmpty(info.friendnick)
.ifNullOrEmpty(qq)
}"
} else {
at.content = "@$qq"
}
} else { } else {
at.content = "@${data["name"].asStringOrNull.ifNullOrEmpty(qq)}" at.content = "@$name"
} }
at.atType = MsgConstant.ATTYPEONE at.atType = MsgConstant.ATTYPEONE
at.atNtUid = ContactHelper.getUidByUinAsync(qq.toLong()) at.atNtUid = ContactHelper.getUidByUinAsync(qq.toLong())
@ -693,7 +797,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createRecordElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createRecordElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
var file = data["file"].asStringOrNull?.let { var file = data["file"].asStringOrNull?.let {
val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "") val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "")
.replace(" ", "") .replace(" ", "")
@ -721,11 +830,13 @@ internal object MessageMaker {
ptt.duration = QRoute.api(IAIOPttApi::class.java) ptt.duration = QRoute.api(IAIOPttApi::class.java)
.getPttFileDuration(file.absolutePath) .getPttFileDuration(file.absolutePath)
} }
MediaType.Amr -> { MediaType.Amr -> {
LogCenter.log({ "Amr: $file" }, Level.DEBUG) LogCenter.log({ "Amr: $file" }, Level.DEBUG)
ptt.duration = AudioUtils.getDurationSec(file) ptt.duration = AudioUtils.getDurationSec(file)
ptt.formatType = MsgConstant.KPTTFORMATTYPEAMR ptt.formatType = MsgConstant.KPTTFORMATTYPEAMR
} }
MediaType.Pcm -> { MediaType.Pcm -> {
LogCenter.log({ "Pcm To Silk: $file" }, Level.DEBUG) LogCenter.log({ "Pcm To Silk: $file" }, Level.DEBUG)
val result = AudioUtils.pcmToSilk(file) val result = AudioUtils.pcmToSilk(file)
@ -733,6 +844,7 @@ internal object MessageMaker {
file = result.first file = result.first
ptt.formatType = MsgConstant.KPTTFORMATTYPESILK ptt.formatType = MsgConstant.KPTTFORMATTYPESILK
} }
else -> { else -> {
LogCenter.log({ "Audio To SILK: $file" }, Level.DEBUG) LogCenter.log({ "Audio To SILK: $file" }, Level.DEBUG)
val result = AudioUtils.audioToSilk(file) val result = AudioUtils.audioToSilk(file)
@ -749,12 +861,13 @@ internal object MessageMaker {
// QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath) // QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath)
//} //}
if(!(Transfer with when (chatType) { if (!(Transfer with when (chatType) {
MsgConstant.KCHATTYPEGROUP -> Troop(peerId) MsgConstant.KCHATTYPEGROUP -> Troop(peerId)
MsgConstant.KCHATTYPEC2C -> Private(peerId) MsgConstant.KCHATTYPEC2C -> Private(peerId)
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> Private(peerId) MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> Private(peerId)
else -> error("Not supported chatType($chatType) for RecordMsg") else -> error("Not supported chatType($chatType) for RecordMsg")
} trans VoiceResource(file))) { } trans VoiceResource(file))
) {
return Result.failure(RuntimeException("上传语音失败: $file")) return Result.failure(RuntimeException("上传语音失败: $file"))
} }
@ -784,16 +897,23 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createImageElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createImageElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
val isOriginal = data["original"].asBooleanOrNull ?: true val isOriginal = data["original"].asBooleanOrNull ?: true
val isFlash = data["flash"].asBooleanOrNull ?: false val isFlash = data["flash"].asBooleanOrNull ?: false
val filePath = data["file"].asStringOrNull val filePath = data["file"].asStringOrNull
val url = data["url"].asStringOrNull val url = data["url"].asStringOrNull
var file: File? = null var file: File? = null
if (filePath != null) { if (filePath != null) {
val md5 = filePath.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase() val md5 = filePath
.replace(regex = "[{}\\-]".toRegex(), replacement = "")
.split(".")[0].lowercase()
file = if (md5.length == 32) { file = if (md5.length == 32) {
FileUtils.getFile(md5) FileUtils.getFileByMd5(md5)
} else { } else {
FileUtils.parseAndSave(filePath) FileUtils.parseAndSave(filePath)
} }
@ -825,7 +945,8 @@ internal object MessageMaker {
) )
if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize( if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize(
originalPath originalPath
) != file.length()) { ) != file.length()
) {
val thumbPath = msgService.getRichMediaFilePathForMobileQQSend( val thumbPath = msgService.getRichMediaFilePathForMobileQQSend(
RichMediaFilePathInfo( RichMediaFilePathInfo(
2, 0, pic.md5HexStr, file.name, 2, 720, null, "", true 2, 0, pic.md5HexStr, file.name, 2, 720, null, "", true
@ -863,7 +984,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createTextElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createTextElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("text") data.checkAndThrow("text")
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPETEXT elem.elementType = MsgConstant.KELEMTYPETEXT
@ -878,6 +1004,4 @@ internal object MessageMaker {
if (!containsKey(it)) throw ParamsException(it) if (!containsKey(it)) throw ParamsException(it)
} }
} }
operator fun get(type: String): IMaker? = makerArray[type]
} }

View File

@ -2,6 +2,7 @@ package moe.fuqiuluo.qqinterface.servlet.structures
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import moe.fuqiuluo.symbols.Protobuf
@Serializable @Serializable
data class GuildInfo( data class GuildInfo(
@ -59,7 +60,7 @@ data class GetGuildMemberListNextToken(
@SerialName("role_index") val roleIndex: Long, @SerialName("role_index") val roleIndex: Long,
@SerialName("seq") val seq: Int, @SerialName("seq") val seq: Int,
@SerialName("finish") val finish: Boolean @SerialName("finish") val finish: Boolean
) ): Protobuf<GetGuildMemberListNextToken>
@Serializable @Serializable
data class GuildMemberInfo( data class GuildMemberInfo(

View File

@ -14,7 +14,6 @@ import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.utils.MD5 import moe.fuqiuluo.shamrock.utils.MD5
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import mqq.app.AppRuntime import mqq.app.AppRuntime
import mqq.app.MobileQQ
import java.io.File import java.io.File
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.math.abs import kotlin.math.abs
@ -162,6 +161,17 @@ internal abstract class FileTransfer {
const val BUSI_TYPE_VIDEO_EMOTICON_PIC = 1022 const val BUSI_TYPE_VIDEO_EMOTICON_PIC = 1022
const val BUSI_TYPE_VIDEO_EMOTICON_VIDEO = 1021 const val BUSI_TYPE_VIDEO_EMOTICON_VIDEO = 1021
const val TRANSFILE_TYPE_PIC = 1
const val TRANSFILE_TYPE_PIC_EMO = 65538
const val TRANSFILE_TYPE_PIC_THUMB = 65537
const val TRANSFILE_TYPE_PISMA = 49
const val TRANSFILE_TYPE_RAWPIC = 131075
const val TRANSFILE_TYPE_PROFILE_COVER = 35
const val TRANSFILE_TYPE_PTT = 2
const val TRANSFILE_TYPE_PTT_SLICE_TO_TEXT = 327696
const val TRANSFILE_TYPE_QQHEAD_PIC = 131074
internal fun createMessageUniseq(time: Long = System.currentTimeMillis()): Long { internal fun createMessageUniseq(time: Long = System.currentTimeMillis()): Long {
var uniseq = (time / 1000).toInt().toLong() var uniseq = (time / 1000).toInt().toLong()
uniseq = uniseq shl 32 or abs(Random.nextInt()).toLong() uniseq = uniseq shl 32 or abs(Random.nextInt()).toLong()

View File

@ -6,12 +6,11 @@ import com.tencent.mobileqq.transfile.FileMsg
import com.tencent.mobileqq.transfile.api.IProtoReqManager import com.tencent.mobileqq.transfile.api.IProtoReqManager
import com.tencent.mobileqq.transfile.protohandler.RichProto import com.tencent.mobileqq.transfile.protohandler.RichProto
import com.tencent.mobileqq.transfile.protohandler.RichProtoProc import com.tencent.mobileqq.transfile.protohandler.RichProtoProc
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.qqinterface.servlet.BaseSvc
import moe.fuqiuluo.shamrock.helper.ContactHelper
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.hex2ByteArray import moe.fuqiuluo.shamrock.tools.hex2ByteArray
@ -19,21 +18,45 @@ import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.tools.toHexString import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.PlatformUtils import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import moe.fuqiuluo.symbols.decodeProtobuf
import mqq.app.MobileQQ
import protobuf.auto.toByteArray
import protobuf.oidb.TrpcOidb
import protobuf.oidb.cmd0x11c5.C2CUserInfo
import protobuf.oidb.cmd0x11c5.ChannelUserInfo
import protobuf.oidb.cmd0x11c5.ClientMeta
import protobuf.oidb.cmd0x11c5.CodecConfigReq
import protobuf.oidb.cmd0x11c5.CommonHead
import protobuf.oidb.cmd0x11c5.DownloadExt
import protobuf.oidb.cmd0x11c5.DownloadReq
import protobuf.oidb.cmd0x11c5.FileInfo
import protobuf.oidb.cmd0x11c5.FileType
import protobuf.oidb.cmd0x11c5.GroupUserInfo
import protobuf.oidb.cmd0x11c5.IndexNode
import protobuf.oidb.cmd0x11c5.MultiMediaReqHead
import protobuf.oidb.cmd0x11c5.NtV2RichMediaReq
import protobuf.oidb.cmd0x11c5.NtV2RichMediaRsp
import protobuf.oidb.cmd0x11c5.SceneInfo
import protobuf.oidb.cmd0x11c5.VideoDownloadExt
import protobuf.oidb.cmd0xfc2.Oidb0xfc2ChannelInfo import protobuf.oidb.cmd0xfc2.Oidb0xfc2ChannelInfo
import protobuf.oidb.cmd0xfc2.Oidb0xfc2MsgApplyDownloadReq import protobuf.oidb.cmd0xfc2.Oidb0xfc2MsgApplyDownloadReq
import protobuf.oidb.cmd0xfc2.Oidb0xfc2ReqBody import protobuf.oidb.cmd0xfc2.Oidb0xfc2ReqBody
import protobuf.oidb.cmd0xfc2.Oidb0xfc2RspBody import protobuf.oidb.cmd0xfc2.Oidb0xfc2RspBody
import mqq.app.MobileQQ
import tencent.im.cs.cmd0x346.cmd0x346 import tencent.im.cs.cmd0x346.cmd0x346
import tencent.im.oidb.cmd0x6d6.oidb_0x6d6 import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
import tencent.im.oidb.cmd0xe37.cmd0xe37 import tencent.im.oidb.cmd0xe37.cmd0xe37
import tencent.im.oidb.oidb_sso import tencent.im.oidb.oidb_sso
import kotlin.coroutines.resume import kotlin.coroutines.resume
private const val GPRO_PIC = "gchat.qpic.cn"
private const val MULTIMEDIA_DOMAIN = "multimedia.nt.qq.com.cn"
private const val C2C_PIC = "c2cpicdw.qpic.cn"
internal object RichProtoSvc: BaseSvc() { internal object RichProtoSvc: BaseSvc() {
private val requestId = atomic(2L)
suspend fun getGuildFileDownUrl(peerId: String, channelId: String, fileId: String, bizId: Int): String { suspend fun getGuildFileDownUrl(peerId: String, channelId: String, fileId: String, bizId: Int): String {
val buffer = sendOidbAW("OidbSvcTrpcTcp.0xfc2_0", 4034, 0, ProtoBuf.encodeToByteArray( val buffer = sendOidbAW("OidbSvcTrpcTcp.0xfc2_0", 4034, 0, Oidb0xfc2ReqBody(
Oidb0xfc2ReqBody(
msgCmd = 1200, msgCmd = 1200,
msgBusType = 4202, msgBusType = 4202,
msgChannelInfo = Oidb0xfc2ChannelInfo( msgChannelInfo = Oidb0xfc2ChannelInfo(
@ -45,14 +68,16 @@ internal object RichProtoSvc: BaseSvc() {
fieldId = fileId, fieldId = fileId,
supportEncrypt = 0 supportEncrypt = 0
) )
) ).toByteArray()) ?: return ""
)) ?: return ""
val body = oidb_sso.OIDBSSOPkg() val body = oidb_sso.OIDBSSOPkg()
body.mergeFrom(buffer.slice(4)) body.mergeFrom(buffer.slice(4))
ProtoBuf.decodeFromByteArray<Oidb0xfc2RspBody>(body.bytes_bodybuffer.get().toByteArray()).msgApplyDownloadRsp?.let { body.bytes_bodybuffer
it.msgDownloadInfo?.let { .get().toByteArray()
return "https://${it.downloadDomain}${it.downloadUrl}&fname=$fileId&isthumb=0" .decodeProtobuf<Oidb0xfc2RspBody>()
} .msgApplyDownloadRsp?.let {
it.msgDownloadInfo?.let {
return "https://${it.downloadDomain}${it.downloadUrl}&fname=$fileId&isthumb=0"
}
} }
return "" return ""
} }
@ -141,25 +166,197 @@ internal object RichProtoSvc: BaseSvc() {
} }
} }
fun getGroupPicDownUrl( suspend fun getGroupPicDownUrl(
md5: String originalUrl: String,
md5: String,
peer: String = "",
fileId: String = "",
sha: String = "",
fileSize: ULong = 0uL,
width: UInt = 0u,
height: UInt = 0u
): String { ): String {
return "http://gchat.qpic.cn/gchatpic_new/0/0-0-${md5.uppercase()}/0?term=2" val isNtServer = originalUrl.startsWith("/download")
val domain = if (isNtServer) MULTIMEDIA_DOMAIN else GPRO_PIC
if (originalUrl.isNotEmpty()) {
if (isNtServer && !originalUrl.contains("rkey=")) {
getNtPicRKey(
fileId = fileId,
md5 = md5,
sha = sha,
fileSize = fileSize,
width = width,
height = height
) {
sceneType = 2u
grp = GroupUserInfo(peer.toULong())
}.onSuccess {
return "https://$domain$originalUrl$it"
}.onFailure {
LogCenter.log("getGroupPicDownUrl: ${it.stackTraceToString()}", Level.WARN)
}
}
return "https://$domain$originalUrl"
}
return "https://$domain/gchatpic_new/0/0-0-${md5.uppercase()}/0?term=2"
} }
fun getC2CPicDownUrl( suspend fun getC2CPicDownUrl(
md5: String originalUrl: String,
md5: String,
peer: String = "",
fileId: String = "",
sha: String = "",
fileSize: ULong = 0uL,
width: UInt = 0u,
height: UInt = 0u
): String { ): String {
return "https://c2cpicdw.qpic.cn/offpic_new/0/123-0-${md5.uppercase()}/0?term=2" val isNtServer = originalUrl.startsWith("/download")
val domain = if (isNtServer) MULTIMEDIA_DOMAIN else C2C_PIC
if (originalUrl.isNotEmpty()) {
if (fileId.isNotEmpty()) getNtPicRKey(
fileId = fileId,
md5 = md5,
sha = sha,
fileSize = fileSize,
width = width,
height = height
) {
sceneType = 1u
c2c = C2CUserInfo(
accountType = 2u,
uid = ContactHelper.getUidByUinAsync(peer.toLong())
)
}.onSuccess {
if (isNtServer && !originalUrl.contains("rkey=")) {
return "https://$domain$originalUrl$it"
}
}.onFailure {
LogCenter.log("getC2CPicDownUrl: ${it.stackTraceToString()}", Level.WARN)
}
if (isNtServer && !originalUrl.contains("rkey=")) {
return "https://$domain$originalUrl&rkey="
}
return "https://$domain$originalUrl"
}
return "https://$$domain/offpic_new/0/123-0-${md5}/0?term=2"
} }
fun getGuildPicDownUrl(md5: String): String { suspend fun getGuildPicDownUrl(
return "https://gchat.qpic.cn/qmeetpic/0/0-0-${md5.uppercase()}/0?term=2" originalUrl: String,
md5: String,
peer: String = "",
subPeer: String = "",
fileId: String = "",
sha: String = "",
fileSize: ULong = 0uL,
width: UInt = 0u,
height: UInt = 0u
): String {
val isNtServer = originalUrl.startsWith("/download")
val domain = if (isNtServer) MULTIMEDIA_DOMAIN else GPRO_PIC
if (originalUrl.isNotEmpty()) {
if (isNtServer && !originalUrl.contains("rkey=")) {
getNtPicRKey(
fileId = fileId,
md5 = md5,
sha = sha,
fileSize = fileSize,
width = width,
height = height
) {
sceneType = 3u
channel = ChannelUserInfo(peer.toULong(), subPeer.toULong(), 1u)
}.onSuccess {
return "https://$domain$originalUrl$it"
}.onFailure {
LogCenter.log("getGuildPicDownUrl: ${it.stackTraceToString()}", Level.WARN)
}
return "https://$domain$originalUrl&rkey="
}
return "https://$domain$originalUrl"
}
return "https://$domain/qmeetpic/0/0-0-${md5.uppercase()}/0?term=2"
}
suspend fun getNtPicRKey(
fileId: String,
md5: String,
sha: String,
fileSize: ULong,
width: UInt,
height: UInt,
sceneBuilder: suspend SceneInfo.() -> Unit
): Result<String> {
runCatching {
val req = run {
NtV2RichMediaReq(
head = MultiMediaReqHead(
commonHead = CommonHead(
requestId = requestId.incrementAndGet().toULong(),
cmd = 200u
),
sceneInfo = SceneInfo(
requestType = 2u,
businessType = 1u,
).apply {
sceneBuilder()
},
clientMeta = ClientMeta(2u)
),
download = DownloadReq(
IndexNode(
FileInfo(
fileSize = fileSize,
md5 = md5.lowercase(),
sha1 = sha.lowercase(),
name = "${md5}.jpg",
fileType = FileType(
fileType = 1u,
picFormat = 1000u,
videoFormat = 0u,
voiceFormat = 0u
),
width = width,
height = height,
time = 0u,
original = 1u
),
fileUuid = fileId,
storeId = 1u,
uploadTime = 0u,
ttl = 0u,
subType = 0u,
storeAppId = 0u
),
DownloadExt(
video = VideoDownloadExt(
busiType = 0u,
subBusiType = 0u,
msgCodecConfig = CodecConfigReq(
platformChipinfo = "",
osVer = "",
deviceName = ""
),
flag = 1u
)
)
)
)
}.toByteArray()
val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true)?.slice(4)
buffer?.decodeProtobuf<TrpcOidb>()?.buffer?.decodeProtobuf<NtV2RichMediaRsp>()?.download?.rkeyParam?.let {
return Result.success(it)
}
}.onFailure {
return Result.failure(it)
}
return Result.failure(Exception("unable to get c2c nt pic"))
} }
suspend fun getC2CVideoDownUrl( suspend fun getC2CVideoDownUrl(
peerId: String, peerId: String,
md5Hex: String, md5: ByteArray,
fileUUId: String fileUUId: String
): String { ): String {
return suspendCancellableCoroutine { return suspendCancellableCoroutine {
@ -175,7 +372,7 @@ internal object RichProtoSvc: BaseSvc() {
downReq.troopUin = peerId downReq.troopUin = peerId
downReq.clientType = 2 downReq.clientType = 2
downReq.fileId = fileUUId downReq.fileId = fileUUId
downReq.md5 = md5Hex.hex2ByteArray() downReq.md5 = md5
downReq.busiType = FileTransfer.BUSI_TYPE_SHORT_VIDEO downReq.busiType = FileTransfer.BUSI_TYPE_SHORT_VIDEO
downReq.subBusiType = 0 downReq.subBusiType = 0
downReq.fileType = FileTransfer.VIDEO_FORMAT_MP4 downReq.fileType = FileTransfer.VIDEO_FORMAT_MP4
@ -202,7 +399,7 @@ internal object RichProtoSvc: BaseSvc() {
suspend fun getGroupVideoDownUrl( suspend fun getGroupVideoDownUrl(
peerId: String, peerId: String,
md5Hex: String, md5: ByteArray,
fileUUId: String fileUUId: String
): String { ): String {
return suspendCancellableCoroutine { return suspendCancellableCoroutine {
@ -218,7 +415,7 @@ internal object RichProtoSvc: BaseSvc() {
downReq.troopUin = peerId downReq.troopUin = peerId
downReq.clientType = 2 downReq.clientType = 2
downReq.fileId = fileUUId downReq.fileId = fileUUId
downReq.md5 = md5Hex.hex2ByteArray() downReq.md5 = md5
downReq.busiType = FileTransfer.BUSI_TYPE_SHORT_VIDEO downReq.busiType = FileTransfer.BUSI_TYPE_SHORT_VIDEO
downReq.subBusiType = 0 downReq.subBusiType = 0
downReq.fileType = FileTransfer.VIDEO_FORMAT_MP4 downReq.fileType = FileTransfer.VIDEO_FORMAT_MP4
@ -321,12 +518,4 @@ internal object RichProtoSvc: BaseSvc() {
RichProtoProc.procRichProtoReq(richProtoReq) RichProtoProc.procRichProtoReq(richProtoReq)
} }
} }
suspend fun getGuildPttDownUrl(
peerId: String,
md5Hex: String,
fileUUId: String
): String {
return "unsupported"
}
} }

View File

@ -6,6 +6,7 @@ import com.tencent.mobileqq.transfile.TransferRequest
import moe.fuqiuluo.shamrock.utils.MD5 import moe.fuqiuluo.shamrock.utils.MD5
import java.io.File import java.io.File
import moe.fuqiuluo.qqinterface.servlet.transfile.ResourceType.* import moe.fuqiuluo.qqinterface.servlet.transfile.ResourceType.*
import moe.fuqiuluo.shamrock.helper.TransfileHelper
internal object Transfer: FileTransfer() { internal object Transfer: FileTransfer() {
private val ROUTE = mapOf<ContactType, Map<ResourceType, suspend TransTarget.(Resource) -> Boolean>>( private val ROUTE = mapOf<ContactType, Map<ResourceType, suspend TransTarget.(Resource) -> Boolean>>(
@ -84,11 +85,14 @@ internal object Transfer: FileTransfer() {
file: File, file: File,
wait: Boolean = true wait: Boolean = true
): Boolean { ): Boolean {
return transC2CResource(peerId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_SHARE, wait) { return transC2CResource(peerId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_CAMERA, wait) {
val picUpExtraInfo = TransferRequest.PicUpExtraInfo() val picUpExtraInfo = TransferRequest.PicUpExtraInfo()
picUpExtraInfo.mIsRaw = true picUpExtraInfo.mIsRaw = false
picUpExtraInfo.mUinType = FileMsg.UIN_BUDDY
it.mPicSendSource = 8 it.mPicSendSource = 8
it.mExtraObj = picUpExtraInfo it.mExtraObj = picUpExtraInfo
it.mIsPresend = true
it.delayShowProgressTimeInMs = 2000
} }
} }
@ -97,10 +101,13 @@ internal object Transfer: FileTransfer() {
file: File, file: File,
wait: Boolean = true wait: Boolean = true
): Boolean { ): Boolean {
return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_SHARE, wait) { return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_CAMERA, wait) {
val picUpExtraInfo = TransferRequest.PicUpExtraInfo() val picUpExtraInfo = TransferRequest.PicUpExtraInfo()
picUpExtraInfo.mIsRaw = true //picUpExtraInfo.mIsRaw = !TransfileHelper.isGifFile(file)
picUpExtraInfo.mIsRaw = false
picUpExtraInfo.mUinType = FileMsg.UIN_TROOP
it.mPicSendSource = 8 it.mPicSendSource = 8
it.delayShowProgressTimeInMs = 2000
it.mExtraObj = picUpExtraInfo it.mExtraObj = picUpExtraInfo
} }
} }

View File

@ -2,7 +2,7 @@ package moe.fuqiuluo.shamrock.helper
internal abstract class InternalMessageMakerError(why: String): RuntimeException(why) internal abstract class InternalMessageMakerError(why: String): RuntimeException(why)
internal class ParamsException(key: String): InternalMessageMakerError("Lack of param $key") internal class ParamsException(key: String): InternalMessageMakerError("Lack of param `$key`")
internal class IllegalParamsException(key: String): InternalMessageMakerError("Illegal param $key") internal class IllegalParamsException(key: String): InternalMessageMakerError("Illegal param $key")

View File

@ -17,7 +17,7 @@ internal object LocalCacheHelper: BaseSvc() {
} }
fun getCachePttFile(md5: String): File { fun getCachePttFile(md5: String): File {
val file = FileUtils.getFile(md5) val file = FileUtils.getFileByMd5(md5)
return if (file.exists()) file else getCurrentPttPath().resolve("$md5.amr") return if (file.exists()) file else getCurrentPttPath().resolve("$md5.amr")
} }
} }

View File

@ -16,18 +16,18 @@ import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import moe.fuqiuluo.qqinterface.servlet.MsgSvc import moe.fuqiuluo.qqinterface.servlet.MsgSvc
import moe.fuqiuluo.qqinterface.servlet.msg.MessageMaker import moe.fuqiuluo.qqinterface.servlet.msg.messageelement.MessageElementMaker
import moe.fuqiuluo.qqinterface.servlet.msg.msgelement.MsgElementMaker
import moe.fuqiuluo.shamrock.helper.db.MessageDB import moe.fuqiuluo.shamrock.helper.db.MessageDB
import moe.fuqiuluo.shamrock.helper.db.MessageMapping import moe.fuqiuluo.shamrock.helper.db.MessageMapping
import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asJsonObjectOrNull import moe.fuqiuluo.shamrock.tools.asJsonObjectOrNull
import moe.fuqiuluo.shamrock.tools.asString import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.json import moe.fuqiuluo.shamrock.tools.json
import moe.fuqiuluo.shamrock.tools.jsonArray import moe.fuqiuluo.shamrock.tools.jsonArray
import protobuf.message.MessageElement
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.math.abs import kotlin.math.abs
internal object MessageHelper { internal object MessageHelper {
@ -39,7 +39,7 @@ internal object MessageHelper {
fromId: String = peerId fromId: String = peerId
): SendMsgResult { ): SendMsgResult {
val uniseq = generateMsgId(chatType) val uniseq = generateMsgId(chatType)
val msg = messageArrayToMessageElements(chatType, uniseq.qqMsgId, peerId, decodeCQCode(message)).also { val msg = messageArrayToMsgElements(chatType, uniseq.qqMsgId, peerId, decodeCQCode(message)).also {
if (it.second.isEmpty() && !it.first) { if (it.second.isEmpty() && !it.first) {
error("消息合成失败,请查看日志或者检查输入。") error("消息合成失败,请查看日志或者检查输入。")
} else if (it.second.isEmpty()) { } else if (it.second.isEmpty()) {
@ -51,7 +51,14 @@ internal object MessageHelper {
return sendMessageWithoutMsgId(chatType, peerId, msg, fromId, callback) return sendMessageWithoutMsgId(chatType, peerId, msg, fromId, callback)
} }
suspend fun resendMsg(chatType: Int, peerId: String, fromId: String, msgId: Long, retryCnt: Int, msgHashId: Int): Result<SendMsgResult> { suspend fun resendMsg(
chatType: Int,
peerId: String,
fromId: String,
msgId: Long,
retryCnt: Int,
msgHashId: Int
): Result<SendMsgResult> {
val contact = generateContact(chatType, peerId, fromId) val contact = generateContact(chatType, peerId, fromId)
return resendMsg(contact, msgId, retryCnt, msgHashId) return resendMsg(contact, msgId, retryCnt, msgHashId)
} }
@ -60,11 +67,11 @@ internal object MessageHelper {
if (retryCnt < 0) return Result.failure(SendMsgException("消息发送超时次数过多")) if (retryCnt < 0) return Result.failure(SendMsgException("消息发送超时次数过多"))
val service = QRoute.api(IMsgService::class.java) val service = QRoute.api(IMsgService::class.java)
val result = withTimeoutOrNull(15000) { val result = withTimeoutOrNull(15000) {
if(suspendCancellableCoroutine { if (suspendCancellableCoroutine {
service.resendMsg(contact, msgId) { result, _ -> service.resendMsg(contact, msgId) { result, _ ->
it.resume(result) it.resume(result)
} }
} != 0) { } != 0) {
resendMsg(contact, msgId, retryCnt - 1, msgHashId) resendMsg(contact, msgId, retryCnt - 1, msgHashId)
} else { } else {
Result.success(SendMsgResult(msgHashId, msgId, System.currentTimeMillis())) Result.success(SendMsgResult(msgHashId, msgId, System.currentTimeMillis()))
@ -82,7 +89,7 @@ internal object MessageHelper {
callback: IOperateCallback callback: IOperateCallback
): Result<SendMsgResult> { ): Result<SendMsgResult> {
val uniseq = generateMsgId(chatType) val uniseq = generateMsgId(chatType)
val msg = messageArrayToMessageElements(chatType, uniseq.qqMsgId, peerId, message).also { val msg = messageArrayToMsgElements(chatType, uniseq.qqMsgId, peerId, message).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。") if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
}.second.filter { }.second.filter {
it.elementType != -1 it.elementType != -1
@ -166,7 +173,7 @@ internal object MessageHelper {
fromId: String = peerId fromId: String = peerId
): SendMsgResult { ): SendMsgResult {
val uniseq = generateMsgId(chatType) val uniseq = generateMsgId(chatType)
val msg = messageArrayToMessageElements(chatType, uniseq.qqMsgId, peerId, message).also { val msg = messageArrayToMsgElements(chatType, uniseq.qqMsgId, peerId, message).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。") if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
}.second.filter { }.second.filter {
it.elementType != -1 it.elementType != -1
@ -224,7 +231,7 @@ internal object MessageHelper {
fromId: String = peerId fromId: String = peerId
): SendMsgResult { ): SendMsgResult {
val uniseq = generateMsgId(chatType) val uniseq = generateMsgId(chatType)
val msg = messageArrayToMessageElements(chatType, uniseq.qqMsgId, peerId, message).also { val msg = messageArrayToMsgElements(chatType, uniseq.qqMsgId, peerId, message).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。") if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
}.second.filter { }.second.filter {
it.elementType != -1 it.elementType != -1
@ -233,7 +240,7 @@ internal object MessageHelper {
return if (!message.isEmpty()) { return if (!message.isEmpty()) {
val service = QRoute.api(IMsgService::class.java) val service = QRoute.api(IMsgService::class.java)
return suspendCancellableCoroutine { return suspendCancellableCoroutine {
service.sendMsg(contact, uniseq.qqMsgId, msg) { code, why -> service.sendMsg(contact, uniseq.qqMsgId, msg) { _, _ ->
it.resume(uniseq.copy(msgTime = System.currentTimeMillis())) it.resume(uniseq.copy(msgTime = System.currentTimeMillis()))
} }
} }
@ -243,10 +250,11 @@ internal object MessageHelper {
} }
suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact { suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact {
val peerId = when(chatType) { val peerId = when (chatType) {
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> { MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
ContactHelper.getUidByUinAsync(id.toLong()) ContactHelper.getUidByUinAsync(id.toLong())
} }
else -> id else -> id
} }
return if (chatType == MsgConstant.KCHATTYPEGUILD) { return if (chatType == MsgConstant.KCHATTYPEGUILD) {
@ -276,12 +284,51 @@ internal object MessageHelper {
} }
} }
suspend fun messageArrayToMessageElements(chatType: Int, msgId: Long, targetUin: String, messageList: JsonArray): Pair<Boolean, ArrayList<MsgElement>> { suspend fun messageArrayToMsgElements(
chatType: Int,
msgId: Long,
targetUin: String,
messageList: JsonArray
): Pair<Boolean, ArrayList<MsgElement>> {
val msgList = arrayListOf<MsgElement>() val msgList = arrayListOf<MsgElement>()
var hasActionMsg = false var hasActionMsg = false
messageList.forEach { messageList.forEach {
val msg = it.jsonObject val msg = it.jsonObject
val maker = MessageMaker[msg["type"].asString] val maker = MsgElementMaker[msg["type"].asString]
if (maker != null) {
try {
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
maker(chatType, msgId, targetUin, data).onSuccess { msgElem ->
msgList.add(msgElem)
}.onFailure {
if (it.javaClass != ActionMsgException::class.java) {
throw it
} else {
hasActionMsg = true
}
}
} catch (e: Throwable) {
LogCenter.log(e.stackTraceToString(), Level.ERROR)
}
} else {
LogCenter.log("不支持的消息类型: ${msg["type"].asString}", Level.ERROR)
return false to arrayListOf()
}
}
return hasActionMsg to msgList
}
suspend fun messageArrayToMessageElements(
chatType: Int,
msgId: Long,
targetUin: String,
messageList: JsonArray
): Pair<Boolean, ArrayList<MessageElement>> {
val msgList = arrayListOf<MessageElement>()
var hasActionMsg = false
messageList.forEach {
val msg = it.jsonObject
val maker = MessageElementMaker[msg["type"].asString]
if (maker != null) { if (maker != null) {
try { try {
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
@ -353,6 +400,21 @@ internal object MessageHelper {
database.messageMappingDao().insert(mapping) database.messageMappingDao().insert(mapping)
} }
fun saveMsgMappingNotExist(
hash: Int,
qqMsgId: Long,
time: Long,
chatType: Int,
peerId: String,
subPeerId: String,
msgSeq: Int,
subChatType: Int = chatType
) {
val database = MessageDB.getInstance()
val mapping = MessageMapping(hash, qqMsgId, chatType, subChatType, peerId, time, msgSeq, subPeerId)
database.messageMappingDao().insertNotExist(mapping)
}
external fun createMessageUniseq(chatType: Int, time: Long): Long external fun createMessageUniseq(chatType: Int, time: Long): Long
fun decodeCQCode(code: String): JsonArray { fun decodeCQCode(code: String): JsonArray {
@ -374,22 +436,6 @@ internal object MessageHelper {
return arrayList.jsonArray return arrayList.jsonArray
} }
fun encodeCQCode(msg: List<Map<String, JsonElement>>): String {
return nativeEncodeCQCode(msg.map {
val params = hashMapOf<String, String>()
it.forEach { (key, value) ->
if (key != "type") {
value.asJsonObject.forEach { param, element ->
params[param] = element.asString
}
} else {
params["_type"] = value.asString
}
}
params
})
}
private external fun nativeDecodeCQCode(code: String): List<Map<String, String>> private external fun nativeDecodeCQCode(code: String): List<Map<String, String>>
private external fun nativeEncodeCQCode(segment: List<Map<String, String>>): String external fun nativeEncodeCQCode(segment: List<Map<String, String>>): String
} }

View File

@ -29,7 +29,7 @@ internal object MusicHelper {
chatType, chatType,
peerId, peerId,
msgId, msgId,
ArkAppInfo.NeteaseMusic, ArkAppInfo.NetEaseMusic,
title.ifBlank { name }, title.ifBlank { name },
singerName, singerName,
jumpUrl, jumpUrl,

Some files were not shown because too many files have changed in this diff Show More