Shamrock: support upload resource by NtKernel

Signed-off-by: 白池 <whitechi73@outlook.com>
This commit is contained in:
白池 2024-02-25 11:42:42 +08:00
parent 92ebe0c6a8
commit fca66f3259
24 changed files with 309 additions and 91 deletions

View File

@ -6,7 +6,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
public interface IKernelMsgService { public interface IKernelMsgService {
void deleteMsg(Contact contact, ArrayList<Long> msgIdList, IOperateCallback callback); void deleteMsg(Contact contact, ArrayList<Long> msgIdList, IOperateCallback cb);
void fetchLongMsg(Contact contact, long msgId); void fetchLongMsg(Contact contact, long msgId);

View File

@ -9,6 +9,7 @@ import io.ktor.utils.io.core.writeFully
import io.ktor.utils.io.core.writeInt import io.ktor.utils.io.core.writeInt
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.qqinterface.servlet.msg.MessageTempHandler
import moe.fuqiuluo.shamrock.remote.action.handlers.GetHistoryMsg import moe.fuqiuluo.shamrock.remote.action.handlers.GetHistoryMsg
import moe.fuqiuluo.shamrock.remote.service.listener.AioListener import moe.fuqiuluo.shamrock.remote.service.listener.AioListener
@ -80,11 +81,11 @@ internal object PacketSvc: BaseSvc() {
fakeReceive("trpc.msg.olpush.OlPushService.MsgPush", 10000, msgPush.toByteArray()) fakeReceive("trpc.msg.olpush.OlPushService.MsgPush", 10000, msgPush.toByteArray())
return withTimeoutOrNull(5000L) { return withTimeoutOrNull(5000L) {
suspendCancellableCoroutine { suspendCancellableCoroutine {
AioListener.registerTemporaryMsgListener(msgSeq) { MessageTempHandler.registerTemporaryMsgListener(msgSeq) {
it.resume(this.msgId) it.resume(this.msgId)
} }
it.invokeOnCancellation { it.invokeOnCancellation {
AioListener.unregisterTemporaryMsgListener(msgSeq) MessageTempHandler.unregisterTemporaryMsgListener(msgSeq)
} }
} }
} ?: -1L } ?: -1L

View File

@ -1,16 +1,9 @@
package moe.fuqiuluo.qqinterface.servlet.ark package moe.fuqiuluo.qqinterface.servlet.ark
import com.tencent.mobileqq.pb.ByteStringMicro
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.qqinterface.servlet.BaseSvc
import moe.fuqiuluo.qqinterface.servlet.TicketSvc import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.remote.service.listener.AioListener
import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77 import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77
import kotlin.coroutines.resume
import kotlin.time.Duration.Companion.seconds
internal object ArkMsgSvc: BaseSvc() { internal object ArkMsgSvc: BaseSvc() {
fun tryShareMusic( fun tryShareMusic(

View File

@ -6,10 +6,10 @@ import io.ktor.client.request.url
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.http.encodeURLQueryComponent import io.ktor.http.encodeURLQueryComponent
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import moe.fuqiuluo.qqinterface.servlet.TicketSvc import moe.fuqiuluo.qqinterface.servlet.TicketSvc
import moe.fuqiuluo.qqinterface.servlet.ark.data.Region
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.* import moe.fuqiuluo.shamrock.tools.*

View File

@ -1,4 +1,5 @@
package moe.fuqiuluo.qqinterface.servlet.ark package moe.fuqiuluo.qqinterface.servlet.ark.data
sealed class ArkAppInfo( sealed class ArkAppInfo(
val appId: Long, val appId: Long,
val version: String, val version: String,

View File

@ -1,4 +1,4 @@
package moe.fuqiuluo.qqinterface.servlet.ark package moe.fuqiuluo.qqinterface.servlet.ark.data
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -0,0 +1,34 @@
package moe.fuqiuluo.qqinterface.servlet.msg
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import java.util.Collections
internal object MessageTempHandler {
// 通过MSG SEQ临时监听器
private val tempMessageListenerMap = Collections.synchronizedMap(HashMap<Long, suspend MsgRecord.() -> Unit>())
fun registerTemporaryMsgListener(
msgSeq: Long,
listener: suspend MsgRecord.() -> Unit
) {
LogCenter.log({ "注册临时消息监听器: $msgSeq" }, Level.DEBUG)
tempMessageListenerMap[msgSeq] = listener
}
fun unregisterTemporaryMsgListener(msgSeq: Long) {
tempMessageListenerMap.remove(msgSeq)
}
suspend fun notify(record: MsgRecord): Boolean {
tempMessageListenerMap.firstNotNullOfOrNull {
if (it.key == record.msgSeq) it else null
}?.let {
it.value(record)
tempMessageListenerMap.remove(it.key)
return true
}
return false
}
}

View File

@ -12,10 +12,10 @@ import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc
import moe.fuqiuluo.qqinterface.servlet.msg.toJson import moe.fuqiuluo.qqinterface.servlet.msg.toJson
import moe.fuqiuluo.qqinterface.servlet.msg.toSegments 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.data.PictureResource
import moe.fuqiuluo.qqinterface.servlet.transfile.Private import moe.fuqiuluo.qqinterface.servlet.transfile.data.Private
import moe.fuqiuluo.qqinterface.servlet.transfile.Transfer import moe.fuqiuluo.qqinterface.servlet.transfile.Transfer
import moe.fuqiuluo.qqinterface.servlet.transfile.Troop import moe.fuqiuluo.qqinterface.servlet.transfile.data.Troop
import moe.fuqiuluo.shamrock.helper.* import moe.fuqiuluo.shamrock.helper.*
import moe.fuqiuluo.shamrock.helper.ActionMsgException import moe.fuqiuluo.shamrock.helper.ActionMsgException
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level

View File

@ -3,7 +3,6 @@ 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
@ -17,17 +16,17 @@ import kotlinx.serialization.json.JsonPrimitive
import moe.fuqiuluo.qqinterface.servlet.CardSvc import moe.fuqiuluo.qqinterface.servlet.CardSvc
import moe.fuqiuluo.qqinterface.servlet.GroupSvc import moe.fuqiuluo.qqinterface.servlet.GroupSvc
import moe.fuqiuluo.qqinterface.servlet.LbsSvc import moe.fuqiuluo.qqinterface.servlet.LbsSvc
import moe.fuqiuluo.qqinterface.servlet.ark.ArkAppInfo import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo
import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc
import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc
import moe.fuqiuluo.qqinterface.servlet.transfile.* import moe.fuqiuluo.qqinterface.servlet.transfile.*
import moe.fuqiuluo.qqinterface.servlet.transfile.FileTransfer import moe.fuqiuluo.qqinterface.servlet.transfile.FileTransfer
import moe.fuqiuluo.qqinterface.servlet.transfile.PictureResource import moe.fuqiuluo.qqinterface.servlet.transfile.data.PictureResource
import moe.fuqiuluo.qqinterface.servlet.transfile.Private import moe.fuqiuluo.qqinterface.servlet.transfile.data.Private
import moe.fuqiuluo.qqinterface.servlet.transfile.Transfer import moe.fuqiuluo.qqinterface.servlet.transfile.Transfer
import moe.fuqiuluo.qqinterface.servlet.transfile.Troop import moe.fuqiuluo.qqinterface.servlet.transfile.data.Troop
import moe.fuqiuluo.qqinterface.servlet.transfile.VideoResource import moe.fuqiuluo.qqinterface.servlet.transfile.data.VideoResource
import moe.fuqiuluo.qqinterface.servlet.transfile.VoiceResource import moe.fuqiuluo.qqinterface.servlet.transfile.data.VoiceResource
import moe.fuqiuluo.shamrock.helper.ActionMsgException import moe.fuqiuluo.shamrock.helper.ActionMsgException
import moe.fuqiuluo.shamrock.helper.ContactHelper import moe.fuqiuluo.shamrock.helper.ContactHelper
import moe.fuqiuluo.shamrock.helper.IllegalParamsException import moe.fuqiuluo.shamrock.helper.IllegalParamsException

View File

@ -0,0 +1,19 @@
package moe.fuqiuluo.qqinterface.servlet.structures
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class UploadResult(
@SerialName("files") val files: List<CommFileInfo>
)
@Serializable
data class CommFileInfo(
@SerialName("mode_id") val modeId: Long,
@SerialName("name") val fileName: String,
@SerialName("size") val fileSize: Long,
@SerialName("md5") val md5: String,
@SerialName("uuid") val uuid: String,
@SerialName("sub_id") val subId: String,
)

View File

@ -1,14 +1,29 @@
package moe.fuqiuluo.qqinterface.servlet.transfile package moe.fuqiuluo.qqinterface.servlet.transfile
import android.graphics.BitmapFactory
import androidx.exifinterface.media.ExifInterface
import com.tencent.qqnt.kernel.nativeinterface.CommonFileInfo
import com.tencent.qqnt.kernel.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.PicElement
import com.tencent.qqnt.kernel.nativeinterface.QQNTWrapperUtil
import com.tencent.qqnt.kernel.nativeinterface.RichMediaFilePathInfo
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import kotlinx.serialization.SerialName import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.Serializable import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.qqinterface.servlet.BaseSvc
import moe.fuqiuluo.qqinterface.servlet.TicketSvc import moe.fuqiuluo.qqinterface.servlet.TicketSvc
import moe.fuqiuluo.qqinterface.servlet.transfile.data.TryUpPicData
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.tools.hex2ByteArray import moe.fuqiuluo.shamrock.tools.hex2ByteArray
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import moe.fuqiuluo.shamrock.xposed.helper.msgService
import moe.fuqiuluo.symbols.decodeProtobuf import moe.fuqiuluo.symbols.decodeProtobuf
import protobuf.auto.toByteArray import protobuf.auto.toByteArray
import protobuf.oidb.TrpcOidb import protobuf.oidb.TrpcOidb
@ -31,13 +46,115 @@ import protobuf.oidb.cmd0x388.Cmd0x388ReqBody
import protobuf.oidb.cmd0x388.Cmd0x388RspBody import protobuf.oidb.cmd0x388.Cmd0x388RspBody
import protobuf.oidb.cmd0x388.TryUpImgReq import protobuf.oidb.cmd0x388.TryUpImgReq
import java.io.File import java.io.File
import kotlin.coroutines.resume
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.nextUInt import kotlin.random.nextUInt
import kotlin.random.nextULong import kotlin.random.nextULong
import kotlin.time.Duration
internal object NtV2RichMediaSvc: BaseSvc() { internal object NtV2RichMediaSvc: BaseSvc() {
private const val GROUP_PIC_UPLOAD_TO = "100000000"
private val requestIdSeq = atomic(2L) private val requestIdSeq = atomic(2L)
/**
* 批量上传图片
*/
suspend fun tryUploadGroupPicByNt(
imageFiles: ArrayList<File>,
timeout: Duration
): Result<MutableList<CommonFileInfo>> {
require(imageFiles.size in 1 .. 10) { "imageFiles.size() must be in 1 .. 10" }
val messages = imageFiles.map { file ->
val elem = MsgElement()
runCatching {
elem.elementType = MsgConstant.KELEMTYPEPIC
val pic = PicElement()
pic.md5HexStr = QQNTWrapperUtil.CppProxy.genFileMd5Hex(file.absolutePath)
val msgService = NTServiceFetcher.kernelService.msgService!!
val originalPath = msgService.getRichMediaFilePathForMobileQQSend(
RichMediaFilePathInfo(
2, 0, pic.md5HexStr, file.name, 1, 0, null, "", true
)
)
if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize(
originalPath
) != file.length()
) {
val thumbPath = msgService.getRichMediaFilePathForMobileQQSend(
RichMediaFilePathInfo(
2, 0, pic.md5HexStr, file.name, 2, 720, null, "", true
)
)
QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath)
QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, thumbPath)
}
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(file.absolutePath, options)
val exifInterface = ExifInterface(file.absolutePath)
val orientation = exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED
)
if (orientation != ExifInterface.ORIENTATION_ROTATE_90 && orientation != ExifInterface.ORIENTATION_ROTATE_270) {
pic.picWidth = options.outWidth
pic.picHeight = options.outHeight
} else {
pic.picWidth = options.outHeight
pic.picHeight = options.outWidth
}
pic.sourcePath = file.absolutePath
pic.fileSize = QQNTWrapperUtil.CppProxy.getFileSize(file.absolutePath)
pic.original = true
pic.picType = FileUtils.getPicType(file)
elem.picElement = pic
}.onFailure {
LogCenter.log(it.stackTraceToString(), Level.WARN)
elem.elementType = 0
}
return@map elem
}.filter {
it.elementType == MsgConstant.KELEMTYPEPIC
}
if (messages.isEmpty()) {
return Result.failure(Exception("no valid image files"))
}
val result: MutableList<CommonFileInfo> = withTimeoutOrNull(timeout) {
suspendCancellableCoroutine {
val result = mutableListOf<CommonFileInfo>()
val uniseq = MessageHelper.generateMsgId(MsgConstant.KCHATTYPEGROUP)
val contact = Contact(MsgConstant.KCHATTYPEGROUP, GROUP_PIC_UPLOAD_TO, GROUP_PIC_UPLOAD_TO)
RichMediaUploadHandler.registerListener(uniseq.qqMsgId) upload@{
if (uniseq.qqMsgId == msgId) {
result.add(commonFileInfo)
}
return@upload false
}
MessageHelper.sendMessageWithMsgId(
contact = contact,
message = ArrayList(messages),
uniseq = uniseq.qqMsgId
) { code, _ ->
NTServiceFetcher.kernelService
.wrapperSession.msgService
.deleteMsg(contact, arrayListOf(uniseq.qqMsgId), null)
RichMediaUploadHandler.removeListener(uniseq.qqMsgId)
if (code != 110 && code != 4) {
it.resume(null)
} else {
it.resume(result)
}
}
it.invokeOnCancellation {
RichMediaUploadHandler.removeListener(uniseq.qqMsgId)
}
}
} ?: return Result.failure(Exception("timeout"))
return Result.success(result)
}
/** /**
* 获取NT图片的RKEY * 获取NT图片的RKEY
*/ */
@ -173,6 +290,9 @@ internal object NtV2RichMediaSvc: BaseSvc() {
LogCenter.log("requestUploadPic => rsp: $rsp") LogCenter.log("requestUploadPic => rsp: $rsp")
} }
/**
* 使用OldBDH获取图片上传状态以及图片上传服务器
*/
suspend fun requestUploadGroupPic( suspend fun requestUploadGroupPic(
groupId: ULong, groupId: ULong,
md5: String, md5: String,
@ -215,13 +335,5 @@ internal object NtV2RichMediaSvc: BaseSvc() {
) )
} }
} }
@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,
)
} }

