SendMessageHandler.kt 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. /*
  2. * Copyright 2020 Mamoe Technologies and contributors.
  3. *
  4. * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
  5. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
  6. *
  7. * https://github.com/mamoe/mirai/blob/master/LICENSE
  8. */
  9. package net.mamoe.mirai.internal.contact
  10. import kotlinx.coroutines.CompletableDeferred
  11. import kotlinx.coroutines.Deferred
  12. import kotlinx.coroutines.async
  13. import net.mamoe.mirai.contact.*
  14. import net.mamoe.mirai.event.nextEventOrNull
  15. import net.mamoe.mirai.internal.asQQAndroidBot
  16. import net.mamoe.mirai.internal.getMiraiImpl
  17. import net.mamoe.mirai.internal.message.*
  18. import net.mamoe.mirai.internal.network.Packet
  19. import net.mamoe.mirai.internal.network.QQAndroidClient
  20. import net.mamoe.mirai.internal.network.components.ClockHolder.Companion.clock
  21. import net.mamoe.mirai.internal.network.components.MessageSvcSyncer
  22. import net.mamoe.mirai.internal.network.handler.logger
  23. import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor.SendGroupMessageReceipt
  24. import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageProcessor.SendPrivateMessageReceipt
  25. import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
  26. import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
  27. import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement
  28. import net.mamoe.mirai.internal.network.protocol.packet.chat.MusicSharePacket
  29. import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.*
  30. import net.mamoe.mirai.internal.utils.ImagePatcher
  31. import net.mamoe.mirai.message.MessageReceipt
  32. import net.mamoe.mirai.message.data.*
  33. import net.mamoe.mirai.utils.castOrNull
  34. import net.mamoe.mirai.utils.currentTimeSeconds
  35. /**
  36. * 处理 mirai 消息系统 `Message` 到协议数据结构的转换.
  37. *
  38. * 外部调用 [sendMessageImpl]
  39. */
  40. internal abstract class SendMessageHandler<C : Contact> {
  41. abstract val contact: C
  42. abstract val senderName: String
  43. val messageSourceKind: MessageSourceKind
  44. get() {
  45. return when (contact) {
  46. is Group -> MessageSourceKind.GROUP
  47. is Friend -> MessageSourceKind.FRIEND
  48. is Member -> MessageSourceKind.TEMP
  49. is Stranger -> MessageSourceKind.STRANGER
  50. else -> error("Unsupported contact: $contact")
  51. }
  52. }
  53. val bot get() = contact.bot.asQQAndroidBot()
  54. val targetUserUin: Long? get() = contact.castOrNull<User>()?.uin
  55. val targetGroupUin: Long? get() = contact.castOrNull<Group>()?.uin
  56. val targetGroupCode: Long? get() = contact.castOrNull<Group>()?.groupCode
  57. val targetOtherClientBotUin: Long? get() = contact.castOrNull<OtherClient>()?.bot?.id
  58. val targetUin: Long get() = targetGroupUin ?: targetOtherClientBotUin ?: contact.id
  59. val groupInfo: MsgComm.GroupInfo?
  60. get() = if (isToGroup) MsgComm.GroupInfo(
  61. groupCode = targetGroupCode!!,
  62. groupCard = senderName // Cinnamon
  63. ) else null
  64. // For ForwardMessage display
  65. val ForwardMessage.INode.groupInfo: MsgComm.GroupInfo
  66. get() = MsgComm.GroupInfo(
  67. groupCode = if (isToGroup) targetGroupCode!! else 0,
  68. groupCard = senderName
  69. )
  70. val isToGroup: Boolean get() = contact is Group
  71. suspend fun MessageChain.convertToLongMessageIfNeeded(
  72. step: SendMessageStep,
  73. ): MessageChain {
  74. suspend fun sendLongImpl(): MessageChain {
  75. val resId = uploadLongMessageHighway(this)
  76. return this + RichMessage.longMessage(
  77. brief = takeContent(27),
  78. resId = resId,
  79. timeSeconds = currentTimeSeconds()
  80. ) // LongMessageInternal replaces all contents and preserves metadata
  81. }
  82. return when (step) {
  83. SendMessageStep.FIRST -> {
  84. // 只需要在第一次发送的时候验证长度
  85. // 后续重试直接跳过
  86. if (contains(ForceAsLongMessage)) {
  87. return sendLongImpl()
  88. }
  89. if (!contains(IgnoreLengthCheck)) {
  90. verifyLength(this, contact)
  91. }
  92. this
  93. }
  94. SendMessageStep.LONG_MESSAGE -> {
  95. if (contains(DontAsLongMessage)) this // fragmented
  96. else sendLongImpl()
  97. }
  98. SendMessageStep.FRAGMENTED -> this
  99. }
  100. }
  101. /**
  102. * Final process. Convert transformed message to protocol internals and transfer to server
  103. */
  104. suspend fun sendMessagePacket(
  105. originalMessage: Message,
  106. transformedMessage: MessageChain,
  107. finalMessage: MessageChain,
  108. isMiraiInternal: Boolean,
  109. step: SendMessageStep,
  110. ): MessageReceipt<C> {
  111. bot.components[MessageSvcSyncer].joinSync()
  112. val group = contact
  113. var source: Deferred<OnlineMessageSource.Outgoing>? = null
  114. bot.network.run {
  115. sendMessageMultiProtocol(
  116. bot.client, finalMessage,
  117. fragmented = step == SendMessageStep.FRAGMENTED
  118. ) { source = it }.forEach { packet ->
  119. when (val resp = packet.sendAndExpect<Packet>()) {
  120. is MessageSvcPbSendMsg.Response -> {
  121. if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) {
  122. return when (step) {
  123. SendMessageStep.FIRST -> {
  124. sendMessageImpl(
  125. originalMessage,
  126. transformedMessage,
  127. isMiraiInternal,
  128. SendMessageStep.LONG_MESSAGE,
  129. )
  130. }
  131. SendMessageStep.LONG_MESSAGE -> {
  132. sendMessageImpl(
  133. originalMessage,
  134. transformedMessage,
  135. isMiraiInternal,
  136. SendMessageStep.FRAGMENTED,
  137. )
  138. }
  139. else -> {
  140. throw MessageTooLargeException(
  141. group,
  142. originalMessage,
  143. finalMessage,
  144. "Message '${finalMessage.content.take(10)}' is too large."
  145. )
  146. }
  147. }
  148. }
  149. if (resp is MessageSvcPbSendMsg.Response.ServiceUnavailable) {
  150. throw IllegalStateException("Send message to $contact failed, server service is unavailable.")
  151. }
  152. if (resp is MessageSvcPbSendMsg.Response.Failed) {
  153. val contact = contact
  154. when (resp.resultType) {
  155. 120 -> if (contact is Group) throw BotIsBeingMutedException(contact, originalMessage)
  156. 121 -> if (AtAll in finalMessage) throw IllegalStateException("Send message to $contact failed, reached maximum AtAll times limit.")
  157. 299 -> if (contact is Group) throw SendMessageFailedException(
  158. contact,
  159. SendMessageFailedException.Reason.GROUP_CHAT_LIMITED,
  160. originalMessage
  161. )
  162. }
  163. }
  164. check(resp is MessageSvcPbSendMsg.Response.SUCCESS) {
  165. "Send message failed: $resp"
  166. }
  167. }
  168. is MusicSharePacket.Response -> {
  169. resp.pkg.checkSuccess("send music share")
  170. source = CompletableDeferred(constructSourceForSpecialMessage(finalMessage, 3116))
  171. }
  172. // is CommonOidbResponse<*> -> {
  173. // when (resp.toResult("send message").getOrThrow()) {
  174. // is Oidb0x6d9.FeedsRspBody -> {
  175. // }
  176. // }
  177. // }
  178. }
  179. }
  180. val sourceAwait = source?.await() ?: error("Internal error: source is not initialized")
  181. try {
  182. sourceAwait.ensureSequenceIdAvailable()
  183. } catch (e: Exception) {
  184. bot.network.logger.warning(
  185. "Timeout awaiting sequenceId for message(${finalMessage.content.take(10)}). Some features may not work properly",
  186. e
  187. )
  188. }
  189. return sourceAwait.createMessageReceipt(contact, true)
  190. }
  191. }
  192. private suspend fun sendMessageMultiProtocol(
  193. client: QQAndroidClient,
  194. message: MessageChain,
  195. fragmented: Boolean,
  196. sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit,
  197. ): List<OutgoingPacket> {
  198. message.takeSingleContent<MusicShare>()?.let { musicShare ->
  199. return listOf(
  200. MusicSharePacket(
  201. client, musicShare, contact.id,
  202. targetKind = if (isToGroup) MessageSourceKind.GROUP else MessageSourceKind.FRIEND // always FRIEND
  203. )
  204. )
  205. }
  206. message.takeSingleContent<FileMessage>()?.let { file ->
  207. file.checkIsImpl()
  208. sourceCallback(contact.async { constructSourceForSpecialMessage(message, 2021) })
  209. return listOf(FileManagement.Feed(client, contact.id, file.busId, file.id))
  210. }
  211. return messageSvcSendMessage(client, contact, message, fragmented, sourceCallback)
  212. }
  213. abstract val messageSvcSendMessage: (
  214. client: QQAndroidClient,
  215. contact: C,
  216. message: MessageChain,
  217. fragmented: Boolean,
  218. sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit,
  219. ) -> List<OutgoingPacket>
  220. abstract suspend fun constructSourceForSpecialMessage(
  221. finalMessage: MessageChain,
  222. fromAppId: Int,
  223. ): OnlineMessageSource.Outgoing
  224. open suspend fun uploadLongMessageHighway(
  225. chain: MessageChain,
  226. ): String = with(contact) {
  227. return getMiraiImpl().uploadMessageHighway(
  228. bot, this@SendMessageHandler,
  229. listOf(
  230. ForwardMessage.Node(
  231. senderId = bot.id,
  232. time = currentTimeSeconds().toInt(),
  233. messageChain = chain,
  234. senderName = bot.nick
  235. )
  236. ),
  237. true
  238. )
  239. }
  240. open suspend fun preConversionTransformedMessage(message: Message): Message = message
  241. open suspend fun conversionMessageChain(chain: MessageChain): MessageChain = chain
  242. open suspend fun postTransformActions(chain: MessageChain) {
  243. }
  244. }
  245. /**
  246. * 处理需要 `suspend` 操作的消息转换. 这个转换只会在发送消息时进行, 而不会在处理合并转发 [net.mamoe.mirai.internal.network.protocol.packet.chat.calculateValidationData] 等其他操作时进行.
  247. * 在发包前还会进行最后的 [net.mamoe.mirai.internal.message.toRichTextElems] 转换, 这个转换会为所有操作使用.
  248. *
  249. * - [ForwardMessage] -> [ForwardMessageInternal] (by uploading through highway)
  250. * - ... any others for future
  251. */
  252. internal suspend fun <C : Contact> SendMessageHandler<C>.transformSpecialMessages(message: Message): MessageChain {
  253. suspend fun processForwardMessage(
  254. forward: ForwardMessage,
  255. ): ForwardMessageInternal {
  256. if (!(message is MessageChain && message.contains(IgnoreLengthCheck))) {
  257. check(forward.nodeList.size <= 200) {
  258. throw MessageTooLargeException(
  259. contact, forward, forward,
  260. "ForwardMessage allows up to 200 nodes, but found ${forward.nodeList.size}"
  261. )
  262. }
  263. val tmp = ArrayList<SingleMessage>(
  264. forward.nodeList.sumOf { it.messageChain.size }
  265. )
  266. forward.nodeList.forEach { tmp.addAll(it.messageChain) }
  267. // toMessageChain will lose some element
  268. @Suppress("INVISIBLE_MEMBER")
  269. createMessageChainImplOptimized(tmp).verifyLength(forward, contact)
  270. }
  271. val resId = getMiraiImpl().uploadMessageHighway(
  272. bot = contact.bot,
  273. sendMessageHandler = this,
  274. message = forward.nodeList,
  275. isLong = false,
  276. )
  277. return RichMessage.forwardMessage(
  278. resId = resId,
  279. fileName = currentTimeSeconds().toString(),
  280. forwardMessage = forward,
  281. )
  282. }
  283. // loses MessageMetadata and other message types but fine for now.
  284. return message.takeSingleContent<ForwardMessage>()?.let { processForwardMessage(it) }?.toMessageChain()
  285. ?: message.toMessageChain()
  286. }
  287. /**
  288. * Send a message, and covert messages
  289. *
  290. * Don't recall this function.
  291. */
  292. internal suspend fun <C : Contact> SendMessageHandler<C>.sendMessage(
  293. originalMessage: Message,
  294. transformedMessage: Message,
  295. isMiraiInternal: Boolean,
  296. step: SendMessageStep,
  297. ): MessageReceipt<C> = sendMessageImpl(
  298. originalMessage,
  299. conversionMessageChain(
  300. transformSpecialMessages(
  301. preConversionTransformedMessage(transformedMessage)
  302. )
  303. ),
  304. isMiraiInternal,
  305. step
  306. )
  307. /**
  308. * Might be recalled with [transformedMessage] `is` [LongMessageInternal] if length estimation failed (sendMessagePacket)
  309. */
  310. private suspend fun <C : Contact> SendMessageHandler<C>.sendMessageImpl(
  311. originalMessage: Message,
  312. transformedMessage: MessageChain,
  313. isMiraiInternal: Boolean,
  314. step: SendMessageStep,
  315. ): MessageReceipt<C> { // Result cannot be in interface.
  316. if (!isMiraiInternal && step == SendMessageStep.FIRST) {
  317. transformedMessage.verifySendingValid()
  318. }
  319. val chain = transformedMessage.convertToLongMessageIfNeeded(step)
  320. chain.findIsInstance<QuoteReply>()?.source?.ensureSequenceIdAvailable()
  321. postTransformActions(chain)
  322. return sendMessagePacket(originalMessage, transformedMessage, chain, isMiraiInternal, step)
  323. }
  324. internal sealed class UserSendMessageHandler<C : AbstractUser>(
  325. override val contact: C,
  326. ) : SendMessageHandler<C>() {
  327. override val senderName: String get() = bot.nick
  328. override suspend fun constructSourceForSpecialMessage(
  329. finalMessage: MessageChain,
  330. fromAppId: Int,
  331. ): OnlineMessageSource.Outgoing {
  332. throw UnsupportedOperationException("Sending MusicShare or FileMessage to User is not yet supported")
  333. }
  334. }
  335. internal class FriendSendMessageHandler(
  336. contact: FriendImpl,
  337. ) : UserSendMessageHandler<FriendImpl>(contact) {
  338. override val messageSvcSendMessage: (client: QQAndroidClient, contact: FriendImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit) -> List<OutgoingPacket> =
  339. MessageSvcPbSendMsg::createToFriend
  340. override suspend fun constructSourceForSpecialMessage(
  341. finalMessage: MessageChain,
  342. fromAppId: Int
  343. ): OnlineMessageSource.Outgoing {
  344. val receipt: SendPrivateMessageReceipt = nextEventOrNull(3000) {
  345. it.bot === bot && it.fromAppId == fromAppId
  346. } ?: SendPrivateMessageReceipt.EMPTY
  347. return OnlineMessageSourceToFriendImpl(
  348. internalIds = intArrayOf(receipt.messageRandom),
  349. sequenceIds = intArrayOf(receipt.sequenceId),
  350. sender = bot,
  351. target = contact,
  352. time = bot.clock.server.currentTimeSeconds().toInt(),
  353. originalMessage = finalMessage
  354. )
  355. }
  356. }
  357. internal class StrangerSendMessageHandler(
  358. contact: StrangerImpl,
  359. ) : UserSendMessageHandler<StrangerImpl>(contact) {
  360. override val messageSvcSendMessage: (client: QQAndroidClient, contact: StrangerImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit) -> List<OutgoingPacket> =
  361. MessageSvcPbSendMsg::createToStranger
  362. }
  363. internal class GroupTempSendMessageHandler(
  364. contact: NormalMemberImpl,
  365. ) : UserSendMessageHandler<NormalMemberImpl>(contact) {
  366. override val messageSvcSendMessage: (client: QQAndroidClient, contact: NormalMemberImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit) -> List<OutgoingPacket> =
  367. MessageSvcPbSendMsg::createToTemp
  368. }
  369. internal open class GroupSendMessageHandler(
  370. override val contact: GroupImpl,
  371. ) : SendMessageHandler<GroupImpl>() {
  372. override val messageSvcSendMessage: (client: QQAndroidClient, contact: GroupImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit) -> List<OutgoingPacket> =
  373. MessageSvcPbSendMsg::createToGroup
  374. override val senderName: String
  375. get() = contact.botAsMember.nameCardOrNick
  376. override suspend fun conversionMessageChain(chain: MessageChain): MessageChain = chain.map { element ->
  377. when (element) {
  378. is OfflineGroupImage -> {
  379. contact.fixImageFileId(element)
  380. element
  381. }
  382. is FriendImage -> {
  383. contact.updateFriendImageForGroupMessage(element)
  384. }
  385. else -> element
  386. }
  387. }.toMessageChain()
  388. override suspend fun constructSourceForSpecialMessage(
  389. finalMessage: MessageChain,
  390. fromAppId: Int,
  391. ): OnlineMessageSource.Outgoing {
  392. val receipt: SendGroupMessageReceipt = nextEventOrNull(3000) {
  393. it.bot === bot && it.fromAppId == fromAppId
  394. } ?: SendGroupMessageReceipt.EMPTY
  395. return OnlineMessageSourceToGroupImpl(
  396. contact,
  397. internalIds = intArrayOf(receipt.messageRandom),
  398. providedSequenceIds = intArrayOf(receipt.sequenceId),
  399. sender = bot,
  400. target = contact,
  401. time = bot.clock.server.currentTimeSeconds().toInt(),
  402. originalMessage = finalMessage
  403. )
  404. }
  405. companion object {
  406. private suspend fun GroupImpl.fixImageFileId(image: OfflineGroupImage) {
  407. bot.components[ImagePatcher].patchOfflineGroupImage(this, image)
  408. }
  409. private suspend fun GroupImpl.updateFriendImageForGroupMessage(image: FriendImage): OfflineGroupImage {
  410. return bot.components[ImagePatcher].patchFriendImageToGroupImage(this, image)
  411. }
  412. }
  413. }