7 Commits

Author SHA1 Message Date
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
9 changed files with 158 additions and 25 deletions

View File

@ -8,5 +8,5 @@ import protobuf.message.element.*
data class MessageElement(
@ProtoNumber(1) val text: TextElement? = null,
@ProtoNumber(51) val json: JsonElement? = null,
@ProtoNumber(53) val richMedia: RichMediaElement? = null,
@ProtoNumber(53) val commElem: CommonElement? = null,
)

View File

@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class RichMediaElement(
data class CommonElement(
@ProtoNumber(1) val type: Int? = null,
@ProtoNumber(2) val data: ByteArray? = null,
@ProtoNumber(3) val u1: Int? = null,

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 + ",}";
}
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) {
this.id = "";
this.label = "";

View File

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

View File

@ -11,6 +11,9 @@ import com.tencent.qqnt.aio.adapter.api.IAIOPttApi
import com.tencent.qqnt.kernel.nativeinterface.ArkElement
import com.tencent.qqnt.kernel.nativeinterface.FaceBubbleElement
import com.tencent.qqnt.kernel.nativeinterface.FaceElement
import com.tencent.qqnt.kernel.nativeinterface.InlineKeyboardButton
import com.tencent.qqnt.kernel.nativeinterface.InlineKeyboardElement
import com.tencent.qqnt.kernel.nativeinterface.InlineKeyboardRow
import com.tencent.qqnt.kernel.nativeinterface.MarkdownElement
import com.tencent.qqnt.kernel.nativeinterface.MarketFaceElement
import com.tencent.qqnt.kernel.nativeinterface.MarketFaceSupportSize
@ -53,9 +56,11 @@ import moe.fuqiuluo.shamrock.helper.LogicException
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.helper.MusicHelper
import moe.fuqiuluo.shamrock.helper.ParamsException
import moe.fuqiuluo.shamrock.tools.asBoolean
import moe.fuqiuluo.shamrock.tools.asBooleanOrNull
import moe.fuqiuluo.shamrock.tools.asInt
import moe.fuqiuluo.shamrock.tools.asIntOrNull
import moe.fuqiuluo.shamrock.tools.asJsonArray
import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asLong
import moe.fuqiuluo.shamrock.tools.asString
@ -108,8 +113,62 @@ internal object MessageMaker {
//"node" to MessageMaker::createNodeElem,
//"multi_msg" to MessageMaker::createLongMsgStruct,
"bubble_face" to MessageMaker::createBubbleFaceElem,
"inline_keyboard" to MessageMaker::createInlineKeywordElem
)
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")
val faceId = data["id"].asInt
@ -569,10 +628,10 @@ internal object MessageMaker {
}
private suspend fun createMarkdownElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> {
data.checkAndThrow("text")
data.checkAndThrow("content")
val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEMARKDOWN
val markdown = MarkdownElement(data["text"].asString)
val markdown = MarkdownElement(data["content"].asString)
elem.markdownElement = markdown
return Result.success(elem)
}

View File

@ -58,6 +58,7 @@ internal object MessageConvert {
//MsgConstant.KELEMTYPEMULTIFORWARD to XmlMultiMsgConverter,
//MsgConstant.KELEMTYPESTRUCTLONGMSG to XmlLongMsgConverter,
MsgConstant.KELEMTYPEFACEBUBBLE to BubbleFaceConverter,
MsgConstant.KELEMTYPEINLINEKEYBOARD to InlineKeyboardConverter,
)
}

View File

@ -3,6 +3,10 @@ package moe.fuqiuluo.qqinterface.servlet.msg.convert
import com.tencent.mobileqq.qmmkv.QMMKV
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import kotlinx.serialization.json.add
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonArray
import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
import moe.fuqiuluo.shamrock.helper.ContactHelper
import moe.fuqiuluo.shamrock.helper.Level
@ -13,6 +17,7 @@ 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
import moe.fuqiuluo.shamrock.tools.json
import mqq.app.MobileQQ
import kotlin.jvm.internal.Intrinsics
@ -209,7 +214,15 @@ internal sealed class MessageElemConverter: IMessageConvert {
element: MsgElement
): MessageSegment {
val video = element.videoElement
val md5 = video.fileName.split(".")[0]
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",
@ -218,7 +231,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
"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)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
else -> unknownChatType(chatType)
}
).also {
@ -495,6 +508,53 @@ internal sealed class MessageElemConverter: IMessageConvert {
}
}
data object InlineKeyboardConverter: MessageElemConverter() {
override suspend fun convert(
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()
)
)
}
}
protected fun unknownChatType(chatType: Int) {
throw UnsupportedOperationException("Not supported chat type: $chatType")
}