View File

@ -1,4 +1,4 @@
package moe.fuqiuluo.shamrock.remote.service.api package moe.fuqiuluo.qqinterface.servlet.transfile
import com.tencent.qqnt.kernel.nativeinterface.FileTransNotifyInfo import com.tencent.qqnt.kernel.nativeinterface.FileTransNotifyInfo

View File

@ -1,14 +1,19 @@
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.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
import java.io.File import java.io.File
import moe.fuqiuluo.qqinterface.servlet.transfile.ResourceType.* import moe.fuqiuluo.qqinterface.servlet.transfile.data.ResourceType.*
import moe.fuqiuluo.shamrock.helper.TransfileHelper import moe.fuqiuluo.qqinterface.servlet.transfile.data.ContactType
import moe.fuqiuluo.qqinterface.servlet.transfile.data.PictureResource
import moe.fuqiuluo.qqinterface.servlet.transfile.data.Resource
import moe.fuqiuluo.qqinterface.servlet.transfile.data.ResourceType
import moe.fuqiuluo.qqinterface.servlet.transfile.data.TransTarget
import moe.fuqiuluo.qqinterface.servlet.transfile.data.VideoResource
import moe.fuqiuluo.qqinterface.servlet.transfile.data.VoiceResource
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>>(

View File

@ -1,4 +1,4 @@
package moe.fuqiuluo.qqinterface.servlet.transfile package moe.fuqiuluo.qqinterface.servlet.transfile.data
import com.tencent.mobileqq.data.MessageRecord import com.tencent.mobileqq.data.MessageRecord

View File

@ -1,4 +1,4 @@
package moe.fuqiuluo.qqinterface.servlet.transfile package moe.fuqiuluo.qqinterface.servlet.transfile.data
import java.io.File import java.io.File

View File

@ -0,0 +1,13 @@
package moe.fuqiuluo.qqinterface.servlet.transfile.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@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,
)

