ReceiveMessageHandler.kt 20 KB

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