ReceiveMessageHandler.kt 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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.message
  10. import io.ktor.utils.io.core.*
  11. import net.mamoe.mirai.Bot
  12. import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep
  13. import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight
  14. import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements
  15. import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.toAudio
  16. import net.mamoe.mirai.internal.message.data.LongMessageInternal
  17. import net.mamoe.mirai.internal.message.data.OnlineAudioImpl
  18. import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade
  19. import net.mamoe.mirai.internal.message.protocol.impl.PokeMessageProtocol.Companion.UNSUPPORTED_POKE_MESSAGE_PLAIN
  20. import net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol.Companion.UNSUPPORTED_MERGED_MESSAGE_PLAIN
  21. import net.mamoe.mirai.internal.message.source.*
  22. import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
  23. import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
  24. import net.mamoe.mirai.message.data.*
  25. import net.mamoe.mirai.utils.structureToString
  26. import net.mamoe.mirai.utils.toLongUnsigned
  27. /**
  28. * 只在手动构造 [OfflineMessageSource] 时调用
  29. */
  30. internal fun ImMsgBody.SourceMsg.toMessageChainNoSource(
  31. bot: Bot,
  32. messageSourceKind: MessageSourceKind,
  33. groupIdOrZero: Long,
  34. refineContext: RefineContext = EmptyRefineContext,
  35. facade: MessageProtocolFacade = MessageProtocolFacade
  36. ): MessageChain {
  37. val elements = this.elems
  38. return buildMessageChain(elements.size + 1) {
  39. facade.decode(elements, groupIdOrZero, messageSourceKind, bot, this, null)
  40. }.cleanupRubbishMessageElements().refineLight(bot, refineContext)
  41. }
  42. internal suspend fun List<MsgComm.Msg>.toMessageChainOnline(
  43. bot: Bot,
  44. groupIdOrZero: Long,
  45. messageSourceKind: MessageSourceKind,
  46. refineContext: RefineContext = EmptyRefineContext,
  47. facade: MessageProtocolFacade = MessageProtocolFacade
  48. ): MessageChain {
  49. return toMessageChain(bot, groupIdOrZero, true, messageSourceKind, facade).refineDeep(bot, refineContext)
  50. }
  51. internal fun getMessageSourceKindFromC2cCmdOrNull(c2cCmd: Int): MessageSourceKind? {
  52. return when (c2cCmd) {
  53. 11 -> MessageSourceKind.FRIEND // bot 给其他人发消息
  54. 4 -> MessageSourceKind.FRIEND // bot 给自己作为好友发消息 (非 other client)
  55. 1 -> MessageSourceKind.GROUP
  56. else -> null
  57. }
  58. }
  59. internal fun getMessageSourceKindFromC2cCmd(c2cCmd: Int): MessageSourceKind {
  60. return getMessageSourceKindFromC2cCmdOrNull(c2cCmd) ?: error("Could not get source kind from c2cCmd: $c2cCmd")
  61. }
  62. internal suspend fun MsgComm.Msg.toMessageChainOnline(
  63. bot: Bot,
  64. refineContext: RefineContext = EmptyRefineContext,
  65. facade: MessageProtocolFacade = MessageProtocolFacade,
  66. ): MessageChain {
  67. val kind = getMessageSourceKindFromC2cCmd(msgHead.c2cCmd)
  68. val groupId = when (kind) {
  69. MessageSourceKind.GROUP -> msgHead.groupInfo?.groupCode ?: 0
  70. else -> 0
  71. }
  72. return listOf(this).toMessageChainOnline(bot, groupId, kind, refineContext, facade)
  73. }
  74. //internal fun List<MsgComm.Msg>.toMessageChainOffline(
  75. // bot: Bot,
  76. // groupIdOrZero: Long,
  77. // messageSourceKind: MessageSourceKind
  78. //): MessageChain {
  79. // return toMessageChain(bot, groupIdOrZero, false, messageSourceKind).refineLight(bot)
  80. //}
  81. internal fun List<MsgComm.Msg>.toMessageChainNoSource(
  82. bot: Bot,
  83. groupIdOrZero: Long,
  84. messageSourceKind: MessageSourceKind,
  85. refineContext: RefineContext = EmptyRefineContext,
  86. ): MessageChain {
  87. return toMessageChain(bot, groupIdOrZero, null, messageSourceKind).refineLight(bot, refineContext)
  88. }
  89. private fun List<MsgComm.Msg>.toMessageChain(
  90. bot: Bot,
  91. groupIdOrZero: Long,
  92. onlineSource: Boolean?,
  93. messageSourceKind: MessageSourceKind,
  94. facade: MessageProtocolFacade = MessageProtocolFacade,
  95. ): MessageChain {
  96. try {
  97. return toMessageChainImpl(bot, groupIdOrZero, onlineSource, messageSourceKind, facade)
  98. } catch (e: Exception) {
  99. throw IllegalStateException(
  100. "Failed to transform internal message to facade message, msg=${[email protected]()}",
  101. e
  102. )
  103. }
  104. }
  105. private fun List<MsgComm.Msg>.toMessageChainImpl(
  106. bot: Bot,
  107. groupIdOrZero: Long,
  108. onlineSource: Boolean?,
  109. messageSourceKind: MessageSourceKind,
  110. facade: MessageProtocolFacade = MessageProtocolFacade,
  111. ): MessageChain {
  112. val messageList = this
  113. val builder = MessageChainBuilder(messageList.sumOf { it.msgBody.richText.elems.size })
  114. if (onlineSource != null) {
  115. builder.add(ReceiveMessageTransformer.createMessageSource(bot, onlineSource, messageSourceKind, messageList))
  116. }
  117. messageList.forEach { msg ->
  118. facade.decode(msg.msgBody.richText.elems, groupIdOrZero, messageSourceKind, bot, builder, msg)
  119. }
  120. for (msg in messageList) {
  121. msg.msgBody.richText.ptt?.toAudio()?.let { builder.add(it) }
  122. }
  123. return builder.build().cleanupRubbishMessageElements()
  124. }
  125. /**
  126. * 接收消息的解析器. 将 [MsgComm.Msg] 转换为对应的 [SingleMessage]
  127. * @see joinToMessageChain
  128. */
  129. internal object ReceiveMessageTransformer {
  130. fun createMessageSource(
  131. bot: Bot,
  132. onlineSource: Boolean,
  133. messageSourceKind: MessageSourceKind,
  134. messageList: List<MsgComm.Msg>,
  135. ): MessageSource {
  136. return when (onlineSource) {
  137. true -> {
  138. when (messageSourceKind) {
  139. MessageSourceKind.TEMP -> OnlineMessageSourceFromTempImpl(bot, messageList)
  140. MessageSourceKind.GROUP -> OnlineMessageSourceFromGroupImpl(bot, messageList)
  141. MessageSourceKind.FRIEND -> OnlineMessageSourceFromFriendImpl(bot, messageList)
  142. MessageSourceKind.STRANGER -> OnlineMessageSourceFromStrangerImpl(bot, messageList)
  143. }
  144. }
  145. false -> {
  146. OfflineMessageSourceImplData(bot, messageList, messageSourceKind)
  147. }
  148. }
  149. }
  150. fun MessageChainBuilder.compressContinuousPlainText() {
  151. var index = 0
  152. val builder = StringBuilder()
  153. while (index + 1 < size) {
  154. val elm0 = get(index)
  155. val elm1 = get(index + 1)
  156. if (elm0 is PlainText && elm1 is PlainText) {
  157. builder.setLength(0)
  158. var end = -1
  159. for (i in index until size) {
  160. val elm = get(i)
  161. if (elm is PlainText) {
  162. end = i
  163. builder.append(elm.content)
  164. } else break
  165. }
  166. set(index, PlainText(builder.toString()))
  167. // do delete
  168. val index1 = index + 1
  169. repeat(end - index) {
  170. removeAt(index1)
  171. }
  172. }
  173. index++
  174. }
  175. // delete empty plain text
  176. removeAll { it is PlainText && it.content.isEmpty() }
  177. }
  178. fun MessageChain.cleanupRubbishMessageElements(): MessageChain {
  179. val builder = MessageChainBuilder(initialSize = count()).also {
  180. it.addAll(this)
  181. }
  182. kotlin.run moveQuoteReply@{ // Move QuoteReply after MessageSource
  183. val exceptedQuoteReplyIndex = builder.indexOfFirst { it is MessageSource } + 1
  184. val quoteReplyIndex = builder.indexOfFirst { it is QuoteReply }
  185. if (quoteReplyIndex < 1) return@moveQuoteReply
  186. if (quoteReplyIndex != exceptedQuoteReplyIndex) {
  187. val qr = builder[quoteReplyIndex]
  188. builder.removeAt(quoteReplyIndex)
  189. builder.add(exceptedQuoteReplyIndex, qr)
  190. }
  191. }
  192. kotlin.run quote@{
  193. val quoteReplyIndex = builder.indexOfFirst { it is QuoteReply }
  194. if (quoteReplyIndex >= 0) {
  195. // QuoteReply + At + PlainText(space 1)
  196. if (quoteReplyIndex < builder.size - 1) {
  197. if (builder[quoteReplyIndex + 1] is At) {
  198. builder.removeAt(quoteReplyIndex + 1)
  199. }
  200. if (quoteReplyIndex < builder.size - 1) {
  201. val elm = builder[quoteReplyIndex + 1]
  202. if (elm is PlainText && elm.content.startsWith(' ')) {
  203. if (elm.content.length == 1) {
  204. builder.removeAt(quoteReplyIndex + 1)
  205. } else {
  206. builder[quoteReplyIndex + 1] = PlainText(elm.content.substring(1))
  207. }
  208. }
  209. }
  210. return@quote
  211. }
  212. }
  213. }
  214. // TIM audios
  215. if (builder.any { it is Audio }) {
  216. builder.remove(UNSUPPORTED_VOICE_MESSAGE_PLAIN)
  217. }
  218. kotlin.run { // VipFace
  219. val vipFaceIndex = builder.indexOfFirst { it is VipFace }
  220. if (vipFaceIndex >= 0 && vipFaceIndex < builder.size - 1) {
  221. val l = builder[vipFaceIndex] as VipFace
  222. val text = builder[vipFaceIndex + 1]
  223. if (text is PlainText) {
  224. if (text.content.length == 4 + (l.count / 10) + l.kind.name.length) {
  225. builder.removeAt(vipFaceIndex + 1)
  226. }
  227. }
  228. }
  229. }
  230. fun removeSuffixText(index: Int, text: PlainText) {
  231. if (index >= 0 && index < builder.size - 1) {
  232. if (builder[index + 1] == text) {
  233. builder.removeAt(index + 1)
  234. }
  235. }
  236. }
  237. removeSuffixText(builder.indexOfFirst { it is LongMessageInternal }, UNSUPPORTED_MERGED_MESSAGE_PLAIN)
  238. removeSuffixText(builder.indexOfFirst { it is PokeMessage }, UNSUPPORTED_POKE_MESSAGE_PLAIN)
  239. builder.compressContinuousPlainText()
  240. return builder.asMessageChain()
  241. }
  242. fun ImMsgBody.Ptt.toAudio() = OnlineAudioImpl(
  243. filename = fileName.decodeToString(),
  244. fileMd5 = fileMd5,
  245. fileSize = fileSize.toLongUnsigned(),
  246. codec = AudioCodec.fromId(format),
  247. url = downPara.decodeToString(),
  248. length = time.toLongUnsigned(),
  249. originalPtt = this,
  250. )
  251. }