mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 05:12:17 +00:00
Compare commits
3 Commits
68ea62ea0b
...
92ebe0c6a8
Author | SHA1 | Date | |
---|---|---|---|
92ebe0c6a8 | |||
6b1147d065 | |||
720313124c |
21
app/proguard-rules.pro
vendored
21
app/proguard-rules.pro
vendored
@ -199,17 +199,36 @@
|
|||||||
|
|
||||||
-keep class com.arthenica.ffmpegkit.NativeLoader { *; }
|
-keep class com.arthenica.ffmpegkit.NativeLoader { *; }
|
||||||
|
|
||||||
-keep class moe.fuqiuluo.** { *; }
|
-keep class moe.fuqiuluo.shamrock.app.** { *; }
|
||||||
|
-keep class moe.fuqiuluo.shamrock.ui.** { *; }
|
||||||
|
-keep class moe.fuqiuluo.shamrock.MainActivity { *; }
|
||||||
|
-keep class moe.fuqiuluo.shamrock.MainActivityKt { *; }
|
||||||
|
-keep class moe.fuqiuluo.shamrock.Manifest { *; }
|
||||||
|
|
||||||
|
-keep class moe.fuqiuluo.shamrock.xposed.** { *; }
|
||||||
|
-keep class moe.fuqiuluo.shamrock.helper.** { *; }
|
||||||
|
|
||||||
|
# tencent interfaces rules
|
||||||
-keep class com.tencent.** { *; }
|
-keep class com.tencent.** { *; }
|
||||||
-keep class com.qq.** { *; }
|
-keep class com.qq.** { *; }
|
||||||
-keep class com.google.gson.** { *; }
|
-keep class com.google.gson.** { *; }
|
||||||
-keep class de.** { *; }
|
-keep class de.** { *; }
|
||||||
|
-keep class epic.** { *; }
|
||||||
|
-keep class friendlist.** { *; }
|
||||||
|
-keep class KQQ.** { *; }
|
||||||
-keep class mqq.** { *; }
|
-keep class mqq.** { *; }
|
||||||
|
-keep class msf.** { *; }
|
||||||
|
-keep class oicq.** { *; }
|
||||||
-keep class QQService.** { *; }
|
-keep class QQService.** { *; }
|
||||||
-keep class SummaryCard.** { *; }
|
-keep class SummaryCard.** { *; }
|
||||||
-keep class tencent.** { *; }
|
-keep class tencent.** { *; }
|
||||||
|
-keep class VIP.** { *; }
|
||||||
|
|
||||||
-keepclassmembers class * {
|
-keepclassmembers class * {
|
||||||
native <methods>;
|
native <methods>;
|
||||||
}
|
}
|
||||||
-keep class io.netty.** { *; }
|
-keep class io.netty.** { *; }
|
||||||
|
|
||||||
|
-keepclasseswithmembernames class * {
|
||||||
|
native <methods>;
|
||||||
|
}
|
||||||
|
@ -2,16 +2,14 @@ package moe.fuqiuluo.shamrock
|
|||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
*
|
*
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
*/
|
*/
|
||||||
class ExampleUnitTest {
|
class ExampleUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun addition_isCorrect() {
|
fun test() {
|
||||||
assertEquals(4, 2 + 2)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -15,7 +15,7 @@ data class ContentHead(
|
|||||||
@ProtoNumber(8) val u6: Int? = null,
|
@ProtoNumber(8) val u6: Int? = null,
|
||||||
@ProtoNumber(9) val u7: Int? = null,
|
@ProtoNumber(9) val u7: Int? = null,
|
||||||
@ProtoNumber(11) val msgSeq: Long? = null,
|
@ProtoNumber(11) val msgSeq: Long? = null,
|
||||||
@ProtoNumber(12) val msgRandom: Long = Long.MIN_VALUE,
|
@ProtoNumber(12) val msgRandom: Long = Long.MIN_VALUE, // 0x0100000000000000L xor msgViaRandom
|
||||||
@ProtoNumber(14) val u4: Long? = null,
|
@ProtoNumber(14) val u4: Long? = null,
|
||||||
@ProtoNumber(15) val forwardHead: ForwardHead? = null,
|
@ProtoNumber(15) val forwardHead: ForwardHead? = null,
|
||||||
@ProtoNumber(28) val u5: Long? = null
|
@ProtoNumber(28) val u5: Long? = null
|
||||||
|
@ -30,7 +30,7 @@ data class CustomFace(
|
|||||||
@ProtoNumber(23) var height: UInt? = null,
|
@ProtoNumber(23) var height: UInt? = null,
|
||||||
@ProtoNumber(24) var source: UInt? = null,
|
@ProtoNumber(24) var source: UInt? = null,
|
||||||
@ProtoNumber(25) var size: UInt? = null,
|
@ProtoNumber(25) var size: UInt? = null,
|
||||||
@ProtoNumber(26) var origin: UInt? = null,
|
@ProtoNumber(26) var origin: Boolean? = null,
|
||||||
@ProtoNumber(27) var thumbWidth: UInt? = null,
|
@ProtoNumber(27) var thumbWidth: UInt? = null,
|
||||||
@ProtoNumber(28) var thumbHeight: UInt? = null,
|
@ProtoNumber(28) var thumbHeight: UInt? = null,
|
||||||
@ProtoNumber(29) var showLen: UInt? = null,
|
@ProtoNumber(29) var showLen: UInt? = null,
|
||||||
|
@ -5,7 +5,7 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class FaceMsg(
|
data class FaceMsg(
|
||||||
@ProtoNumber(1) val id: Int? = null,
|
@ProtoNumber(1) val index: Int? = null,
|
||||||
@ProtoNumber(2) var old: ByteArray? = null,
|
@ProtoNumber(2) var old: ByteArray? = null,
|
||||||
@ProtoNumber(11) var buf: ByteArray? = null,
|
@ProtoNumber(11) var buf: ByteArray? = null,
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ data class GeneralFlags(
|
|||||||
@ProtoNumber(4) val rpId: ByteArray? = null,
|
@ProtoNumber(4) val rpId: ByteArray? = null,
|
||||||
@ProtoNumber(5) val prpFold: UInt? = null,
|
@ProtoNumber(5) val prpFold: UInt? = null,
|
||||||
@ProtoNumber(6) val longTextFlag: UInt? = null,
|
@ProtoNumber(6) val longTextFlag: UInt? = null,
|
||||||
@ProtoNumber(7) val longTextResid: ByteArray? = null,
|
@ProtoNumber(7) val longTextResid: String? = null,
|
||||||
@ProtoNumber(8) val groupType: UInt? = null,
|
@ProtoNumber(8) val groupType: UInt? = null,
|
||||||
@ProtoNumber(9) val toUinFlag: UInt? = null,
|
@ProtoNumber(9) val toUinFlag: UInt? = null,
|
||||||
@ProtoNumber(10) val glamourLevel: UInt? = null,
|
@ProtoNumber(10) val glamourLevel: UInt? = null,
|
||||||
|
@ -14,10 +14,10 @@ data class NotOnlineImage(
|
|||||||
@ProtoNumber(7) val picMd5: ByteArray? = null,
|
@ProtoNumber(7) val picMd5: ByteArray? = null,
|
||||||
@ProtoNumber(8) val picHeight: UInt? = null,
|
@ProtoNumber(8) val picHeight: UInt? = null,
|
||||||
@ProtoNumber(9) val picWidth: UInt? = null,
|
@ProtoNumber(9) val picWidth: UInt? = null,
|
||||||
@ProtoNumber(10) val resId: ByteArray? = null,
|
@ProtoNumber(10) val resId: ByteArray? = null, // md5 + ".jpg"
|
||||||
@ProtoNumber(11) val flag: ByteArray? = null,
|
@ProtoNumber(11) val flag: ByteArray? = null,
|
||||||
@ProtoNumber(12) val thumbUrl: String? = null,
|
@ProtoNumber(12) val thumbUrl: String? = null,
|
||||||
@ProtoNumber(13) val original: UInt? = null,
|
@ProtoNumber(13) val original: Boolean? = null,
|
||||||
@ProtoNumber(14) val bigUrl: String? = null,
|
@ProtoNumber(14) val bigUrl: String? = null,
|
||||||
@ProtoNumber(15) val origUrl: String? = null,
|
@ProtoNumber(15) val origUrl: String? = null,
|
||||||
@ProtoNumber(16) val bizType: UInt? = null,
|
@ProtoNumber(16) val bizType: UInt? = null,
|
||||||
|
@ -21,7 +21,7 @@ data class SourceMsg(
|
|||||||
companion object {
|
companion object {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PbReserve(
|
data class PbReserve(
|
||||||
@ProtoNumber(3) var field3: ULong? = null,
|
@ProtoNumber(3) var msgRand: ULong? = null,
|
||||||
@ProtoNumber(6) var senderUid: String? = null,
|
@ProtoNumber(6) var senderUid: String? = null,
|
||||||
@ProtoNumber(7) var receiverUid: String? = null,
|
@ProtoNumber(7) var receiverUid: String? = null,
|
||||||
@ProtoNumber(8) var field8: Int? = null,
|
@ProtoNumber(8) var field8: Int? = null,
|
||||||
|
@ -10,5 +10,12 @@ data class TextMsg(
|
|||||||
@ProtoNumber(3) val attr6Buf: ByteArray? = null,
|
@ProtoNumber(3) val attr6Buf: ByteArray? = null,
|
||||||
@ProtoNumber(4) val attr7Buf: ByteArray? = null,
|
@ProtoNumber(4) val attr7Buf: ByteArray? = null,
|
||||||
@ProtoNumber(11) val buf: ByteArray? = null,
|
@ProtoNumber(11) val buf: ByteArray? = null,
|
||||||
@ProtoNumber(12) val pbReserve: ByteArray? = null,
|
@ProtoNumber(12) val pbReserve: PbReserve? = null,
|
||||||
|
){
|
||||||
|
companion object {
|
||||||
|
@Serializable
|
||||||
|
data class PbReserve(
|
||||||
|
@ProtoNumber(1) val field1: String? = null, // [打 call]] 请使用最新版手机 QQ 体验新功能
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@ data class Row(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Button(
|
data class Button(
|
||||||
@ProtoNumber(1) val id: Int? = null,
|
@ProtoNumber(1) val id: String? = null,
|
||||||
@ProtoNumber(2) val renderData: RenderData? = null,
|
@ProtoNumber(2) val renderData: RenderData? = null,
|
||||||
@ProtoNumber(3) val action: Action? = null,
|
@ProtoNumber(3) val action: Action? = null,
|
||||||
)
|
)
|
||||||
@ -41,8 +41,8 @@ data class Action(
|
|||||||
@ProtoNumber(2) val permission: Permission? = null,
|
@ProtoNumber(2) val permission: Permission? = null,
|
||||||
@ProtoNumber(4) val unsupportTips: String? = null,
|
@ProtoNumber(4) val unsupportTips: String? = null,
|
||||||
@ProtoNumber(5) val data: String? = null,
|
@ProtoNumber(5) val data: String? = null,
|
||||||
@ProtoNumber(6) val reply: Boolean? = null,
|
@ProtoNumber(7) val reply: Boolean? = null,
|
||||||
@ProtoNumber(7) val enter: Boolean? = null,
|
@ProtoNumber(8) val enter: Boolean? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -11,7 +11,7 @@ data class QFaceExtra(
|
|||||||
@ProtoNumber(3) val faceId: Int? = null,
|
@ProtoNumber(3) val faceId: Int? = null,
|
||||||
@ProtoNumber(4) val field4: Int? = null,
|
@ProtoNumber(4) val field4: Int? = null,
|
||||||
@ProtoNumber(5) val field5: Int? = null,
|
@ProtoNumber(5) val field5: Int? = null,
|
||||||
@ProtoNumber(6) val field6: String? = null,
|
@ProtoNumber(6) val result: String? = null,
|
||||||
@ProtoNumber(7) val faceText: String? = null,
|
@ProtoNumber(7) val faceText: String? = null,
|
||||||
@ProtoNumber(9) val field9: Int? = null
|
@ProtoNumber(9) val field9: Int? = null
|
||||||
) : Protobuf<QFaceExtra>
|
) : Protobuf<QFaceExtra>
|
||||||
|
63
protobuf/src/main/java/protobuf/oidb/cmd0x388/Cmd0x388Req.kt
Normal file
63
protobuf/src/main/java/protobuf/oidb/cmd0x388/Cmd0x388Req.kt
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package protobuf.oidb.cmd0x388
|
||||||
|
|
||||||
|
import com.google.protobuf.Internal.EMPTY_BYTE_ARRAY
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
|
import moe.fuqiuluo.symbols.Protobuf
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Cmd0x388ReqBody(
|
||||||
|
@ProtoNumber(1) var netType: Int = 0,
|
||||||
|
@ProtoNumber(2) var subCmd: Int = 0,
|
||||||
|
@ProtoNumber(3) var msgTryUpImg: ArrayList<TryUpImgReq>? = null,
|
||||||
|
// @ProtoNumber(4) var rpt_msg_getimg_url_req: ArrayList<GetImgUrlReq>? = null,
|
||||||
|
@ProtoNumber(5) var msgTryUpPttReq: ArrayList<TryUpPttReq>? = null,
|
||||||
|
// @ProtoNumber(6) var msgGetPttUrlReq: ArrayList<GetPttUrlReq>? = null,
|
||||||
|
@ProtoNumber(7) var commandId: Int = 0,
|
||||||
|
// @ProtoNumber(8) var rpt_msg_del_img_req: ArrayList<DelImgReq>? = null,
|
||||||
|
@ProtoNumber(1001) var extension: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
): Protobuf<Cmd0x388ReqBody>
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TryUpImgReq(
|
||||||
|
@ProtoNumber(1) var groupCode: Long = 0,
|
||||||
|
@ProtoNumber(2) var srcUin: Long = 0,
|
||||||
|
@ProtoNumber(3) var fileId: Long? = null,
|
||||||
|
@ProtoNumber(4) var fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(5) var fileSize: Long = 0,
|
||||||
|
@ProtoNumber(6) var fileName: String = "",
|
||||||
|
@ProtoNumber(7) var srcTerm: Int = 0,
|
||||||
|
@ProtoNumber(8) var platformType: Int = 0,
|
||||||
|
@ProtoNumber(9) var buType: Int = 0,
|
||||||
|
@ProtoNumber(10) var picWidth: Int = 0,
|
||||||
|
@ProtoNumber(11) var picHeight: Int = 0,
|
||||||
|
@ProtoNumber(12) var picType: Int = 0,
|
||||||
|
@ProtoNumber(13) var buildVer: String = "",
|
||||||
|
@ProtoNumber(14) var innerIp: Int = 0,
|
||||||
|
@ProtoNumber(15) var appPicType: Int = 0,
|
||||||
|
@ProtoNumber(16) var originalPic: Int = 0,
|
||||||
|
@ProtoNumber(17) var fileIndex: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(18) var dstUin: Long = 0,
|
||||||
|
@ProtoNumber(19) var srvUpload: Int? = null,
|
||||||
|
@ProtoNumber(20) var transferUrl: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TryUpPttReq(
|
||||||
|
@ProtoNumber(1) var groupCode: Long = 0,
|
||||||
|
@ProtoNumber(2) var srcUin: Long = 0,
|
||||||
|
@ProtoNumber(3) var fileId: Long = 0,
|
||||||
|
@ProtoNumber(4) var fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(5) var fileSize: Long = 0,
|
||||||
|
@ProtoNumber(6) var fileName: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(7) var srcTerm: Int = 0,
|
||||||
|
@ProtoNumber(8) var platformType: Int = 0,
|
||||||
|
@ProtoNumber(9) var buType: Int = 0,
|
||||||
|
@ProtoNumber(10) var buildVer: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(11) var innerIp: Int = 0,
|
||||||
|
@ProtoNumber(12) var voiceLength: Int = 0,
|
||||||
|
@ProtoNumber(13) var newUpChan: Boolean = false,
|
||||||
|
@ProtoNumber(14) var codec: Int = 0,
|
||||||
|
@ProtoNumber(15) var voiceType: Int = 0,
|
||||||
|
@ProtoNumber(16) var buId: Int = 0,
|
||||||
|
)
|
78
protobuf/src/main/java/protobuf/oidb/cmd0x388/Cmd0x388Rsp.kt
Normal file
78
protobuf/src/main/java/protobuf/oidb/cmd0x388/Cmd0x388Rsp.kt
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
@file:OptIn(ExperimentalSerializationApi::class)
|
||||||
|
|
||||||
|
package protobuf.oidb.cmd0x388
|
||||||
|
|
||||||
|
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 Cmd0x388RspBody(
|
||||||
|
@ProtoNumber(1) var clientIp: UInt = 0u,
|
||||||
|
@ProtoNumber(2) var subCmd: UInt = 0u,
|
||||||
|
@ProtoNumber(3) var msgTryUpImgRsp: ArrayList<TryUpImgRsp>? = null,
|
||||||
|
@ProtoNumber(5) var msgTryUpPttRsp: ArrayList<TryUpPttRsp>? = null,
|
||||||
|
): Protobuf<Cmd0x388RspBody>
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TryUpPttRsp(
|
||||||
|
@ProtoNumber(1) var fileId: Long = 0,
|
||||||
|
@ProtoNumber(2) var result: Int = 0,
|
||||||
|
@ProtoNumber(3) var failMsg: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(4) var fileExit: Boolean = false,
|
||||||
|
@ProtoNumber(5) var upIp: List<Int>? = null,
|
||||||
|
@ProtoNumber(6) var upPort: List<Int>? = null,
|
||||||
|
@ProtoNumber(7) var upUkey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(8) var fileid: Long = 0,
|
||||||
|
@ProtoNumber(9) var upOffset: Long = 0,
|
||||||
|
@ProtoNumber(10) var blockSize: Long = 0,
|
||||||
|
@ProtoNumber(11) var fileKey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(12) var channelType: Int = 0,
|
||||||
|
@ProtoNumber(26) var msgUpIp6: ArrayList<IPv6Info>? = null,
|
||||||
|
@ProtoNumber(27) var clientIp6: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
): Protobuf<TryUpPttRsp>
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TryUpImgRsp(
|
||||||
|
@ProtoNumber(1) var extFileId: ULong = 0u, // 没有实际作用
|
||||||
|
@ProtoNumber(2) var result: UInt = 0u,
|
||||||
|
@ProtoNumber(3) var faiMsg: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(4) var fileExist: Boolean = false,
|
||||||
|
@ProtoNumber(5) var msgImgInfo: ImgInfo? = null, // 里面只是一堆垃圾
|
||||||
|
@ProtoNumber(6) var upIp: ArrayList<Long>? = null,
|
||||||
|
@ProtoNumber(7) var upPort: ArrayList<Int>? = null,
|
||||||
|
@ProtoNumber(8) var ukey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(9) var fileId: Long = 0,
|
||||||
|
@ProtoNumber(10) var upOffset: ULong = 0u,
|
||||||
|
@ProtoNumber(11) var blockSize: Long = 0,
|
||||||
|
@ProtoNumber(12) var bool_new_big_chan: Boolean = false,
|
||||||
|
@ProtoNumber(26) var rpt_msg_up_ip6: ArrayList<IPv6Info>? = null,
|
||||||
|
@ProtoNumber(27) var client_ip6: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(1001) var msg_info4busi: TryUpInfo4Busi? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TryUpInfo4Busi(
|
||||||
|
@ProtoNumber(1) var down_domain: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(2) var thumb_down_url: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(3) var original_down_url: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(4) var big_down_url: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(5) var file_resid: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class IPv6Info(
|
||||||
|
@ProtoNumber(1) var ip6: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(2) var port: UInt = 0u,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ImgInfo(
|
||||||
|
@ProtoNumber(1) var file_md5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(2) var file_type: UInt = 0u,
|
||||||
|
@ProtoNumber(3) var file_size: ULong = 0u,
|
||||||
|
@ProtoNumber(4) var file_width: UInt = 0u,
|
||||||
|
@ProtoNumber(5) var file_height: UInt = 0u,
|
||||||
|
)
|
@ -0,0 +1,74 @@
|
|||||||
|
package com.tencent.mobileqq.data;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MessageForPic extends MessageRecord {
|
||||||
|
public String SpeedInfo;
|
||||||
|
public String actMsgContentValue;
|
||||||
|
public String action;
|
||||||
|
public String bigMsgUrl;
|
||||||
|
public String bigThumbMsgUrl;
|
||||||
|
public int busiType;
|
||||||
|
public int fileSizeFlag;
|
||||||
|
public long groupFileID;
|
||||||
|
public long height;
|
||||||
|
public int imageType;
|
||||||
|
public boolean isInMixedMsg;
|
||||||
|
public boolean isMixed;
|
||||||
|
public boolean isRead;
|
||||||
|
public boolean isShareAppActionMsg;
|
||||||
|
public String localUUID;
|
||||||
|
public int mCurrlength;
|
||||||
|
public int mDownloadLength;
|
||||||
|
public long mPresendTransferedSize;
|
||||||
|
public int mShowLength;
|
||||||
|
public String md5;
|
||||||
|
//@RecordForTest
|
||||||
|
// public msg_ctrl$MsgCtrl msgCtrl;
|
||||||
|
public int msgVia;
|
||||||
|
public int ntChatType;
|
||||||
|
public String ntPeerUid;
|
||||||
|
public String path;
|
||||||
|
//public PicMessageExtraData picExtraData;
|
||||||
|
public int picExtraFlag;
|
||||||
|
public Object picExtraObject;
|
||||||
|
public int previewed;
|
||||||
|
public String rawMsgUrl;
|
||||||
|
/// public ReportInfo reportInfo;
|
||||||
|
//public MsgRecordParams rootMsgRecordParams;
|
||||||
|
public String serverStoreSource;
|
||||||
|
public long shareAppID;
|
||||||
|
public long size;
|
||||||
|
public long subTypeId;
|
||||||
|
public int thumbHeight;
|
||||||
|
public String thumbMsgUrl;
|
||||||
|
public int thumbWidth;
|
||||||
|
//public ThumbWidthHeightDP thumbWidthHeightDP;
|
||||||
|
public int type;
|
||||||
|
public String uuid;
|
||||||
|
public long width;
|
||||||
|
public boolean isDownStatusReady = false;
|
||||||
|
public int subMsgId = 0;
|
||||||
|
public int isReport = 0;
|
||||||
|
public int subVersion = 5;
|
||||||
|
public int preDownState = -1;
|
||||||
|
public int preDownNetworkType = -1;
|
||||||
|
public long DSKey = 0;
|
||||||
|
public int mNotPredownloadReason = 0;
|
||||||
|
public int subThumbWidth = -1;
|
||||||
|
public int subThumbHeight = -1;
|
||||||
|
public int aiofileType = -1;
|
||||||
|
public int subMsgType = -1;
|
||||||
|
public boolean bEnableEnc = false;
|
||||||
|
public int thumbSize = -1;
|
||||||
|
public boolean isBlessPic = false;
|
||||||
|
public boolean sync2Story = false;
|
||||||
|
public boolean isQzonePic = false;
|
||||||
|
public boolean isStoryPhoto = false;
|
||||||
|
public long replyRealSourceMsgId = -1;
|
||||||
|
|
||||||
|
public String toLogString() {
|
||||||
|
return "path:" + this.path + ",uuid:" + this.uuid + ",md5:" + this.md5 + ",size:" + this.size + ",groupFileID:" + this.groupFileID;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
package com.tencent.mobileqq.transfile;
|
package com.tencent.mobileqq.transfile;
|
||||||
|
|
||||||
public class BaseTransProcessor {
|
import com.tencent.mobileqq.utils.httputils.IHttpCommunicatorListener;
|
||||||
|
|
||||||
|
public class BaseTransProcessor implements IHttpCommunicatorListener {
|
||||||
public FileMsg file;
|
public FileMsg file;
|
||||||
|
|
||||||
public long getFileStatus() {
|
public long getFileStatus() {
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.tencent.mobileqq.transfile;
|
||||||
|
|
||||||
|
public class BaseUploadProcessor extends BaseTransProcessor {
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,9 @@
|
|||||||
package com.tencent.mobileqq.transfile;
|
package com.tencent.mobileqq.transfile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
public class FileMsg {
|
public class FileMsg {
|
||||||
public static final int STATUS_FILE_EXPIRED = 5002;
|
public static final int STATUS_FILE_EXPIRED = 5002;
|
||||||
public static final int STATUS_FILE_TRANSFERING = 5000;
|
public static final int STATUS_FILE_TRANSFERING = 5000;
|
||||||
@ -107,4 +111,58 @@ public class FileMsg {
|
|||||||
public static final int UIN_BUDDY = 0;
|
public static final int UIN_BUDDY = 0;
|
||||||
public static final int UIN_DISCUSS = 2;
|
public static final int UIN_DISCUSS = 2;
|
||||||
public static final int UIN_TROOP = 1;
|
public static final int UIN_TROOP = 1;
|
||||||
|
|
||||||
|
public String domain;
|
||||||
|
public String downDomain;
|
||||||
|
public long endTime;
|
||||||
|
public int errorCode;
|
||||||
|
public String errorMessage;
|
||||||
|
public File file;
|
||||||
|
public long fileID;
|
||||||
|
public String fileKey;
|
||||||
|
public String fileMd5;
|
||||||
|
public String filePath;
|
||||||
|
public long fileSize;
|
||||||
|
public int fileType;
|
||||||
|
public String fileUrl;
|
||||||
|
public String forwardTaskKey;
|
||||||
|
public String friendUin;
|
||||||
|
public int isRead;
|
||||||
|
public boolean isStreamMode;
|
||||||
|
public int lastStatus;
|
||||||
|
public byte[] localFileMd5;
|
||||||
|
public String logTag;
|
||||||
|
public long mSecMsgId;
|
||||||
|
public long mSubMsgId;
|
||||||
|
public String mUin;
|
||||||
|
public String msgImageData;
|
||||||
|
public String msgTime;
|
||||||
|
public String orgiDownUrl;
|
||||||
|
public String peerUin;
|
||||||
|
public int picScale;
|
||||||
|
public long picThumbSize;
|
||||||
|
public BaseTransProcessor processor;
|
||||||
|
public boolean processorDoReportSelf;
|
||||||
|
public int pttSlicePos;
|
||||||
|
public String pttSliceText;
|
||||||
|
public OutputStream revStream;
|
||||||
|
public String selfUin;
|
||||||
|
public InputStream sendStream;
|
||||||
|
public String serverPath;
|
||||||
|
public long startTime;
|
||||||
|
public int status;
|
||||||
|
public long stepUrlCost;
|
||||||
|
public String suffixType;
|
||||||
|
public String thumbDownUrl;
|
||||||
|
public String thumbPath;
|
||||||
|
public String thumbUrl;
|
||||||
|
public String tmpFilePath;
|
||||||
|
public byte[] transferData;
|
||||||
|
public long transferedSize;
|
||||||
|
public String uKey;
|
||||||
|
public int uinType;
|
||||||
|
public long uniseq;
|
||||||
|
public String[] urls;
|
||||||
|
public byte[] userInfo;
|
||||||
|
public String uuidPath;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
package com.tencent.mobileqq.transfile;
|
package com.tencent.mobileqq.transfile;
|
||||||
|
|
||||||
public class GroupPicUploadProcessor {
|
import com.tencent.mobileqq.utils.httputils.IHttpCommunicatorListener;
|
||||||
|
|
||||||
|
public class GroupPicUploadProcessor extends BaseUploadProcessor {
|
||||||
}
|
}
|
||||||
|
@ -208,6 +208,8 @@ public final class PicElement implements IKernelModel {
|
|||||||
this.isFlashPic = bool;
|
this.isFlashPic = bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setStoreID(int i2) {
|
||||||
|
}
|
||||||
public void setMd5HexStr(String str) {
|
public void setMd5HexStr(String str) {
|
||||||
this.md5HexStr = str;
|
this.md5HexStr = str;
|
||||||
}
|
}
|
||||||
|
178
xposed/proguard-rules.pro
vendored
178
xposed/proguard-rules.pro
vendored
@ -19,181 +19,3 @@
|
|||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
|
|
||||||
# Never inline methods, but allow shrinking and obfuscation.
|
|
||||||
-keepclassmembernames,allowobfuscation,allowshrinking class androidx.core.view.ViewCompat$Api* {
|
|
||||||
<methods>;
|
|
||||||
}
|
|
||||||
-keepclassmembernames,allowobfuscation,allowshrinking class androidx.core.view.WindowInsetsCompat$*Impl* {
|
|
||||||
<methods>;
|
|
||||||
}
|
|
||||||
-keepclassmembernames,allowobfuscation,allowshrinking class androidx.core.app.NotificationCompat$*$Api*Impl {
|
|
||||||
<methods>;
|
|
||||||
}
|
|
||||||
-keepclassmembernames,allowobfuscation,allowshrinking class androidx.core.os.UserHandleCompat$Api*Impl {
|
|
||||||
<methods>;
|
|
||||||
}
|
|
||||||
-keepclassmembernames,allowobfuscation,allowshrinking class androidx.core.widget.EdgeEffectCompat$Api*Impl {
|
|
||||||
<methods>;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Keep metadata about inner emulated classes. This helps with
|
|
||||||
# reflection on older platforms, but can be omitted if the
|
|
||||||
# metadata usage is not present in the app.
|
|
||||||
|
|
||||||
-keepclassmembers class * {
|
|
||||||
** CREATOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Keep the special static methods that are required in all enumeration
|
|
||||||
# classes.
|
|
||||||
-keepclassmembers enum * {
|
|
||||||
public static **[] values();
|
|
||||||
public static ** valueOf(java.lang.String);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep public class androidx.appcompat.widget.** { *; }
|
|
||||||
-keep public class androidx.appcompat.app.** { *; }
|
|
||||||
-keep public class androidx.appcompat.view.menu.** { *; }
|
|
||||||
|
|
||||||
# Ensure that reflectively-loaded inflater is not obfuscated. This can be
|
|
||||||
# removed when we stop supporting AAPT1 builds.
|
|
||||||
-keepnames class androidx.appcompat.app.AppCompatViewInflater
|
|
||||||
# aapt is not able to read app::actionViewClass and app:actionProviderClass to produce proguard
|
|
||||||
# keep rules. Add a commonly used SearchView to the keep list until b/109831488 is resolved.
|
|
||||||
-keep class androidx.appcompat.widget.SearchView { <init>(...); }
|
|
||||||
|
|
||||||
# CoordinatorLayout resolves the behaviors of its child components with reflection.
|
|
||||||
-keep public class * extends androidx.coordinatorlayout.widget.CoordinatorLayout$Behavior {
|
|
||||||
public <init>(android.content.Context, android.util.AttributeSet);
|
|
||||||
public <init>();
|
|
||||||
}
|
|
||||||
|
|
||||||
# Make sure we keep annotations for CoordinatorLayout's DefaultBehavior
|
|
||||||
-keepattributes RuntimeVisible*Annotation*
|
|
||||||
|
|
||||||
-if class androidx.appcompat.app.AppCompatViewInflater
|
|
||||||
-keep class com.google.android.material.theme.MaterialComponentsViewInflater {
|
|
||||||
<init>();
|
|
||||||
}
|
|
||||||
|
|
||||||
# Keep `Companion` object fields of serializable classes.
|
|
||||||
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
|
|
||||||
-if @kotlinx.serialization.Serializable class **
|
|
||||||
-keepclassmembers class <1> {
|
|
||||||
static <1>$Companion Companion;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
|
|
||||||
-if @kotlinx.serialization.Serializable class ** {
|
|
||||||
static **$* *;
|
|
||||||
}
|
|
||||||
-keepclassmembers class <2>$<3> {
|
|
||||||
kotlinx.serialization.KSerializer serializer(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
# Keep `INSTANCE.serializer()` of serializable objects.
|
|
||||||
-if @kotlinx.serialization.Serializable class ** {
|
|
||||||
public static ** INSTANCE;
|
|
||||||
}
|
|
||||||
-keepclassmembers class <1> {
|
|
||||||
public static <1> INSTANCE;
|
|
||||||
kotlinx.serialization.KSerializer serializer(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
|
|
||||||
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
|
|
||||||
|
|
||||||
# Don't print notes about potential mistakes or omissions in the configuration for kotlinx-serialization classes
|
|
||||||
# See also https://github.com/Kotlin/kotlinx.serialization/issues/1900
|
|
||||||
-dontnote kotlinx.serialization.**
|
|
||||||
|
|
||||||
# Serialization core uses `java.lang.ClassValue` for caching inside these specified classes.
|
|
||||||
# If there is no `java.lang.ClassValue` (for example, in Android), then R8/ProGuard will print a warning.
|
|
||||||
# However, since in this case they will not be used, we can disable these warnings
|
|
||||||
-dontwarn kotlinx.serialization.internal.ClassValueReferences
|
|
||||||
|
|
||||||
# Rule to save runtime annotations on serializable class.
|
|
||||||
# If the R8 full mode is used, annotations are removed from classes-files.
|
|
||||||
#
|
|
||||||
# For the annotation serializer, it is necessary to read the `Serializable` annotation inside the serializer<T>() function - if it is present,
|
|
||||||
# then `SealedClassSerializer` is used, if absent, then `PolymorphicSerializer'.
|
|
||||||
#
|
|
||||||
# When using R8 full mode, all interfaces will be serialized using `PolymorphicSerializer`.
|
|
||||||
#
|
|
||||||
# see https://github.com/Kotlin/kotlinx.serialization/issues/2050
|
|
||||||
|
|
||||||
-if @kotlinx.serialization.Serializable class **
|
|
||||||
-keep, allowshrinking, allowoptimization, allowobfuscation, allowaccessmodification class <1>
|
|
||||||
|
|
||||||
# Entry point for retaining MainDispatcherLoader which uses a ServiceLoader.
|
|
||||||
-keep class kotlinx.coroutines.Dispatchers {
|
|
||||||
** getMain();
|
|
||||||
}
|
|
||||||
|
|
||||||
# Entry point for retaining CoroutineExceptionHandlerImpl.handlers which uses a ServiceLoader.
|
|
||||||
-keep class kotlinx.coroutines.CoroutineExceptionHandlerKt {
|
|
||||||
void handleCoroutineException(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
# Entry point for the rest of coroutines machinery
|
|
||||||
-keep class kotlinx.coroutines.BuildersKt {
|
|
||||||
** runBlocking(...);
|
|
||||||
** launch(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
# We are cheating a bit by not having android.jar on R8's library classpath. Ignore those warnings.
|
|
||||||
-ignorewarnings
|
|
||||||
|
|
||||||
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
|
|
||||||
-keep class kotlinx.coroutines.android.AndroidExceptionPreHandler {*;}
|
|
||||||
|
|
||||||
# Statically turn off all debugging facilities and assertions
|
|
||||||
-keepclassmembers class io.ktor.** { volatile <fields>; }
|
|
||||||
-keep class io.ktor.** { *; }
|
|
||||||
-keep class kotlinx.coroutines.** { *; }
|
|
||||||
-dontwarn kotlinx.atomicfu.**
|
|
||||||
-dontwarn io.netty.**
|
|
||||||
-dontwarn com.typesafe.**
|
|
||||||
-assumenosideeffects class * implements org.slf4j.Logger {
|
|
||||||
public *** trace(...);
|
|
||||||
public *** debug(...);
|
|
||||||
public *** info(...);
|
|
||||||
public *** warn(...);
|
|
||||||
public *** error(...);
|
|
||||||
}
|
|
||||||
-keep class kotlin.reflect.jvm.internal.** { *; }
|
|
||||||
|
|
||||||
-keep class com.arthenica.ffmpegkit.FFmpegKitConfig {
|
|
||||||
native <methods>;
|
|
||||||
void log(long, int, byte[]);
|
|
||||||
void statistics(long, int, float, float, long , int, double, double);
|
|
||||||
int safOpen(int);
|
|
||||||
int safClose(int);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep class com.arthenica.ffmpegkit.AbiDetect {
|
|
||||||
native <methods>;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep class com.arthenica.ffmpegkit.NativeLoader { *; }
|
|
||||||
|
|
||||||
-keep class moe.fuqiuluo.** { *; }
|
|
||||||
-keep class com.tencent.** { *; }
|
|
||||||
-keep class com.qq.** { *; }
|
|
||||||
-keep class com.google.gson.** { *; }
|
|
||||||
-keep class de.** { *; }
|
|
||||||
-keep class mqq.** { *; }
|
|
||||||
-keep class QQService.** { *; }
|
|
||||||
-keep class SummaryCard.** { *; }
|
|
||||||
-keep class tencent.** { *; }
|
|
||||||
|
|
||||||
-keepclassmembers class * {
|
|
||||||
native <methods>;
|
|
||||||
}
|
|
||||||
-keep class io.netty.** { *; }
|
|
||||||
|
|
||||||
-keep class moe.fuqiuluo.** implements com.tencent.qqnt.kernel.nativeinterface.** {
|
|
||||||
*;
|
|
||||||
}
|
|
||||||
|
@ -222,7 +222,7 @@ internal object MsgSvc : BaseSvc() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendMultiMsg(
|
suspend fun uploadMultiMsg(
|
||||||
uid: String,
|
uid: String,
|
||||||
groupUin: String?,
|
groupUin: String?,
|
||||||
messages: List<PushMsgBody>,
|
messages: List<PushMsgBody>,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet.msg
|
package moe.fuqiuluo.qqinterface.servlet.msg
|
||||||
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.converter.MessageElementConverter
|
import moe.fuqiuluo.qqinterface.servlet.msg.converter.ElemConverter
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.converter.MsgElementConverter
|
import moe.fuqiuluo.qqinterface.servlet.msg.converter.MsgElementConverter
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
@ -21,13 +21,21 @@ internal suspend fun List<Elem>.toSegments(
|
|||||||
1
|
1
|
||||||
} else if (msg.face != null) {
|
} else if (msg.face != null) {
|
||||||
2
|
2
|
||||||
|
} else if (msg.notOnlineImage != null) {
|
||||||
|
4
|
||||||
|
} else if (msg.customFace != null) {
|
||||||
|
8
|
||||||
|
} else if (msg.generalFlags != null) {
|
||||||
|
37
|
||||||
|
} else if (msg.srcMsg != null) {
|
||||||
|
45
|
||||||
} else if (msg.lightApp != null) {
|
} else if (msg.lightApp != null) {
|
||||||
51
|
51
|
||||||
} else if (msg.commonElem != null) {
|
} else if (msg.commonElem != null) {
|
||||||
53
|
53
|
||||||
} else
|
} else
|
||||||
throw UnsupportedOperationException("不支持的消息element类型:$msg")
|
throw UnsupportedOperationException("不支持的消息element类型:$msg")
|
||||||
val converter = MessageElementConverter[elementType]
|
val converter = ElemConverter[elementType]
|
||||||
converter?.invoke(chatType, peerId, subPeer, msg)
|
converter?.invoke(chatType, peerId, subPeer, msg)
|
||||||
?: throw UnsupportedOperationException("不支持的消息element类型:$elementType")
|
?: throw UnsupportedOperationException("不支持的消息element类型:$elementType")
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
|
@ -8,10 +8,10 @@ import moe.fuqiuluo.shamrock.tools.json
|
|||||||
|
|
||||||
internal data class MessageSegment(
|
internal data class MessageSegment(
|
||||||
val type: String,
|
val type: String,
|
||||||
val data: Map<String, Any> = emptyMap()
|
val data: Map<String, Any?> = emptyMap()
|
||||||
) {
|
) {
|
||||||
fun toJson(): JsonObject {
|
fun toJson(): JsonObject {
|
||||||
return hashMapOf(
|
return mapOf(
|
||||||
"type" to type.json,
|
"type" to type.json,
|
||||||
"data" to data.json
|
"data" to data.json
|
||||||
).json
|
).json
|
||||||
@ -26,7 +26,7 @@ internal fun List<MessageSegment>.toJson(): JsonArray {
|
|||||||
|
|
||||||
internal fun List<MessageSegment>.toListMap(): List<Map<String, JsonElement>> {
|
internal fun List<MessageSegment>.toListMap(): List<Map<String, JsonElement>> {
|
||||||
return this.map {
|
return this.map {
|
||||||
hashMapOf(
|
mapOf(
|
||||||
"type" to it.type.json,
|
"type" to it.type.json,
|
||||||
"data" to it.data.json
|
"data" to it.data.json
|
||||||
).json
|
).json
|
||||||
|
@ -0,0 +1,621 @@
|
|||||||
|
package moe.fuqiuluo.qqinterface.servlet.msg.converter
|
||||||
|
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
|
import io.ktor.util.*
|
||||||
|
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.qqinterface.servlet.transfile.RichProtoSvc
|
||||||
|
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||||
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
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.utils.DeflateTools
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asString
|
||||||
|
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||||
|
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||||
|
import protobuf.message.Elem
|
||||||
|
import protobuf.message.element.commelem.ButtonExtra
|
||||||
|
import protobuf.message.element.commelem.MarkdownExtra
|
||||||
|
import protobuf.message.element.commelem.QFaceExtra
|
||||||
|
|
||||||
|
|
||||||
|
internal typealias IElemConverter = suspend (Int, String, String, Elem) -> MessageSegment
|
||||||
|
|
||||||
|
internal object ElemConverter {
|
||||||
|
private val convertMap = mapOf(
|
||||||
|
1 to ElemConverter::convertTextElem,
|
||||||
|
2 to ElemConverter::convertFaceElem,
|
||||||
|
4 to ElemConverter::convertNotOnlineImageElem,
|
||||||
|
8 to ElemConverter::convertCustomFaceElem,
|
||||||
|
// MsgConstant.KELEMTYPEPTT to ElemConverter::convertVoiceElem,
|
||||||
|
// MsgConstant.KELEMTYPEVIDEO to ElemConverter::convertVideoElem,
|
||||||
|
// MsgConstant.KELEMTYPEMARKETFACE to ElemConverter::convertMarketFaceElem,
|
||||||
|
37 to ElemConverter::convertGeneralFlagsElem,
|
||||||
|
45 to ElemConverter::convertReplyElem,
|
||||||
|
51 to ElemConverter::convertStructJsonElem,
|
||||||
|
53 to ElemConverter::convertCommonElem,
|
||||||
|
// MsgConstant.KELEMTYPEGRAYTIP to ElemConverter::convertGrayTipsElem,
|
||||||
|
// MsgConstant.KELEMTYPEFILE to ElemConverter::convertFileElem,
|
||||||
|
// //MsgConstant.KELEMTYPEMULTIFORWARD to ElemConverter::convertXmlMultiMsgElem,
|
||||||
|
// //MsgConstant.KELEMTYPESTRUCTLONGMSG to ElemConverter::convertXmlLongMsgElem,
|
||||||
|
// MsgConstant.KELEMTYPEFACEBUBBLE to ElemConverter::convertBubbleFaceElem,
|
||||||
|
)
|
||||||
|
|
||||||
|
operator fun get(type: Int): IElemConverter? = convertMap[type]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本 / 艾特 消息转换消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertTextElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: Elem
|
||||||
|
): 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 = mapOf(
|
||||||
|
"qq" to uin
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return MessageSegment(
|
||||||
|
type = "text",
|
||||||
|
data = mapOf(
|
||||||
|
"text" to text.str!!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小表情 / 戳一戳 消息转换消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertFaceElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: Elem
|
||||||
|
): MessageSegment {
|
||||||
|
val face = element.face!!
|
||||||
|
return MessageSegment(
|
||||||
|
type = "face",
|
||||||
|
data = mapOf(
|
||||||
|
"id" to face.index!!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片消息转换消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertCustomFaceElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: Elem
|
||||||
|
): MessageSegment {
|
||||||
|
val customFace = element.customFace!!
|
||||||
|
|
||||||
|
val md5 = customFace.md5.toHexString()
|
||||||
|
|
||||||
|
ImageDB.getInstance().imageMappingDao().insert(
|
||||||
|
ImageMapping(md5.uppercase(), chatType, customFace.size!!.toLong())
|
||||||
|
)
|
||||||
|
|
||||||
|
val origUrl = customFace.origUrl!!
|
||||||
|
|
||||||
|
return MessageSegment(
|
||||||
|
type = "image",
|
||||||
|
data = mapOf(
|
||||||
|
"file" to md5,
|
||||||
|
"url" to when (chatType) {
|
||||||
|
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
|
||||||
|
origUrl,
|
||||||
|
md5
|
||||||
|
)
|
||||||
|
|
||||||
|
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(origUrl, md5)
|
||||||
|
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(origUrl, md5)
|
||||||
|
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
|
||||||
|
},
|
||||||
|
"type" to if (customFace.origin == true) "original" else "show"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun convertNotOnlineImageElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: Elem
|
||||||
|
): MessageSegment {
|
||||||
|
val notOnlineImage = element.notOnlineImage!!
|
||||||
|
|
||||||
|
val md5 = notOnlineImage.picMd5.toHexString()
|
||||||
|
|
||||||
|
ImageDB.getInstance().imageMappingDao().insert(
|
||||||
|
ImageMapping(md5.uppercase(), chatType, notOnlineImage.fileLen!!.toLong())
|
||||||
|
)
|
||||||
|
|
||||||
|
val origUrl = notOnlineImage.origUrl!!
|
||||||
|
|
||||||
|
return MessageSegment(
|
||||||
|
type = "image",
|
||||||
|
data = mapOf(
|
||||||
|
"file" to md5,
|
||||||
|
"url" to when (chatType) {
|
||||||
|
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
|
||||||
|
origUrl,
|
||||||
|
md5
|
||||||
|
)
|
||||||
|
|
||||||
|
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(origUrl, md5)
|
||||||
|
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(origUrl, md5)
|
||||||
|
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
|
||||||
|
},
|
||||||
|
"type" to if (notOnlineImage.original == true) "original" else "show"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun convertGeneralFlagsElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: Elem
|
||||||
|
): MessageSegment {
|
||||||
|
val generalFlags = element.generalFlags!!
|
||||||
|
if (generalFlags.longTextFlag == 1u) {
|
||||||
|
return MessageSegment(
|
||||||
|
type = "general_flags",
|
||||||
|
data = mapOf(
|
||||||
|
"res_id" to generalFlags.longTextResid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
throw UnknownError("no segment")
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 语音消息转换消息段
|
||||||
|
// */
|
||||||
|
// private suspend fun convertVoiceElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: Elem
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val record = element.pttElement
|
||||||
|
//
|
||||||
|
// val md5 = if (record.fileName.startsWith("silk"))
|
||||||
|
// record.fileName.substring(5)
|
||||||
|
// else record.md5HexStr
|
||||||
|
//
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "record",
|
||||||
|
// data = mapOf(
|
||||||
|
// "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: Elem
|
||||||
|
// ): 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 = mapOf(
|
||||||
|
// "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: Elem
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val face = element.marketFaceElement
|
||||||
|
// return when (face.emojiId.lowercase()) {
|
||||||
|
// "4823d3adb15df08014ce5d6796b76ee1" -> MessageSegment("dice")
|
||||||
|
// "83c8a293ae65ca140f348120a77448ee" -> MessageSegment("rps")
|
||||||
|
// else -> MessageSegment(
|
||||||
|
// type = "mface",
|
||||||
|
// data = mapOf(
|
||||||
|
// "id" to face.emojiId
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
/**
|
||||||
|
* 回复消息转消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertReplyElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: Elem
|
||||||
|
): MessageSegment {
|
||||||
|
val srcMsg = element.srcMsg!!
|
||||||
|
val msgId = srcMsg.pbReserve?.msgRand?.toLong() ?: 0
|
||||||
|
val msgHash = if (msgId != 0L) {
|
||||||
|
MessageHelper.generateMsgIdHash(chatType, msgId)
|
||||||
|
} else {
|
||||||
|
val msgSeq = srcMsg.origSeqs?.first()?.toInt() ?: 0
|
||||||
|
MessageDB.getInstance().messageMappingDao()
|
||||||
|
.queryByMsgSeq(chatType, peerId, msgSeq)?.msgHashId
|
||||||
|
?: kotlin.run {
|
||||||
|
LogCenter.log("消息映射关系未找到: Message($msgSeq)", Level.WARN)
|
||||||
|
MessageHelper.generateMsgIdHash(chatType, msgId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageSegment(
|
||||||
|
type = "reply",
|
||||||
|
data = mapOf(
|
||||||
|
"id" to msgHash
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON消息转消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertStructJsonElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: Elem
|
||||||
|
): MessageSegment {
|
||||||
|
val data = element.lightApp!!.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 = mapOf(
|
||||||
|
"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 = mapOf(
|
||||||
|
"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 = mapOf(
|
||||||
|
"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 convertCommonElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: Elem
|
||||||
|
): MessageSegment {
|
||||||
|
val commonElem = element.commonElem!!
|
||||||
|
return when (commonElem.serviceType) {
|
||||||
|
|
||||||
|
37 -> {
|
||||||
|
val qFaceExtra = commonElem.elem!!.decodeProtobuf<QFaceExtra>()
|
||||||
|
when (qFaceExtra.faceId) {
|
||||||
|
358 -> MessageSegment(
|
||||||
|
type = "dice",
|
||||||
|
data = mapOf(
|
||||||
|
"result" to qFaceExtra.result!!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
359 -> MessageSegment(
|
||||||
|
type = "rps",
|
||||||
|
data = mapOf(
|
||||||
|
"result" to qFaceExtra.result!!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> MessageSegment(
|
||||||
|
type = "face",
|
||||||
|
data = mapOf(
|
||||||
|
"id" to qFaceExtra.faceId!!,
|
||||||
|
"big" to true,
|
||||||
|
"result" to qFaceExtra.result!! // (1布 2剪 3锤) (骰子123456)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
45 -> {
|
||||||
|
val markdownExtra = commonElem.elem!!.decodeProtobuf<MarkdownExtra>()
|
||||||
|
MessageSegment(
|
||||||
|
type = "markdown",
|
||||||
|
data = mapOf(
|
||||||
|
"content" to markdownExtra.content!!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
46 -> {
|
||||||
|
val buttonExtra = commonElem.elem!!.decodeProtobuf<ButtonExtra>()
|
||||||
|
MessageSegment(
|
||||||
|
type = "button",
|
||||||
|
data = buttonExtra.field1!!.let {
|
||||||
|
mapOf(
|
||||||
|
"buttons" to it.rows!!.map { row ->
|
||||||
|
row.buttons!!.map { button ->
|
||||||
|
val renderData = button.renderData
|
||||||
|
val action = button.action
|
||||||
|
val permission = action?.permission
|
||||||
|
mapOf(
|
||||||
|
"id" to button.id,
|
||||||
|
"render_data" to mapOf(
|
||||||
|
"label" to (renderData?.label ?: ""),
|
||||||
|
"visited_label" to (renderData?.visitedLabel ?: ""),
|
||||||
|
"style" to (renderData?.style ?: 0)
|
||||||
|
),
|
||||||
|
"action" to mapOf(
|
||||||
|
"type" to (action?.type ?: 0),
|
||||||
|
"permission" to mapOf(
|
||||||
|
"type" to (permission?.type ?: 0),
|
||||||
|
"specify_role_ids" to permission?.specifyRoleIds,
|
||||||
|
"specify_user_ids" to permission?.specifyUserIds
|
||||||
|
),
|
||||||
|
"unsupport_tips" to (action?.unsupportTips ?: ""),
|
||||||
|
"data" to (action?.data ?: ""),
|
||||||
|
"reply" to action?.reply,
|
||||||
|
"enter" to action?.enter,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appid" to it.appid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> MessageSegment(
|
||||||
|
type = "common",
|
||||||
|
data = mapOf(
|
||||||
|
"data" to commonElem.elem!!.encodeBase64()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 灰色提示条消息过滤
|
||||||
|
// */
|
||||||
|
// private suspend fun convertGrayTipsElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: Elem
|
||||||
|
// ): 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: Elem
|
||||||
|
// ): 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: Elem
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val multiMsg = element.multiForwardElem
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "forward",
|
||||||
|
// data = mapOf(
|
||||||
|
// "id" to multiMsg.resId
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private suspend fun convertXmlLongMsgElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: Elem
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val longMsg = element.structLongElem
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "forward",
|
||||||
|
// data = mapOf(
|
||||||
|
// "id" to longMsg.resId
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// private suspend fun convertBubbleFaceElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: Elem
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val bubbleElement = element.faceBubbleElement
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "bubble_face",
|
||||||
|
// data = mapOf(
|
||||||
|
// "id" to bubbleElement.yellowFaceInfo.index,
|
||||||
|
// "count" to (bubbleElement.faceCount ?: 1),
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,568 +0,0 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet.msg.converter
|
|
||||||
|
|
||||||
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.utils.DeflateTools
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asString
|
|
||||||
import protobuf.message.Elem
|
|
||||||
|
|
||||||
|
|
||||||
internal typealias IMessageElementConverter = suspend (Int, String, String, Elem) -> 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: Elem
|
|
||||||
): 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 {
|
|
||||||
return MessageSegment(
|
|
||||||
type = "text",
|
|
||||||
data = hashMapOf(
|
|
||||||
"text" to text.str!!
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * 小表情 / 戳一戳 消息转换消息段
|
|
||||||
// */
|
|
||||||
// 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: Elem
|
|
||||||
): MessageSegment {
|
|
||||||
val data = element.lightApp!!.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()
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
}
|
|
@ -8,6 +8,9 @@ import moe.fuqiuluo.qqinterface.servlet.CardSvc
|
|||||||
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.msg.toJson
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.msg.toSegments
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.*
|
import moe.fuqiuluo.qqinterface.servlet.transfile.*
|
||||||
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
|
||||||
@ -18,6 +21,7 @@ import moe.fuqiuluo.shamrock.helper.ActionMsgException
|
|||||||
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.LogicException
|
import moe.fuqiuluo.shamrock.helper.LogicException
|
||||||
|
import moe.fuqiuluo.shamrock.helper.MessageHelper.messageArrayToMessageElements
|
||||||
import moe.fuqiuluo.shamrock.helper.ParamsException
|
import moe.fuqiuluo.shamrock.helper.ParamsException
|
||||||
import moe.fuqiuluo.shamrock.tools.*
|
import moe.fuqiuluo.shamrock.tools.*
|
||||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||||
@ -35,34 +39,31 @@ import kotlin.random.nextULong
|
|||||||
|
|
||||||
internal typealias IMessageElementMaker = suspend (Int, Long, String, JsonObject) -> Result<Elem>
|
internal typealias IMessageElementMaker = suspend (Int, Long, String, JsonObject) -> Result<Elem>
|
||||||
|
|
||||||
internal object MessageElementMaker {
|
internal object ElemMaker {
|
||||||
private val makerArray = hashMapOf(
|
private val makerArray = hashMapOf(
|
||||||
"text" to MessageElementMaker::createTextElem,
|
"text" to ElemMaker::createTextElem,
|
||||||
"at" to MessageElementMaker::createAtElem,
|
"at" to ElemMaker::createAtElem,
|
||||||
"face" to MessageElementMaker::createFaceElem,
|
"face" to ElemMaker::createFaceElem,
|
||||||
"pic" to MessageElementMaker::createImageElem,
|
"pic" to ElemMaker::createImageElem,
|
||||||
"image" to MessageElementMaker::createImageElem,
|
"image" to ElemMaker::createImageElem,
|
||||||
// "voice" to MessageElementMaker::createRecordElem,
|
// "voice" to MessageElementMaker::createRecordElem,
|
||||||
// "record" to MessageElementMaker::createRecordElem,
|
// "record" to MessageElementMaker::createRecordElem,
|
||||||
// "video" to MessageElementMaker::createVideoElem,
|
// "video" to MessageElementMaker::createVideoElem,
|
||||||
"markdown" to MessageElementMaker::createMarkdownElem,
|
"markdown" to ElemMaker::createMarkdownElem,
|
||||||
"button" to MessageElementMaker::createButtonElem,
|
"button" to ElemMaker::createButtonElem,
|
||||||
"inline_keyboard" to MessageElementMaker::createButtonElem,
|
"inline_keyboard" to ElemMaker::createButtonElem,
|
||||||
// "dice" to MessageElementMaker::createDiceElem,
|
"dice" to ElemMaker::createNewDiceElem,
|
||||||
// "rps" to MessageElementMaker::createRpsElem,
|
"rps" to ElemMaker::createNewRpsElem,
|
||||||
"basketball" to MessageElementMaker::createBasketballElem,
|
"poke" to ElemMaker::createPokeElem,
|
||||||
"new_dice" to MessageElementMaker::createNewDiceElem,
|
|
||||||
"new_rps" to MessageElementMaker::createNewRpsElem,
|
|
||||||
"poke" to MessageElementMaker::createPokeElem,
|
|
||||||
// "anonymous" to MessageElementMaker::createAnonymousElem,
|
// "anonymous" to MessageElementMaker::createAnonymousElem,
|
||||||
// "share" to MessageElementMaker::createShareElem,
|
// "share" to MessageElementMaker::createShareElem,
|
||||||
// "contact" to MessageElementMaker::createContactElem,
|
// "contact" to MessageElementMaker::createContactElem,
|
||||||
// "location" to MessageElementMaker::createLocationElem,
|
// "location" to MessageElementMaker::createLocationElem,
|
||||||
// "music" to MessageElementMaker::createMusicElem,
|
// "music" to MessageElementMaker::createMusicElem,
|
||||||
"reply" to MessageElementMaker::createReplyElem,
|
"reply" to ElemMaker::createReplyElem,
|
||||||
// "touch" to MessageElementMaker::createTouchElem,
|
// "touch" to MessageElementMaker::createTouchElem,
|
||||||
// "weather" to MessageElementMaker::createWeatherElem,
|
"weather" to ElemMaker::createWeatherElem,
|
||||||
"json" to MessageElementMaker::createJsonElem,
|
"json" to ElemMaker::createJsonElem,
|
||||||
// "node" to MessageMaker::createNodeElem,
|
// "node" to MessageMaker::createNodeElem,
|
||||||
//"multi_msg" to MessageMaker::createLongMsgStruct,
|
//"multi_msg" to MessageMaker::createLongMsgStruct,
|
||||||
//"bubble_face" to MessageElementMaker::createBubbleFaceElem,
|
//"bubble_face" to MessageElementMaker::createBubbleFaceElem,
|
||||||
@ -111,7 +112,11 @@ internal object MessageElementMaker {
|
|||||||
else -> {
|
else -> {
|
||||||
qq = qqStr.toLong()
|
qq = qqStr.toLong()
|
||||||
type = 0
|
type = 0
|
||||||
"@" + (data["name"].asStringOrNull ?: GroupSvc.getTroopMemberInfoByUinV2(peerId.toLong(), qq, true)
|
"@" + (data["name"].asStringOrNull ?: GroupSvc.getTroopMemberInfoByUinV2(
|
||||||
|
peerId.toLong(),
|
||||||
|
qq,
|
||||||
|
true
|
||||||
|
)
|
||||||
.let {
|
.let {
|
||||||
val info = it.getOrNull()
|
val info = it.getOrNull()
|
||||||
if (info == null)
|
if (info == null)
|
||||||
@ -164,9 +169,31 @@ internal object MessageElementMaker {
|
|||||||
data: JsonObject
|
data: JsonObject
|
||||||
): Result<Elem> {
|
): Result<Elem> {
|
||||||
data.checkAndThrow("id")
|
data.checkAndThrow("id")
|
||||||
val elem = Elem(
|
val faceId = data["id"].asInt
|
||||||
face = FaceMsg(data["id"].asInt)
|
val elem = if (data["big"].asBooleanOrNull == true) {
|
||||||
|
Elem(
|
||||||
|
commonElem = CommonElem(
|
||||||
|
serviceType = 37,
|
||||||
|
elem = QFaceExtra(
|
||||||
|
packId = "1",
|
||||||
|
stickerId = "1",
|
||||||
|
faceId = faceId,
|
||||||
|
field4 = 1,
|
||||||
|
field5 = 1,
|
||||||
|
result = "",
|
||||||
|
faceText = "", //todo 表情名字
|
||||||
|
field9 = 1
|
||||||
|
).toByteArray(),
|
||||||
|
businessType = 1
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Elem(
|
||||||
|
face = FaceMsg(
|
||||||
|
index = faceId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
return Result.success(elem)
|
return Result.success(elem)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,7 +286,7 @@ internal object MessageElementMaker {
|
|||||||
width = picWidth.toUInt(),
|
width = picWidth.toUInt(),
|
||||||
height = picHeight.toUInt(),
|
height = picHeight.toUInt(),
|
||||||
size = QQNTWrapperUtil.CppProxy.getFileSize(file.absolutePath).toUInt(),
|
size = QQNTWrapperUtil.CppProxy.getFileSize(file.absolutePath).toUInt(),
|
||||||
origin = if (isOriginal) 1u else 0u,
|
origin = isOriginal,
|
||||||
thumbWidth = 0u,
|
thumbWidth = 0u,
|
||||||
thumbHeight = 0u,
|
thumbHeight = 0u,
|
||||||
pbReserve = CustomFace.Companion.PbReserve(field1 = 0)
|
pbReserve = CustomFace.Companion.PbReserve(field1 = 0)
|
||||||
@ -279,7 +306,7 @@ internal object MessageElementMaker {
|
|||||||
picHeight = picWidth.toUInt(),
|
picHeight = picWidth.toUInt(),
|
||||||
picWidth = picHeight.toUInt(),
|
picWidth = picHeight.toUInt(),
|
||||||
resId = "".toByteArray(),
|
resId = "".toByteArray(),
|
||||||
original = if (isOriginal) 1u else 0u, // true
|
original = isOriginal, // true
|
||||||
pbReserve = NotOnlineImage.Companion.PbReserve(field1 = 0)
|
pbReserve = NotOnlineImage.Companion.PbReserve(field1 = 0)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -324,7 +351,7 @@ internal object MessageElementMaker {
|
|||||||
),
|
),
|
||||||
type = 0u,
|
type = 0u,
|
||||||
pbReserve = SourceMsg.Companion.PbReserve(
|
pbReserve = SourceMsg.Companion.PbReserve(
|
||||||
field3 = Random.nextULong(),
|
msgRand = Random.nextInt().toULong(),
|
||||||
field8 = Random.nextInt(0, 10000)
|
field8 = Random.nextInt(0, 10000)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -340,10 +367,19 @@ internal object MessageElementMaker {
|
|||||||
senderUin = msg.senderUin.toULong(),
|
senderUin = msg.senderUin.toULong(),
|
||||||
time = msg.msgTime.toULong(),
|
time = msg.msgTime.toULong(),
|
||||||
flag = 1u,
|
flag = 1u,
|
||||||
// elems = msg.elements.toSegments(),
|
elems = messageArrayToMessageElements(
|
||||||
|
msg.chatType,
|
||||||
|
msg.msgId,
|
||||||
|
msg.peerUin.toString(),
|
||||||
|
msg.elements.toSegments(
|
||||||
|
msg.chatType,
|
||||||
|
if (msg.chatType == MsgConstant.KCHATTYPEGUILD) msg.guildId else msg.peerUin.toString(),
|
||||||
|
msg.channelId ?: msg.peerUin.toString()
|
||||||
|
).toJson()
|
||||||
|
).second,
|
||||||
type = 0u,
|
type = 0u,
|
||||||
pbReserve = SourceMsg.Companion.PbReserve(
|
pbReserve = SourceMsg.Companion.PbReserve(
|
||||||
field3 = Random.nextULong(),
|
msgRand = Random.nextULong(),
|
||||||
senderUid = msg.senderUid,
|
senderUid = msg.senderUid,
|
||||||
receiverUid = TicketSvc.getUid(),
|
receiverUid = TicketSvc.getUid(),
|
||||||
field8 = Random.nextInt(0, 10000)
|
field8 = Random.nextInt(0, 10000)
|
||||||
@ -370,6 +406,38 @@ internal object MessageElementMaker {
|
|||||||
return Result.success(elem)
|
return Result.success(elem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun createWeatherElem(
|
||||||
|
chatType: Int,
|
||||||
|
msgId: Long,
|
||||||
|
peerId: String,
|
||||||
|
data: JsonObject
|
||||||
|
): Result<Elem> {
|
||||||
|
var code = data["code"].asIntOrNull
|
||||||
|
|
||||||
|
if (code == null) {
|
||||||
|
data.checkAndThrow("city")
|
||||||
|
val city = data["city"].asString
|
||||||
|
code = WeatherSvc.searchCity(city).onFailure {
|
||||||
|
LogCenter.log("无法获取城市天气: $city", Level.ERROR)
|
||||||
|
}.getOrNull()?.firstOrNull()?.adcode
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code != null) {
|
||||||
|
WeatherSvc.fetchWeatherCard(code).onSuccess {
|
||||||
|
// OidbSvc.0xdc2_34
|
||||||
|
// 00 00 00 DF 08 C2 1B 10 22 22 C4 01 0A B7 01 08 A2 E0 F2 2F 10 01 18 00 2A 02 08 01 58 FB 91 F6 AE 02 62 A1 01 08 01 52 08 E5 8C 97 E4 BA AC 20 20 5A 19 2D 33 C2 B0 2F 33 C2 B0 0A E7 A9 BA E6 B0 94 E8 B4 A8 E9 87 8F 3A E8 89 AF 62 11 5B E5 88 86 E4 BA AB 5D 20 E5 8C 97 E4 BA AC 20 20 6A 25 68 74 74 70 73 3A 2F 2F 77 65 61 74 68 65 72 2E 6D 70 2E 71 71 2E 63 6F 6D 2F 3F 73 74 3D 30 26 5F 77 76 3D 31 72 3E 68 74 74 70 73 3A 2F 2F 69 6D 67 63 61 63 68 65 2E 71 71 2E 63 6F 6D 2F 61 63 2F 71 71 77 65 61 74 68 65 72 2F 69 6D 61 67 65 2F 73 68 61 72 65 5F 69 63 6F 6E 2F 66 69 6E 65 2E 70 6E 67 12 08 08 01 10 FB 91 F6 AE 02 32 0D 61 6E 64 72 6F 69 64 20 39 2E 30 2E 38
|
||||||
|
return createJsonElem(
|
||||||
|
chatType, msgId, peerId, it["weekStore"]
|
||||||
|
.asJsonObject["share"].asJsonObject
|
||||||
|
)
|
||||||
|
}.onFailure {
|
||||||
|
LogCenter.log("无法发送天气分享", Level.ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.failure(ActionMsgException)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun createPokeElem(
|
private suspend fun createPokeElem(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
msgId: Long,
|
msgId: Long,
|
||||||
@ -391,31 +459,6 @@ internal object MessageElementMaker {
|
|||||||
return Result.success(elem)
|
return Result.success(elem)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createBasketballElem(
|
|
||||||
chatType: Int,
|
|
||||||
msgId: Long,
|
|
||||||
peerId: String,
|
|
||||||
data: JsonObject
|
|
||||||
): Result<Elem> {
|
|
||||||
val elem = Elem(
|
|
||||||
commonElem = CommonElem(
|
|
||||||
serviceType = 37,
|
|
||||||
elem = QFaceExtra(
|
|
||||||
packId = "1",
|
|
||||||
stickerId = "13",
|
|
||||||
faceId = 114,
|
|
||||||
field4 = 1,
|
|
||||||
field5 = 2,
|
|
||||||
field6 = "",
|
|
||||||
faceText = "/篮球",
|
|
||||||
field9 = 1
|
|
||||||
).toByteArray(),
|
|
||||||
businessType = 2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return Result.success(elem)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun createNewDiceElem(
|
private suspend fun createNewDiceElem(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
msgId: Long,
|
msgId: Long,
|
||||||
@ -431,7 +474,7 @@ internal object MessageElementMaker {
|
|||||||
faceId = 358,
|
faceId = 358,
|
||||||
field4 = 1,
|
field4 = 1,
|
||||||
field5 = 2,
|
field5 = 2,
|
||||||
field6 = "",
|
result = "",
|
||||||
faceText = "/骰子",
|
faceText = "/骰子",
|
||||||
field9 = 1
|
field9 = 1
|
||||||
).toByteArray(),
|
).toByteArray(),
|
||||||
@ -456,7 +499,7 @@ internal object MessageElementMaker {
|
|||||||
faceId = 359,
|
faceId = 359,
|
||||||
field4 = 1,
|
field4 = 1,
|
||||||
field5 = 2,
|
field5 = 2,
|
||||||
field6 = "",
|
result = "",
|
||||||
faceText = "/包剪锤",
|
faceText = "/包剪锤",
|
||||||
field9 = 1
|
field9 = 1
|
||||||
).toByteArray(),
|
).toByteArray(),
|
||||||
@ -489,19 +532,20 @@ internal object MessageElementMaker {
|
|||||||
peerId: String,
|
peerId: String,
|
||||||
data: JsonObject
|
data: JsonObject
|
||||||
): Result<Elem> {
|
): Result<Elem> {
|
||||||
data.checkAndThrow("rows")
|
data.checkAndThrow("buttons")
|
||||||
val elem = Elem(
|
val elem = Elem(
|
||||||
commonElem = CommonElem(
|
commonElem = CommonElem(
|
||||||
serviceType = 46,
|
serviceType = 46,
|
||||||
elem = ButtonExtra(
|
elem = ButtonExtra(
|
||||||
field1 = Object1(
|
field1 = Object1(
|
||||||
rows = data["rows"].asJsonArray.map { row ->
|
rows = data["buttons"].asJsonArray.map { row ->
|
||||||
Row(buttons = row.asJsonArray.map {
|
Row(buttons = row.asJsonArray.map {
|
||||||
val button = it.asJsonObject
|
val button = it.asJsonObject
|
||||||
val renderData = button["render_data"].asJsonObject
|
val renderData = button["render_data"].asJsonObject
|
||||||
val action = button["action"].asJsonObject
|
val action = button["action"].asJsonObject
|
||||||
|
val permission = action["permission"].asJsonObject
|
||||||
Button(
|
Button(
|
||||||
id = button["id"].asIntOrNull,
|
id = button["id"].asStringOrNull,
|
||||||
renderData = RenderData(
|
renderData = RenderData(
|
||||||
label = renderData["label"].asString,
|
label = renderData["label"].asString,
|
||||||
visitedLabel = renderData["visited_label"].asString,
|
visitedLabel = renderData["visited_label"].asString,
|
||||||
@ -510,9 +554,9 @@ internal object MessageElementMaker {
|
|||||||
action = Action(
|
action = Action(
|
||||||
type = action["type"].asInt,
|
type = action["type"].asInt,
|
||||||
permission = Permission(
|
permission = Permission(
|
||||||
type = action["permission"].asJsonObject["type"].asInt,
|
type = permission["type"].asInt,
|
||||||
specifyRoleIds = action["permission"].asJsonObject["specify_role_ids"].asJsonArrayOrNull?.map { id -> id.asString },
|
specifyRoleIds = permission["specify_role_ids"].asJsonArrayOrNull?.map { id -> id.asString },
|
||||||
specifyUserIds = action["permission"].asJsonObject["specify_user_ids"].asJsonArrayOrNull?.map { id -> id.asString }
|
specifyUserIds = permission["specify_user_ids"].asJsonArrayOrNull?.map { id -> id.asString }
|
||||||
),
|
),
|
||||||
unsupportTips = action["unsupport_tips"].asString,
|
unsupportTips = action["unsupport_tips"].asString,
|
||||||
data = action["data"].asString,
|
data = action["data"].asString,
|
@ -3,6 +3,7 @@ package moe.fuqiuluo.qqinterface.servlet.msg.maker
|
|||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import com.tencent.mobileqq.app.QQAppInterface
|
import com.tencent.mobileqq.app.QQAppInterface
|
||||||
|
import com.tencent.mobileqq.data.MessageForPic
|
||||||
import com.tencent.mobileqq.emoticon.QQSysFaceUtil
|
import com.tencent.mobileqq.emoticon.QQSysFaceUtil
|
||||||
import com.tencent.mobileqq.pb.ByteStringMicro
|
import com.tencent.mobileqq.pb.ByteStringMicro
|
||||||
import com.tencent.mobileqq.qroute.QRoute
|
import com.tencent.mobileqq.qroute.QRoute
|
||||||
@ -57,37 +58,37 @@ import kotlin.random.nextInt
|
|||||||
|
|
||||||
internal typealias IMsgElementMaker = suspend (Int, Long, String, JsonObject) -> Result<MsgElement>
|
internal typealias IMsgElementMaker = suspend (Int, Long, String, JsonObject) -> Result<MsgElement>
|
||||||
|
|
||||||
internal object MsgElementMaker {
|
internal object NtMsgElementMaker {
|
||||||
private val makerMap = hashMapOf(
|
private val makerMap = hashMapOf(
|
||||||
"text" to MsgElementMaker::createTextElem,
|
"text" to NtMsgElementMaker::createTextElem,
|
||||||
"face" to MsgElementMaker::createFaceElem,
|
"face" to NtMsgElementMaker::createFaceElem,
|
||||||
"pic" to MsgElementMaker::createImageElem,
|
"pic" to NtMsgElementMaker::createImageElem,
|
||||||
"image" to MsgElementMaker::createImageElem,
|
"image" to NtMsgElementMaker::createImageElem,
|
||||||
"voice" to MsgElementMaker::createRecordElem,
|
"voice" to NtMsgElementMaker::createRecordElem,
|
||||||
"record" to MsgElementMaker::createRecordElem,
|
"record" to NtMsgElementMaker::createRecordElem,
|
||||||
"at" to MsgElementMaker::createAtElem,
|
"at" to NtMsgElementMaker::createAtElem,
|
||||||
"video" to MsgElementMaker::createVideoElem,
|
"video" to NtMsgElementMaker::createVideoElem,
|
||||||
"markdown" to MsgElementMaker::createMarkdownElem,
|
"markdown" to NtMsgElementMaker::createMarkdownElem,
|
||||||
"dice" to MsgElementMaker::createDiceElem,
|
"dice" to NtMsgElementMaker::createDiceElem,
|
||||||
"rps" to MsgElementMaker::createRpsElem,
|
"rps" to NtMsgElementMaker::createRpsElem,
|
||||||
"poke" to MsgElementMaker::createPokeElem,
|
"poke" to NtMsgElementMaker::createPokeElem,
|
||||||
"anonymous" to MsgElementMaker::createAnonymousElem,
|
"anonymous" to NtMsgElementMaker::createAnonymousElem,
|
||||||
"share" to MsgElementMaker::createShareElem,
|
"share" to NtMsgElementMaker::createShareElem,
|
||||||
"contact" to MsgElementMaker::createContactElem,
|
"contact" to NtMsgElementMaker::createContactElem,
|
||||||
"location" to MsgElementMaker::createLocationElem,
|
"location" to NtMsgElementMaker::createLocationElem,
|
||||||
"music" to MsgElementMaker::createMusicElem,
|
"music" to NtMsgElementMaker::createMusicElem,
|
||||||
"reply" to MsgElementMaker::createReplyElem,
|
"reply" to NtMsgElementMaker::createReplyElem,
|
||||||
"touch" to MsgElementMaker::createTouchElem,
|
"touch" to NtMsgElementMaker::createTouchElem,
|
||||||
"weather" to MsgElementMaker::createWeatherElem,
|
"weather" to NtMsgElementMaker::createWeatherElem,
|
||||||
"json" to MsgElementMaker::createJsonElem,
|
"json" to NtMsgElementMaker::createJsonElem,
|
||||||
"new_dice" to MsgElementMaker::createNewDiceElem,
|
"new_dice" to NtMsgElementMaker::createNewDiceElem,
|
||||||
"new_rps" to MsgElementMaker::createNewRpsElem,
|
"new_rps" to NtMsgElementMaker::createNewRpsElem,
|
||||||
"basketball" to MsgElementMaker::createBasketballElem,
|
"basketball" to NtMsgElementMaker::createBasketballElem,
|
||||||
//"node" to MessageMaker::createNodeElem,
|
//"node" to MessageMaker::createNodeElem,
|
||||||
//"multi_msg" to MessageMaker::createLongMsgStruct,
|
//"multi_msg" to MessageMaker::createLongMsgStruct,
|
||||||
"bubble_face" to MsgElementMaker::createBubbleFaceElem,
|
"bubble_face" to NtMsgElementMaker::createBubbleFaceElem,
|
||||||
"button" to MsgElementMaker::createInlineKeywordElem,
|
"button" to NtMsgElementMaker::createInlineKeywordElem,
|
||||||
"inline_keyboard" to MsgElementMaker::createInlineKeywordElem
|
"inline_keyboard" to NtMsgElementMaker::createInlineKeywordElem
|
||||||
)
|
)
|
||||||
|
|
||||||
operator fun get(type: String): IMsgElementMaker? = makerMap[type]
|
operator fun get(type: String): IMsgElementMaker? = makerMap[type]
|
||||||
@ -332,7 +333,6 @@ internal object MsgElementMaker {
|
|||||||
LogCenter.log("无法发送天气分享", Level.ERROR)
|
LogCenter.log("无法发送天气分享", Level.ERROR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.failure(ActionMsgException)
|
return Result.failure(ActionMsgException)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1006,6 +1006,10 @@ internal object MsgElementMaker {
|
|||||||
pic.picSubType = data["subType"].asIntOrNull ?: 0
|
pic.picSubType = data["subType"].asIntOrNull ?: 0
|
||||||
pic.isFlashPic = isFlash
|
pic.isFlashPic = isFlash
|
||||||
|
|
||||||
|
//if (PlatformUtils.getQQVersionCode() >= PlatformUtils.QQ_9_0_8_VER && !ShamrockConfig.enableOldBDH()) {
|
||||||
|
// pic.storeID = 1
|
||||||
|
//}
|
||||||
|
|
||||||
elem.picElement = pic
|
elem.picElement = pic
|
||||||
|
|
||||||
return Result.success(elem)
|
return Result.success(elem)
|
@ -1,5 +1,7 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet.transfile
|
package moe.fuqiuluo.qqinterface.servlet.transfile
|
||||||
|
|
||||||
|
import com.tencent.mobileqq.data.MessageRecord
|
||||||
|
|
||||||
internal enum class ContactType {
|
internal enum class ContactType {
|
||||||
TROOP,
|
TROOP,
|
||||||
PRIVATE,
|
PRIVATE,
|
||||||
@ -8,12 +10,20 @@ internal enum class ContactType {
|
|||||||
internal interface TransTarget {
|
internal interface TransTarget {
|
||||||
val id: String
|
val id: String
|
||||||
val type: ContactType
|
val type: ContactType
|
||||||
|
|
||||||
|
val mRec: MessageRecord?
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class Troop(override val id: String): TransTarget {
|
internal class Troop(
|
||||||
|
override val id: String,
|
||||||
|
override val mRec: MessageRecord? = null
|
||||||
|
): TransTarget {
|
||||||
override val type: ContactType = ContactType.TROOP
|
override val type: ContactType = ContactType.TROOP
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class Private(override val id: String): TransTarget {
|
internal class Private(
|
||||||
|
override val id: String,
|
||||||
|
override val mRec: MessageRecord? = null
|
||||||
|
): TransTarget {
|
||||||
override val type: ContactType = ContactType.PRIVATE
|
override val type: ContactType = ContactType.PRIVATE
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,18 @@
|
|||||||
|
|
||||||
package moe.fuqiuluo.qqinterface.servlet.transfile
|
package moe.fuqiuluo.qqinterface.servlet.transfile
|
||||||
|
|
||||||
|
import com.tencent.mobileqq.transfile.BaseTransProcessor
|
||||||
import com.tencent.mobileqq.transfile.FileMsg
|
import com.tencent.mobileqq.transfile.FileMsg
|
||||||
import com.tencent.mobileqq.transfile.TransferRequest
|
import com.tencent.mobileqq.transfile.TransferRequest
|
||||||
import com.tencent.mobileqq.transfile.api.ITransFileController
|
import com.tencent.mobileqq.transfile.api.ITransFileController
|
||||||
|
import com.tencent.mobileqq.utils.httputils.IHttpCommunicatorListener
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
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 moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
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
|
||||||
@ -81,6 +84,7 @@ internal abstract class FileTransfer {
|
|||||||
}
|
}
|
||||||
suspendCancellableCoroutine { continuation ->
|
suspendCancellableCoroutine { continuation ->
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
|
lateinit var processor: IHttpCommunicatorListener
|
||||||
while (
|
while (
|
||||||
//service.findProcessor(
|
//service.findProcessor(
|
||||||
// transferRequest.keyForTransfer // uin + uniseq
|
// transferRequest.keyForTransfer // uin + uniseq
|
||||||
@ -89,8 +93,13 @@ internal abstract class FileTransfer {
|
|||||||
// 如果上传处理器依旧存在,说明没有上传成功
|
// 如果上传处理器依旧存在,说明没有上传成功
|
||||||
&& service.isWorking.get()
|
&& service.isWorking.get()
|
||||||
) {
|
) {
|
||||||
|
processor = service.findProcessor(runtime.currentAccountUin, transferRequest.mUniseq)
|
||||||
delay(100)
|
delay(100)
|
||||||
}
|
}
|
||||||
|
if (processor is BaseTransProcessor && processor.file != null) {
|
||||||
|
val fileMsg = processor.file
|
||||||
|
LogCenter.log("[OldBDH] 资源上传结束(fileId = ${fileMsg.fileID}, fileKey = ${fileMsg.fileKey}, path = ${fileMsg.filePath})")
|
||||||
|
}
|
||||||
continuation.resume(true)
|
continuation.resume(true)
|
||||||
}
|
}
|
||||||
// 实现取消上传器
|
// 实现取消上传器
|
||||||
|
@ -0,0 +1,227 @@
|
|||||||
|
package moe.fuqiuluo.qqinterface.servlet.transfile
|
||||||
|
|
||||||
|
import kotlinx.atomicfu.atomic
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
||||||
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
|
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||||
|
import protobuf.auto.toByteArray
|
||||||
|
import protobuf.oidb.TrpcOidb
|
||||||
|
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.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.UploadInfo
|
||||||
|
import protobuf.oidb.cmd0x11c5.UploadReq
|
||||||
|
import protobuf.oidb.cmd0x11c5.VideoDownloadExt
|
||||||
|
import protobuf.oidb.cmd0x388.Cmd0x388ReqBody
|
||||||
|
import protobuf.oidb.cmd0x388.Cmd0x388RspBody
|
||||||
|
import protobuf.oidb.cmd0x388.TryUpImgReq
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.random.nextUInt
|
||||||
|
import kotlin.random.nextULong
|
||||||
|
|
||||||
|
internal object NtV2RichMediaSvc: BaseSvc() {
|
||||||
|
private val requestIdSeq = atomic(2L)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取NT图片的RKEY
|
||||||
|
*/
|
||||||
|
suspend fun getNtPicRKey(
|
||||||
|
fileId: String,
|
||||||
|
md5: String,
|
||||||
|
sha: String,
|
||||||
|
fileSize: ULong,
|
||||||
|
width: UInt,
|
||||||
|
height: UInt,
|
||||||
|
sceneBuilder: suspend SceneInfo.() -> Unit
|
||||||
|
): Result<String> {
|
||||||
|
runCatching {
|
||||||
|
val req = NtV2RichMediaReq(
|
||||||
|
head = MultiMediaReqHead(
|
||||||
|
commonHead = CommonHead(
|
||||||
|
requestId = requestIdSeq.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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求上传Nt图片
|
||||||
|
*/
|
||||||
|
suspend fun requestUploadNtPic(
|
||||||
|
file: File,
|
||||||
|
md5: String,
|
||||||
|
sha: String,
|
||||||
|
name: String,
|
||||||
|
width: UInt,
|
||||||
|
height: UInt,
|
||||||
|
sceneBuilder: suspend SceneInfo.() -> Unit
|
||||||
|
) {
|
||||||
|
val req = NtV2RichMediaReq(
|
||||||
|
head = MultiMediaReqHead(
|
||||||
|
commonHead = CommonHead(
|
||||||
|
requestId = requestIdSeq.incrementAndGet().toULong(),
|
||||||
|
cmd = 100u
|
||||||
|
),
|
||||||
|
sceneInfo = SceneInfo(
|
||||||
|
requestType = 2u,
|
||||||
|
businessType = 1u,
|
||||||
|
).apply {
|
||||||
|
sceneBuilder()
|
||||||
|
},
|
||||||
|
clientMeta = ClientMeta(2u)
|
||||||
|
),
|
||||||
|
upload = UploadReq(
|
||||||
|
listOf(UploadInfo(
|
||||||
|
FileInfo(
|
||||||
|
fileSize = file.length().toULong(),
|
||||||
|
md5 = md5,
|
||||||
|
sha1 = sha,
|
||||||
|
name = name,
|
||||||
|
fileType = FileType(
|
||||||
|
fileType = 1u,
|
||||||
|
picFormat = 1000u,
|
||||||
|
videoFormat = 0u,
|
||||||
|
voiceFormat = 0u
|
||||||
|
),
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
time = 0u,
|
||||||
|
original = 1u
|
||||||
|
),
|
||||||
|
subFileType = 0u
|
||||||
|
)),
|
||||||
|
tryFastUploadCompleted = true,
|
||||||
|
srvSendMsg = false,
|
||||||
|
clientRandomId = Random.nextULong(),
|
||||||
|
compatQMsgSceneType = 1u,
|
||||||
|
clientSeq = Random.nextUInt(),
|
||||||
|
noNeedCompatMsg = true
|
||||||
|
)
|
||||||
|
).toByteArray()
|
||||||
|
val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11c5_100", 4549, 100, req, true)?.slice(4)
|
||||||
|
val rsp = buffer?.decodeProtobuf<TrpcOidb>()?.buffer?.decodeProtobuf<NtV2RichMediaRsp>()
|
||||||
|
LogCenter.log("requestUploadPic => rsp: $rsp")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun requestUploadGroupPic(
|
||||||
|
groupId: ULong,
|
||||||
|
md5: String,
|
||||||
|
fileSize: ULong,
|
||||||
|
width: UInt,
|
||||||
|
height: UInt,
|
||||||
|
): Result<TryUpPicData> {
|
||||||
|
return runCatching {
|
||||||
|
val rspBuffer = sendBufferAW("ImgStore.GroupPicUp", true, Cmd0x388ReqBody(
|
||||||
|
netType = 3,
|
||||||
|
subCmd = 1,
|
||||||
|
msgTryUpImg = arrayListOf(
|
||||||
|
TryUpImgReq(
|
||||||
|
groupCode = groupId.toLong(),
|
||||||
|
srcUin = TicketSvc.getLongUin(),
|
||||||
|
fileMd5 = md5.hex2ByteArray(),
|
||||||
|
fileSize = fileSize.toLong(),
|
||||||
|
fileName = "$md5.jpg",
|
||||||
|
srcTerm = 2,
|
||||||
|
platformType = 9,
|
||||||
|
buType = 212,
|
||||||
|
picWidth = width.toInt(),
|
||||||
|
picHeight = height.toInt(),
|
||||||
|
picType = 1000,
|
||||||
|
buildVer = "1.0.0",
|
||||||
|
originalPic = 1,
|
||||||
|
fileIndex = byteArrayOf(),
|
||||||
|
srvUpload = 0
|
||||||
|
)
|
||||||
|
),
|
||||||
|
).toByteArray())!!
|
||||||
|
val rsp = rspBuffer.decodeProtobuf<Cmd0x388RspBody>()
|
||||||
|
.msgTryUpImgRsp!!.first()
|
||||||
|
TryUpPicData(
|
||||||
|
uKey = rsp.ukey,
|
||||||
|
exist = rsp.fileExist,
|
||||||
|
fileId = rsp.fileId.toULong(),
|
||||||
|
upIp = rsp.upIp,
|
||||||
|
upPort = rsp.upPort
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TryUpPicData(
|
||||||
|
@SerialName("ukey") val uKey: ByteArray,
|
||||||
|
@SerialName("exist") val exist: Boolean,
|
||||||
|
@SerialName("file_id") val fileId: ULong,
|
||||||
|
@SerialName("up_ip") var upIp: ArrayList<Long>? = null,
|
||||||
|
@SerialName("up_port") var upPort: ArrayList<Int>? = null,
|
||||||
|
)
|
||||||
|
}
|
@ -10,6 +10,7 @@ import kotlinx.atomicfu.atomic
|
|||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc.getNtPicRKey
|
||||||
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
|
||||||
@ -53,8 +54,6 @@ private const val MULTIMEDIA_DOMAIN = "multimedia.nt.qq.com.cn"
|
|||||||
private const val C2C_PIC = "c2cpicdw.qpic.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, Oidb0xfc2ReqBody(
|
val buffer = sendOidbAW("OidbSvcTrpcTcp.0xfc2_0", 4034, 0, Oidb0xfc2ReqBody(
|
||||||
msgCmd = 1200,
|
msgCmd = 1200,
|
||||||
@ -105,7 +104,7 @@ internal object RichProtoSvc: BaseSvc() {
|
|||||||
|
|
||||||
val domain = if (!result.download_file_rsp.str_download_dns.has())
|
val domain = if (!result.download_file_rsp.str_download_dns.has())
|
||||||
("https://" + result.download_file_rsp.str_download_ip.get())
|
("https://" + result.download_file_rsp.str_download_ip.get())
|
||||||
else ("http://" + result.download_file_rsp.str_download_dns.get())
|
else ("http://" + result.download_file_rsp.str_download_dns.get().toByteArray().decodeToString())
|
||||||
val downloadUrl = result.download_file_rsp.bytes_download_url.get().toByteArray().toHexString()
|
val downloadUrl = result.download_file_rsp.bytes_download_url.get().toByteArray().toHexString()
|
||||||
val appId = MobileQQ.getMobileQQ().appId
|
val appId = MobileQQ.getMobileQQ().appId
|
||||||
val version = PlatformUtils.getQQVersion(MobileQQ.getContext())
|
val version = PlatformUtils.getQQVersion(MobileQQ.getContext())
|
||||||
@ -279,81 +278,6 @@ internal object RichProtoSvc: BaseSvc() {
|
|||||||
return "https://$domain/qmeetpic/0/0-0-${md5.uppercase()}/0?term=2"
|
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,
|
||||||
md5: ByteArray,
|
md5: ByteArray,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet.transfile
|
package moe.fuqiuluo.qqinterface.servlet.transfile
|
||||||
|
|
||||||
|
import com.tencent.mobileqq.data.MessageForPic
|
||||||
import com.tencent.mobileqq.data.MessageForShortVideo
|
import com.tencent.mobileqq.data.MessageForShortVideo
|
||||||
|
import com.tencent.mobileqq.data.MessageRecord
|
||||||
import com.tencent.mobileqq.transfile.FileMsg
|
import com.tencent.mobileqq.transfile.FileMsg
|
||||||
import com.tencent.mobileqq.transfile.TransferRequest
|
import com.tencent.mobileqq.transfile.TransferRequest
|
||||||
import moe.fuqiuluo.shamrock.utils.MD5
|
import moe.fuqiuluo.shamrock.utils.MD5
|
||||||
@ -11,16 +13,15 @@ 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>>(
|
||||||
ContactType.TROOP to mapOf(
|
ContactType.TROOP to mapOf(
|
||||||
Picture to { uploadGroupPic(id, (it as PictureResource).src) },
|
Picture to { uploadGroupPic(id, (it as PictureResource).src, mRec) },
|
||||||
Voice to { uploadGroupVoice(id, (it as VoiceResource).src) },
|
Voice to { uploadGroupVoice(id, (it as VoiceResource).src) },
|
||||||
Video to { uploadGroupVideo(id, (it as VideoResource).src, it.thumb) },
|
Video to { uploadGroupVideo(id, (it as VideoResource).src, it.thumb) },
|
||||||
|
|
||||||
),
|
),
|
||||||
ContactType.PRIVATE to mapOf(
|
ContactType.PRIVATE to mapOf(
|
||||||
Picture to { uploadC2CPic(id, (it as PictureResource).src) },
|
Picture to { uploadC2CPic(id, (it as PictureResource).src, mRec) },
|
||||||
Voice to { uploadC2CVoice(id, (it as VoiceResource).src) },
|
Voice to { uploadC2CVoice(id, (it as VoiceResource).src) },
|
||||||
Video to { uploadC2CVideo(id, (it as VideoResource).src, it.thumb) },
|
Video to { uploadC2CVideo(id, (it as VideoResource).src, it.thumb) },
|
||||||
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -83,6 +84,7 @@ internal object Transfer: FileTransfer() {
|
|||||||
suspend fun uploadC2CPic(
|
suspend fun uploadC2CPic(
|
||||||
peerId: String,
|
peerId: String,
|
||||||
file: File,
|
file: File,
|
||||||
|
record: MessageRecord? = null,
|
||||||
wait: Boolean = true
|
wait: Boolean = true
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return transC2CResource(peerId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_CAMERA, wait) {
|
return transC2CResource(peerId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_CAMERA, wait) {
|
||||||
@ -93,22 +95,24 @@ internal object Transfer: FileTransfer() {
|
|||||||
it.mExtraObj = picUpExtraInfo
|
it.mExtraObj = picUpExtraInfo
|
||||||
it.mIsPresend = true
|
it.mIsPresend = true
|
||||||
it.delayShowProgressTimeInMs = 2000
|
it.delayShowProgressTimeInMs = 2000
|
||||||
|
it.mRec = record
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun uploadGroupPic(
|
suspend fun uploadGroupPic(
|
||||||
groupId: String,
|
groupId: String,
|
||||||
file: File,
|
file: File,
|
||||||
|
record: MessageRecord? = null,
|
||||||
wait: Boolean = true
|
wait: Boolean = true
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_CAMERA, 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 = !TransfileHelper.isGifFile(file)
|
|
||||||
picUpExtraInfo.mIsRaw = false
|
picUpExtraInfo.mIsRaw = false
|
||||||
picUpExtraInfo.mUinType = FileMsg.UIN_TROOP
|
picUpExtraInfo.mUinType = FileMsg.UIN_TROOP
|
||||||
it.mPicSendSource = 8
|
it.mPicSendSource = 8
|
||||||
it.delayShowProgressTimeInMs = 2000
|
it.delayShowProgressTimeInMs = 2000
|
||||||
it.mExtraObj = picUpExtraInfo
|
it.mExtraObj = picUpExtraInfo
|
||||||
|
it.mRec = record
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ 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.maker.MessageElementMaker
|
import moe.fuqiuluo.qqinterface.servlet.msg.maker.ElemMaker
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.maker.MsgElementMaker
|
import moe.fuqiuluo.qqinterface.servlet.msg.maker.NtMsgElementMaker
|
||||||
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
|
||||||
@ -29,6 +29,7 @@ import moe.fuqiuluo.shamrock.tools.jsonArray
|
|||||||
import protobuf.message.Elem
|
import protobuf.message.Elem
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
internal object MessageHelper {
|
internal object MessageHelper {
|
||||||
suspend fun sendMessageWithoutMsgId(
|
suspend fun sendMessageWithoutMsgId(
|
||||||
@ -66,12 +67,15 @@ internal object MessageHelper {
|
|||||||
suspend fun resendMsg(contact: Contact, msgId: Long, retryCnt: Int, msgHashId: Int): Result<SendMsgResult> {
|
suspend fun resendMsg(contact: Contact, msgId: Long, retryCnt: Int, msgHashId: Int): Result<SendMsgResult> {
|
||||||
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(15.seconds) {
|
||||||
if (suspendCancellableCoroutine {
|
val resendRet = suspendCancellableCoroutine {
|
||||||
service.resendMsg(contact, msgId) { result, _ ->
|
service.resendMsg(contact, msgId) { result, _ ->
|
||||||
it.resume(result)
|
it.resume(result)
|
||||||
}
|
}
|
||||||
} != 0) {
|
}
|
||||||
|
if (resendRet != 0 &&
|
||||||
|
resendRet != 4 // 使用OldBDH 100%触发
|
||||||
|
) {
|
||||||
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()))
|
||||||
@ -287,18 +291,18 @@ internal object MessageHelper {
|
|||||||
suspend fun messageArrayToMsgElements(
|
suspend fun messageArrayToMsgElements(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
msgId: Long,
|
msgId: Long,
|
||||||
targetUin: String,
|
peerId: String,
|
||||||
messageList: JsonArray
|
messageList: JsonArray
|
||||||
): Pair<Boolean, ArrayList<MsgElement>> {
|
): 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 = MsgElementMaker[msg["type"].asString]
|
val maker = NtMsgElementMaker[msg["type"].asString]
|
||||||
if (maker != null) {
|
if (maker != null) {
|
||||||
try {
|
try {
|
||||||
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
|
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
|
||||||
maker(chatType, msgId, targetUin, data).onSuccess { msgElem ->
|
maker(chatType, msgId, peerId, data).onSuccess { msgElem ->
|
||||||
msgList.add(msgElem)
|
msgList.add(msgElem)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
if (it.javaClass != ActionMsgException::class.java) {
|
if (it.javaClass != ActionMsgException::class.java) {
|
||||||
@ -321,18 +325,18 @@ internal object MessageHelper {
|
|||||||
suspend fun messageArrayToMessageElements(
|
suspend fun messageArrayToMessageElements(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
msgId: Long,
|
msgId: Long,
|
||||||
targetUin: String,
|
peerId: String,
|
||||||
messageList: JsonArray
|
messageList: JsonArray
|
||||||
): Pair<Boolean, ArrayList<Elem>> {
|
): Pair<Boolean, ArrayList<Elem>> {
|
||||||
val msgList = arrayListOf<Elem>()
|
val msgList = arrayListOf<Elem>()
|
||||||
var hasActionMsg = false
|
var hasActionMsg = false
|
||||||
messageList.forEach {
|
messageList.forEach {
|
||||||
val msg = it.jsonObject
|
val msg = it.jsonObject
|
||||||
val maker = MessageElementMaker[msg["type"].asString]
|
val maker = ElemMaker[msg["type"].asString]
|
||||||
if (maker != null) {
|
if (maker != null) {
|
||||||
try {
|
try {
|
||||||
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
|
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
|
||||||
maker(chatType, msgId, targetUin, data).onSuccess { msgElem ->
|
maker(chatType, msgId, peerId, data).onSuccess { msgElem ->
|
||||||
msgList.add(msgElem)
|
msgList.add(msgElem)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
if (it.javaClass != ActionMsgException::class.java) {
|
if (it.javaClass != ActionMsgException::class.java) {
|
||||||
@ -427,11 +431,11 @@ internal object MessageHelper {
|
|||||||
params[key] = value.json
|
params[key] = value.json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val data = hashMapOf(
|
val data = mapOf(
|
||||||
"type" to it["_type"]!!.json,
|
"type" to it["_type"]!!.json,
|
||||||
"data" to JsonObject(params)
|
"data" to JsonObject(params)
|
||||||
)
|
)
|
||||||
arrayList.add(JsonObject(data))
|
arrayList.add(data.json)
|
||||||
}
|
}
|
||||||
return arrayList.jsonArray
|
return arrayList.jsonArray
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,10 @@ internal object GetGroupRootFiles: IActionHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend operator fun invoke(groupId: Long, echo: JsonElement = EmptyJsonString): String {
|
suspend operator fun invoke(groupId: Long, echo: JsonElement = EmptyJsonString): String {
|
||||||
FileSvc.getGroupRootFiles(groupId).onSuccess {
|
return ok(
|
||||||
return ok(it, echo = echo)
|
FileSvc.getGroupRootFiles(groupId).getOrElse { return error(why = "获取失败: $it", echo = echo) },
|
||||||
}.getOrNull()
|
echo = echo
|
||||||
return error(why = "获取失败", echo = echo)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val requiredParams: Array<String> = arrayOf("group_id")
|
override val requiredParams: Array<String> = arrayOf("group_id")
|
||||||
|
@ -65,16 +65,6 @@ internal object GetHistoryMsg : IActionHandler() {
|
|||||||
val msgList = ArrayList<MessageDetail>().apply {
|
val msgList = ArrayList<MessageDetail>().apply {
|
||||||
addAll(result.data!!.map { msg ->
|
addAll(result.data!!.map { msg ->
|
||||||
val msgHash = MessageHelper.generateMsgIdHash(msg.chatType, msg.msgId)
|
val msgHash = MessageHelper.generateMsgIdHash(msg.chatType, msg.msgId)
|
||||||
MessageHelper.saveMsgMappingNotExist(
|
|
||||||
hash = msgHash,
|
|
||||||
qqMsgId = msg.msgId,
|
|
||||||
chatType = msg.chatType,
|
|
||||||
subChatType = msg.chatType,
|
|
||||||
peerId = msg.peerUin.toString(),
|
|
||||||
msgSeq = msg.msgSeq.toInt(),
|
|
||||||
time = msg.msgTime,
|
|
||||||
subPeerId = msg.channelId ?: msg.peerUin.toString()
|
|
||||||
)
|
|
||||||
MessageDetail(
|
MessageDetail(
|
||||||
time = msg.msgTime.toInt(),
|
time = msg.msgTime.toInt(),
|
||||||
msgType = MessageHelper.obtainDetailTypeByMsgType(msg.chatType),
|
msgType = MessageHelper.obtainDetailTypeByMsgType(msg.chatType),
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
package moe.fuqiuluo.shamrock.remote.action.handlers
|
||||||
|
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc
|
||||||
|
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||||
|
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||||
|
import moe.fuqiuluo.symbols.OneBotHandler
|
||||||
|
|
||||||
|
@OneBotHandler("request_upload_group_image")
|
||||||
|
internal object RequestUploadGroupImage: IActionHandler() {
|
||||||
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
|
val md5 = session.getString("md5").uppercase()
|
||||||
|
val fileSize = session.getLong("file_size")
|
||||||
|
val width = session.getInt("width")
|
||||||
|
val height = session.getInt("height")
|
||||||
|
val groupId = session.getString("group_id")
|
||||||
|
NtV2RichMediaSvc.requestUploadGroupPic(
|
||||||
|
groupId.toULong(),
|
||||||
|
md5,
|
||||||
|
fileSize.toULong(),
|
||||||
|
width.toUInt(),
|
||||||
|
height.toUInt()
|
||||||
|
).onSuccess {
|
||||||
|
return ok(it, session.echo)
|
||||||
|
}.onFailure {
|
||||||
|
return error(it.message ?: it.toString(), session.echo)
|
||||||
|
}
|
||||||
|
return logic("request_upload_group_image failed", session.echo)
|
||||||
|
}
|
||||||
|
}
|
@ -80,7 +80,6 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
fromId: String = peerId,
|
fromId: String = peerId,
|
||||||
echo: JsonElement = EmptyJsonString
|
echo: JsonElement = EmptyJsonString
|
||||||
): String {
|
): String {
|
||||||
kotlin.runCatching {
|
|
||||||
var uid: String? = null
|
var uid: String? = null
|
||||||
var groupUin: String? = null
|
var groupUin: String? = null
|
||||||
|
|
||||||
@ -88,11 +87,11 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
val desc = MutableList(messages.size) { "" }
|
val desc = MutableList(messages.size) { "" }
|
||||||
|
|
||||||
val msgs = messages.map { msg ->
|
val msgs = messages.map { msg ->
|
||||||
|
kotlin.runCatching {
|
||||||
val data = msg.asJsonObject["data"].asJsonObject
|
val data = msg.asJsonObject["data"].asJsonObject
|
||||||
if (data.containsKey("id")) {
|
if (data.containsKey("id")) {
|
||||||
val record = MsgSvc.getMsg(data["id"].asInt).getOrElse {
|
val record = MsgSvc.getMsg(data["id"].asInt).getOrElse {
|
||||||
LogCenter.log("合并转发消息节点消息(id = ${data["id"].asInt})获取失败:$it", Level.WARN)
|
error("合并转发消息节点消息(id = ${data["id"].asInt})获取失败:$it")
|
||||||
return@map null
|
|
||||||
}
|
}
|
||||||
if (record.chatType == MsgConstant.KCHATTYPEGROUP) groupUin = record.peerUin.toString()
|
if (record.chatType == MsgConstant.KCHATTYPEGROUP) groupUin = record.peerUin.toString()
|
||||||
if (record.chatType == MsgConstant.KCHATTYPEC2C) uid = record.peerUid
|
if (record.chatType == MsgConstant.KCHATTYPEC2C) uid = record.peerUid
|
||||||
@ -147,21 +146,21 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
).also {
|
).also {
|
||||||
desc[++i] = record.sendMemberName.ifEmpty { record.sendNickName } + ": "
|
desc[++i] = record.sendMemberName.ifEmpty { record.sendNickName } + ": "
|
||||||
}.map {
|
}.map {
|
||||||
when (it.type) {
|
desc[++i] += when (it.type) {
|
||||||
"text" -> desc[i] += it.data["text"] as String
|
"text" -> it.data["text"] as String
|
||||||
|
"at" -> "@${it.data["name"] as String? ?: it.data["qq"] as String}"
|
||||||
"at" -> desc[i] += "@${it.data["name"] as String? ?: it.data["qq"] as String}"
|
"face" -> "[表情]"
|
||||||
|
"voice" -> "[语音]"
|
||||||
"face" -> desc[i] += "[表情]"
|
"node" -> "[合并转发消息]"
|
||||||
|
"markdown" -> "[Markdown消息]"
|
||||||
"voice" -> desc[i] += "[语音]"
|
"button" -> "[Button类型]"
|
||||||
|
else -> "[未知消息类型]"
|
||||||
"node" -> desc[i] += "[合并转发消息]"
|
|
||||||
}
|
}
|
||||||
it.toJson()
|
it.toJson()
|
||||||
}.json
|
}.json
|
||||||
).also {
|
).also {
|
||||||
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
|
if (it.second.isEmpty() && !it.first)
|
||||||
|
error("消息合成失败,请查看日志或者检查输入。")
|
||||||
}.second
|
}.second
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -198,84 +197,91 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
body = MsgBody(
|
body = MsgBody(
|
||||||
richText = RichText(
|
richText = RichText(
|
||||||
elements = MessageHelper.messageArrayToMessageElements(
|
elements = MessageHelper.messageArrayToMessageElements(
|
||||||
1,
|
chatType = MsgConstant.KCHATTYPEGROUP,
|
||||||
Random.nextLong(),
|
msgId = Random.nextLong(),
|
||||||
data["uin"]?.asString ?: TicketSvc.getUin(),
|
peerId = data["uin"]?.asString ?: TicketSvc.getUin(),
|
||||||
when (data["content"]) {
|
messageList = when (data["content"]) {
|
||||||
is JsonObject -> listOf(data["content"] as JsonObject).json
|
is JsonObject -> listOf(data["content"] as JsonObject).json
|
||||||
is JsonArray -> data["content"] as JsonArray
|
is JsonArray -> data["content"] as JsonArray
|
||||||
else -> MessageHelper.decodeCQCode(data["content"].asString)
|
else -> MessageHelper.decodeCQCode(data["content"].asString)
|
||||||
}.also {
|
}.also {
|
||||||
desc[++i] = "${
|
desc[++i] =
|
||||||
data["name"].asStringOrNull ?: data["uin"].asStringOrNull
|
(data["name"].asStringOrNull ?: data["uin"].asStringOrNull
|
||||||
?: TicketSvc.getNickname()
|
?: TicketSvc.getNickname() )+ ": "
|
||||||
}: "
|
|
||||||
}.onEach {
|
}.onEach {
|
||||||
val type = it.asJsonObject["type"].asString
|
val type = it.asJsonObject["type"].asString
|
||||||
val itData = it.asJsonObject["data"].asJsonObject
|
val itData = it.asJsonObject["data"].asJsonObject
|
||||||
when (type) {
|
desc[i] += when (type) {
|
||||||
"text" -> desc[i] += itData["text"].asString
|
"text" -> itData["text"].asString
|
||||||
"at" -> desc[i] += "@${itData["name"].asStringOrNull ?: itData["qq"].asString}"
|
"at" -> "@${itData["name"].asStringOrNull ?: itData["qq"].asString}"
|
||||||
"face" -> desc[i] += "[表情]"
|
"face" -> "[表情]"
|
||||||
"image" -> desc[i] += "[图片]"
|
"image" -> "[图片]"
|
||||||
"voice" -> desc[i] += "[语音]"
|
"voice" -> "[语音]"
|
||||||
"node" -> desc[i] += "[合并转发消息]"
|
"node" -> "[合并转发消息]"
|
||||||
|
"markdown" -> "[Markdown消息]"
|
||||||
|
"button" -> "[Button类型]"
|
||||||
|
else -> "[未知消息类型]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).also {
|
).also {
|
||||||
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
|
if (it.second.isEmpty() && !it.first)
|
||||||
|
error("消息合成失败,请查看日志或者检查输入。")
|
||||||
}.second
|
}.second
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
LogCenter.log("消息节点缺少id或content字段", Level.WARN)
|
error("消息节点缺少id或content字段")
|
||||||
|
}
|
||||||
|
}.getOrElse {
|
||||||
|
LogCenter.log("消息节点解析失败:$it", Level.WARN)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}.filterNotNull().ifEmpty { return logic("消息节点为空", echo) }
|
}.filterNotNull().ifEmpty { return logic("消息节点为空", echo) }
|
||||||
|
|
||||||
|
|
||||||
val resid = MsgSvc.sendMultiMsg(uid ?: TicketSvc.getUid(), groupUin, msgs)
|
kotlin.runCatching {
|
||||||
|
val resid = MsgSvc.uploadMultiMsg(uid ?: TicketSvc.getUid(), groupUin, msgs)
|
||||||
.getOrElse { return logic(it.message ?: "", echo) }
|
.getOrElse { return logic(it.message ?: "", echo) }
|
||||||
val uniseq = UUID.randomUUID().toString().uppercase()
|
val uniseq = UUID.randomUUID().toString().uppercase()
|
||||||
|
|
||||||
val result = MsgSvc.sendToAio(
|
val result = MsgSvc.sendToAio(
|
||||||
chatType, peerId,
|
chatType, peerId,
|
||||||
listOf(
|
listOf(
|
||||||
hashMapOf(
|
mapOf(
|
||||||
"type" to "json",
|
"type" to "json",
|
||||||
"data" to hashMapOf(
|
"data" to mapOf(
|
||||||
"data" to hashMapOf(
|
"data" to mapOf(
|
||||||
"app" to "com.tencent.multimsg",
|
"app" to "com.tencent.multimsg",
|
||||||
"config" to hashMapOf(
|
"config" to mapOf(
|
||||||
"autosize" to 1,
|
"autosize" to 1,
|
||||||
"forward" to 1,
|
"forward" to 1,
|
||||||
"round" to 1,
|
"round" to 1,
|
||||||
"type" to "normal",
|
"type" to "normal",
|
||||||
"width" to 300
|
"width" to 300
|
||||||
).json,
|
),
|
||||||
"desc" to "[聊天记录]",
|
"desc" to "[聊天记录]",
|
||||||
"extra" to hashMapOf(
|
"extra" to mapOf(
|
||||||
"filename" to uniseq,
|
"filename" to uniseq,
|
||||||
"tsum" to 2
|
"tsum" to 2
|
||||||
).json.toString(),
|
).json.toString(),
|
||||||
"meta" to hashMapOf(
|
"meta" to mapOf(
|
||||||
"detail" to hashMapOf(
|
"detail" to mapOf(
|
||||||
"news" to desc.slice(0..if (i < 3) i else 3)
|
"news" to desc.slice(0..if (i < 3) i else 3)
|
||||||
.map { hashMapOf("text" to it).json }.json,
|
.map { mapOf("text" to it) },
|
||||||
"resid" to resid,
|
"resid" to resid,
|
||||||
"source" to "群聊的聊天记录",
|
"source" to "群聊的聊天记录",
|
||||||
"summary" to "查看${msgs.size}条转发消息",
|
"summary" to "查看${msgs.size}条转发消息",
|
||||||
"uniseq" to uniseq
|
"uniseq" to uniseq
|
||||||
).json
|
)
|
||||||
).json,
|
),
|
||||||
"prompt" to "[聊天记录]",
|
"prompt" to "[聊天记录]",
|
||||||
"ver" to "0.0.0.5",
|
"ver" to "0.0.0.5",
|
||||||
"view" to "contact"
|
"view" to "contact"
|
||||||
).json,
|
),
|
||||||
"resid" to resid
|
"resid" to resid
|
||||||
).json
|
)
|
||||||
).json
|
)
|
||||||
).json, fromId, 3
|
).json, fromId, 3
|
||||||
).getOrElse { return logic(it.message ?: "", echo) }
|
).getOrElse { return logic(it.message ?: "", echo) }
|
||||||
|
|
||||||
@ -286,7 +292,7 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
), echo = echo
|
), echo = echo
|
||||||
)
|
)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
return error("error: $it", echo)
|
return error("合并转发消息失败: $it", echo)
|
||||||
}
|
}
|
||||||
return logic("合并转发消息失败(unknown error)", echo)
|
return logic("合并转发消息失败(unknown error)", echo)
|
||||||
}
|
}
|
||||||
|
@ -15,13 +15,13 @@ internal object SendGroupMessage: IActionHandler() {
|
|||||||
return if (session.isString("message")) {
|
return if (session.isString("message")) {
|
||||||
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
||||||
val message = session.getString("message")
|
val message = session.getString("message")
|
||||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId.toString(), message, autoEscape, echo = session.echo, retryCnt = retryCnt ?: 3, recallDuration = recallDuration)
|
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId.toString(), message, autoEscape, echo = session.echo, retryCnt = retryCnt ?: 5, recallDuration = recallDuration)
|
||||||
} else if (session.isObject("message")) {
|
} else if (session.isObject("message")) {
|
||||||
val message = session.getObject("message")
|
val message = session.getObject("message")
|
||||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId.toString(), listOf( message ).jsonArray, session.echo, retryCnt = retryCnt ?: 3, recallDuration = recallDuration)
|
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId.toString(), listOf( message ).jsonArray, session.echo, retryCnt = retryCnt ?: 5, recallDuration = recallDuration)
|
||||||
} else {
|
} else {
|
||||||
val message = session.getArray("message")
|
val message = session.getArray("message")
|
||||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId.toString(), message, session.echo, retryCnt = retryCnt ?: 3, recallDuration = recallDuration)
|
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId.toString(), message, session.echo, retryCnt = retryCnt ?: 5, recallDuration = recallDuration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,13 +55,13 @@ internal object SendMessage: IActionHandler() {
|
|||||||
return if (session.isString("message")) {
|
return if (session.isString("message")) {
|
||||||
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
||||||
val message = session.getString("message")
|
val message = session.getString("message")
|
||||||
invoke(chatType, peerId, message, autoEscape, echo = session.echo, fromId = fromId, retryCnt = retryCnt ?: 3, recallDuration = recallDuration)
|
invoke(chatType, peerId, message, autoEscape, echo = session.echo, fromId = fromId, retryCnt = retryCnt ?: 5, recallDuration = recallDuration)
|
||||||
} else if (session.isArray("message")) {
|
} else if (session.isArray("message")) {
|
||||||
val message = session.getArray("message")
|
val message = session.getArray("message")
|
||||||
invoke(chatType, peerId, message, session.echo, fromId = fromId, retryCnt ?: 3, recallDuration = recallDuration)
|
invoke(chatType, peerId, message, session.echo, fromId = fromId, retryCnt ?: 5, recallDuration = recallDuration)
|
||||||
} else {
|
} else {
|
||||||
val message = session.getObject("message")
|
val message = session.getObject("message")
|
||||||
invoke(chatType, peerId, listOf( message ).jsonArray, session.echo, fromId = fromId, retryCnt ?: 3, recallDuration = recallDuration)
|
invoke(chatType, peerId, listOf( message ).jsonArray, session.echo, fromId = fromId, retryCnt ?: 5, recallDuration = recallDuration)
|
||||||
}
|
}
|
||||||
} catch (e: ParamsException) {
|
} catch (e: ParamsException) {
|
||||||
return noParam(e.message!!, session.echo)
|
return noParam(e.message!!, session.echo)
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package moe.fuqiuluo.shamrock.remote.action.handlers
|
package moe.fuqiuluo.shamrock.remote.action.handlers
|
||||||
|
|
||||||
import kotlinx.atomicfu.atomic
|
import kotlinx.atomicfu.atomic
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||||
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||||
|
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
||||||
import moe.fuqiuluo.symbols.OneBotHandler
|
import moe.fuqiuluo.symbols.OneBotHandler
|
||||||
import protobuf.auto.toByteArray
|
import protobuf.auto.toByteArray
|
||||||
import protobuf.message.*
|
import protobuf.message.*
|
||||||
@ -19,10 +21,16 @@ internal object SendMsgByResid : IActionHandler() {
|
|||||||
private val msgSeq = atomic(1000)
|
private val msgSeq = atomic(1000)
|
||||||
|
|
||||||
override suspend fun internalHandle(session: ActionSession): String {
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
val resid = session.getString("resid")
|
val resId = session.getString("res_id")
|
||||||
val peerId = session.getString("peer")
|
val peerId = session.getString("peer_id")
|
||||||
|
val messageType = session.getString("message_type")
|
||||||
|
invoke(resId, peerId, messageType)
|
||||||
|
return ok("ok", session.echo)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend operator fun invoke(peerId: String, resId: String, messageType: String, echo: JsonElement = EmptyJsonString): String {
|
||||||
val req = PbSendMsgReq(
|
val req = PbSendMsgReq(
|
||||||
routingHead = when (session.getStringOrNull("message_type")) {
|
routingHead = when (messageType) {
|
||||||
"group" -> RoutingHead(grp = Grp(peerId.toUInt()))
|
"group" -> RoutingHead(grp = Grp(peerId.toUInt()))
|
||||||
"private" -> RoutingHead(c2c = C2C(peerId.toUInt()))
|
"private" -> RoutingHead(c2c = C2C(peerId.toUInt()))
|
||||||
else -> RoutingHead(grp = Grp(peerId.toUInt()))
|
else -> RoutingHead(grp = Grp(peerId.toUInt()))
|
||||||
@ -34,7 +42,7 @@ internal object SendMsgByResid : IActionHandler() {
|
|||||||
Elem(
|
Elem(
|
||||||
generalFlags = GeneralFlags(
|
generalFlags = GeneralFlags(
|
||||||
longTextFlag = 1u,
|
longTextFlag = 1u,
|
||||||
longTextResid = resid.toByteArray()
|
longTextResid = resId
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -45,6 +53,6 @@ internal object SendMsgByResid : IActionHandler() {
|
|||||||
msgVia = 0u
|
msgVia = 0u
|
||||||
)
|
)
|
||||||
BaseSvc.sendBufferAW("MessageSvc.PbSendMsg", true, req.toByteArray())
|
BaseSvc.sendBufferAW("MessageSvc.PbSendMsg", true, req.toByteArray())
|
||||||
return ok("ok", session.echo)
|
return ok("ok", echo)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,7 +24,7 @@ internal object SendPrivateMessage : IActionHandler() {
|
|||||||
autoEscape = autoEscape,
|
autoEscape = autoEscape,
|
||||||
echo = session.echo,
|
echo = session.echo,
|
||||||
fromId = groupId?.toString() ?: userId.toString(),
|
fromId = groupId?.toString() ?: userId.toString(),
|
||||||
retryCnt = retryCnt ?: 3,
|
retryCnt = retryCnt ?: 5,
|
||||||
recallDuration = recallDuration
|
recallDuration = recallDuration
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -34,7 +34,7 @@ internal object SendPrivateMessage : IActionHandler() {
|
|||||||
message = if (session.isArray("message")) session.getArray("message") else listOf(session.getObject("message")).jsonArray,
|
message = if (session.isArray("message")) session.getArray("message") else listOf(session.getObject("message")).jsonArray,
|
||||||
echo = session.echo,
|
echo = session.echo,
|
||||||
fromId = groupId?.toString() ?: userId.toString(),
|
fromId = groupId?.toString() ?: userId.toString(),
|
||||||
retryCnt = retryCnt ?: 3,
|
retryCnt = retryCnt ?: 5,
|
||||||
recallDuration = recallDuration
|
recallDuration = recallDuration
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,59 +1,55 @@
|
|||||||
package moe.fuqiuluo.shamrock.remote.api
|
package moe.fuqiuluo.shamrock.remote.api
|
||||||
|
|
||||||
import io.ktor.server.routing.Routing
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.TextElement
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||||
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.ShamrockVersion
|
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||||
|
import moe.fuqiuluo.shamrock.remote.action.handlers.SendMsgByResid
|
||||||
|
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||||
|
import moe.fuqiuluo.shamrock.tools.fetchOrThrow
|
||||||
|
import moe.fuqiuluo.shamrock.tools.getOrPost
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||||
|
|
||||||
fun Routing.testAction() {
|
fun Routing.testAction() {
|
||||||
if(ShamrockVersion.contains("dev")) {
|
if (ShamrockConfig.isDev()) {
|
||||||
LogCenter.log("testAction is enabled.", Level.WARN)
|
LogCenter.log("testAction is enabled.", Level.WARN)
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
getOrPost("/send_msg_by_resid") {
|
||||||
get("/test/createUidFromTinyId") {
|
val resId = fetchOrThrow("res_id")
|
||||||
val selfId = fetchOrThrow("selfId").toLong()
|
val peerId = fetchOrThrow("peer_Id")
|
||||||
val peerId = fetchOrThrow("peerId").toLong()
|
val messageType = fetchOrThrow("message_type")
|
||||||
call.respondText(NTServiceFetcher.kernelService.wrapperSession.msgService.createUidFromTinyId(selfId, peerId))
|
call.respondText(SendMsgByResid(resId, peerId, messageType))
|
||||||
}
|
}
|
||||||
|
|
||||||
get("/test/addSendMsg") {
|
getOrPost("/createUidFromTinyId") {
|
||||||
|
val selfId = fetchOrThrow("selfId").toLong()
|
||||||
|
val peerId = fetchOrThrow("peerId")
|
||||||
|
call.respondText(NTServiceFetcher.kernelService.wrapperSession.msgService.createUidFromTinyId(selfId, peerId.toLong()))
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrPost("/addSendMsg") {
|
||||||
val msgService = NTServiceFetcher.kernelService.wrapperSession.msgService
|
val msgService = NTServiceFetcher.kernelService.wrapperSession.msgService
|
||||||
val msgId = msgService.getMsgUniqueId(System.currentTimeMillis())
|
val msgId = msgService.getMsgUniqueId(System.currentTimeMillis())
|
||||||
msgService.addSendMsg(msgId, MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin()), arrayListOf(
|
msgService.addSendMsg(msgId,
|
||||||
|
MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin()),
|
||||||
|
arrayListOf(
|
||||||
MsgElement().apply {
|
MsgElement().apply {
|
||||||
elementType = MsgConstant.KELEMTYPETEXT
|
elementType = MsgConstant.KELEMTYPETEXT
|
||||||
textElement = TextElement().apply {
|
textElement = TextElement().apply {
|
||||||
content = "测试消息"
|
content = "测试消息"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
), hashMapOf())
|
),
|
||||||
|
hashMapOf())
|
||||||
call.respondText("ok")
|
call.respondText("ok")
|
||||||
}*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
get("/test/getMsgs") {
|
|
||||||
kotlin.runCatching {
|
|
||||||
val msgService = NTServiceFetcher.kernelService.wrapperSession.msgService
|
|
||||||
val msgs = suspendCoroutine {
|
|
||||||
msgService.getMsgs(Contact(MsgConstant.KCHATTYPEGROUP, "884587317", ""), 0L, 20, true, object: IMsgOperateCallback{
|
|
||||||
override fun onResult(code: Int, why: String?, msgs: ArrayList<MsgRecord>?) {
|
|
||||||
it.resume(msgs)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
if (msgs == null) {
|
|
||||||
call.respondText("failed")
|
|
||||||
return@get
|
|
||||||
}
|
|
||||||
|
|
||||||
call.respondText("msg -> " + msgs.map { it.toCQCode() }.joinToString("\n"))
|
|
||||||
}.onFailure {
|
|
||||||
call.respondText("failed: ${it.stackTraceToString()}")
|
|
||||||
return@get
|
|
||||||
}
|
|
||||||
|
|
||||||
}*/
|
|
||||||
}
|
}
|
@ -12,7 +12,6 @@ import moe.fuqiuluo.qqinterface.servlet.FriendSvc.requestFriendSystemMsgNew
|
|||||||
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.GroupSvc.requestGroupSystemMsgNew
|
import moe.fuqiuluo.qqinterface.servlet.GroupSvc.requestGroupSystemMsgNew
|
||||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getLongUin
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getLongUin
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
|
|
||||||
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
|
||||||
@ -30,11 +29,7 @@ import moe.fuqiuluo.symbols.decodeProtobuf
|
|||||||
import protobuf.message.ContentHead
|
import protobuf.message.ContentHead
|
||||||
import protobuf.message.MsgBody
|
import protobuf.message.MsgBody
|
||||||
import protobuf.message.ResponseHead
|
import protobuf.message.ResponseHead
|
||||||
import protobuf.message.multimedia.RichMediaForPicData
|
|
||||||
import protobuf.push.*
|
import protobuf.push.*
|
||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
private val RKEY_PATTERN = Pattern.compile("rkey=([A-Za-z0-9_-]+)")
|
|
||||||
|
|
||||||
internal object PrimitiveListener {
|
internal object PrimitiveListener {
|
||||||
fun registerListener() {
|
fun registerListener() {
|
||||||
@ -94,23 +89,10 @@ internal object PrimitiveListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onGroupMessage(msgTime: Long, body: MsgBody) {
|
private fun onGroupMessage(msgTime: Long, body: MsgBody) {
|
||||||
/*runCatching {
|
runCatching {
|
||||||
body.richText?.elements?.filter {
|
|
||||||
it.commonElem != null && it.commonElem!!.serviceType == 48
|
|
||||||
}?.map {
|
|
||||||
it.commonElem!!.elem!!.decodeProtobuf<RichMediaForPicData>()
|
|
||||||
}?.forEach {
|
|
||||||
it.display?.show?.download?.url?.let {
|
|
||||||
RKEY_PATTERN.matcher(it).takeIf {
|
|
||||||
it.find()
|
|
||||||
}?.group(1)?.let { rkey ->
|
|
||||||
LogCenter.log("更新NT RKEY成功:$rkey")
|
|
||||||
RichProtoSvc.multiMediaRKey = rkey
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun onC2CPoke(msgTime: Long, body: MsgBody) {
|
private suspend fun onC2CPoke(msgTime: Long, body: MsgBody) {
|
||||||
val event = body.msgContent!!.decodeProtobuf<C2CCommonTipsEvent>()
|
val event = body.msgContent!!.decodeProtobuf<C2CCommonTipsEvent>()
|
||||||
|
@ -42,37 +42,41 @@ val String.asJson: JsonElement
|
|||||||
val String.asJsonObject: JsonObject
|
val String.asJsonObject: JsonObject
|
||||||
get() = Json.parseToJsonElement(this).asJsonObject
|
get() = Json.parseToJsonElement(this).asJsonObject
|
||||||
|
|
||||||
val Collection<Any>.json: JsonArray
|
val Collection<Any?>.json: JsonArray
|
||||||
get() {
|
get() {
|
||||||
val arrayList = arrayListOf<JsonElement>()
|
val arrayList = arrayListOf<JsonElement>()
|
||||||
forEach {
|
forEach {
|
||||||
|
if (it != null) {
|
||||||
when (it) {
|
when (it) {
|
||||||
is JsonElement -> arrayList.add(it)
|
is JsonElement -> arrayList.add(it)
|
||||||
is Number -> arrayList.add(it.json)
|
is Number -> arrayList.add(it.json)
|
||||||
is String -> arrayList.add(it.json)
|
is String -> arrayList.add(it.json)
|
||||||
is Boolean -> arrayList.add(it.json)
|
is Boolean -> arrayList.add(it.json)
|
||||||
is Map<*, *> -> arrayList.add((it as Map<String, Any>).json)
|
is Map<*, *> -> arrayList.add((it as Map<String, Any?>).json)
|
||||||
is Collection<*> -> arrayList.add((it as Collection<Any>).json)
|
is Collection<*> -> arrayList.add((it as Collection<Any?>).json)
|
||||||
else -> error("unknown array type: ${it::class.java}")
|
else -> error("unknown array type: ${it::class.java}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return arrayList.jsonArray
|
return arrayList.jsonArray
|
||||||
}
|
}
|
||||||
|
|
||||||
val Map<String, Any>.json: JsonObject
|
val Map<String, Any?>.json: JsonObject
|
||||||
get() {
|
get() {
|
||||||
val map = hashMapOf<String, JsonElement>()
|
val map = hashMapOf<String, JsonElement>()
|
||||||
forEach { (key, any) ->
|
forEach { (key, any) ->
|
||||||
|
if (any != null) {
|
||||||
when (any) {
|
when (any) {
|
||||||
is JsonElement -> map[key] = any
|
is JsonElement -> map[key] = any
|
||||||
is Number -> map[key] = any.json
|
is Number -> map[key] = any.json
|
||||||
is String -> map[key] = any.json
|
is String -> map[key] = any.json
|
||||||
is Boolean -> map[key] = any.json
|
is Boolean -> map[key] = any.json
|
||||||
is Map<*, *> -> map[key] = (any as Map<String, Any>).json
|
is Map<*, *> -> map[key] = (any as Map<String, Any?>).json
|
||||||
is Collection<*> -> map[key] = (any as Collection<Any>).json
|
is Collection<*> -> map[key] = (any as Collection<Any?>).json
|
||||||
else -> error("unknown object type: ${any::class.java}")
|
else -> error("unknown object type: ${any::class.java}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return map.jsonObject
|
return map.jsonObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +117,8 @@ internal class XposedEntry: IXposedHookLoadPackage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun execStartupInit(ctx: Context) {
|
private fun execStartupInit(ctx: Context) {
|
||||||
|
log("Shamrock: Executing startup init: $ctx")
|
||||||
|
|
||||||
if (sec_static_stage_inited) return
|
if (sec_static_stage_inited) return
|
||||||
|
|
||||||
val classLoader = ctx.classLoader.also { requireNotNull(it) }
|
val classLoader = ctx.classLoader.also { requireNotNull(it) }
|
||||||
|
Reference in New Issue
Block a user