Skip to content
This repository has been archived by the owner on Aug 12, 2024. It is now read-only.

Commit

Permalink
Shamrock: 实现消息服务
Browse files Browse the repository at this point in the history
Signed-off-by: 白池 <whitechi73@outlook.com>
  • Loading branch information
whitechi73 committed Mar 16, 2024
1 parent 3a07116 commit 6c9b282
Show file tree
Hide file tree
Showing 25 changed files with 2,966 additions and 53 deletions.
8 changes: 4 additions & 4 deletions xposed/src/main/java/kritor/service/GroupService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,13 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
)
}

GroupHelper.setGroupUniqueTitle(request.groupId, when(request.targetCase!!) {
GroupHelper.setGroupUniqueTitle(request.groupId.toString(), when(request.targetCase!!) {
SetGroupUniqueTitleRequest.TargetCase.TARGET_UIN -> request.targetUin
SetGroupUniqueTitleRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
.withDescription("target not set")
)
}, request.uniqueTitle)
}.toString(), request.uniqueTitle)

return setGroupUniqueTitleResponse { }
}
Expand Down Expand Up @@ -253,13 +253,13 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()

@Grpc("GroupService", "GetGroupMemberInfo")
override suspend fun getGroupMemberInfo(request: GetGroupMemberInfoRequest): GetGroupMemberInfoResponse {
val memberInfo = GroupHelper.getTroopMemberInfoByUin(request.groupId, when(request.targetCase!!) {
val memberInfo = GroupHelper.getTroopMemberInfoByUin(request.groupId.toString(), when(request.targetCase!!) {
GetGroupMemberInfoRequest.TargetCase.UIN -> request.uin
GetGroupMemberInfoRequest.TargetCase.UID -> ContactHelper.getUinByUidAsync(request.uid).toLong()
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
.withDescription("target not set")
)
}).onFailure {
}.toString()).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group member info").withCause(it))
}.getOrThrow()
return getGroupMemberInfoResponse {
Expand Down
324 changes: 324 additions & 0 deletions xposed/src/main/java/kritor/service/MessageService.kt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import qq.service.QQInterfaces
import qq.service.msg.toKritorMessages
import qq.service.msg.toKritorEventMessages

internal object GlobalEventTransmitter: QQInterfaces() {
private val messageEventFlow by lazy {
Expand Down Expand Up @@ -80,7 +80,7 @@ internal object GlobalEventTransmitter: QQInterfaces() {
this.uid = record.senderUid
this.nick = record.sendNickName
}
this.elements.addAll(elements.toKritorMessages(record))
this.elements.addAll(elements.toKritorEventMessages(record))
})
return true
}
Expand All @@ -104,7 +104,7 @@ internal object GlobalEventTransmitter: QQInterfaces() {
this.uid = record.senderUid
this.nick = record.sendNickName
}
this.elements.addAll(elements.toKritorMessages(record))
this.elements.addAll(elements.toKritorEventMessages(record))
})
return true
}
Expand All @@ -130,7 +130,7 @@ internal object GlobalEventTransmitter: QQInterfaces() {
this.uid = record.senderUid
this.nick = record.sendNickName
}
this.elements.addAll(elements.toKritorMessages(record))
this.elements.addAll(elements.toKritorEventMessages(record))
})
return true
}
Expand All @@ -146,15 +146,15 @@ internal object GlobalEventTransmitter: QQInterfaces() {
this.messageSeq = record.msgSeq
this.contact = contact {
this.scene = scene
this.peer = record.channelId.toString()
this.subPeer = record.guildId
this.peer = record.guildId ?: ""
this.subPeer = record.channelId ?: ""
}
this.sender = sender {
this.uin = record.senderUin
this.uid = record.senderUid
this.nick = record.sendNickName
}
this.elements.addAll(elements.toKritorMessages(record))
this.elements.addAll(elements.toKritorEventMessages(record))
})
return true
}
Expand Down
11 changes: 11 additions & 0 deletions xposed/src/main/java/qq/service/QQInterfaces.kt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ abstract class QQInterfaces {
app.sendToService(to)
}

fun sendBuffer(
cmd: String,
isProto: Boolean,
data: ByteArray,
) {
val toServiceMsg = createToServiceMsg(cmd)
toServiceMsg.putWupBuffer(data)
toServiceMsg.addAttribute("req_pb_protocol_flag", isProto)
sendToServiceMsg(toServiceMsg)
}

