ReceiveMessageHandler.kt 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  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 kotlinx.io.core.discardExact
  11. import kotlinx.io.core.readUInt
  12. import kotlinx.io.core.readUShort
  13. import kotlinx.serialization.json.Json
  14. import net.mamoe.mirai.Bot
  15. import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep
  16. import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight
  17. import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements
  18. import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.joinToMessageChain
  19. import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.toAudio
  20. import net.mamoe.mirai.internal.network.protocol.data.proto.*
  21. import net.mamoe.mirai.internal.utils.io.serialization.loadAs
  22. import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
  23. import net.mamoe.mirai.message.data.*
  24. import net.mamoe.mirai.utils.*
  25. /**
  26. * 只在手动构造 [OfflineMessageSource] 时调用
  27. */
  28. internal fun ImMsgBody.SourceMsg.toMessageChainNoSource(
  29. bot: Bot,
  30. messageSourceKind: MessageSourceKind,
  31. groupIdOrZero: Long,
  32. refineContext: RefineContext = EmptyRefineContext,
  33. ): MessageChain {
  34. val elements = this.elems
  35. return buildMessageChain(elements.size + 1) {
  36. joinToMessageChain(elements, groupIdOrZero, messageSourceKind, bot, this)
  37. }.cleanupRubbishMessageElements().refineLight(bot, refineContext)
  38. }
  39. internal suspend fun List<MsgComm.Msg>.toMessageChainOnline(
  40. bot: Bot,
  41. groupIdOrZero: Long,
  42. messageSourceKind: MessageSourceKind,
  43. refineContext: RefineContext = EmptyRefineContext,
  44. ): MessageChain {
  45. return toMessageChain(bot, groupIdOrZero, true, messageSourceKind).refineDeep(bot, refineContext)
  46. }
  47. internal suspend fun MsgComm.Msg.toMessageChainOnline(
  48. bot: Bot,
  49. refineContext: RefineContext = EmptyRefineContext,
  50. ): MessageChain {
  51. fun getSourceKind(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 -> error("Could not get source kind from c2cCmd: $c2cCmd")
  57. }
  58. }
  59. val kind = getSourceKind(msgHead.c2cCmd)
  60. val groupId = when (kind) {
  61. MessageSourceKind.GROUP -> msgHead.groupInfo?.groupCode ?: 0
  62. else -> 0
  63. }
  64. return listOf(this).toMessageChainOnline(bot, groupId, kind, refineContext)
  65. }
  66. //internal fun List<MsgComm.Msg>.toMessageChainOffline(
  67. // bot: Bot,
  68. // groupIdOrZero: Long,
  69. // messageSourceKind: MessageSourceKind
  70. //): MessageChain {
  71. // return toMessageChain(bot, groupIdOrZero, false, messageSourceKind).refineLight(bot)
  72. //}
  73. internal fun List<MsgComm.Msg>.toMessageChainNoSource(
  74. bot: Bot,
  75. groupIdOrZero: Long,
  76. messageSourceKind: MessageSourceKind,
  77. refineContext: RefineContext = EmptyRefineContext,
  78. ): MessageChain {
  79. return toMessageChain(bot, groupIdOrZero, null, messageSourceKind).refineLight(bot, refineContext)
  80. }
  81. private fun List<MsgComm.Msg>.toMessageChain(
  82. bot: Bot,
  83. groupIdOrZero: Long,
  84. onlineSource: Boolean?,
  85. messageSourceKind: MessageSourceKind,
  86. ): MessageChain {
  87. val messageList = this
  88. val elements = messageList.flatMap { it.msgBody.richText.elems }
  89. val builder = MessageChainBuilder(elements.size)
  90. if (onlineSource != null) {
  91. builder.add(ReceiveMessageTransformer.createMessageSource(bot, onlineSource, messageSourceKind, messageList))
  92. }
  93. joinToMessageChain(elements, groupIdOrZero, messageSourceKind, bot, builder)
  94. for (msg in messageList) {
  95. msg.msgBody.richText.ptt?.toAudio()?.let { builder.add(it) }
  96. }
  97. return builder.build().cleanupRubbishMessageElements()
  98. }
  99. /**
  100. * 接收消息的解析器. 将 [MsgComm.Msg] 转换为对应的 [SingleMessage]
  101. * @see joinToMessageChain
  102. */
  103. internal object ReceiveMessageTransformer {
  104. fun createMessageSource(
  105. bot: Bot,
  106. onlineSource: Boolean,
  107. messageSourceKind: MessageSourceKind,
  108. messageList: List<MsgComm.Msg>,
  109. ): MessageSource {
  110. return when (onlineSource) {
  111. true -> {
  112. when (messageSourceKind) {
  113. MessageSourceKind.TEMP -> OnlineMessageSourceFromTempImpl(bot, messageList)
  114. MessageSourceKind.GROUP -> OnlineMessageSourceFromGroupImpl(bot, messageList)
  115. MessageSourceKind.FRIEND -> OnlineMessageSourceFromFriendImpl(bot, messageList)
  116. MessageSourceKind.STRANGER -> OnlineMessageSourceFromStrangerImpl(bot, messageList)
  117. }
  118. }
  119. false -> {
  120. OfflineMessageSourceImplData(bot, messageList, messageSourceKind)
  121. }
  122. }
  123. }
  124. fun joinToMessageChain(
  125. elements: List<ImMsgBody.Elem>,
  126. groupIdOrZero: Long,
  127. messageSourceKind: MessageSourceKind,
  128. bot: Bot,
  129. builder: MessageChainBuilder,
  130. ) {
  131. // ProtoBuf.encodeToHexString(elements).soutv("join")
  132. // (this._miraiContentToString().soutv())
  133. for (element in elements) {
  134. transformElement(element, groupIdOrZero, messageSourceKind, bot, builder)
  135. when {
  136. element.richMsg != null -> decodeRichMessage(element.richMsg, builder)
  137. }
  138. }
  139. }
  140. private fun transformElement(
  141. element: ImMsgBody.Elem,
  142. groupIdOrZero: Long,
  143. messageSourceKind: MessageSourceKind,
  144. bot: Bot,
  145. builder: MessageChainBuilder,
  146. ) {
  147. when {
  148. element.srcMsg != null -> decodeSrcMsg(element.srcMsg, builder, bot, messageSourceKind, groupIdOrZero)
  149. element.notOnlineImage != null -> builder.add(OnlineFriendImageImpl(element.notOnlineImage))
  150. element.customFace != null -> decodeCustomFace(element.customFace, builder)
  151. element.face != null -> builder.add(Face(element.face.index))
  152. element.text != null -> decodeText(element.text, builder)
  153. element.marketFace != null -> builder.add(MarketFaceInternal(element.marketFace))
  154. element.lightApp != null -> decodeLightApp(element.lightApp, builder)
  155. element.customElem != null -> decodeCustomElem(element.customElem, builder)
  156. element.commonElem != null -> decodeCommonElem(element.commonElem, builder)
  157. element.transElemInfo != null -> decodeTransElem(element.transElemInfo, builder)
  158. element.elemFlags2 != null
  159. || element.extraInfo != null
  160. || element.generalFlags != null
  161. || element.anonGroupMsg != null
  162. -> {
  163. // ignore
  164. }
  165. else -> {
  166. UnsupportedMessageImpl(element).takeIf {
  167. it.struct.isNotEmpty()
  168. }?.let(builder::add)
  169. // println(it._miraiContentToString())
  170. }
  171. }
  172. }
  173. fun MessageChainBuilder.compressContinuousPlainText() {
  174. var index = 0
  175. val builder = StringBuilder()
  176. while (index + 1 < size) {
  177. val elm0 = get(index)
  178. val elm1 = get(index + 1)
  179. if (elm0 is PlainText && elm1 is PlainText) {
  180. builder.setLength(0)
  181. var end = -1
  182. for (i in index until size) {
  183. val elm = get(i)
  184. if (elm is PlainText) {
  185. end = i
  186. builder.append(elm.content)
  187. } else break
  188. }
  189. set(index, PlainText(builder.toString()))
  190. // do delete
  191. val index1 = index + 1
  192. repeat(end - index) {
  193. removeAt(index1)
  194. }
  195. }
  196. index++
  197. }
  198. }
  199. fun MessageChain.cleanupRubbishMessageElements(): MessageChain {
  200. var previousLast: SingleMessage? = null
  201. var last: SingleMessage? = null
  202. return buildMessageChain(initialSize = this.count()) {
  203. this@cleanupRubbishMessageElements.forEach { element ->
  204. @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
  205. if (last is LongMessageInternal && element is PlainText) {
  206. if (element == UNSUPPORTED_MERGED_MESSAGE_PLAIN) {
  207. previousLast = last
  208. last = element
  209. return@forEach
  210. }
  211. }
  212. if (last is PokeMessage && element is PlainText) {
  213. if (element == UNSUPPORTED_POKE_MESSAGE_PLAIN) {
  214. previousLast = last
  215. last = element
  216. return@forEach
  217. }
  218. }
  219. if (last is VipFace && element is PlainText) {
  220. val l = last as VipFace
  221. if (element.content.length == 4 + (l.count / 10) + l.kind.name.length) {
  222. previousLast = last
  223. last = element
  224. return@forEach
  225. }
  226. }
  227. // 解决tim发送的语音无法正常识别
  228. if (element is PlainText) {
  229. if (element == UNSUPPORTED_VOICE_MESSAGE_PLAIN) {
  230. previousLast = last
  231. last = element
  232. return@forEach
  233. }
  234. }
  235. if (element is PlainText && last is At && previousLast is QuoteReply
  236. && element.content.startsWith(' ')
  237. ) {
  238. // Android QQ 发送, 是 Quote+At+PlainText(" xxx") // 首空格
  239. removeLastOrNull() // At
  240. val new = PlainText(element.content.substring(1))
  241. add(new)
  242. previousLast = null
  243. last = new
  244. return@forEach
  245. }
  246. if (element is QuoteReply) {
  247. // 客户端为兼容早期不支持 QuoteReply 的客户端而添加的 At
  248. removeLastOrNull()?.let { rm ->
  249. if ((rm as? PlainText)?.content != " ") add(rm)
  250. else removeLastOrNull()?.let { rm2 ->
  251. if (rm2 !is At) add(rm2)
  252. }
  253. }
  254. }
  255. append(element)
  256. previousLast = last
  257. last = element
  258. }
  259. // 处理分片信息
  260. compressContinuousPlainText()
  261. }
  262. }
  263. private fun decodeText(text: ImMsgBody.Text, list: MessageChainBuilder) {
  264. if (text.attr6Buf.isEmpty()) {
  265. list.add(PlainText(text.str))
  266. } else {
  267. val id: Long
  268. text.attr6Buf.read {
  269. discardExact(7)
  270. id = readUInt().toLong()
  271. }
  272. if (id == 0L) {
  273. list.add(AtAll)
  274. } else {
  275. list.add(At(id)) // element.text.str
  276. }
  277. }
  278. }
  279. private fun decodeSrcMsg(
  280. srcMsg: ImMsgBody.SourceMsg,
  281. list: MessageChainBuilder,
  282. bot: Bot,
  283. messageSourceKind: MessageSourceKind,
  284. groupIdOrZero: Long,
  285. ) {
  286. list.add(QuoteReply(OfflineMessageSourceImplData(srcMsg, bot, messageSourceKind, groupIdOrZero)))
  287. }
  288. private fun decodeCustomFace(
  289. customFace: ImMsgBody.CustomFace,
  290. builder: MessageChainBuilder,
  291. ) {
  292. builder.add(OnlineGroupImageImpl(customFace))
  293. customFace.pbReserve.let {
  294. if (it.isNotEmpty() && it.loadAs(CustomFace.ResvAttr.serializer()).msgImageShow != null) {
  295. builder.add(ShowImageFlag)
  296. }
  297. }
  298. }
  299. private fun decodeLightApp(
  300. lightApp: ImMsgBody.LightAppElem,
  301. list: MessageChainBuilder,
  302. ) {
  303. val content = runWithBugReport("解析 lightApp",
  304. { "resId=" + lightApp.msgResid + "data=" + lightApp.data.toUHexString() }) {
  305. when (lightApp.data[0].toInt()) {
  306. 0 -> lightApp.data.encodeToString(offset = 1)
  307. 1 -> lightApp.data.unzip(1).encodeToString()
  308. else -> error("unknown compression flag=${lightApp.data[0]}")
  309. }
  310. }
  311. list.add(LightAppInternal(content))
  312. }
  313. private fun decodeCustomElem(
  314. customElem: ImMsgBody.CustomElem,
  315. list: MessageChainBuilder,
  316. ) {
  317. customElem.data.read {
  318. kotlin.runCatching {
  319. CustomMessage.load(this)
  320. }.fold(
  321. onFailure = {
  322. if (it is CustomMessage.Companion.CustomMessageFullDataDeserializeInternalException) {
  323. throw IllegalStateException(
  324. "Internal error: " +
  325. "exception while deserializing CustomMessage head data," +
  326. " data=${customElem.data.toUHexString()}", it
  327. )
  328. } else {
  329. it as CustomMessage.Companion.CustomMessageFullDataDeserializeUserException
  330. throw IllegalStateException(
  331. "User error: " +
  332. "exception while deserializing CustomMessage body," +
  333. " body=${it.body.toUHexString()}", it
  334. )
  335. }
  336. },
  337. onSuccess = {
  338. if (it != null) {
  339. list.add(it)
  340. }
  341. }
  342. )
  343. }
  344. }
  345. private fun decodeTransElem(
  346. transElement: ImMsgBody.TransElem,
  347. list: MessageChainBuilder,
  348. ) {
  349. // file
  350. // type=24
  351. when (transElement.elemType) {
  352. 24 -> transElement.elemValue.read {
  353. // group file feed
  354. // 01 00 77 08 06 12 0A 61 61 61 61 61 61 2E 74 78 74 1A 06 31 35 42 79 74 65 3A 5F 12 5D 08 66 12 25 2F 64 37 34 62 62 66 33 61 2D 37 62 32 35 2D 31 31 65 62 2D 38 34 66 38 2D 35 34 35 32 30 30 37 62 35 64 39 66 18 0F 22 0A 61 61 61 61 61 61 2E 74 78 74 28 00 3A 00 42 20 61 33 32 35 66 36 33 34 33 30 65 37 61 30 31 31 66 37 64 30 38 37 66 63 33 32 34 37 35 34 39 63
  355. // fun getFileRsrvAttr(file: ObjMsg.MsgContentInfo.MsgFile): HummerResv21.ResvAttr? {
  356. // if (file.ext.isEmpty()) return null
  357. // val element = kotlin.runCatching {
  358. // jsonForFileDecode.parseToJsonElement(file.ext) as? JsonObject
  359. // }.getOrNull() ?: return null
  360. // val extInfo = element["ExtInfo"]?.toString()?.decodeBase64() ?: return null
  361. // return extInfo.loadAs(HummerResv21.ResvAttr.serializer())
  362. // }
  363. val var7 = readByte()
  364. if (var7 == 1.toByte()) {
  365. while (remaining > 2) {
  366. val proto = readProtoBuf(ObjMsg.ObjMsg.serializer(), readUShort().toInt())
  367. // proto.msgType=6
  368. val file = proto.msgContentInfo.firstOrNull()?.msgFile ?: continue // officially get(0) only.
  369. // val attr = getFileRsrvAttr(file) ?: continue
  370. // val info = attr.forwardExtFileInfo ?: continue
  371. list.add(
  372. FileMessageImpl(
  373. id = file.filePath,
  374. busId = file.busId, // path i.e. /a99e95fa-7b2d-11eb-adae-5452007b698a
  375. name = file.fileName,
  376. size = file.fileSize
  377. )
  378. )
  379. }
  380. }
  381. }
  382. }
  383. }
  384. private val jsonForFileDecode = Json {
  385. isLenient = true
  386. coerceInputValues = true
  387. }
  388. private fun decodeCommonElem(
  389. commonElem: ImMsgBody.CommonElem,
  390. list: MessageChainBuilder,
  391. ) {
  392. when (commonElem.serviceType) {
  393. 23 -> {
  394. val proto =
  395. commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype23.serializer())
  396. list.add(VipFace(VipFace.Kind(proto.faceType, proto.faceSummary), proto.faceBubbleCount))
  397. }
  398. 2 -> {
  399. val proto =
  400. commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype2.serializer())
  401. list.add(PokeMessage(
  402. proto.vaspokeName.takeIf { it.isNotEmpty() }
  403. ?: PokeMessage.values.firstOrNull { it.id == proto.vaspokeId && it.pokeType == proto.pokeType }?.name
  404. .orEmpty(),
  405. proto.pokeType,
  406. proto.vaspokeId
  407. )
  408. )
  409. }
  410. 3 -> {
  411. val proto =
  412. commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype3.serializer())
  413. if (proto.flashTroopPic != null) {
  414. list.add(FlashImage(OnlineGroupImageImpl(proto.flashTroopPic)))
  415. }
  416. if (proto.flashC2cPic != null) {
  417. list.add(FlashImage(OnlineFriendImageImpl(proto.flashC2cPic)))
  418. }
  419. }
  420. 33 -> {
  421. val proto =
  422. commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype33.serializer())
  423. list.add(Face(proto.index))
  424. }
  425. }
  426. }
  427. private fun decodeRichMessage(
  428. richMsg: ImMsgBody.RichMsg,
  429. builder: MessageChainBuilder,
  430. ) {
  431. val content = runWithBugReport("解析 richMsg", { richMsg.template1.toUHexString() }) {
  432. when (richMsg.template1[0].toInt()) {
  433. 0 -> richMsg.template1.encodeToString(offset = 1)
  434. 1 -> richMsg.template1.unzip(1).encodeToString()
  435. else -> error("unknown compression flag=${richMsg.template1[0]}")
  436. }
  437. }
  438. when (richMsg.serviceId) {
  439. // 5: 使用微博长图转换功能分享到QQ群
  440. /*
  441. <?xml version="1.0" encoding="utf-8"?><msg serviceID="5" templateID="12345" brief="[分享]想要沐浴阳光,就别钻进
  442. 阴影。 ???" ><item layout="0"><image uuid="{E5F68BD5-05F8-148B-9DA7-FECD026D30AD}.jpg" md5="E5F68BD505F8148B9DA7FECD026D
  443. 30AD" GroupFiledid="2167263882" minWidth="120" minHeight="120" maxWidth="180" maxHeight="180" /></item><source name="新
  444. 浪微博" icon="http://i.gtimg.cn/open/app_icon/00/73/69/03//100736903_100_m.png" appid="100736903" action="" i_actionData
  445. ="" a_actionData="" url=""/></msg>
  446. */
  447. /**
  448. * json?
  449. */
  450. 1 -> @Suppress("DEPRECATION_ERROR")
  451. builder.add(SimpleServiceMessage(1, content))
  452. /**
  453. * [LongMessageInternal], [ForwardMessage]
  454. */
  455. 35 -> {
  456. fun findStringProperty(name: String): String {
  457. return content.substringAfter("$name=\"", "").substringBefore("\"", "")
  458. }
  459. val resId = findStringProperty("m_resid")
  460. val msg = if (resId.isEmpty()) {
  461. // Nested ForwardMessage
  462. val fileName = findStringProperty("m_fileName")
  463. if (fileName.isNotEmpty() && findStringProperty("action") == "viewMultiMsg") {
  464. ForwardMessageInternal(content, null, fileName)
  465. } else {
  466. SimpleServiceMessage(35, content)
  467. }
  468. } else when (findStringProperty("multiMsgFlag").toIntOrNull()) {
  469. 1 -> LongMessageInternal(content, resId)
  470. 0 -> ForwardMessageInternal(content, resId, null)
  471. else -> {
  472. // from PC QQ
  473. if (findStringProperty("action") == "viewMultiMsg") {
  474. ForwardMessageInternal(content, resId, null)
  475. } else {
  476. SimpleServiceMessage(35, content)
  477. }
  478. }
  479. }
  480. builder.add(msg)
  481. }
  482. // 104 新群员入群的消息
  483. else -> {
  484. builder.add(SimpleServiceMessage(richMsg.serviceId, content))
  485. }
  486. }
  487. }
  488. fun ImMsgBody.Ptt.toAudio() = OnlineAudioImpl(
  489. filename = fileName.encodeToString(),
  490. fileMd5 = fileMd5,
  491. fileSize = fileSize.toLongUnsigned(),
  492. codec = AudioCodec.fromId(format),
  493. url = downPara.encodeToString(),
  494. length = time.toLongUnsigned(),
  495. originalPtt = this,
  496. )
  497. }