View File

@ -205,27 +205,24 @@ internal object MessageHelper {
fun sendMessageWithMsgId( fun sendMessageWithMsgId(
contact: Contact, contact: Contact,
message: ArrayList<MsgElement>, message: ArrayList<MsgElement>,
uniseq: Long,
callback: IOperateCallback callback: IOperateCallback
): SendMsgResult { ): SendMsgResult {
val uniseq = generateMsgId(contact.chatType)
val nonMsg: Boolean = message.isEmpty() val nonMsg: Boolean = message.isEmpty()
return if (!nonMsg) { if (!nonMsg) {
val service = QRoute.api(IMsgService::class.java) val service = QRoute.api(IMsgService::class.java)
if (callback is MsgSvc.MessageCallback) {
callback.msgHash = uniseq.msgHashId
}
service.sendMsg( service.sendMsg(
contact, contact,
uniseq.qqMsgId, uniseq,
message, message,
callback callback
) )
uniseq.copy(msgTime = System.currentTimeMillis())
} else {
uniseq.copy(msgTime = 0, msgHashId = 0)
} }
return SendMsgResult(
msgTime = if (nonMsg) 0 else System.currentTimeMillis(),
msgHashId = 0,
qqMsgId = uniseq
)
} }
suspend fun sendMessageNoCb( suspend fun sendMessageNoCb(

View File

@ -3,7 +3,7 @@ package moe.fuqiuluo.shamrock.helper
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import moe.fuqiuluo.qqinterface.servlet.ark.ArkAppInfo import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo
import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc
import moe.fuqiuluo.shamrock.tools.GlobalClient import moe.fuqiuluo.shamrock.tools.GlobalClient
import moe.fuqiuluo.shamrock.tools.asInt import moe.fuqiuluo.shamrock.tools.asInt

View File

@ -24,8 +24,7 @@ internal object SendMsgByResid : IActionHandler() {
val resId = session.getString("res_id") val resId = session.getString("res_id")
val peerId = session.getString("peer_id") val peerId = session.getString("peer_id")
val messageType = session.getString("message_type") val messageType = session.getString("message_type")
invoke(resId, peerId, messageType) return invoke(peerId, resId, messageType, session.echo)
return ok("ok", session.echo)
} }
suspend operator fun invoke(peerId: String, resId: String, messageType: String, echo: JsonElement = EmptyJsonString): String { suspend operator fun invoke(peerId: String, resId: String, messageType: String, echo: JsonElement = EmptyJsonString): String {
@ -55,4 +54,6 @@ internal object SendMsgByResid : IActionHandler() {
BaseSvc.sendBufferAW("MessageSvc.PbSendMsg", true, req.toByteArray()) BaseSvc.sendBufferAW("MessageSvc.PbSendMsg", true, req.toByteArray())
return ok("ok", echo) return ok("ok", echo)
} }
override val requiredParams: Array<String> = arrayOf("res_id", "peer_id", "message_type")
} }

View File

@ -1,15 +1,18 @@
package moe.fuqiuluo.shamrock.remote.action.handlers package moe.fuqiuluo.shamrock.remote.action.handlers
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
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.jsonArray import moe.fuqiuluo.shamrock.tools.jsonArray
import moe.fuqiuluo.symbols.OneBotHandler import moe.fuqiuluo.symbols.OneBotHandler
@OneBotHandler("send_private_msg", ["send_private_message"]) @OneBotHandler("send_private_msg", ["send_private_message", "send_friend_msg"])
internal object SendPrivateMessage : IActionHandler() { internal object SendPrivateMessage : IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String { override suspend fun internalHandle(session: ActionSession): String {
val userId = session.getLong("user_id") val userId = session.getString("user_id").let {
if (it == "self") TicketSvc.getUin() else it
}
val groupId = session.getLongOrNull("group_id") val groupId = session.getLongOrNull("group_id")
val chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP val chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP
val retryCnt = session.getIntOrNull("retry_cnt") val retryCnt = session.getIntOrNull("retry_cnt")
@ -19,21 +22,21 @@ internal object SendPrivateMessage : IActionHandler() {
val message = session.getString("message") val message = session.getString("message")
SendMessage( SendMessage(
chatType = chatType, chatType = chatType,
peerId = userId.toString(), peerId = userId,
message = message, message = message,
autoEscape = autoEscape, autoEscape = autoEscape,
echo = session.echo, echo = session.echo,
fromId = groupId?.toString() ?: userId.toString(), fromId = groupId?.toString() ?: userId,
retryCnt = retryCnt ?: 5, retryCnt = retryCnt ?: 5,
recallDuration = recallDuration recallDuration = recallDuration
) )
} else { } else {
SendMessage( SendMessage(
chatType = chatType, chatType = chatType,
peerId = userId.toString(), peerId = userId,
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,
retryCnt = retryCnt ?: 5, retryCnt = retryCnt ?: 5,
recallDuration = recallDuration recallDuration = recallDuration
) )

View File

@ -22,7 +22,7 @@ import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.helper.TransfileHelper import moe.fuqiuluo.shamrock.helper.TransfileHelper
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.remote.service.api.RichMediaUploadHandler import moe.fuqiuluo.qqinterface.servlet.transfile.RichMediaUploadHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.utils.FileUtils import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.shamrock.utils.MD5 import moe.fuqiuluo.shamrock.utils.MD5

View File

@ -0,0 +1,63 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.structures.CommFileInfo
import moe.fuqiuluo.qqinterface.servlet.structures.UploadResult
import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.symbols.OneBotHandler
import kotlin.time.Duration.Companion.seconds
@OneBotHandler("upload_group_image", ["upload_group_pic"])
internal object UploadGroupPic: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val pic = session.getString("file")
return invoke(pic, session.echo)
}
suspend operator fun invoke(
picture: String,
echo: JsonElement = EmptyJsonString
): String {
if (ShamrockConfig.isDev()) {
val file = picture.let {
val md5 = it.replace(
regex = "[{}\\-]".toRegex(),
replacement = ""
).split(".")[0].lowercase()
if (md5.length == 32) {
FileUtils.getFileByMd5(it)
} else {
FileUtils.parseAndSave(it)
}
}
if (!file.exists()) {
return logic("picture file is not exists", echo)
}
NtV2RichMediaSvc.tryUploadGroupPicByNt(
imageFiles = arrayListOf(file),
timeout = 30.seconds
).onSuccess {
return ok(UploadResult(it.map {
CommFileInfo(
modeId = it.fileModelId,
fileName = it.fileName,
fileSize = it.fileSize,
md5 = it.md5,
uuid = it.uuid,
subId = it.subId
)
}), echo)
}.onFailure {
return logic("upload failed: ${it.message ?: it.toString()}", echo)
}
}
return logic("upload failed", echo)
}
override val requiredParams: Array<String> = arrayOf("file")
}

View File

@ -14,16 +14,13 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.shamrock.helper.ContactHelper
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.MessageHelper import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.helper.TransfileHelper import moe.fuqiuluo.shamrock.helper.TransfileHelper
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.remote.service.api.RichMediaUploadHandler import moe.fuqiuluo.qqinterface.servlet.transfile.RichMediaUploadHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.utils.FileUtils import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.shamrock.utils.MD5 import moe.fuqiuluo.shamrock.utils.MD5

View File

@ -9,6 +9,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
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.msg.MessageTempHandler
import moe.fuqiuluo.qqinterface.servlet.msg.toCQCode import moe.fuqiuluo.qqinterface.servlet.msg.toCQCode
import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
@ -16,7 +17,7 @@ import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.db.MessageDB import moe.fuqiuluo.shamrock.helper.db.MessageDB
import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter
import moe.fuqiuluo.shamrock.remote.service.api.RichMediaUploadHandler import moe.fuqiuluo.qqinterface.servlet.transfile.RichMediaUploadHandler
import moe.fuqiuluo.shamrock.remote.service.data.push.MessageTempSource import moe.fuqiuluo.shamrock.remote.service.data.push.MessageTempSource
import moe.fuqiuluo.shamrock.remote.service.data.push.PostType import moe.fuqiuluo.shamrock.remote.service.data.push.PostType
import java.util.ArrayList import java.util.ArrayList
@ -24,9 +25,6 @@ import java.util.Collections
import kotlin.collections.HashMap import kotlin.collections.HashMap
internal object AioListener : IKernelMsgListener { internal object AioListener : IKernelMsgListener {
// 通过MSG SEQ临时监听器
private val tempMessageListenerMap = Collections.synchronizedMap(HashMap<Long, suspend MsgRecord.() -> Unit>())
override fun onRecvMsg(msgList: ArrayList<MsgRecord>) { override fun onRecvMsg(msgList: ArrayList<MsgRecord>) {
if (msgList.isEmpty()) return if (msgList.isEmpty()) return
@ -37,27 +35,9 @@ internal object AioListener : IKernelMsgListener {
} }
} }
fun registerTemporaryMsgListener(
msgSeq: Long,
listener: suspend MsgRecord.() -> Unit
) {
LogCenter.log({ "注册临时消息监听器: $msgSeq" }, Level.DEBUG)
tempMessageListenerMap[msgSeq] = listener
}
fun unregisterTemporaryMsgListener(msgSeq: Long) {
tempMessageListenerMap.remove(msgSeq)
}
private suspend fun handleMsg(record: MsgRecord) { private suspend fun handleMsg(record: MsgRecord) {
try { try {
tempMessageListenerMap.firstNotNullOfOrNull { if (MessageTempHandler.notify(record)) return
if (it.key == record.msgSeq) it else null
}?.let {
it.value(record)
tempMessageListenerMap.remove(it.key)
return
}
if (record.msgSeq < 0) return if (record.msgSeq < 0) return
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId) val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
@ -432,7 +412,7 @@ internal object AioListener : IKernelMsgListener {
} }
override fun onRichMediaUploadComplete(notifyInfo: FileTransNotifyInfo) { override fun onRichMediaUploadComplete(notifyInfo: FileTransNotifyInfo) {
LogCenter.log("onRichMediaUploadComplete($notifyInfo)", Level.DEBUG) LogCenter.log("[BDH] 资源上传完成(${notifyInfo.trasferStatus}, ${notifyInfo.fileId}, ${notifyInfo.msgId}, ${notifyInfo.commonFileInfo})")
RichMediaUploadHandler.notify(notifyInfo) RichMediaUploadHandler.notify(notifyInfo)
} }