@DelicateCoroutinesApi
suspend fun sendBufferAW(
cmd: String,
Expand Down
58 changes: 58 additions & 0 deletions xposed/src/main/java/qq/service/bdh/ResourceData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package qq.service.bdh

import com.tencent.mobileqq.data.MessageRecord
import java.io.File

internal enum class ContactType {
TROOP,
PRIVATE,
}

internal interface TransTarget {
val id: String
val type: ContactType

val mRec: MessageRecord?
}

internal class Troop(
override val id: String,
override val mRec: MessageRecord? = null
): TransTarget {
override val type: ContactType = ContactType.TROOP
}

internal class Private(
override val id: String,
override val mRec: MessageRecord? = null
): TransTarget {
override val type: ContactType = ContactType.PRIVATE
}

internal enum class ResourceType {
Picture,
Video,
Voice
}

internal interface Resource {
val type: ResourceType
}

internal data class PictureResource(
val src: File
): Resource {
override val type = ResourceType.Picture
}

internal data class VideoResource(
val src: File, val thumb: File
): Resource {
override val type = ResourceType.Video
}

internal data class VoiceResource(
val src: File
): Resource {
override val type = ResourceType.Voice
}
135 changes: 135 additions & 0 deletions xposed/src/main/java/qq/service/bdh/Transfer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package qq.service.bdh

import com.tencent.mobileqq.data.MessageForShortVideo
import com.tencent.mobileqq.data.MessageRecord
import com.tencent.mobileqq.transfile.FileMsg
import com.tencent.mobileqq.transfile.TransferRequest
import moe.fuqiuluo.shamrock.utils.MD5
import qq.service.bdh.ResourceType.*
import java.io.File

internal object Transfer: FileTransfer() {
private val ROUTE = mapOf<ContactType, Map<ResourceType, suspend TransTarget.(Resource) -> Boolean>>(
ContactType.TROOP to mapOf(
Picture to { uploadGroupPic(id, (it as PictureResource).src, mRec) },
Voice to { uploadGroupVoice(id, (it as VoiceResource).src) },
Video to { uploadGroupVideo(id, (it as VideoResource).src, it.thumb) },

),
ContactType.PRIVATE to mapOf(
Picture to { uploadC2CPic(id, (it as PictureResource).src, mRec) },
Voice to { uploadC2CVoice(id, (it as VoiceResource).src) },
Video to { uploadC2CVideo(id, (it as VideoResource).src, it.thumb) },
)
)

suspend fun uploadC2CVideo(
userId: String,
file: File,
thumb: File,
wait: Boolean = true
): Boolean {
return transC2CResource(userId, file, FileMsg.TRANSFILE_TYPE_SHORT_VIDEO_C2C, BUSI_TYPE_SHORT_VIDEO, wait) {
it.mSourceVideoCodecFormat = VIDEO_FORMAT_MP4
it.mRec = MessageForShortVideo().also {
it.busiType = BUSI_TYPE_SHORT_VIDEO
}
it.mThumbPath = thumb.absolutePath
it.mThumbMd5 = MD5.genFileMd5Hex(thumb.absolutePath)
}
}

suspend fun uploadGroupVideo(
groupId: String,
file: File,
thumb: File,
wait: Boolean = true
): Boolean {
return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_SHORT_VIDEO_TROOP, BUSI_TYPE_SHORT_VIDEO, wait) {
it.mSourceVideoCodecFormat = VIDEO_FORMAT_MP4
it.mRec = MessageForShortVideo().also {
it.busiType = BUSI_TYPE_SHORT_VIDEO
}
it.mThumbPath = thumb.absolutePath
it.mThumbMd5 = MD5.genFileMd5Hex(thumb.absolutePath)
}
}

suspend fun uploadC2CVoice(
userId: String,
file: File,
wait: Boolean = true
): Boolean {
return transC2CResource(userId, file, FileMsg.TRANSFILE_TYPE_PTT, 1002, wait) {
it.mPttUploadPanel = 3
it.mPttCompressFinish = true
it.mIsPttPreSend = true
}
}

suspend fun uploadGroupVoice(
groupId: String,
file: File,
wait: Boolean = true
): Boolean {
return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_PTT, 1002, wait) {
it.mPttUploadPanel = 3
it.mPttCompressFinish = true
it.mIsPttPreSend = true
}
}

suspend fun uploadC2CPic(
peerId: String,
file: File,
record: MessageRecord? = null,
wait: Boolean = true
): Boolean {
return transC2CResource(peerId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_CAMERA, wait) {
val picUpExtraInfo = TransferRequest.PicUpExtraInfo()
picUpExtraInfo.mIsRaw = false
picUpExtraInfo.mUinType = FileMsg.UIN_BUDDY
it.mPicSendSource = 8
it.mExtraObj = picUpExtraInfo
it.mIsPresend = true
it.delayShowProgressTimeInMs = 2000
it.mRec = record
}
}

