SendMessageHandler.kt 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  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 contact.StrangerImpl
  11. import net.mamoe.mirai.contact.*
  12. import net.mamoe.mirai.event.nextEventOrNull
  13. import net.mamoe.mirai.internal.MiraiImpl
  14. import net.mamoe.mirai.internal.asQQAndroidBot
  15. import net.mamoe.mirai.internal.forwardMessage
  16. import net.mamoe.mirai.internal.longMessage
  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.protocol.data.proto.MsgComm
  21. import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
  22. import net.mamoe.mirai.internal.network.protocol.packet.chat.MusicSharePacket
  23. import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
  24. import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.*
  25. import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToFriend
  26. import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToGroup
  27. import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
  28. import net.mamoe.mirai.message.MessageReceipt
  29. import net.mamoe.mirai.message.data.*
  30. import net.mamoe.mirai.utils.castOrNull
  31. import net.mamoe.mirai.utils.currentTimeSeconds
  32. /**
  33. * 通用处理器
  34. */
  35. internal abstract class SendMessageHandler<C : Contact> {
  36. abstract val contact: C
  37. abstract val senderName: String
  38. val messageSourceKind: MessageSourceKind
  39. get() {
  40. return when (contact) {
  41. is Group -> MessageSourceKind.GROUP
  42. is Friend -> MessageSourceKind.FRIEND
  43. is Member -> MessageSourceKind.TEMP
  44. is Stranger -> MessageSourceKind.STRANGER
  45. else -> error("Unsupported contact: $contact")
  46. }
  47. }
  48. val bot get() = contact.bot.asQQAndroidBot()
  49. val targetUserUin: Long? get() = contact.castOrNull<User>()?.uin
  50. val targetGroupUin: Long? get() = contact.castOrNull<Group>()?.uin
  51. val targetGroupCode: Long? get() = contact.castOrNull<Group>()?.groupCode
  52. val targetOtherClientBotUin: Long? get() = contact.castOrNull<OtherClient>()?.bot?.id
  53. val targetUin: Long get() = targetGroupUin ?: targetOtherClientBotUin ?: contact.id
  54. val groupInfo: MsgComm.GroupInfo?
  55. get() = if (isToGroup) MsgComm.GroupInfo(
  56. groupCode = targetGroupCode!!,
  57. groupCard = senderName // Cinnamon
  58. ) else null
  59. // For ForwardMessage display
  60. val ForwardMessage.INode.groupInfo: MsgComm.GroupInfo
  61. get() = MsgComm.GroupInfo(
  62. groupCode = if (isToGroup) targetGroupCode!! else 0,
  63. groupCard = senderName
  64. )
  65. val isToGroup: Boolean get() = contact is Group
  66. suspend fun MessageChain.convertToLongMessageIfNeeded(
  67. step: SendMessageStep,
  68. ): MessageChain {
  69. suspend fun sendLongImpl(): MessageChain {
  70. val resId = uploadLongMessageHighway(this)
  71. return this + RichMessage.longMessage(
  72. brief = takeContent(27),
  73. resId = resId,
  74. timeSeconds = currentTimeSeconds()
  75. ) // LongMessageInternal replaces all contents and preserves metadata
  76. }
  77. return when (step) {
  78. SendMessageStep.FIRST -> {
  79. // 只需要在第一次发送的时候验证长度
  80. // 后续重试直接跳过
  81. if (contains(ForceAsLongMessage)) {
  82. return sendLongImpl()
  83. }
  84. if (!contains(IgnoreLengthCheck)) {
  85. verityLength(this, contact)
  86. }
  87. this
  88. }
  89. SendMessageStep.LONG_MESSAGE -> {
  90. if (contains(DontAsLongMessage)) this // fragmented
  91. else sendLongImpl()
  92. }
  93. SendMessageStep.FRAGMENTED -> this
  94. }
  95. }
  96. /**
  97. * Final process
  98. */
  99. suspend fun sendMessagePacket(
  100. originalMessage: Message,
  101. transformedMessage: MessageChain,
  102. finalMessage: MessageChain,
  103. step: SendMessageStep,
  104. ): MessageReceipt<C> {
  105. val group = contact
  106. var source: OnlineMessageSource.Outgoing? = null
  107. bot.network.run {
  108. sendMessageMultiProtocol(
  109. bot.client, finalMessage,
  110. fragmented = step == SendMessageStep.FRAGMENTED
  111. ) { source = it }.forEach { packet ->
  112. when (val resp = packet.sendAndExpect<Packet>()) {
  113. is MessageSvcPbSendMsg.Response -> {
  114. if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) {
  115. return when (step) {
  116. SendMessageStep.FIRST -> {
  117. sendMessageImpl(originalMessage, transformedMessage, SendMessageStep.LONG_MESSAGE)
  118. }
  119. SendMessageStep.LONG_MESSAGE -> {
  120. sendMessageImpl(originalMessage, transformedMessage, SendMessageStep.FRAGMENTED)
  121. }
  122. else -> {
  123. throw MessageTooLargeException(
  124. group,
  125. originalMessage,
  126. finalMessage,
  127. "Message '${finalMessage.content.take(10)}' is too large."
  128. )
  129. }
  130. }
  131. }
  132. check(resp is MessageSvcPbSendMsg.Response.SUCCESS) {
  133. "Send group message failed: $resp"
  134. }
  135. }
  136. is MusicSharePacket.Response -> {
  137. resp.pkg.checkSuccess("send group music share")
  138. source = constructSourceFromMusicShareResponse(finalMessage, resp)
  139. }
  140. }
  141. }
  142. check(source != null) {
  143. "Internal error: source is not initialized"
  144. }
  145. try {
  146. source!!.ensureSequenceIdAvailable()
  147. } catch (e: Exception) {
  148. bot.network.logger.warning(
  149. "Timeout awaiting sequenceId for group message(${finalMessage.content.take(10)}). Some features may not work properly",
  150. e
  151. )
  152. }
  153. return MessageReceipt(source!!, contact)
  154. }
  155. }
  156. private fun sendMessageMultiProtocol(
  157. client: QQAndroidClient,
  158. message: MessageChain,
  159. fragmented: Boolean,
  160. sourceCallback: (OnlineMessageSource.Outgoing) -> Unit
  161. ): List<OutgoingPacket> {
  162. message.takeSingleContent<MusicShare>()?.let { musicShare ->
  163. return listOf(
  164. MusicSharePacket(
  165. client, musicShare, contact.id,
  166. targetKind = if (isToGroup) MessageSourceKind.GROUP else MessageSourceKind.FRIEND // always FRIEND
  167. )
  168. )
  169. }
  170. return messageSvcSendMessage(client, contact, message, fragmented, sourceCallback)
  171. }
  172. abstract val messageSvcSendMessage: (
  173. client: QQAndroidClient,
  174. contact: C,
  175. message: MessageChain,
  176. fragmented: Boolean,
  177. sourceCallback: (OnlineMessageSource.Outgoing) -> Unit,
  178. ) -> List<OutgoingPacket>
  179. abstract suspend fun constructSourceFromMusicShareResponse(
  180. finalMessage: MessageChain,
  181. response: MusicSharePacket.Response
  182. ): OnlineMessageSource.Outgoing
  183. open suspend fun uploadLongMessageHighway(
  184. chain: MessageChain
  185. ): String = with(contact) {
  186. return MiraiImpl.uploadMessageHighway(
  187. bot, this@SendMessageHandler,
  188. listOf(
  189. ForwardMessage.Node(
  190. senderId = bot.id,
  191. time = currentTimeSeconds().toInt(),
  192. messageChain = chain,
  193. senderName = bot.nick
  194. )
  195. ),
  196. true
  197. )
  198. }
  199. open suspend fun preConversionTransformedMessage(message: Message): Message = message
  200. open suspend fun conversionMessageChain(chain: MessageChain): MessageChain = chain
  201. open suspend fun postTransformActions(chain: MessageChain) {
  202. }
  203. }
  204. /**
  205. * - [ForwardMessage] -> [ForwardMessageInternal] (by uploading through highway)
  206. * - ... any others for future
  207. */
  208. internal suspend fun <C : Contact> SendMessageHandler<C>.transformSpecialMessages(message: Message): MessageChain {
  209. suspend fun processForwardMessage(
  210. forward: ForwardMessage
  211. ): ForwardMessageInternal {
  212. if (!(message is MessageChain && message.contains(IgnoreLengthCheck))) {
  213. check(forward.nodeList.size <= 200) {
  214. throw MessageTooLargeException(
  215. contact, forward, forward,
  216. "ForwardMessage allows up to 200 nodes, but found ${forward.nodeList.size}"
  217. )
  218. }
  219. }
  220. val resId = MiraiImpl.uploadMessageHighway(
  221. bot = contact.bot,
  222. sendMessageHandler = this,
  223. message = forward.nodeList,
  224. isLong = false,
  225. )
  226. return RichMessage.forwardMessage(
  227. resId = resId,
  228. timeSeconds = currentTimeSeconds(),
  229. forwardMessage = forward,
  230. )
  231. }
  232. fun processDice(dice: Dice): MarketFaceImpl {
  233. return MarketFaceImpl(dice.toJceStruct())
  234. }
  235. return message.takeSingleContent<ForwardMessage>()?.let { processForwardMessage(it) }?.toMessageChain()
  236. ?: message.takeSingleContent<Dice>()?.let { processDice(it) }?.toMessageChain()
  237. ?: message.toMessageChain()
  238. }
  239. /**
  240. * Send a message, and covert messages
  241. *
  242. * Don't recall this function.
  243. */
  244. internal suspend fun <C : Contact> SendMessageHandler<C>.sendMessage(
  245. originalMessage: Message,
  246. transformedMessage: Message,
  247. step: SendMessageStep,
  248. ): MessageReceipt<C> = sendMessageImpl(
  249. originalMessage,
  250. conversionMessageChain(
  251. transformSpecialMessages(
  252. preConversionTransformedMessage(transformedMessage)
  253. )
  254. ),
  255. step
  256. )
  257. /**
  258. * Might be recalled with [transformedMessage] `is` [LongMessageInternal] if length estimation failed (sendMessagePacket)
  259. */
  260. internal suspend fun <C : Contact> SendMessageHandler<C>.sendMessageImpl(
  261. originalMessage: Message,
  262. transformedMessage: MessageChain,
  263. step: SendMessageStep,
  264. ): MessageReceipt<C> { // Result cannot be in interface.
  265. val chain = transformedMessage.convertToLongMessageIfNeeded(step)
  266. chain.findIsInstance<QuoteReply>()?.source?.ensureSequenceIdAvailable()
  267. postTransformActions(chain)
  268. return sendMessagePacket(originalMessage, transformedMessage, chain, step)
  269. }
  270. internal sealed class UserSendMessageHandler<C : AbstractUser>(
  271. override val contact: C,
  272. ) : SendMessageHandler<C>() {
  273. override val senderName: String get() = bot.nick
  274. override suspend fun constructSourceFromMusicShareResponse(
  275. finalMessage: MessageChain,
  276. response: MusicSharePacket.Response
  277. ): OnlineMessageSource.Outgoing {
  278. throw UnsupportedOperationException("Sending MusicShare to user is not yet supported")
  279. }
  280. }
  281. internal class FriendSendMessageHandler(
  282. contact: FriendImpl,
  283. ) : UserSendMessageHandler<FriendImpl>(contact) {
  284. override val messageSvcSendMessage: (client: QQAndroidClient, contact: FriendImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSource.Outgoing) -> Unit) -> List<OutgoingPacket> =
  285. MessageSvcPbSendMsg::createToFriend
  286. }
  287. internal class StrangerSendMessageHandler(
  288. contact: StrangerImpl,
  289. ) : UserSendMessageHandler<StrangerImpl>(contact) {
  290. override val messageSvcSendMessage: (client: QQAndroidClient, contact: StrangerImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSource.Outgoing) -> Unit) -> List<OutgoingPacket> =
  291. MessageSvcPbSendMsg::createToStranger
  292. }
  293. internal class GroupTempSendMessageHandler(
  294. contact: NormalMemberImpl,
  295. ) : UserSendMessageHandler<NormalMemberImpl>(contact) {
  296. override val messageSvcSendMessage: (client: QQAndroidClient, contact: NormalMemberImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSource.Outgoing) -> Unit) -> List<OutgoingPacket> =
  297. MessageSvcPbSendMsg::createToTemp
  298. }
  299. internal class GroupSendMessageHandler(
  300. override val contact: GroupImpl,
  301. ) : SendMessageHandler<GroupImpl>() {
  302. override val messageSvcSendMessage: (client: QQAndroidClient, contact: GroupImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSource.Outgoing) -> Unit) -> List<OutgoingPacket> =
  303. MessageSvcPbSendMsg::createToGroup
  304. override val senderName: String
  305. get() = contact.botAsMember.nameCardOrNick
  306. override suspend fun conversionMessageChain(chain: MessageChain): MessageChain = chain.map { element ->
  307. when (element) {
  308. is OfflineGroupImage -> {
  309. contact.fixImageFileId(element)
  310. element
  311. }
  312. is FriendImage -> {
  313. contact.updateFriendImageForGroupMessage(element)
  314. }
  315. else -> element
  316. }
  317. }.toMessageChain()
  318. override suspend fun constructSourceFromMusicShareResponse(
  319. finalMessage: MessageChain,
  320. response: MusicSharePacket.Response
  321. ): OnlineMessageSource.Outgoing {
  322. val receipt: OnlinePushPbPushGroupMsg.SendGroupMessageReceipt =
  323. nextEventOrNull(3000) { it.fromAppId == 3116 }
  324. ?: OnlinePushPbPushGroupMsg.SendGroupMessageReceipt.EMPTY
  325. return OnlineMessageSourceToGroupImpl(
  326. contact,
  327. internalIds = intArrayOf(receipt.messageRandom),
  328. providedSequenceIds = intArrayOf(receipt.sequenceId),
  329. sender = bot,
  330. target = contact,
  331. time = currentTimeSeconds().toInt(),
  332. originalMessage = finalMessage
  333. )
  334. }
  335. companion object {
  336. private suspend fun GroupImpl.fixImageFileId(image: OfflineGroupImage) {
  337. if (image.fileId == null) {
  338. val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp(
  339. bot.client,
  340. uin = bot.id,
  341. groupCode = this.id,
  342. md5 = image.md5,
  343. size = 1,
  344. ).sendAndExpect(bot)
  345. when (response) {
  346. is ImgStore.GroupPicUp.Response.Failed -> {
  347. image.fileId = 0 // Failed
  348. }
  349. is ImgStore.GroupPicUp.Response.FileExists -> {
  350. image.fileId = response.fileId.toInt()
  351. }
  352. is ImgStore.GroupPicUp.Response.RequireUpload -> {
  353. image.fileId = response.fileId.toInt()
  354. }
  355. }
  356. }
  357. }
  358. /**
  359. * Ensures server holds the cache
  360. */
  361. private suspend fun GroupImpl.updateFriendImageForGroupMessage(image: FriendImage): OfflineGroupImage {
  362. bot.network.run {
  363. val response = ImgStore.GroupPicUp(
  364. bot.client,
  365. uin = bot.id,
  366. groupCode = id,
  367. md5 = image.md5,
  368. size = if (image is OnlineFriendImageImpl) image.delegate.fileLen else 0
  369. ).sendAndExpect<ImgStore.GroupPicUp.Response>()
  370. return OfflineGroupImage(image.imageId).also { img ->
  371. when (response) {
  372. is ImgStore.GroupPicUp.Response.FileExists -> {
  373. img.fileId = response.fileId.toInt()
  374. }
  375. is ImgStore.GroupPicUp.Response.RequireUpload -> {
  376. img.fileId = response.fileId.toInt()
  377. }
  378. is ImgStore.GroupPicUp.Response.Failed -> {
  379. img.fileId = 0
  380. }
  381. }
  382. }
  383. }
  384. }
  385. }
  386. }