View File

@ -167,9 +167,10 @@ internal object RichProtoSvc: BaseSvc() {
originalUrl: String,
md5: String,
): String {
val domain = if (originalUrl.startsWith("/download")) GPRO_PIC_NT else GPRO_PIC
val isNtServer = originalUrl.startsWith("/download")
val domain = if (isNtServer) GPRO_PIC_NT else GPRO_PIC
if (originalUrl.isNotEmpty()) {
if (!originalUrl.contains("rkey=")) {
if (isNtServer && !originalUrl.contains("rkey=")) {
return "https://$domain$originalUrl&rkey=$multiMediaRKey"
}
return "https://$domain$originalUrl"
@ -191,9 +192,10 @@ internal object RichProtoSvc: BaseSvc() {
originalUrl: String,
md5: String
): String {
val domain = if (originalUrl.startsWith("/download")) GPRO_PIC_NT else GPRO_PIC
val isNtServer = originalUrl.startsWith("/download")
val domain = if (isNtServer) GPRO_PIC_NT else GPRO_PIC
if (originalUrl.isNotEmpty()) {
if (!originalUrl.contains("rkey=")) {
if (isNtServer && !originalUrl.contains("rkey=")) {
return "https://$domain$originalUrl&rkey=$multiMediaRKey"
}
return "https://$domain$originalUrl"
@ -203,7 +205,7 @@ internal object RichProtoSvc: BaseSvc() {
suspend fun getC2CVideoDownUrl(
peerId: String,
md5Hex: String,
md5: ByteArray,
fileUUId: String
): String {
return suspendCancellableCoroutine {
@ -219,7 +221,7 @@ internal object RichProtoSvc: BaseSvc() {
downReq.troopUin = peerId
downReq.clientType = 2
downReq.fileId = fileUUId
downReq.md5 = md5Hex.hex2ByteArray()
downReq.md5 = md5
downReq.busiType = FileTransfer.BUSI_TYPE_SHORT_VIDEO
downReq.subBusiType = 0
downReq.fileType = FileTransfer.VIDEO_FORMAT_MP4
@ -246,7 +248,7 @@ internal object RichProtoSvc: BaseSvc() {
suspend fun getGroupVideoDownUrl(
peerId: String,
md5Hex: String,
md5: ByteArray,
fileUUId: String
): String {
return suspendCancellableCoroutine {
@ -262,7 +264,7 @@ internal object RichProtoSvc: BaseSvc() {
downReq.troopUin = peerId
downReq.clientType = 2
downReq.fileId = fileUUId
downReq.md5 = md5Hex.hex2ByteArray()
downReq.md5 = md5
downReq.busiType = FileTransfer.BUSI_TYPE_SHORT_VIDEO
downReq.subBusiType = 0
downReq.fileType = FileTransfer.VIDEO_FORMAT_MP4

View File

@ -109,10 +109,11 @@ internal object PrimitiveListener {
}
private fun onGroupMessage(msgTime: Long, body: MessageBody) {
runCatching {
body.rich?.elements?.filter {
it.richMedia != null
it.commElem != null && it.commElem!!.type == 48
}?.map {
ProtoBuf.decodeFromByteArray<RichMediaForPicData>(it.richMedia!!.data!!)
ProtoBuf.decodeFromByteArray<RichMediaForPicData>(it.commElem!!.data!!)
}?.forEach {
it.display?.show?.download?.url?.let {
RKEY_PATTERN.matcher(it).takeIf {
@ -123,6 +124,8 @@ internal object PrimitiveListener {
}
}
}
}
}
private suspend fun onC2CPoke(msgTime: Long, richMsg: MessageBody) {