suspend fun uploadGroupPic(
groupId: String,
file: File,
record: MessageRecord? = null,
wait: Boolean = true
): Boolean {
return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_CAMERA, wait) {
val picUpExtraInfo = TransferRequest.PicUpExtraInfo()
picUpExtraInfo.mIsRaw = false
picUpExtraInfo.mUinType = FileMsg.UIN_TROOP
it.mPicSendSource = 8
it.delayShowProgressTimeInMs = 2000
it.mExtraObj = picUpExtraInfo
it.mRec = record
}
}

operator fun get(contactType: ContactType, resourceType: ResourceType): suspend TransTarget.(Resource) -> Boolean {
return (ROUTE[contactType] ?: error("unsupported contact type: $contactType"))[resourceType]
?: error("Unsupported resource type: $resourceType")
}
}

internal suspend infix fun TransferTaskBuilder.trans(res: Resource): Boolean {
return Transfer[contact.type, res.type](contact, res)
}

internal class TransferTaskBuilder {
lateinit var contact: TransTarget
}

internal infix fun Transfer.with(contact: TransTarget): TransferTaskBuilder {
return TransferTaskBuilder().also {
it.contact = contact
}
}
21 changes: 21 additions & 0 deletions xposed/src/main/java/qq/service/contact/ContactExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package qq.service.contact

import com.tencent.qqnt.kernel.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import io.kritor.message.Scene

suspend fun Contact.longPeer(): Long {
return when(this.chatType) {
MsgConstant.KCHATTYPEGROUP -> peerUid.toLong()
MsgConstant.KCHATTYPETEMPC2CFROMGROUP, MsgConstant.KCHATTYPEC2C -> if (peerUid.startsWith("u_")) ContactHelper.getUinByUidAsync(peerUid).toLong() else peerUid.toLong()
else -> 0L
}
}

suspend fun io.kritor.message.Contact.longPeer(): Long {
return when(this.scene) {
Scene.GROUP -> peer.toLong()
Scene.FRIEND, Scene.STRANGER, Scene.STRANGER_FROM_GROUP -> if (peer.startsWith("u_")) ContactHelper.getUinByUidAsync(peer).toLong() else peer.toLong()
else -> 0L
}
}
31 changes: 31 additions & 0 deletions xposed/src/main/java/qq/service/contact/ContactHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst.PARAM_SELF_UIN
import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst.PARAM_TARGET_UIN
import com.tencent.mobileqq.profilecard.api.IProfileProtocolService
import com.tencent.mobileqq.profilecard.observer.ProfileCardObserver
import com.tencent.protofile.join_group_link.join_group_link
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import moe.fuqiuluo.shamrock.tools.slice
import qq.service.internals.NTServiceFetcher
import qq.service.QQInterfaces
import tencent.im.oidb.cmd0x11b2.oidb_0x11b2
import tencent.im.oidb.oidb_sso
import kotlin.coroutines.resume

internal object ContactHelper: QQInterfaces() {
Expand Down Expand Up @@ -177,4 +181,31 @@ internal object ContactHelper: QQInterfaces() {
}
}[peerId]!!
}

suspend fun getSharePrivateArkMsg(peerId: Long): String {
val reqBody = oidb_0x11b2.BusinessCardV3Req()
reqBody.uin.set(peerId)
reqBody.jump_url.set("mqqapi://card/show_pslcard?src_type=internal&source=sharecard&version=1&uin=$peerId")

val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11ca_0", 4790, 0, reqBody.toByteArray())
?: error("unable to fetch contact ark_json_text")

val body = oidb_sso.OIDBSSOPkg()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val rsp = oidb_0x11b2.BusinessCardV3Rsp()
rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray())
return rsp.signed_ark_msg.get()
}

suspend fun getShareTroopArkMsg(groupId: Long): String {
val reqBody = join_group_link.ReqBody()
reqBody.get_ark.set(true)
reqBody.type.set(1)
reqBody.group_code.set(groupId)
val fromServiceMsg = sendBufferAW("GroupSvc.JoinGroupLink", true, reqBody.toByteArray())
?: error("unable to fetch contact ark_json_text")
val body = join_group_link.RspBody()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
return body.signed_ark.get().toStringUtf8()
}
}
Loading

0 comments on commit 6c9b282

Please sign in to comment.