MiraiImpl.kt 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991
  1. /*
  2. * Copyright 2019-2021 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/dev/LICENSE
  8. */
  9. package net.mamoe.mirai.internal
  10. import io.ktor.client.*
  11. import io.ktor.client.engine.okhttp.*
  12. import io.ktor.client.features.*
  13. import io.ktor.client.request.*
  14. import io.ktor.client.request.forms.*
  15. import io.ktor.http.*
  16. import io.ktor.utils.io.core.*
  17. import kotlinx.coroutines.SupervisorJob
  18. import kotlinx.coroutines.currentCoroutineContext
  19. import kotlinx.io.core.discardExact
  20. import kotlinx.io.core.readBytes
  21. import kotlinx.serialization.json.Json
  22. import kotlinx.serialization.json.JsonObject
  23. import kotlinx.serialization.json.jsonPrimitive
  24. import kotlinx.serialization.json.long
  25. import net.mamoe.mirai.*
  26. import net.mamoe.mirai.contact.*
  27. import net.mamoe.mirai.data.*
  28. import net.mamoe.mirai.event.Event
  29. import net.mamoe.mirai.event.broadcast
  30. import net.mamoe.mirai.event.events.*
  31. import net.mamoe.mirai.internal.contact.*
  32. import net.mamoe.mirai.internal.contact.info.FriendInfoImpl
  33. import net.mamoe.mirai.internal.contact.info.FriendInfoImpl.Companion.impl
  34. import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
  35. import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl.Companion.impl
  36. import net.mamoe.mirai.internal.message.*
  37. import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep
  38. import net.mamoe.mirai.internal.network.components.EventDispatcher
  39. import net.mamoe.mirai.internal.network.components.EventDispatcherScopeFlag
  40. import net.mamoe.mirai.internal.network.highway.*
  41. import net.mamoe.mirai.internal.network.protocol.data.jce.SvcDevLoginInfo
  42. import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
  43. import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg
  44. import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
  45. import net.mamoe.mirai.internal.network.protocol.data.proto.MsgTransmit
  46. import net.mamoe.mirai.internal.network.protocol.packet.chat.*
  47. import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
  48. import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
  49. import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
  50. import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
  51. import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard
  52. import net.mamoe.mirai.internal.network.psKey
  53. import net.mamoe.mirai.internal.network.sKey
  54. import net.mamoe.mirai.internal.utils.MiraiProtocolInternal
  55. import net.mamoe.mirai.internal.utils.crypto.TEA
  56. import net.mamoe.mirai.internal.utils.io.serialization.loadAs
  57. import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
  58. import net.mamoe.mirai.message.MessageSerializers
  59. import net.mamoe.mirai.message.action.Nudge
  60. import net.mamoe.mirai.message.data.*
  61. import net.mamoe.mirai.message.data.Image.Key.IMAGE_ID_REGEX
  62. import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_1
  63. import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_2
  64. import net.mamoe.mirai.utils.*
  65. import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
  66. import kotlin.math.absoluteValue
  67. import kotlin.random.Random
  68. internal fun getMiraiImpl() = Mirai as MiraiImpl
  69. @OptIn(LowLevelApi::class)
  70. // not object for ServiceLoader.
  71. internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
  72. companion object INSTANCE : MiraiImpl() {
  73. @Suppress("ObjectPropertyName", "unused", "DEPRECATION_ERROR")
  74. private val _init = Mirai.let {
  75. MessageSerializers.registerSerializer(OfflineGroupImage::class, OfflineGroupImage.serializer())
  76. MessageSerializers.registerSerializer(OfflineFriendImage::class, OfflineFriendImage.serializer())
  77. MessageSerializers.registerSerializer(OnlineFriendImageImpl::class, OnlineFriendImageImpl.serializer())
  78. MessageSerializers.registerSerializer(OnlineGroupImageImpl::class, OnlineGroupImageImpl.serializer())
  79. MessageSerializers.registerSerializer(MarketFaceImpl::class, MarketFaceImpl.serializer())
  80. MessageSerializers.registerSerializer(FileMessageImpl::class, FileMessageImpl.serializer())
  81. // MessageSource
  82. MessageSerializers.registerSerializer(
  83. OnlineMessageSourceFromGroupImpl::class,
  84. OnlineMessageSourceFromGroupImpl.serializer()
  85. )
  86. MessageSerializers.registerSerializer(
  87. OnlineMessageSourceFromFriendImpl::class,
  88. OnlineMessageSourceFromFriendImpl.serializer()
  89. )
  90. MessageSerializers.registerSerializer(
  91. OnlineMessageSourceFromTempImpl::class,
  92. OnlineMessageSourceFromTempImpl.serializer()
  93. )
  94. MessageSerializers.registerSerializer(
  95. OnlineMessageSourceFromStrangerImpl::class,
  96. OnlineMessageSourceFromStrangerImpl.serializer()
  97. )
  98. MessageSerializers.registerSerializer(
  99. OnlineMessageSourceToGroupImpl::class,
  100. OnlineMessageSourceToGroupImpl.serializer()
  101. )
  102. MessageSerializers.registerSerializer(
  103. OnlineMessageSourceToFriendImpl::class,
  104. OnlineMessageSourceToFriendImpl.serializer()
  105. )
  106. MessageSerializers.registerSerializer(
  107. OnlineMessageSourceToTempImpl::class,
  108. OnlineMessageSourceToTempImpl.serializer()
  109. )
  110. MessageSerializers.registerSerializer(
  111. OnlineMessageSourceToStrangerImpl::class,
  112. OnlineMessageSourceToStrangerImpl.serializer()
  113. )
  114. MessageSerializers.registerSerializer(
  115. OfflineMessageSourceImplData::class,
  116. OfflineMessageSourceImplData.serializer()
  117. )
  118. MessageSerializers.registerSerializer(
  119. OfflineMessageSourceImplData::class,
  120. OfflineMessageSourceImplData.serializer()
  121. )
  122. MessageSerializers.registerSerializer(
  123. UnsupportedMessageImpl::class,
  124. UnsupportedMessageImpl.serializer()
  125. )
  126. MessageSerializers.registerSerializer(
  127. OnlineAudioImpl::class,
  128. OnlineAudioImpl.serializer()
  129. )
  130. MessageSerializers.registerSerializer(
  131. OfflineAudioImpl::class,
  132. OfflineAudioImpl.serializer()
  133. )
  134. }
  135. }
  136. @Suppress("DEPRECATION")
  137. override val BotFactory: BotFactory
  138. get() = BotFactoryImpl
  139. override var FileCacheStrategy: FileCacheStrategy = net.mamoe.mirai.utils.FileCacheStrategy.PlatformDefault
  140. override var Http: HttpClient = HttpClient(OkHttp) {
  141. install(HttpTimeout) {
  142. this.requestTimeoutMillis = 30_0000
  143. this.connectTimeoutMillis = 30_0000
  144. this.socketTimeoutMillis = 30_0000
  145. }
  146. }
  147. @OptIn(LowLevelApi::class)
  148. override suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent) {
  149. @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
  150. check(event.responded.compareAndSet(false, true)) {
  151. "the request $this has already been responded"
  152. }
  153. check(!event.bot.friends.contains(event.fromId)) {
  154. "the request $event is outdated: You had already responded it on another device."
  155. }
  156. solveNewFriendRequestEvent(
  157. event.bot,
  158. eventId = event.eventId,
  159. fromId = event.fromId,
  160. fromNick = event.fromNick,
  161. accept = true,
  162. blackList = false
  163. )
  164. event.bot.getFriend(event.fromId)?.let { friend ->
  165. FriendAddEvent(friend).broadcast()
  166. }
  167. }
  168. override suspend fun refreshKeys(bot: Bot) {
  169. // TODO: 2021/4/14 MiraiImpl.refreshKeysNow
  170. }
  171. override suspend fun rejectNewFriendRequest(event: NewFriendRequestEvent, blackList: Boolean) {
  172. @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
  173. check(event.responded.compareAndSet(false, true)) {
  174. "the request $event has already been responded"
  175. }
  176. check(!event.bot.friends.contains(event.fromId)) {
  177. "the request $event is outdated: You had already responded it on another device."
  178. }
  179. solveNewFriendRequestEvent(
  180. event.bot,
  181. eventId = event.eventId,
  182. fromId = event.fromId,
  183. fromNick = event.fromNick,
  184. accept = false,
  185. blackList = blackList
  186. )
  187. }
  188. override suspend fun acceptMemberJoinRequest(event: MemberJoinRequestEvent) {
  189. @Suppress("DuplicatedCode")
  190. checkGroupPermission(event.bot, event.groupId) { event::class.simpleName ?: "<anonymous class>" }
  191. @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
  192. check(event.responded.compareAndSet(false, true)) {
  193. "the request $this has already been responded"
  194. }
  195. if (event.group?.contains(event.fromId) == true) return
  196. solveMemberJoinRequestEvent(
  197. bot = event.bot,
  198. eventId = event.eventId,
  199. fromId = event.fromId,
  200. fromNick = event.fromNick,
  201. groupId = event.groupId,
  202. accept = true,
  203. blackList = false
  204. )
  205. }
  206. @Suppress("DuplicatedCode")
  207. override suspend fun rejectMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean, message: String) {
  208. checkGroupPermission(event.bot, event.groupId) { event::class.simpleName ?: "<anonymous class>" }
  209. @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
  210. check(event.responded.compareAndSet(false, true)) {
  211. "the request $this has already been responded"
  212. }
  213. if (event.group?.contains(event.fromId) == true) return
  214. solveMemberJoinRequestEvent(
  215. bot = event.bot,
  216. eventId = event.eventId,
  217. fromId = event.fromId,
  218. fromNick = event.fromNick,
  219. groupId = event.groupId,
  220. accept = false,
  221. blackList = blackList,
  222. message = message
  223. )
  224. }
  225. private inline fun checkGroupPermission(eventBot: Bot, groupId: Long, eventName: () -> String) {
  226. val group = eventBot.getGroup(groupId)
  227. ?: kotlin.run {
  228. error(
  229. "A ${eventName()} is outdated. Group $groupId not found for bot ${eventBot.id}. " +
  230. "This is because bot isn't in the group anymore"
  231. )
  232. }
  233. group.checkBotPermission(MemberPermission.ADMINISTRATOR)
  234. }
  235. override suspend fun getOnlineOtherClientsList(bot: Bot, mayIncludeSelf: Boolean): List<OtherClientInfo> {
  236. bot.asQQAndroidBot()
  237. val response = bot.network.run {
  238. StatSvc.GetDevLoginInfo(bot.client).sendAndExpect()
  239. }
  240. fun SvcDevLoginInfo.toOtherClientInfo() = OtherClientInfo(
  241. iAppId.toInt(),
  242. Platform.getByTerminalId(iTerType?.toInt() ?: 0),
  243. deviceName.orEmpty(),
  244. deviceTypeInfo.orEmpty()
  245. )
  246. return response.deviceList.map { it.toOtherClientInfo() }.let { result ->
  247. if (mayIncludeSelf) result else result.filterNot {
  248. it.appId == MiraiProtocolInternal[bot.configuration.protocol].id.toInt()
  249. }
  250. }
  251. }
  252. override suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean) {
  253. checkGroupPermission(event.bot, event.groupId) { event::class.simpleName ?: "<anonymous class>" }
  254. @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
  255. check(event.responded.compareAndSet(false, true)) {
  256. "the request $this has already been responded"
  257. }
  258. solveMemberJoinRequestEvent(
  259. bot = event.bot,
  260. eventId = event.eventId,
  261. fromId = event.fromId,
  262. fromNick = event.fromNick,
  263. groupId = event.groupId,
  264. accept = null,
  265. blackList = blackList
  266. )
  267. }
  268. override suspend fun acceptInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) =
  269. solveInvitedJoinGroupRequest(event, accept = true)
  270. override suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) =
  271. solveInvitedJoinGroupRequest(event, accept = false)
  272. override suspend fun broadcastEvent(event: Event) {
  273. if (currentCoroutineContext()[EventDispatcherScopeFlag] != null) {
  274. // called by [EventDispatcher]
  275. return super.broadcastEvent(event)
  276. }
  277. if (event is BotEvent) {
  278. val bot = event.bot
  279. if (bot is QQAndroidBot) {
  280. bot.components[EventDispatcher].broadcast(event)
  281. }
  282. } else {
  283. super.broadcastEvent(event)
  284. }
  285. }
  286. private suspend fun solveInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent, accept: Boolean) {
  287. @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
  288. check(event.responded.compareAndSet(false, true)) {
  289. "the request $this has already been responded"
  290. }
  291. check(!event.bot.groups.contains(event.groupId)) {
  292. "the request $this is outdated: Bot has been already in the group."
  293. }
  294. solveBotInvitedJoinGroupRequestEvent(
  295. bot = event.bot,
  296. eventId = event.eventId,
  297. invitorId = event.invitorId,
  298. groupId = event.groupId,
  299. accept = accept
  300. )
  301. }
  302. @LowLevelApi
  303. override fun newFriend(bot: Bot, friendInfo: FriendInfo): FriendImpl {
  304. return FriendImpl(
  305. bot.asQQAndroidBot(),
  306. bot.coroutineContext,
  307. friendInfo.impl(),
  308. )
  309. }
  310. @LowLevelApi
  311. override fun newStranger(bot: Bot, strangerInfo: StrangerInfo): StrangerImpl {
  312. return StrangerImpl(
  313. bot.asQQAndroidBot(),
  314. bot.coroutineContext,
  315. strangerInfo.impl(),
  316. )
  317. }
  318. @OptIn(LowLevelApi::class)
  319. override suspend fun getRawGroupList(bot: Bot): Sequence<Long> {
  320. bot.asQQAndroidBot()
  321. return bot.network.run {
  322. FriendList.GetTroopListSimplify(bot.client).sendAndExpect(retry = 2)
  323. }.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode }
  324. }
  325. @OptIn(LowLevelApi::class)
  326. override suspend fun getRawGroupMemberList(
  327. bot: Bot,
  328. groupUin: Long,
  329. groupCode: Long,
  330. ownerId: Long
  331. ): Sequence<MemberInfo> =
  332. bot.asQQAndroidBot().network.run {
  333. var nextUin = 0L
  334. var sequence = sequenceOf<MemberInfoImpl>()
  335. while (true) {
  336. val data = FriendList.GetTroopMemberList(
  337. client = bot.client,
  338. targetGroupUin = groupUin,
  339. targetGroupCode = groupCode,
  340. nextUin = nextUin
  341. ).sendAndExpect(retry = 3)
  342. sequence += data.members.asSequence().map { troopMemberInfo ->
  343. MemberInfoImpl(bot.client, troopMemberInfo, ownerId)
  344. }
  345. nextUin = data.nextUin
  346. if (nextUin == 0L) {
  347. break
  348. }
  349. }
  350. return sequence
  351. }
  352. override suspend fun recallGroupMessageRaw(
  353. bot: Bot,
  354. groupCode: Long,
  355. messageIds: IntArray,
  356. messageInternalIds: IntArray,
  357. ): Boolean = bot.asQQAndroidBot().run {
  358. val response = network.run {
  359. PbMessageSvc.PbMsgWithDraw.createForGroupMessage(
  360. client,
  361. groupCode,
  362. messageIds,
  363. messageInternalIds
  364. ).sendAndExpect()
  365. }
  366. response is PbMessageSvc.PbMsgWithDraw.Response.Success
  367. }
  368. override suspend fun recallFriendMessageRaw(
  369. bot: Bot,
  370. targetId: Long,
  371. messageIds: IntArray,
  372. messageInternalIds: IntArray,
  373. time: Int,
  374. ): Boolean = bot.asQQAndroidBot().run {
  375. val response = network.run {
  376. PbMessageSvc.PbMsgWithDraw.createForFriendMessage(
  377. client,
  378. targetId,
  379. messageIds,
  380. messageInternalIds,
  381. time,
  382. ).sendAndExpect()
  383. }
  384. response is PbMessageSvc.PbMsgWithDraw.Response.Success
  385. }
  386. override suspend fun recallGroupTempMessageRaw(
  387. bot: Bot,
  388. groupUin: Long,
  389. targetId: Long,
  390. messageIds: IntArray,
  391. messageInternalIds: IntArray,
  392. time: Int
  393. ): Boolean = bot.asQQAndroidBot().run {
  394. val response = network.run {
  395. PbMessageSvc.PbMsgWithDraw.createForGroupTempMessage(
  396. client,
  397. groupUin,
  398. targetId,
  399. messageIds,
  400. messageInternalIds,
  401. time,
  402. ).sendAndExpect()
  403. }
  404. response is PbMessageSvc.PbMsgWithDraw.Response.Success
  405. }
  406. @Suppress("RemoveExplicitTypeArguments") // false positive
  407. override suspend fun recallMessage(bot: Bot, source: MessageSource) = bot.asQQAndroidBot().run {
  408. check(source is MessageSourceInternal)
  409. source.ensureSequenceIdAvailable()
  410. @Suppress("BooleanLiteralArgument", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") // false positive
  411. check(!source.isRecalledOrPlanned.get() && source.isRecalledOrPlanned.compareAndSet(false, true)) {
  412. "$source had already been recalled."
  413. }
  414. val response: PbMessageSvc.PbMsgWithDraw.Response = when (source) {
  415. is OnlineMessageSourceToGroupImpl,
  416. is OnlineMessageSourceFromGroupImpl
  417. -> {
  418. val group = when (source) {
  419. is OnlineMessageSourceToGroupImpl -> source.target
  420. is OnlineMessageSourceFromGroupImpl -> source.group
  421. else -> error("stub")
  422. }
  423. if (bot.id != source.fromId) {
  424. group.checkBotPermission(MemberPermission.ADMINISTRATOR)
  425. }
  426. network.run {
  427. PbMessageSvc.PbMsgWithDraw.createForGroupMessage(
  428. bot.asQQAndroidBot().client,
  429. group.id,
  430. source.sequenceIds,
  431. source.internalIds
  432. ).sendAndExpect()
  433. }
  434. }
  435. is OnlineMessageSourceFromFriendImpl,
  436. is OnlineMessageSourceToFriendImpl,
  437. is OnlineMessageSourceFromStrangerImpl,
  438. is OnlineMessageSourceToStrangerImpl,
  439. -> network.run {
  440. check(source.fromId == bot.id) {
  441. "can only recall a message sent by bot"
  442. }
  443. PbMessageSvc.PbMsgWithDraw.createForFriendMessage(
  444. bot.client,
  445. source.targetId,
  446. source.sequenceIds,
  447. source.internalIds,
  448. source.time
  449. ).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
  450. }
  451. is OnlineMessageSourceFromTempImpl,
  452. is OnlineMessageSourceToTempImpl
  453. -> network.run {
  454. check(source.fromId == bot.id) {
  455. "can only recall a message sent by bot"
  456. }
  457. source as OnlineMessageSourceToTempImpl
  458. PbMessageSvc.PbMsgWithDraw.createForGroupTempMessage(
  459. bot.client,
  460. (source.target.group as GroupImpl).uin,
  461. source.targetId,
  462. source.sequenceIds,
  463. source.internalIds,
  464. source.time
  465. ).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
  466. }
  467. is OfflineMessageSource -> network.run {
  468. when (source.kind) {
  469. MessageSourceKind.FRIEND, MessageSourceKind.STRANGER -> {
  470. check(source.fromId == bot.id) {
  471. "can only recall a message sent by bot"
  472. }
  473. PbMessageSvc.PbMsgWithDraw.createForFriendMessage(
  474. bot.client,
  475. source.targetId,
  476. source.sequenceIds,
  477. source.internalIds,
  478. source.time
  479. ).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
  480. }
  481. MessageSourceKind.TEMP -> {
  482. check(source.fromId == bot.id) {
  483. "can only recall a message sent by bot"
  484. }
  485. PbMessageSvc.PbMsgWithDraw.createForGroupTempMessage(
  486. bot.client,
  487. source.targetId, // groupUin
  488. source.targetId, // memberUin
  489. source.sequenceIds,
  490. source.internalIds,
  491. source.time
  492. ).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
  493. }
  494. MessageSourceKind.GROUP -> {
  495. PbMessageSvc.PbMsgWithDraw.createForGroupMessage(
  496. bot.client,
  497. source.targetId,
  498. source.sequenceIds,
  499. source.internalIds
  500. ).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
  501. }
  502. }
  503. }
  504. else -> error("stub!")
  505. }
  506. // 1001: No message meets the requirements (实际上是没权限, 管理员在尝试撤回群主的消息)
  507. // 154: timeout
  508. // 3: <no message>
  509. check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.ids.contentToString()}: $response" }
  510. }
  511. private val json = Json {
  512. isLenient = true
  513. ignoreUnknownKeys = true
  514. }
  515. @LowLevelApi
  516. @MiraiExperimentalApi
  517. override suspend fun getRawGroupActiveData(bot: Bot, groupId: Long, page: Int): GroupActiveData =
  518. bot.asQQAndroidBot().run {
  519. val rep = network.run {
  520. Mirai.Http.get<String> {
  521. url("https://qqweb.qq.com/c/activedata/get_mygroup_data")
  522. parameter("bkn", bkn)
  523. parameter("gc", groupId)
  524. if (page != -1) {
  525. parameter("page", page)
  526. }
  527. headers {
  528. append(
  529. "cookie",
  530. "uin=o${bot.id}; skey=${bot.sKey}; p_uin=o${bot.id};"
  531. )
  532. }
  533. }
  534. }
  535. return json.decodeFromString(GroupActiveData.serializer(), rep)
  536. }
  537. @LowLevelApi
  538. @MiraiExperimentalApi
  539. override suspend fun getRawGroupHonorListData(
  540. bot: Bot,
  541. groupId: Long,
  542. type: GroupHonorType
  543. ): GroupHonorListData? = bot.asQQAndroidBot().run {
  544. val rep = network.run {
  545. Mirai.Http.get<String> {
  546. url("https://qun.qq.com/interactive/honorlist")
  547. parameter("gc", groupId)
  548. parameter("type", type.value)
  549. headers {
  550. append(
  551. "cookie",
  552. "uin=o${bot.id};" +
  553. " skey=${bot.sKey};" +
  554. " p_uin=o${bot.id};" +
  555. " p_skey=${bot.psKey("qun.qq.com")}; "
  556. )
  557. }
  558. }
  559. }
  560. val jsonText = Regex("""window.__INITIAL_STATE__=(.+?)</script>""").find(rep)?.groupValues?.get(1)
  561. return jsonText?.let { json.decodeFromString(GroupHonorListData.serializer(), it) }
  562. }
  563. internal open suspend fun uploadMessageHighway(
  564. bot: Bot,
  565. sendMessageHandler: SendMessageHandler<*>,
  566. message: Collection<ForwardMessage.INode>,
  567. isLong: Boolean,
  568. ): String = with(bot.asQQAndroidBot()) {
  569. message.forEach {
  570. it.messageChain.ensureSequenceIdAvailable()
  571. }
  572. val data = message.calculateValidationData(
  573. client = client,
  574. random = Random.nextInt().absoluteValue,
  575. sendMessageHandler,
  576. isLong,
  577. )
  578. val response = network.run {
  579. MultiMsg.ApplyUp.createForGroup(
  580. buType = if (isLong) 1 else 2,
  581. client = bot.client,
  582. messageData = data,
  583. dstUin = sendMessageHandler.targetUin
  584. ).sendAndExpect()
  585. }
  586. val resId: String
  587. when (response) {
  588. is MultiMsg.ApplyUp.Response.MessageTooLarge ->
  589. error(
  590. "Internal error: message is too large, but this should be handled before sending. "
  591. )
  592. is MultiMsg.ApplyUp.Response.RequireUpload -> {
  593. resId = response.proto.msgResid
  594. val body = LongMsg.ReqBody(
  595. subcmd = 1,
  596. platformType = 9,
  597. termType = 5,
  598. msgUpReq = listOf(
  599. LongMsg.MsgUpReq(
  600. msgType = 3, // group
  601. dstUin = sendMessageHandler.targetUin,
  602. msgId = 0,
  603. msgUkey = response.proto.msgUkey,
  604. needCache = 0,
  605. storeType = 2,
  606. msgContent = data.data
  607. )
  608. )
  609. ).toByteArray(LongMsg.ReqBody.serializer())
  610. body.toExternalResource().use { resource ->
  611. Highway.uploadResourceBdh(
  612. bot = bot,
  613. resource = resource,
  614. kind = when (isLong) {
  615. true -> ResourceKind.LONG_MESSAGE
  616. false -> ResourceKind.FORWARD_MESSAGE
  617. },
  618. commandId = 27,
  619. initialTicket = response.proto.msgSig
  620. )
  621. }
  622. }
  623. }
  624. return resId
  625. }
  626. @LowLevelApi
  627. @MiraiExperimentalApi
  628. override suspend fun solveNewFriendRequestEvent(
  629. bot: Bot,
  630. eventId: Long,
  631. fromId: Long,
  632. fromNick: String,
  633. accept: Boolean,
  634. blackList: Boolean
  635. ): Unit = bot.asQQAndroidBot().run {
  636. network.apply {
  637. NewContact.SystemMsgNewFriend.Action(
  638. bot.client,
  639. eventId = eventId,
  640. fromId = fromId,
  641. accept = accept,
  642. blackList = blackList
  643. ).sendWithoutExpect()
  644. if (!accept) return@apply
  645. @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
  646. bot.friends.delegate.add(newFriend(bot, FriendInfoImpl(fromId, fromNick, "")))
  647. }
  648. }
  649. @LowLevelApi
  650. @MiraiExperimentalApi
  651. override suspend fun solveBotInvitedJoinGroupRequestEvent(
  652. bot: Bot,
  653. eventId: Long,
  654. invitorId: Long,
  655. groupId: Long,
  656. accept: Boolean
  657. ) = bot.asQQAndroidBot().run {
  658. network.run {
  659. NewContact.SystemMsgNewGroup.Action(
  660. bot.client,
  661. eventId = eventId,
  662. fromId = invitorId,
  663. groupId = groupId,
  664. isInvited = true,
  665. accept = accept
  666. ).sendWithoutExpect()
  667. }
  668. }
  669. @LowLevelApi
  670. @MiraiExperimentalApi
  671. override suspend fun solveMemberJoinRequestEvent(
  672. bot: Bot,
  673. eventId: Long,
  674. fromId: Long,
  675. fromNick: String,
  676. groupId: Long,
  677. accept: Boolean?,
  678. blackList: Boolean,
  679. message: String
  680. ) = bot.asQQAndroidBot().run {
  681. network.run {
  682. NewContact.SystemMsgNewGroup.Action(
  683. bot.client,
  684. eventId = eventId,
  685. fromId = fromId,
  686. groupId = groupId,
  687. isInvited = false,
  688. accept = accept,
  689. blackList = blackList,
  690. message = message
  691. ).sendWithoutExpect()
  692. }
  693. // Add member in MsgOnlinePush.PbPushMsg
  694. }
  695. @OptIn(ExperimentalStdlibApi::class)
  696. @LowLevelApi
  697. override suspend fun getGroupVoiceDownloadUrl(
  698. bot: Bot,
  699. md5: ByteArray,
  700. groupId: Long,
  701. dstUin: Long
  702. ): String {
  703. bot.asQQAndroidBot().network.run {
  704. val response = PttStore.GroupPttDown(bot.client, groupId, dstUin, md5).sendAndExpect()
  705. return "http://${response.strDomain}${response.downPara.encodeToString()}"
  706. }
  707. }
  708. override suspend fun muteAnonymousMember(
  709. bot: Bot,
  710. anonymousId: String,
  711. anonymousNick: String,
  712. groupId: Long,
  713. seconds: Int
  714. ) {
  715. bot as QQAndroidBot
  716. val response = Mirai.Http.post<String> {
  717. url("https://qqweb.qq.com/c/anonymoustalk/blacklist")
  718. body = MultiPartFormDataContent(formData {
  719. append("anony_id", anonymousId)
  720. append("group_code", groupId)
  721. append("seconds", seconds)
  722. append("anony_nick", anonymousNick)
  723. append("bkn", bot.bkn)
  724. })
  725. headers {
  726. append(
  727. "cookie",
  728. "uin=o${bot.id}; skey=${bot.sKey};"
  729. )
  730. }
  731. }
  732. val jsonObj = Json.decodeFromString(JsonObject.serializer(), response)
  733. if ((jsonObj["retcode"] ?: jsonObj["cgicode"] ?: error("missing response code")).jsonPrimitive.long != 0L) {
  734. throw IllegalStateException(response)
  735. }
  736. }
  737. override fun createImage(imageId: String): Image {
  738. return when {
  739. imageId matches IMAGE_ID_REGEX -> OfflineGroupImage(imageId)
  740. imageId matches IMAGE_RESOURCE_ID_REGEX_1 -> OfflineFriendImage(imageId)
  741. imageId matches IMAGE_RESOURCE_ID_REGEX_2 -> OfflineFriendImage(imageId)
  742. else ->
  743. @Suppress("INVISIBLE_MEMBER")
  744. throw IllegalArgumentException("Illegal imageId: $imageId. $ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE")
  745. }
  746. }
  747. override fun createFileMessage(id: String, internalId: Int, name: String, size: Long): FileMessage {
  748. return FileMessageImpl(id, internalId, name, size)
  749. }
  750. override fun createUnsupportedMessage(struct: ByteArray): UnsupportedMessage =
  751. UnsupportedMessageImpl(struct.loadAs(ImMsgBody.Elem.serializer()))
  752. @Suppress("DEPRECATION", "OverridingDeprecatedMember")
  753. override suspend fun queryImageUrl(bot: Bot, image: Image): String = when (image) {
  754. is ConstOriginUrlAware -> image.originUrl
  755. is DeferredOriginUrlAware -> image.getUrl(bot)
  756. is SuspendDeferredOriginUrlAware -> image.getUrl(bot)
  757. else -> error("Internal error: unsupported image class: ${image::class.simpleName}")
  758. }
  759. override suspend fun queryProfile(bot: Bot, targetId: Long): UserProfile {
  760. bot.asQQAndroidBot().network.apply {
  761. return SummaryCard.ReqSummaryCard(bot.client, targetId)
  762. .sendAndExpect()
  763. }
  764. }
  765. override suspend fun sendNudge(bot: Bot, nudge: Nudge, receiver: Contact): Boolean {
  766. if (bot.configuration.protocol != BotConfiguration.MiraiProtocol.ANDROID_PHONE) {
  767. throw UnsupportedOperationException("nudge is supported only with protocol ANDROID_PHONE")
  768. }
  769. bot.asQQAndroidBot()
  770. bot.network.run {
  771. return if (receiver is Group) {
  772. receiver.checkIsGroupImpl()
  773. NudgePacket.troopInvoke(
  774. client = bot.client,
  775. messageReceiverGroupCode = receiver.id,
  776. nudgeTargetId = nudge.target.id,
  777. ).sendAndExpect<NudgePacket.Response>().success
  778. } else {
  779. NudgePacket.friendInvoke(
  780. client = bot.client,
  781. messageReceiverUin = receiver.id,
  782. nudgeTargetId = nudge.target.id,
  783. ).sendAndExpect<NudgePacket.Response>().success
  784. }
  785. }
  786. }
  787. override fun getUin(contactOrBot: ContactOrBot): Long {
  788. return when (contactOrBot) {
  789. is Group -> contactOrBot.uin
  790. is User -> contactOrBot.uin
  791. is Bot -> contactOrBot.uin
  792. else -> contactOrBot.id
  793. }
  794. }
  795. override fun constructMessageSource(
  796. botId: Long,
  797. kind: MessageSourceKind,
  798. fromId: Long,
  799. targetId: Long,
  800. ids: IntArray,
  801. time: Int,
  802. internalIds: IntArray,
  803. originalMessage: MessageChain
  804. ): OfflineMessageSource = OfflineMessageSourceImplData(
  805. kind, ids, botId, time, fromId, targetId, originalMessage, internalIds
  806. )
  807. override suspend fun downloadLongMessage(bot: Bot, resourceId: String): MessageChain {
  808. return downloadMultiMsgTransmit(bot, resourceId, ResourceKind.LONG_MESSAGE).msg
  809. .toMessageChainNoSource(bot, 0, MessageSourceKind.GROUP)
  810. .refineDeep(bot)
  811. }
  812. override suspend fun downloadForwardMessage(bot: Bot, resourceId: String): List<ForwardMessage.Node> {
  813. return downloadMultiMsgTransmit(bot, resourceId, ResourceKind.FORWARD_MESSAGE).toForwardMessageNodes(bot)
  814. }
  815. internal open suspend fun MsgTransmit.PbMultiMsgNew.toForwardMessageNodes(
  816. bot: Bot,
  817. context: RefineContext
  818. ): List<ForwardMessage.Node> {
  819. return msg.map { it.toNode(bot, context) }
  820. }
  821. internal open suspend fun MsgTransmit.PbMultiMsgTransmit.toForwardMessageNodes(bot: Bot): List<ForwardMessage.Node> {
  822. val pbs = this.pbItemList.associate {
  823. it.fileName to it.buffer.loadAs(MsgTransmit.PbMultiMsgNew.serializer())
  824. }
  825. val main = pbs["MultiMsg"] ?: return this.msg.map { it.toNode(bot, EmptyRefineContext) }
  826. val context = SimpleRefineContext(mutableMapOf())
  827. context[ForwardMessageInternal.MsgTransmits] = pbs
  828. return main.toForwardMessageNodes(bot, context)
  829. }
  830. protected open suspend fun MsgComm.Msg.toNode(bot: Bot, refineContext: RefineContext): ForwardMessage.Node {
  831. val msg = this
  832. return ForwardMessage.Node(
  833. senderId = msg.msgHead.fromUin,
  834. time = msg.msgHead.msgTime,
  835. senderName = msg.msgHead.groupInfo?.groupCard
  836. ?: msg.msgHead.fromNick.takeIf { it.isNotEmpty() }
  837. ?: msg.msgHead.fromUin.toString(),
  838. messageChain = listOf(msg)
  839. .toMessageChainNoSource(bot, 0, MessageSourceKind.GROUP)
  840. .refineDeep(bot, refineContext)
  841. )
  842. }
  843. private suspend fun downloadMultiMsgTransmit(
  844. bot: Bot,
  845. resourceId: String,
  846. resourceKind: ResourceKind,
  847. ): MsgTransmit.PbMultiMsgTransmit {
  848. bot.asQQAndroidBot()
  849. when (val resp = MultiMsg.ApplyDown(bot.client, 2, resourceId, 1).sendAndExpect(bot)) {
  850. is MultiMsg.ApplyDown.Response.RequireDownload -> {
  851. val http = Mirai.Http
  852. val origin = resp.origin
  853. val data: ByteArray = if (origin.msgExternInfo?.channelType == 2) {
  854. tryDownload(
  855. bot = bot,
  856. host = "https://ssl.htdata.qq.com",
  857. port = 443,
  858. times = 3,
  859. resourceKind = resourceKind,
  860. channelKind = ChannelKind.HTTP
  861. ) { host, _ ->
  862. http.get("$host${origin.thumbDownPara}")
  863. }
  864. } else tryServersDownload(
  865. bot = bot,
  866. servers = origin.uint32DownIp.zip(origin.uint32DownPort),
  867. resourceKind = resourceKind,
  868. channelKind = ChannelKind.HTTP
  869. ) { ip, port ->
  870. http.get("http://$ip:$port${origin.thumbDownPara}")
  871. }
  872. val body = data.read {
  873. check(readByte() == 40.toByte()) {
  874. "bad data while MultiMsg.ApplyDown: ${data.toUHexString()}"
  875. }
  876. val headLength = readInt()
  877. val bodyLength = readInt()
  878. discardExact(headLength)
  879. readBytes(bodyLength)
  880. }
  881. val decrypted = TEA.decrypt(body, origin.msgKey)
  882. val longResp =
  883. decrypted.loadAs(LongMsg.RspBody.serializer())
  884. val down = longResp.msgDownRsp.single()
  885. check(down.result == 0) {
  886. "Message download failed, result=${down.result}, resId=${down.msgResid.encodeToString()}, msgContent=${down.msgContent.toUHexString()}"
  887. }
  888. val content = down.msgContent.ungzip()
  889. return content.loadAs(MsgTransmit.PbMultiMsgTransmit.serializer())
  890. }
  891. MultiMsg.ApplyDown.Response.MessageTooLarge -> {
  892. error("Message is too large and cannot download")
  893. }
  894. }
  895. }
  896. }