mirror of
https://github.com/mamoe/mirai.git
synced 2024-12-31 05:59:18 +08:00
Merge branch 'dev'
This commit is contained in:
commit
9eed2b94ae
30
CHANGELOG.md
30
CHANGELOG.md
@ -1,5 +1,35 @@
|
||||
# Version 1.x
|
||||
|
||||
## `1.3.0` 2020/9/16
|
||||
|
||||
### 新特性
|
||||
|
||||
- 支持群恢复相关事件: `MemberJoinEvent.Retrieve`, `BotJoinGroupEvent.Retrieve` (#531 by @Karlatemp)
|
||||
- 群荣耀获取 (`Bot._lowLevelGetGroupHonorListData`) (#501 by @yyuueexxiinngg)
|
||||
- 戳一戳事件: `FriendNudgedEvent`, `MemberNudgedEvent`, `BotNudgedEvent`.(#600 by @sandtechnology)
|
||||
- 发送戳一戳: `Bot.nudge()`, `User.nudge()`
|
||||
- 为 `BotFactory` 添加伴生对象. 在顶层方法不方便使用时可使用伴生对象的 `Bot` 构建方法
|
||||
|
||||
### 优化和修复
|
||||
- **修复好友消息和事件同步相关问题: **
|
||||
- 部分情况下无法同步好友消息 (#249)
|
||||
- BotInvitedJoinGroupRequestEvent 重复执行两次 (#449)
|
||||
- 群消息可能发送失败 (#527)
|
||||
- 机器人启动后第二次被拉入群聊不会刷新群列表 (#580)
|
||||
- 新群员入群事件只触发一次 (#590)
|
||||
- 入群申请 MemberJoinRequestEvent 没有广播 (#542)
|
||||
- 新成员入群未处理 (#567)
|
||||
- 修复 `At` 之后的多余空格的问题 (#557)
|
||||
- 修复 `QuoteReply` 前多余 `At` 和空格的问题 (#524)
|
||||
- 修复群信息初始值 (`isMuteAll`, `isAllowMemberInvite`, ...) (#286)
|
||||
- 修复 `Voice.url` 的域名缺失问题 (#584 by @Hieuzest)
|
||||
- 修复日志颜色污染的问题 (#596 by @Karlatemp)
|
||||
- 修复 `Bot.nick` 无法获取的问题 (#566)
|
||||
- 修复登录时 `IndexOutOfBoundsException` 的问题 (#598)
|
||||
- 修复 Bot 被踢出群收到的事件类型错误的问题 (#358) (#509 by @sandtechnology)
|
||||
- 修复 `Bot.close` 后必须收到一个数据包才会关闭的问题 (#557)
|
||||
- 优化 `PermissionDeniedException` 的消息内容
|
||||
|
||||
## `1.2.3` 2020/9/11
|
||||
- 在同步事件失败时添加重试, 改善 #249, #482, #542, #567, #590
|
||||
- 修复不断重连同一个服务器的问题 (#589)
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
object Versions {
|
||||
object Mirai {
|
||||
const val version = "1.2.3"
|
||||
const val version = "1.3.0"
|
||||
}
|
||||
|
||||
object Kotlin {
|
||||
|
@ -27,6 +27,7 @@ import net.mamoe.mirai.event.subscribeAlways
|
||||
import net.mamoe.mirai.network.ForceOfflineException
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.qqandroid.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.qqandroid.network.DefaultServerList
|
||||
import net.mamoe.mirai.qqandroid.network.closeAndJoin
|
||||
import net.mamoe.mirai.supervisorJob
|
||||
import net.mamoe.mirai.utils.*
|
||||
@ -91,7 +92,11 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
}
|
||||
bot.logger.info { "Connection lost, retrying login" }
|
||||
|
||||
bot.asQQAndroidBot().client.serverList.removeAt(0)
|
||||
bot.asQQAndroidBot().client.run {
|
||||
if (serverList.isEmpty()) {
|
||||
serverList.addAll(DefaultServerList)
|
||||
} else serverList.removeAt(0)
|
||||
}
|
||||
|
||||
var failed = false
|
||||
val time = measureTime {
|
||||
|
@ -32,6 +32,7 @@ import net.mamoe.mirai.event.events.NewFriendRequestEvent
|
||||
import net.mamoe.mirai.event.internal.MiraiAtomicBoolean
|
||||
import net.mamoe.mirai.getGroupOrNull
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.action.Nudge
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.qqandroid.contact.FriendImpl
|
||||
@ -42,6 +43,7 @@ import net.mamoe.mirai.qqandroid.message.*
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopNum
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.LongMsg
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.*
|
||||
@ -226,6 +228,30 @@ internal class QQAndroidBot constructor(
|
||||
accept = accept
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER")
|
||||
override suspend fun sendNudge(nudge: Nudge, receiver: Contact): Boolean {
|
||||
if (configuration.protocol != BotConfiguration.MiraiProtocol.ANDROID_PHONE) {
|
||||
throw UnsupportedOperationException("nudge is supported only with protocol ANDROID_PHONE")
|
||||
}
|
||||
|
||||
network.run {
|
||||
return if (receiver is Group) {
|
||||
receiver.checkIsGroupImpl()
|
||||
NudgePacket.troopInvoke(
|
||||
client = client,
|
||||
messageReceiverGroupCode = receiver.id,
|
||||
nudgeTargetId = nudge.target.id,
|
||||
).sendAndExpect<NudgePacket.Response>().success
|
||||
} else {
|
||||
NudgePacket.friendInvoke(
|
||||
client = client,
|
||||
messageReceiverUin = receiver.id,
|
||||
nudgeTargetId = nudge.target.id,
|
||||
).sendAndExpect<NudgePacket.Response>().success
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -250,10 +276,15 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
|
||||
override val friends: ContactList<Friend> = ContactList(LockFreeLinkedList())
|
||||
|
||||
@JvmField internal var cachedNick: String? = null
|
||||
override val nick: String get() = cachedNick ?: selfInfo.nick.also { cachedNick = it }
|
||||
override lateinit var nick: String
|
||||
|
||||
internal lateinit var selfInfo: JceFriendInfo
|
||||
internal var selfInfo: JceFriendInfo? = null
|
||||
get() = field ?: error("selfInfo is not yet initialized")
|
||||
set(it) {
|
||||
checkNotNull(it)
|
||||
field = it
|
||||
nick = it.nick
|
||||
}
|
||||
|
||||
override val selfQQ: Friend by lazy {
|
||||
@OptIn(LowLevelAPI::class)
|
||||
@ -311,12 +342,22 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
}.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode }
|
||||
}
|
||||
|
||||
@Suppress(
|
||||
"DeprecatedCallableAddReplaceWith",
|
||||
"FunctionName",
|
||||
"RedundantSuspendModifier",
|
||||
"unused",
|
||||
"unused_parameter"
|
||||
)
|
||||
@Deprecated("")
|
||||
@OptIn(LowLevelAPI::class)
|
||||
override suspend fun _lowLevelQueryGroupInfo(groupCode: Long): GroupInfo = network.run {
|
||||
suspend fun _lowLevelQueryGroupInfo(groupCode: Long, stTroopNum: StTroopNum): GroupInfo = network.run {
|
||||
error("This should not be invoked")
|
||||
/*
|
||||
TroopManagement.GetGroupInfo(
|
||||
client = bot.client,
|
||||
groupCode = groupCode
|
||||
).sendAndExpect<GroupInfoImpl>(retry = 3)
|
||||
).sendAndExpect<GroupInfoImpl>(retry = 3).also { it.stTroopNum = stTroopNum }*/
|
||||
}
|
||||
|
||||
@OptIn(LowLevelAPI::class)
|
||||
@ -608,6 +649,30 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
return json.decodeFromString(GroupActiveData.serializer(), rep)
|
||||
}
|
||||
|
||||
@LowLevelAPI
|
||||
@MiraiExperimentalAPI
|
||||
override suspend fun _lowLevelGetGroupHonorListData(groupId: Long, type: GroupHonorType): GroupHonorListData? {
|
||||
val data = network.async {
|
||||
MiraiPlatformUtils.Http.get<String> {
|
||||
url("https://qun.qq.com/interactive/honorlist")
|
||||
parameter("gc", groupId)
|
||||
parameter("type", type.value)
|
||||
headers {
|
||||
append(
|
||||
"cookie",
|
||||
"uin=o${id};" +
|
||||
" skey=${client.wLoginSigInfo.sKey.data.encodeToString()};" +
|
||||
" p_uin=o${id};" +
|
||||
" p_skey=${client.wLoginSigInfo.psKeyMap["qun.qq.com"]?.data?.encodeToString()}; "
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val rep = data.await()
|
||||
val jsonText = Regex("""window.__INITIAL_STATE__=(.+?)</script>""").find(rep)?.groupValues?.get(1)
|
||||
return jsonText?.let { json.decodeFromString(GroupHonorListData.serializer(), it) }
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
@LowLevelAPI
|
||||
@MiraiExperimentalAPI
|
||||
|
@ -124,6 +124,9 @@ internal class MemberImpl constructor(
|
||||
@Suppress("PropertyName")
|
||||
var _muteTimestamp: Int = memberInfo.muteTimestamp
|
||||
|
||||
@Suppress("PropertyName")
|
||||
var _nudgeTimestamp: Long = 0L
|
||||
|
||||
override val muteTimeRemaining: Int
|
||||
get() = if (_muteTimestamp == 0 || _muteTimestamp == 0xFFFFFFFF.toInt()) {
|
||||
0
|
||||
@ -197,7 +200,7 @@ internal class MemberImpl constructor(
|
||||
private fun checkBotPermissionHigherThanThis(operationName: String) {
|
||||
check(group.botPermission > this.permission) {
|
||||
throw PermissionDeniedException(
|
||||
"`$operationName` operation requires a higher permission, while" +
|
||||
"`$operationName` operation requires a higher permission, while " +
|
||||
"${group.botPermission} < ${this.permission}"
|
||||
)
|
||||
}
|
||||
@ -219,7 +222,6 @@ internal class MemberImpl constructor(
|
||||
net.mamoe.mirai.event.events.MemberUnmuteEvent(this@MemberImpl, null).broadcast()
|
||||
}
|
||||
|
||||
|
||||
@JvmSynthetic
|
||||
override suspend fun kick(message: String) {
|
||||
checkBotPermissionHigherThanThis("kick")
|
||||
|
@ -113,7 +113,9 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
|
||||
}
|
||||
is At -> {
|
||||
elements.add(ImMsgBody.Elem(text = it.toJceData()))
|
||||
elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " ")))
|
||||
// elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " ")))
|
||||
// removed by https://github.com/mamoe/mirai/issues/524
|
||||
// 发送 QuoteReply 消息时无可避免的产生多余空格 #524
|
||||
}
|
||||
is PokeMessage -> {
|
||||
elements.add(
|
||||
@ -151,7 +153,9 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
|
||||
when (val source = it.source) {
|
||||
is OnlineMessageSource.Incoming.FromGroup -> {
|
||||
transformOneMessage(At(source.sender))
|
||||
transformOneMessage(PlainText(" "))
|
||||
// transformOneMessage(PlainText(" "))
|
||||
// removed by https://github.com/mamoe/mirai/issues/524
|
||||
// 发送 QuoteReply 消息时无可避免的产生多余空格 #524
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -297,6 +301,17 @@ private fun MessageChain.cleanupRubbishMessageElements(): MessageChain {
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (element is QuoteReply) {
|
||||
// 客户端为兼容早期不支持 QuoteReply 的客户端而添加的 At
|
||||
removeLastOrNull()?.let { rm ->
|
||||
if ((rm as? PlainText)?.content != " ") add(rm)
|
||||
else removeLastOrNull()?.let { rm2 ->
|
||||
if (rm2 !is At) add(rm2)
|
||||
}
|
||||
}
|
||||
}
|
||||
add(element)
|
||||
last = element
|
||||
}
|
||||
@ -316,7 +331,7 @@ internal val MIRAI_CUSTOM_ELEM_TYPE = "mirai".hashCode() // 103904510
|
||||
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: Bot, list: MessageChainBuilder) {
|
||||
// (this._miraiContentToString())
|
||||
// (this._miraiContentToString().soutv())
|
||||
this.forEach { element ->
|
||||
when {
|
||||
element.srcMsg != null -> {
|
||||
|
@ -246,7 +246,6 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
|
||||
|
||||
// self info
|
||||
data.selfInfo?.run {
|
||||
bot.cachedNick = null
|
||||
bot.selfInfo = this
|
||||
// bot.remark = remark ?: ""
|
||||
// bot.sex = sex
|
||||
@ -272,28 +271,11 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
|
||||
suspend fun StTroopNum.reloadGroup() {
|
||||
retryCatching(3) {
|
||||
bot.groups.delegate.addLast(
|
||||
@Suppress("DuplicatedCode")
|
||||
GroupImpl(
|
||||
bot = bot,
|
||||
coroutineContext = bot.coroutineContext,
|
||||
id = groupCode,
|
||||
groupInfo = bot._lowLevelQueryGroupInfo(groupCode).apply {
|
||||
this as GroupInfoImpl
|
||||
|
||||
if (this.delegate.groupName == null) {
|
||||
this.delegate.groupName = groupName
|
||||
}
|
||||
|
||||
if (this.delegate.groupMemo == null) {
|
||||
this.delegate.groupMemo = groupMemo
|
||||
}
|
||||
|
||||
if (this.delegate.groupUin == null) {
|
||||
this.delegate.groupUin = groupUin
|
||||
}
|
||||
|
||||
this.delegate.groupCode = this@reloadGroup.groupCode
|
||||
},
|
||||
groupInfo = GroupInfoImpl(this),
|
||||
members = bot._lowLevelQueryGroupMemberList(
|
||||
groupUin,
|
||||
groupCode,
|
||||
|
@ -20,6 +20,7 @@ import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.network.NoServerAvailableException
|
||||
import net.mamoe.mirai.qqandroid.BotAccount
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.SyncingCacheList
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FileStoragePushFSSvcListFuckKotlin
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
|
||||
@ -191,9 +192,6 @@ internal open class QQAndroidClient(
|
||||
private val highwayDataTransSequenceIdForApplyUp: AtomicInt = atomic(77918)
|
||||
internal fun nextHighwayDataTransSequenceIdForApplyUp(): Int = highwayDataTransSequenceIdForApplyUp.getAndAdd(2)
|
||||
|
||||
internal val onlinePushCacheList: AtomicResizeCacheList<Short> = AtomicResizeCacheList(20.secondsToMillis)
|
||||
internal val pbPushTransMsgCacheList: AtomicResizeCacheList<Int> = AtomicResizeCacheList(20.secondsToMillis)
|
||||
|
||||
val appClientVersion: Int = 0
|
||||
|
||||
var networkType: NetworkType = NetworkType.WIFI
|
||||
@ -205,16 +203,49 @@ internal open class QQAndroidClient(
|
||||
*/
|
||||
val protocolVersion: Short = 8001
|
||||
|
||||
class C2cMessageSyncData {
|
||||
class MessageSvcSyncData {
|
||||
val firstNotify: AtomicBoolean = atomic(true)
|
||||
|
||||
@Volatile
|
||||
var syncCookie: ByteArray? = null
|
||||
var pubAccountCookie = EMPTY_BYTE_ARRAY
|
||||
var msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY
|
||||
|
||||
|
||||
internal data class PbGetMessageSyncId(
|
||||
val uid: Long,
|
||||
val sequence: Int,
|
||||
val time: Int
|
||||
)
|
||||
|
||||
val pbGetMessageCacheList = SyncingCacheList<PbGetMessageSyncId>()
|
||||
|
||||
internal data class SystemMsgNewGroupSyncId(
|
||||
val sequence: Long,
|
||||
val time: Long
|
||||
)
|
||||
|
||||
val systemMsgNewGroupCacheList = SyncingCacheList<SystemMsgNewGroupSyncId>(10)
|
||||
|
||||
|
||||
internal data class PbPushTransMsgSyncId(
|
||||
val uid: Long,
|
||||
val sequence: Int,
|
||||
val time: Int
|
||||
)
|
||||
|
||||
val pbPushTransMsgCacheList = SyncingCacheList<PbPushTransMsgSyncId>(10)
|
||||
|
||||
internal data class OnlinePushReqPushSyncId(
|
||||
val uid: Long,
|
||||
val sequence: Short,
|
||||
val time: Long
|
||||
)
|
||||
|
||||
val onlinePushReqPushCacheList = SyncingCacheList<OnlinePushReqPushSyncId>(50)
|
||||
}
|
||||
|
||||
val c2cMessageSync = C2cMessageSyncData()
|
||||
val syncingController = MessageSvcSyncData()
|
||||
|
||||
/*
|
||||
* 以下登录使用
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2020 Mamoe Technologies and contributors.
|
||||
* *
|
||||
* * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
* *
|
||||
* * https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.qqandroid.network.protocol
|
||||
|
||||
import net.mamoe.mirai.qqandroid.utils.LinkedList
|
||||
import kotlin.jvm.Synchronized
|
||||
|
||||
internal class SyncingCacheList<E>(private val size: Int = 50) {
|
||||
private val packetIdList = LinkedList<E>()
|
||||
|
||||
@Synchronized // faster than suspending Mutex
|
||||
fun addCache(element: E): Boolean {
|
||||
if (packetIdList.contains(element)) return false // duplicate
|
||||
packetIdList.addLast(element)
|
||||
if (packetIdList.size >= size) packetIdList.removeFirst()
|
||||
return true
|
||||
}
|
||||
}
|
@ -172,13 +172,13 @@ internal class Oidb0x88d : ProtoBuf {
|
||||
@ProtoNumber(12) @JvmField val groupDefaultPage: Int? = null,
|
||||
@ProtoNumber(13) @JvmField val groupInfoSeq: Int? = null,
|
||||
@ProtoNumber(14) @JvmField val groupRoamingTime: Int? = null,
|
||||
@ProtoNumber(15) var groupName: String? = null,
|
||||
@ProtoNumber(16) var groupMemo: String? = null,
|
||||
@ProtoNumber(15) @JvmField val groupName: String? = null,
|
||||
@ProtoNumber(16) @JvmField val groupMemo: String? = null,
|
||||
@ProtoNumber(17) @JvmField val ingGroupFingerMemo: String? = null,
|
||||
@ProtoNumber(18) @JvmField val ingGroupClassText: String? = null,
|
||||
@ProtoNumber(19) @JvmField val groupAllianceCode: List<Int>? = null,
|
||||
@ProtoNumber(20) @JvmField val groupExtraAdmNum: Int? = null,
|
||||
@ProtoNumber(21) var groupUin: Long? = null,
|
||||
@ProtoNumber(21) @JvmField val groupUin: Long? = null,
|
||||
@ProtoNumber(22) @JvmField val groupCurMsgSeq: Int? = null,
|
||||
@ProtoNumber(23) @JvmField val groupLastMsgTime: Int? = null,
|
||||
@ProtoNumber(24) @JvmField val ingGroupQuestion: String? = null,
|
||||
@ -258,7 +258,6 @@ internal class Oidb0x88d : ProtoBuf {
|
||||
@ProtoNumber(98) @JvmField val cmduinRingtoneId: Int? = null,
|
||||
@ProtoNumber(99) @JvmField val groupFlagext4: Int? = null,
|
||||
@ProtoNumber(100) @JvmField val groupFreezeReason: Int? = null,
|
||||
@ProtoNumber(101) var groupCode: Long? = null // mirai 添加
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
@ -2423,3 +2422,17 @@ internal class Cmd0x6ce : ProtoBuf {
|
||||
) : ProtoBuf
|
||||
}
|
||||
|
||||
@Serializable
|
||||
internal class Cmd0xed3 : ProtoBuf {
|
||||
@Serializable
|
||||
internal class RspBody : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class ReqBody(
|
||||
@ProtoNumber(1) @JvmField val toUin: Long = 0L,
|
||||
@ProtoNumber(2) @JvmField val groupCode: Long = 0L,
|
||||
@ProtoNumber(3) @JvmField val msgSeq: Int = 0,
|
||||
@ProtoNumber(4) @JvmField val msgRandom: Int = 0,
|
||||
@ProtoNumber(5) @JvmField val aioUin: Long = 0L
|
||||
) : ProtoBuf
|
||||
}
|
@ -13,10 +13,7 @@ import kotlinx.io.core.*
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.network.Packet
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.MultiMsg
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.NewContact
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.*
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.*
|
||||
@ -148,9 +145,10 @@ internal object KnownPacketFactories {
|
||||
TroopManagement.EditSpecialTitle,
|
||||
TroopManagement.Mute,
|
||||
TroopManagement.GroupOperation,
|
||||
TroopManagement.GetGroupInfo,
|
||||
// TroopManagement.GetGroupInfo,
|
||||
TroopManagement.EditGroupNametag,
|
||||
TroopManagement.Kick,
|
||||
NudgePacket,
|
||||
Heartbeat.Alive,
|
||||
PbMessageSvc.PbMsgWithDraw,
|
||||
MultiMsg.ApplyUp,
|
||||
|
@ -58,7 +58,7 @@ internal class NewContact {
|
||||
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): NewFriendRequestEvent? {
|
||||
readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()).run {
|
||||
val struct = friendmsgs?.firstOrNull()
|
||||
val struct = friendmsgs?.firstOrNull()// 会有重复且无法过滤, 不要用 map
|
||||
return struct?.msg?.run {
|
||||
NewFriendRequestEvent(
|
||||
bot,
|
||||
@ -145,9 +145,16 @@ internal class NewContact {
|
||||
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Packet? {
|
||||
readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()).run {
|
||||
val struct = groupmsgs?.firstOrNull()
|
||||
val struct = groupmsgs?.firstOrNull() ?: return null // 会有重复且无法过滤, 不要用 map
|
||||
|
||||
return struct?.msg?.run {
|
||||
if (!bot.client.syncingController.systemMsgNewGroupCacheList.addCache(
|
||||
QQAndroidClient.MessageSvcSyncData.SystemMsgNewGroupSyncId(struct.msgSeq, struct.msgTime)
|
||||
)
|
||||
) { // duplicate
|
||||
return null
|
||||
}
|
||||
|
||||
return struct.msg?.run {
|
||||
//this.soutv("SystemMsg")
|
||||
when (subType) {
|
||||
1 -> { // 处理被邀请入群 或 处理成员入群申请
|
||||
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.readBytes
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.network.Packet
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0xed3
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.OidbSso
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.loadAs
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
|
||||
|
||||
internal object NudgePacket : OutgoingPacketFactory<NudgePacket.Response>("OidbSvc.0xed3") {
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
|
||||
with(readBytes().loadAs(OidbSso.OIDBSSOPkg.serializer())) {
|
||||
return Response(result == 0, result)
|
||||
}
|
||||
}
|
||||
|
||||
class Response(val success: Boolean, val code: Int) : Packet {
|
||||
override fun toString(): String = "NudgeResponse(success=$success,code=$code)"
|
||||
}
|
||||
|
||||
fun friendInvoke(
|
||||
client: QQAndroidClient,
|
||||
nudgeTargetId: Long,
|
||||
messageReceiverUin: Long,
|
||||
): OutgoingPacket {
|
||||
return buildOutgoingUniPacket(client) {
|
||||
writeProtoBuf(
|
||||
OidbSso.OIDBSSOPkg.serializer(),
|
||||
OidbSso.OIDBSSOPkg(
|
||||
command = 3795,
|
||||
serviceType = 1,
|
||||
result = 0,
|
||||
bodybuffer = Cmd0xed3.ReqBody(
|
||||
toUin = nudgeTargetId,
|
||||
aioUin = messageReceiverUin
|
||||
).toByteArray(Cmd0xed3.ReqBody.serializer())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun troopInvoke(
|
||||
client: QQAndroidClient,
|
||||
messageReceiverGroupCode: Long,
|
||||
nudgeTargetId: Long,
|
||||
): OutgoingPacket {
|
||||
return buildOutgoingUniPacket(client) {
|
||||
writeProtoBuf(
|
||||
OidbSso.OIDBSSOPkg.serializer(),
|
||||
OidbSso.OIDBSSOPkg(
|
||||
command = 3795,
|
||||
serviceType = 1,
|
||||
result = 0,
|
||||
bodybuffer = Cmd0xed3.ReqBody(
|
||||
toUin = nudgeTargetId,
|
||||
groupCode = messageReceiverGroupCode
|
||||
).toByteArray(Cmd0xed3.ReqBody.serializer())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -14,13 +14,13 @@ import kotlinx.io.core.buildPacket
|
||||
import kotlinx.io.core.readBytes
|
||||
import kotlinx.io.core.toByteArray
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.network.Packet
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.ModifyGroupCardReq
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopNum
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.stUinInfo
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.*
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
|
||||
@ -32,19 +32,19 @@ import net.mamoe.mirai.data.GroupInfo as MiraiGroupInfo
|
||||
|
||||
@OptIn(LowLevelAPI::class)
|
||||
internal class GroupInfoImpl(
|
||||
internal val delegate: Oidb0x88d.GroupInfo
|
||||
private val stTroopNum: StTroopNum
|
||||
) : MiraiGroupInfo, Packet, Packet.NoLog {
|
||||
override val uin: Long get() = delegate.groupUin ?: error("cannot find groupUin")
|
||||
override val owner: Long get() = delegate.groupOwner ?: error("cannot find groupOwner")
|
||||
override val groupCode: Long get() = Group.calculateGroupCodeByGroupUin(uin)
|
||||
override val memo: String get() = delegate.groupMemo ?: error("cannot find groupMemo")
|
||||
override val name: String get() = delegate.groupName ?: delegate.longGroupName ?: error("cannot find groupName")
|
||||
override val allowMemberInvite get() = delegate.groupFlagExt?.and(0x000000c0) != 0
|
||||
override val allowAnonymousChat get() = delegate.groupFlagExt?.and(0x40000000) == 0
|
||||
override val autoApprove get() = delegate.groupFlagext3?.and(0x00100000) == 0
|
||||
override val confessTalk get() = delegate.groupFlagext3?.and(0x00002000) == 0
|
||||
override val muteAll: Boolean get() = delegate.shutupTimestamp != 0
|
||||
override val botMuteTimestamp: Int get() = delegate.shutupTimestampMe ?: 0
|
||||
override val uin: Long get() = stTroopNum.groupUin
|
||||
override val owner: Long get() = stTroopNum.dwGroupOwnerUin
|
||||
override val groupCode: Long get() = stTroopNum.groupCode
|
||||
override val memo: String get() = stTroopNum.groupMemo
|
||||
override val name: String get() = stTroopNum.groupName
|
||||
override val allowMemberInvite get() = stTroopNum.dwGroupFlagExt?.and(0x000000c0) != 0L
|
||||
override val allowAnonymousChat get() = stTroopNum.dwGroupFlagExt?.and(0x40000000) == 0L
|
||||
override val autoApprove get() = stTroopNum.dwGroupFlagExt3?.and(0x00100000) == 0L
|
||||
override val confessTalk get() = stTroopNum.dwGroupFlagExt3?.and(0x00002000) == 0L
|
||||
override val muteAll: Boolean get() = stTroopNum.dwShutUpTimestamp != 0L
|
||||
override val botMuteTimestamp: Int get() = stTroopNum.dwMyShutUpTimestamp?.toInt() ?: 0
|
||||
}
|
||||
|
||||
internal class TroopManagement {
|
||||
@ -88,6 +88,7 @@ internal class TroopManagement {
|
||||
|
||||
|
||||
internal object GetGroupInfo : OutgoingPacketFactory<GroupInfoImpl>("OidbSvc.0x88d_7") {
|
||||
@Deprecated("")
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient,
|
||||
groupCode: Long
|
||||
@ -107,8 +108,8 @@ internal class TroopManagement {
|
||||
groupFlagExt = 0,
|
||||
groupFlagext4 = 0,
|
||||
groupFlag = 0,
|
||||
groupFlagext3 = 0,//获取confess
|
||||
noFingerOpenFlag = 0,
|
||||
groupFlagext3 = 1,//获取confess
|
||||
noFingerOpenFlag = 1,
|
||||
cmduinFlagEx2 = 0,
|
||||
groupTypeFlag = 0,
|
||||
appPrivilegeFlag = 0,
|
||||
@ -135,12 +136,14 @@ internal class TroopManagement {
|
||||
}
|
||||
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GroupInfoImpl {
|
||||
error("deprecated")
|
||||
/*
|
||||
with(
|
||||
this.readBytes()
|
||||
.loadAs(OidbSso.OIDBSSOPkg.serializer()).bodybuffer.loadAs(Oidb0x88d.RspBody.serializer()).stzrspgroupinfo!![0].stgroupinfo!!
|
||||
) {
|
||||
return GroupInfoImpl(this)
|
||||
}
|
||||
return GroupInfoImpl()
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.discardExact
|
||||
@ -63,10 +62,6 @@ import kotlin.random.Random
|
||||
internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Response>("MessageSvc.PbGetMsg") {
|
||||
|
||||
|
||||
private val msgUidQueue = ArrayDeque<Long>()
|
||||
private val msgUidSet = hashSetOf<Long>()
|
||||
private val msgQueueMutex = Mutex()
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient,
|
||||
@ -88,7 +83,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
|
||||
whisperSessionId = 0,
|
||||
syncFlag = syncFlag,
|
||||
// serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY,
|
||||
syncCookie = syncCookie ?: client.c2cMessageSync.syncCookie
|
||||
syncCookie = syncCookie ?: client.syncingController.syncCookie
|
||||
?: byteArrayOf()//.also { client.c2cMessageSync.syncCookie = it },
|
||||
// syncFlag = client.c2cMessageSync.syncFlag,
|
||||
//msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf,
|
||||
@ -137,6 +132,17 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun QQAndroidBot.createGroupForBot(groupUin: Long): Group? {
|
||||
val group = getGroupByUinOrNull(groupUin)
|
||||
if (group != null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return getNewGroup(Group.calculateGroupCodeByGroupUin(groupUin))?.apply {
|
||||
groups.delegate.addLast(this)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
|
||||
// 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00
|
||||
@ -155,14 +161,14 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
|
||||
}
|
||||
when (resp.msgRspType) {
|
||||
0 -> {
|
||||
bot.client.c2cMessageSync.syncCookie = resp.syncCookie
|
||||
bot.client.c2cMessageSync.pubAccountCookie = resp.pubAccountCookie
|
||||
bot.client.syncingController.syncCookie = resp.syncCookie
|
||||
bot.client.syncingController.pubAccountCookie = resp.pubAccountCookie
|
||||
}
|
||||
1 -> {
|
||||
bot.client.c2cMessageSync.syncCookie = resp.syncCookie
|
||||
bot.client.syncingController.syncCookie = resp.syncCookie
|
||||
}
|
||||
2 -> {
|
||||
bot.client.c2cMessageSync.pubAccountCookie = resp.pubAccountCookie
|
||||
bot.client.syncingController.pubAccountCookie = resp.pubAccountCookie
|
||||
|
||||
}
|
||||
}
|
||||
@ -170,7 +176,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
|
||||
// bot.logger.debug(resp.msgRspType._miraiContentToString())
|
||||
// bot.logger.debug(resp.syncCookie._miraiContentToString())
|
||||
|
||||
bot.client.c2cMessageSync.msgCtrlBuf = resp.msgCtrlBuf
|
||||
bot.client.syncingController.msgCtrlBuf = resp.msgCtrlBuf
|
||||
|
||||
if (resp.uinPairMsgs == null) {
|
||||
return EmptyResponse
|
||||
@ -184,37 +190,22 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
|
||||
}.also {
|
||||
MessageSvcPbDeleteMsg.delete(bot, it) // 删除消息
|
||||
}
|
||||
.mapNotNull<MsgComm.Msg, Packet> { msg ->
|
||||
|
||||
msgQueueMutex.lock()
|
||||
val msgUid = msg.msgHead.msgUid
|
||||
if (msgUidSet.size > 50) {
|
||||
msgUidSet.remove(msgUidQueue.removeFirst())
|
||||
}
|
||||
if (!msgUidSet.add(msgUid)) {
|
||||
msgQueueMutex.unlock()
|
||||
return@mapNotNull null
|
||||
}
|
||||
msgQueueMutex.unlock()
|
||||
msgUidQueue.addLast(msgUid)
|
||||
|
||||
suspend fun createGroupForBot(groupUin: Long): Group? {
|
||||
val group = bot.getGroupByUinOrNull(groupUin)
|
||||
if (group != null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return bot.getNewGroup(Group.calculateGroupCodeByGroupUin(groupUin))?.apply {
|
||||
bot.groups.delegate.addLast(this)
|
||||
}
|
||||
}
|
||||
.mapNotNull { msg ->
|
||||
if (!bot.client.syncingController.pbGetMessageCacheList.addCache(
|
||||
QQAndroidClient.MessageSvcSyncData.PbGetMessageSyncId(
|
||||
uid = msg.msgHead.msgUid,
|
||||
sequence = msg.msgHead.msgSeq,
|
||||
time = msg.msgHead.msgTime
|
||||
)
|
||||
)
|
||||
) return@mapNotNull null
|
||||
|
||||
when (msg.msgHead.msgType) {
|
||||
33 -> bot.groupListModifyLock.withLock {
|
||||
|
||||
if (msg.msgHead.authUin == bot.id) {
|
||||
// 邀请入群
|
||||
return@mapNotNull createGroupForBot(msg.msgHead.fromUin)?.let {
|
||||
return@mapNotNull bot.createGroupForBot(msg.msgHead.fromUin)?.let {
|
||||
// package: 27 0B 60 E7 01 CA CC 69 8B 83 44 71 47 90 06 B9 DC C0 ED D4 B1 00 30 33 44 30 42 38 46 30 39 37 32 38 35 43 34 31 38 30 33 36 41 34 36 31 36 31 35 32 37 38 46 46 43 30 41 38 30 36 30 36 45 38 31 43 39 41 34 38 37
|
||||
// package: groupUin + 01 CA CC 69 8B 83 + invitorUin + length(06) + string + magicKey
|
||||
val invitorUin = msg.msgBody.msgContent.sliceArray(10..13).toInt().toLong()
|
||||
@ -256,7 +247,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
|
||||
}
|
||||
|
||||
38 -> bot.groupListModifyLock.withLock { // 建群
|
||||
return@mapNotNull createGroupForBot(msg.msgHead.fromUin)
|
||||
return@mapNotNull bot.createGroupForBot(msg.msgHead.fromUin)
|
||||
?.let { BotJoinGroupEvent.Active(it) }
|
||||
}
|
||||
|
||||
@ -265,7 +256,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
|
||||
// msg.msgHead.authUin: 处理人
|
||||
|
||||
return@mapNotNull if (msg.msgHead.toUin == bot.id) {
|
||||
createGroupForBot(msg.msgHead.fromUin)
|
||||
bot.createGroupForBot(msg.msgHead.fromUin)
|
||||
?.let { BotJoinGroupEvent.Active(it) }
|
||||
} else {
|
||||
null
|
||||
@ -427,7 +418,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
|
||||
MessageSvcPbGetMsg(
|
||||
client,
|
||||
MsgSvc.SyncFlag.CONTINUE,
|
||||
bot.client.c2cMessageSync.syncCookie
|
||||
bot.client.syncingController.syncCookie
|
||||
).sendAndExpect<Packet>()
|
||||
}
|
||||
return
|
||||
@ -438,7 +429,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
|
||||
MessageSvcPbGetMsg(
|
||||
client,
|
||||
MsgSvc.SyncFlag.CONTINUE,
|
||||
bot.client.c2cMessageSync.syncCookie
|
||||
bot.client.syncingController.syncCookie
|
||||
).sendAndExpect<Packet>()
|
||||
}
|
||||
return
|
||||
@ -454,28 +445,11 @@ internal suspend fun QQAndroidBot.getNewGroup(groupCode: Long): Group? {
|
||||
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(timeoutMillis = 10_000, retry = 5)
|
||||
}.groups.firstOrNull { it.groupCode == groupCode } ?: return null
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
return GroupImpl(
|
||||
bot = this,
|
||||
coroutineContext = coroutineContext,
|
||||
id = groupCode,
|
||||
groupInfo = _lowLevelQueryGroupInfo(troopNum.groupCode).apply {
|
||||
this as GroupInfoImpl
|
||||
|
||||
if (this.delegate.groupName == null) {
|
||||
this.delegate.groupName = troopNum.groupName
|
||||
}
|
||||
|
||||
if (this.delegate.groupMemo == null) {
|
||||
this.delegate.groupMemo = troopNum.groupMemo
|
||||
}
|
||||
|
||||
if (this.delegate.groupUin == null) {
|
||||
this.delegate.groupUin = troopNum.groupUin
|
||||
}
|
||||
|
||||
this.delegate.groupCode = troopNum.groupCode
|
||||
},
|
||||
groupInfo = GroupInfoImpl(troopNum),
|
||||
members = _lowLevelQueryGroupMemberList(
|
||||
troopNum.groupUin,
|
||||
troopNum.groupCode,
|
||||
|
@ -80,7 +80,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
|
||||
),
|
||||
msgSeq = source.sequenceId,
|
||||
msgRand = source.internalId,
|
||||
syncCookie = client.c2cMessageSync.syncCookie ?: byteArrayOf()
|
||||
syncCookie = client.syncingController.syncCookie ?: byteArrayOf()
|
||||
// msgVia = 1
|
||||
)
|
||||
)
|
||||
@ -110,7 +110,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
|
||||
),
|
||||
msgSeq = source.sequenceId,
|
||||
msgRand = source.internalId,
|
||||
syncCookie = client.c2cMessageSync.syncCookie ?: byteArrayOf()
|
||||
syncCookie = client.syncingController.syncCookie ?: byteArrayOf()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -31,13 +31,13 @@ internal object MessageSvcPushNotify : IncomingPacketFactory<RequestPushNotify>(
|
||||
|
||||
override suspend fun QQAndroidBot.handle(packet: RequestPushNotify, sequenceId: Int): OutgoingPacket? {
|
||||
|
||||
client.c2cMessageSync.firstNotify.loop { firstNotify ->
|
||||
client.syncingController.firstNotify.loop { firstNotify ->
|
||||
network.run {
|
||||
return MessageSvcPbGetMsg(
|
||||
client,
|
||||
MsgSvc.SyncFlag.START,
|
||||
if (firstNotify) {
|
||||
if (!client.c2cMessageSync.firstNotify.compareAndSet(firstNotify, false)) {
|
||||
if (!client.syncingController.firstNotify.compareAndSet(firstNotify, false)) {
|
||||
return@loop
|
||||
}
|
||||
null
|
||||
|
@ -20,15 +20,16 @@ import kotlinx.io.core.readUByte
|
||||
import kotlinx.io.core.readUInt
|
||||
import net.mamoe.mirai.JavaFriendlyAPI
|
||||
import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.event.events.BotGroupPermissionChangeEvent
|
||||
import net.mamoe.mirai.event.events.MemberLeaveEvent
|
||||
import net.mamoe.mirai.event.events.MemberPermissionChangeEvent
|
||||
import net.mamoe.mirai.data.MemberInfo
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.contact.GroupImpl
|
||||
import net.mamoe.mirai.qqandroid.contact.MemberImpl
|
||||
import net.mamoe.mirai.qqandroid.contact.checkIsMemberImpl
|
||||
import net.mamoe.mirai.qqandroid.message.contextualBugReportException
|
||||
import net.mamoe.mirai.qqandroid.network.MultiPacketByIterable
|
||||
import net.mamoe.mirai.qqandroid.network.Packet
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.OnlinePushTrans
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
|
||||
@ -45,52 +46,156 @@ internal object OnlinePushPbPushTransMsg :
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
|
||||
val content = this.readProtoBuf(OnlinePushTrans.PbMsgInfo.serializer())
|
||||
|
||||
|
||||
if (!bot.client.pbPushTransMsgCacheList.ensureNoDuplication(content.msgSeq)) {
|
||||
if (!bot.client.syncingController.pbPushTransMsgCacheList.addCache(
|
||||
content.run {
|
||||
QQAndroidClient.MessageSvcSyncData.PbPushTransMsgSyncId(msgUid, msgSeq, msgTime)
|
||||
}
|
||||
)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
// bot.network.logger.debug { content._miraiContentToString() }
|
||||
|
||||
|
||||
content.msgData.read<Unit> {
|
||||
when (content.msgType) {
|
||||
44 -> {
|
||||
// 3D C4 33 DD 01 FF CD 76 F4 03 C3 7E 2E 34
|
||||
// 群转让
|
||||
// start with 3D C4 33 DD 01 FF
|
||||
// 3D C4 33 DD 01 FF C3 7E 2E 34 CD 76 F4 03
|
||||
// 权限变更
|
||||
// 3D C4 33 DD 01 00/01 .....
|
||||
// 3D C4 33 DD 01 01 C3 7E 2E 34 01
|
||||
this.discardExact(5)
|
||||
val var4 = readByte().toInt()
|
||||
var var5 = 0L
|
||||
val target = readUInt().toLong()
|
||||
if (var4 != 0 && var4 != 1) {
|
||||
var5 = readUInt().toLong()
|
||||
}
|
||||
|
||||
val group = bot.getGroupByUin(content.fromUin) as GroupImpl
|
||||
|
||||
if (var5 == 0L && this.remaining == 1L) {//管理员变更
|
||||
val newPermission =
|
||||
if (this.readByte().toInt() == 1) MemberPermission.ADMINISTRATOR
|
||||
else MemberPermission.MEMBER
|
||||
|
||||
if (target == bot.id) {
|
||||
if (group.botPermission == newPermission) {
|
||||
return null
|
||||
when (val mode = readUByte().toInt()) {
|
||||
0xFF -> {
|
||||
// 群转让 / huifu.qq.com
|
||||
// From -> to
|
||||
val from = readUInt().toLong()
|
||||
val to = readUInt().toLong()
|
||||
val results = ArrayList<Packet>()
|
||||
// println("$from -> $to")
|
||||
if (to == bot.id) {
|
||||
if (bot.getGroupByUinOrNull(content.fromUin) == null) {
|
||||
MessageSvcPbGetMsg.run {
|
||||
results.add(
|
||||
BotJoinGroupEvent.Retrieve(
|
||||
bot.createGroupForBot(content.fromUin)!!
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val group = bot.getGroupByUin(content.fromUin) as GroupImpl
|
||||
if (from == bot.id) {
|
||||
if (group.botPermission != MemberPermission.MEMBER)
|
||||
results.add(
|
||||
BotGroupPermissionChangeEvent(
|
||||
group, group.botPermission.also {
|
||||
group.botAsMember.checkIsMemberImpl().permission =
|
||||
MemberPermission.MEMBER
|
||||
},
|
||||
MemberPermission.MEMBER
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val member = group[from] as MemberImpl
|
||||
if (member.permission != MemberPermission.MEMBER) {
|
||||
results.add(
|
||||
MemberPermissionChangeEvent(
|
||||
member,
|
||||
member.permission.also { member.permission = MemberPermission.MEMBER },
|
||||
MemberPermission.MEMBER
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (to == bot.id) {
|
||||
if (group.botPermission != MemberPermission.OWNER) {
|
||||
results.add(
|
||||
BotGroupPermissionChangeEvent(
|
||||
group,
|
||||
group.botAsMember.permission.also {
|
||||
group.botAsMember.checkIsMemberImpl().permission =
|
||||
MemberPermission.OWNER
|
||||
},
|
||||
MemberPermission.OWNER
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val newOwner = group.getOrNull(to) ?: group.newMember(object : MemberInfo {
|
||||
override val nameCard: String
|
||||
get() = ""
|
||||
override val permission: MemberPermission
|
||||
get() = MemberPermission.OWNER
|
||||
override val specialTitle: String
|
||||
get() = ""
|
||||
override val muteTimestamp: Int
|
||||
get() = 0
|
||||
override val uin: Long
|
||||
get() = to
|
||||
override val nick: String
|
||||
get() = ""
|
||||
}).also { owner ->
|
||||
owner.checkIsMemberImpl().permission = MemberPermission.OWNER
|
||||
group.members.delegate.addLast(owner)
|
||||
results.add(MemberJoinEvent.Retrieve(owner))
|
||||
}
|
||||
if (newOwner.permission != MemberPermission.OWNER) {
|
||||
results.add(
|
||||
MemberPermissionChangeEvent(
|
||||
newOwner,
|
||||
newOwner.permission.also {
|
||||
newOwner.checkIsMemberImpl().permission = MemberPermission.OWNER
|
||||
},
|
||||
MemberPermission.OWNER
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return MultiPacketByIterable(results)
|
||||
}
|
||||
else -> {
|
||||
var var5 = 0L
|
||||
val target = readUInt().toLong()
|
||||
if (mode != 0 && mode != 1) {
|
||||
var5 = readUInt().toLong()
|
||||
}
|
||||
|
||||
return BotGroupPermissionChangeEvent(
|
||||
group,
|
||||
group.botPermission.also {
|
||||
group.botAsMember.checkIsMemberImpl().permission = newPermission
|
||||
},
|
||||
newPermission
|
||||
)
|
||||
} else {
|
||||
val member = group[target] as MemberImpl
|
||||
if (member.permission == newPermission) {
|
||||
return null
|
||||
}
|
||||
val group = bot.getGroupByUin(content.fromUin) as GroupImpl
|
||||
|
||||
return MemberPermissionChangeEvent(
|
||||
member,
|
||||
member.permission.also { member.permission = newPermission },
|
||||
newPermission
|
||||
)
|
||||
if (var5 == 0L && this.remaining == 1L) {//管理员变更
|
||||
val newPermission =
|
||||
if (this.readByte().toInt() == 1) MemberPermission.ADMINISTRATOR
|
||||
else MemberPermission.MEMBER
|
||||
|
||||
if (target == bot.id) {
|
||||
if (group.botPermission == newPermission) {
|
||||
return null
|
||||
}
|
||||
|
||||
return BotGroupPermissionChangeEvent(
|
||||
group,
|
||||
group.botPermission.also {
|
||||
group.botAsMember.checkIsMemberImpl().permission = newPermission
|
||||
},
|
||||
newPermission
|
||||
)
|
||||
} else {
|
||||
val member = group[target] as MemberImpl
|
||||
if (member.permission == newPermission) {
|
||||
return null
|
||||
}
|
||||
|
||||
return MemberPermissionChangeEvent(
|
||||
member,
|
||||
member.permission.also { member.permission = newPermission },
|
||||
newPermission
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,28 +213,41 @@ internal object OnlinePushPbPushTransMsg :
|
||||
A8 32 51 A1
|
||||
83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 39 32 46 45 30 36 31 41 33 37 36 43 44 35 37 35 37 39 45 37 32 34 44 37 37 30 36 46 39 39 43 35 35 33 33 31 34 44 32 44 46 35 45 42 43 31 31 36
|
||||
*/
|
||||
readUInt().toLong() // group, uin or code ?
|
||||
|
||||
discardExact(1)
|
||||
readUInt().toLong() // groupUin
|
||||
readByte().toInt() // follow type
|
||||
val target = readUInt().toLong()
|
||||
val type = readUByte().toInt()
|
||||
val operator = readUInt().toLong()
|
||||
val groupUin = content.fromUin
|
||||
|
||||
when (type) {
|
||||
0x82 -> bot.getGroupByUinOrNull(groupUin)?.let { group ->
|
||||
val member = group.getOrNull(target) as? MemberImpl ?: return null
|
||||
return MemberLeaveEvent.Quit(member.also {
|
||||
member.cancel(CancellationException("Leaved actively"))
|
||||
group.members.delegate.remove(member)
|
||||
})
|
||||
2, 0x82 -> bot.getGroupByUinOrNull(groupUin)?.let { group ->
|
||||
if (target == bot.id) {
|
||||
return BotLeaveEvent.Active(group).also {
|
||||
group.cancel(CancellationException("Leaved actively"))
|
||||
bot.groups.delegate.remove(group)
|
||||
}
|
||||
} else {
|
||||
val member = group.getOrNull(target) as? MemberImpl ?: return null
|
||||
return MemberLeaveEvent.Quit(member.also {
|
||||
member.cancel(CancellationException("Leaved actively"))
|
||||
group.members.delegate.remove(member)
|
||||
})
|
||||
}
|
||||
}
|
||||
0x83 -> bot.getGroupByUin(groupUin).let { group ->
|
||||
val member = group.getOrNull(target) as? MemberImpl ?: return null
|
||||
return MemberLeaveEvent.Kick(member.also {
|
||||
member.cancel(CancellationException("Leaved actively"))
|
||||
group.members.delegate.remove(member)
|
||||
}, group.members[operator])
|
||||
3, 0x83 -> bot.getGroupByUin(groupUin).let { group ->
|
||||
if (target == bot.id) {
|
||||
return BotLeaveEvent.Kick(group.members[operator]).also {
|
||||
group.cancel(CancellationException("Being kicked"))
|
||||
bot.groups.delegate.remove(group)
|
||||
}
|
||||
} else {
|
||||
val member = group.getOrNull(target) as? MemberImpl ?: return null
|
||||
return MemberLeaveEvent.Kick(member.also {
|
||||
member.cancel(CancellationException("Being kicked"))
|
||||
group.members.delegate.remove(member)
|
||||
}, group.members[operator])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,6 @@
|
||||
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
|
||||
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.discardExact
|
||||
import kotlinx.io.core.readBytes
|
||||
@ -23,6 +21,8 @@ import kotlinx.io.core.readUInt
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
import net.mamoe.mirai.JavaFriendlyAPI
|
||||
import net.mamoe.mirai.contact.Friend
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.data.FriendInfo
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.getFriendOrNull
|
||||
@ -37,8 +37,9 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.MsgType0x210
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.OnlinePushPack
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x115
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x122
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x27.SubMsgType0x27.*
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x44
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x44.Submsgtype0x44
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0xb3
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.TroopTips0x857
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
|
||||
@ -66,7 +67,11 @@ internal object OnlinePushReqPush : IncomingPacketFactory<OnlinePushReqPush.ReqP
|
||||
mapper: ByteReadPacket.(msgInfo: MsgInfo) -> Sequence<Packet>
|
||||
): Sequence<Packet> {
|
||||
return asSequence().filter { msg ->
|
||||
client.onlinePushCacheList.ensureNoDuplication(msg.shMsgSeq)
|
||||
!client.syncingController.onlinePushReqPushCacheList.addCache(
|
||||
QQAndroidClient.MessageSvcSyncData.OnlinePushReqPushSyncId(
|
||||
uid = msg.lMsgUid ?: 0, sequence = msg.shMsgSeq, time = msg.uMsgTime
|
||||
)
|
||||
)
|
||||
}.flatMap { it.vMsg.read { mapper(it) } }
|
||||
}
|
||||
|
||||
@ -101,7 +106,7 @@ internal object OnlinePushReqPush : IncomingPacketFactory<OnlinePushReqPush.ReqP
|
||||
528 -> {
|
||||
val notifyMsgBody = readJceStruct(MsgType0x210.serializer())
|
||||
Transformers528[notifyMsgBody.uSubMsgType]
|
||||
?.let { processor -> processor(notifyMsgBody, bot) }
|
||||
?.let { processor -> processor(notifyMsgBody, bot, msgInfo) }
|
||||
?: kotlin.run {
|
||||
bot.network.logger.debug {
|
||||
"unknown group 528 type 0x${notifyMsgBody.uSubMsgType.toUHexString("")}, data: " + notifyMsgBody.vProtobuf.toUHexString()
|
||||
@ -231,6 +236,44 @@ private object Transformers732 : Map<Int, Lambda732> by mapOf(
|
||||
return@lambda732 sequenceOf(GroupAllowAnonymousChatEvent(!new, new, group, operator))
|
||||
},
|
||||
|
||||
//系统提示
|
||||
0x14 to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
|
||||
|
||||
discardExact(1)
|
||||
val grayTip = readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer()).optGeneralGrayTip
|
||||
when (grayTip?.templId) {
|
||||
//戳一戳
|
||||
10043L, 1134L, 1135L -> {
|
||||
//预置数据,服务器将不会提供己方已知消息
|
||||
var action = ""
|
||||
var from: Member = group.botAsMember
|
||||
var target: Member = group.botAsMember
|
||||
var suffix = ""
|
||||
grayTip.msgTemplParam?.map {
|
||||
Pair(it.name.decodeToString(), it.value.decodeToString())
|
||||
}?.asSequence()?.forEach { (key, value) ->
|
||||
run {
|
||||
when (key) {
|
||||
"action_str" -> action = value
|
||||
"uin_str1" -> from = group[value.toLong()]
|
||||
"uin_str2" -> target = group[value.toLong()]
|
||||
"suffix_str" -> suffix = value
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target.id == bot.id) {
|
||||
return@lambda732 sequenceOf(BotNudgedEvent(from, action, suffix))
|
||||
}
|
||||
return@lambda732 sequenceOf(MemberNudgedEvent(from, target, action, suffix))
|
||||
}
|
||||
else -> {
|
||||
bot.network.logger.debug {
|
||||
"Unknown Transformers528 0x14 template\ntemplId=${grayTip?.templId}\nPermList=${grayTip?.msgTemplParam?._miraiContentToString()}"
|
||||
}
|
||||
return@lambda732 emptySequence()
|
||||
}
|
||||
}
|
||||
},
|
||||
// 传字符串信息
|
||||
0x10 to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
|
||||
val dataBytes = readBytes(26)
|
||||
@ -329,17 +372,28 @@ private object Transformers732 : Map<Int, Lambda732> by mapOf(
|
||||
}
|
||||
)
|
||||
|
||||
internal val ignoredLambda528: Lambda528 = lambda528 { emptySequence() }
|
||||
internal val ignoredLambda528: Lambda528 = lambda528 { _, _ -> emptySequence() }
|
||||
|
||||
internal interface Lambda528 {
|
||||
operator fun invoke(msg: MsgType0x210, bot: QQAndroidBot): Sequence<Packet>
|
||||
operator fun invoke(msg: MsgType0x210, bot: QQAndroidBot, msgInfo: MsgInfo): Sequence<Packet>
|
||||
}
|
||||
|
||||
@kotlin.internal.LowPriorityInOverloadResolution
|
||||
internal inline fun lambda528(crossinline block: MsgType0x210.(QQAndroidBot) -> Sequence<Packet>): Lambda528 {
|
||||
return object : Lambda528 {
|
||||
override fun invoke(msg: MsgType0x210, bot: QQAndroidBot): Sequence<Packet> {
|
||||
override fun invoke(msg: MsgType0x210, bot: QQAndroidBot, msgInfo: MsgInfo): Sequence<Packet> {
|
||||
return block(msg, bot)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun lambda528(crossinline block: MsgType0x210.(QQAndroidBot, MsgInfo) -> Sequence<Packet>): Lambda528 {
|
||||
return object : Lambda528 {
|
||||
override fun invoke(msg: MsgType0x210, bot: QQAndroidBot, msgInfo: MsgInfo): Sequence<Packet> {
|
||||
return block(msg, bot, msgInfo)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,7 +407,7 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
|
||||
|
||||
0x8AL to lambda528 { bot ->
|
||||
@Serializable
|
||||
data class Sub8AMsgInfo(
|
||||
class Sub8AMsgInfo(
|
||||
@ProtoNumber(1) val fromUin: Long,
|
||||
@ProtoNumber(2) val botUin: Long,
|
||||
@ProtoNumber(3) val srcId: Int,
|
||||
@ -367,7 +421,7 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
data class Sub8A(
|
||||
class Sub8A(
|
||||
@ProtoNumber(1) val msgInfo: List<Sub8AMsgInfo>,
|
||||
@ProtoNumber(2) val appId: Int, // 1
|
||||
@ProtoNumber(3) val instId: Int, // 1
|
||||
@ -403,7 +457,7 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
|
||||
bot.friends.delegate.addLast(new)
|
||||
return@lambda528 sequenceOf(FriendAddEvent(new))
|
||||
},
|
||||
0xE2L to lambda528 {
|
||||
0xE2L to lambda528 { _ ->
|
||||
// TODO: unknown. maybe messages.
|
||||
// 0A 35 08 00 10 A2 FF 8C F0 03 1A 1B E5 90 8C E6 84 8F E4 BD A0 E7 9A 84 E5 8A A0 E5 A5 BD E5 8F 8B E8 AF B7 E6 B1 82 22 0C E6 BD 9C E6 B1 9F E7 BE A4 E5 8F 8B 28 01
|
||||
// vProtobuf.loadAs(Msgtype0x210.serializer())
|
||||
@ -411,7 +465,7 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
|
||||
return@lambda528 emptySequence()
|
||||
},
|
||||
0x44L to lambda528 { bot ->
|
||||
val msg = vProtobuf.loadAs(Submsgtype0x44.Submsgtype0x44.MsgBody.serializer())
|
||||
val msg = vProtobuf.loadAs(Submsgtype0x44.MsgBody.serializer())
|
||||
when {
|
||||
msg.msgCleanCountMsg != null -> {
|
||||
|
||||
@ -426,20 +480,55 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
|
||||
return@lambda528 emptySequence()
|
||||
},
|
||||
// bot 在其他客户端被踢或主动退出而同步情况
|
||||
0xD4L to lambda528 { bot ->
|
||||
0xD4L to lambda528 { _ ->
|
||||
// this.soutv("0x210")
|
||||
@Serializable
|
||||
data class SubD4(
|
||||
// ok
|
||||
val uin: Long
|
||||
) : ProtoBuf
|
||||
/* @Serializable
|
||||
data class SubD4(
|
||||
// ok
|
||||
val uin: Long
|
||||
) : ProtoBuf
|
||||
|
||||
val uin = vProtobuf.loadAs(SubD4.serializer()).uin
|
||||
val group = bot.getGroupByUinOrNull(uin) ?: bot.getGroupOrNull(uin)
|
||||
return@lambda528 if (group != null && bot.groups.delegate.remove(group)) {
|
||||
group.cancel(CancellationException("Being kicked"))
|
||||
sequenceOf(BotLeaveEvent.Active(group))
|
||||
} else emptySequence()
|
||||
val uin = vProtobuf.loadAs(SubD4.serializer()).uin
|
||||
val group = bot.getGroupByUinOrNull(uin) ?: bot.getGroupOrNull(uin)
|
||||
return@lambda528 if (group != null && bot.groups.delegate.remove(group)) {
|
||||
group.cancel(CancellationException("Being kicked"))
|
||||
sequenceOf(BotLeaveEvent.Active(group))
|
||||
} else emptySequence()*/
|
||||
|
||||
//ignore
|
||||
return@lambda528 emptySequence()
|
||||
},
|
||||
//戳一戳信息等
|
||||
0x122L to lambda528 { bot, _ ->
|
||||
val body = vProtobuf.loadAs(Submsgtype0x122.Submsgtype0x122.MsgBody.serializer())
|
||||
when (body.templId) {
|
||||
//戳一戳
|
||||
1134L, 1135L, 1136L, 10043L -> {
|
||||
//预置数据,服务器将不会提供己方已知消息
|
||||
var from: Friend = bot.selfQQ
|
||||
var action = ""
|
||||
var target: Friend = bot.selfQQ
|
||||
var suffix = ""
|
||||
body.msgTemplParam?.asSequence()?.map {
|
||||
it.name.decodeToString() to it.value.decodeToString()
|
||||
}?.forEach { (key, value) ->
|
||||
when (key) {
|
||||
"action_str" -> action = value
|
||||
"uin_str1" -> from = bot.getFriend(value.toLong())
|
||||
"uin_str2" -> target = bot.getFriend(value.toLong())
|
||||
"suffix_str" -> suffix = value
|
||||
}
|
||||
}
|
||||
return@lambda528 sequenceOf(BotNudgedEvent(from, action, suffix))
|
||||
|
||||
}
|
||||
else -> {
|
||||
bot.logger.debug {
|
||||
"Unknown Transformers528 0x122L template\ntemplId=${body.templId}\nPermList=${body.msgTemplParam?._miraiContentToString()}"
|
||||
}
|
||||
return@lambda528 emptySequence()
|
||||
}
|
||||
}
|
||||
},
|
||||
//好友输入状态
|
||||
0x115L to lambda528 { bot ->
|
||||
@ -579,12 +668,10 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
|
||||
val to = value.encodeToString()
|
||||
if (uin == bot.id) {
|
||||
val from = bot.nick
|
||||
bot.cachedNick = to
|
||||
add(
|
||||
BotNickChangedEvent(
|
||||
bot, from, to
|
||||
)
|
||||
)
|
||||
if (from != to) {
|
||||
bot.nick = to
|
||||
add(BotNickChangedEvent(bot, from, to))
|
||||
}
|
||||
} else {
|
||||
val friend = (bot.getFriendOrNull(uin) ?: return@forEach) as FriendImpl
|
||||
val info = friend.friendInfo
|
||||
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.qqandroid.utils
|
||||
|
||||
// We target JVM and Android only.
|
||||
internal expect class LinkedList<E>() : List<E>, Queue<E>, Deque<E>
|
||||
|
||||
internal interface Queue<E> : MutableCollection<E> {
|
||||
override fun add(element: E): Boolean
|
||||
fun offer(element: E): Boolean
|
||||
fun remove(): E
|
||||
fun poll(): E
|
||||
fun element(): E
|
||||
fun peek(): E
|
||||
}
|
||||
|
||||
internal interface Deque<E> : Queue<E> {
|
||||
fun addFirst(e: E)
|
||||
fun addLast(e: E)
|
||||
fun offerFirst(e: E): Boolean
|
||||
fun offerLast(e: E): Boolean
|
||||
fun removeFirst(): E
|
||||
fun removeLast(): E
|
||||
fun pollFirst(): E
|
||||
fun pollLast(): E
|
||||
val first: E
|
||||
val last: E
|
||||
fun peekFirst(): E
|
||||
fun peekLast(): E
|
||||
fun removeFirstOccurrence(o: E): Boolean
|
||||
fun removeLastOccurrence(o: E): Boolean
|
||||
fun push(e: E)
|
||||
fun pop(): E
|
||||
fun descendingIterator(): Iterator<E>
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2020 Mamoe Technologies and contributors.
|
||||
* *
|
||||
* * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
* *
|
||||
* * https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.qqandroid.utils
|
||||
|
||||
import java.util.LinkedList
|
||||
|
||||
@Suppress("ACTUAL_WITHOUT_EXPECT")
|
||||
internal actual typealias LinkedList<E> = LinkedList<E>
|
@ -10,6 +10,7 @@
|
||||
package net.mamoe.mirai.qqandroid.utils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.Closeable
|
||||
@ -20,6 +21,7 @@ import java.io.BufferedInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.net.Socket
|
||||
import java.net.SocketException
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
@ -38,6 +40,7 @@ internal actual class PlatformSocket : Closeable {
|
||||
if (::socket.isInitialized) {
|
||||
socket.close()
|
||||
}
|
||||
thread.shutdownNow()
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
@ -67,15 +70,17 @@ internal actual class PlatformSocket : Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private val thread = Executors.newSingleThreadExecutor()
|
||||
|
||||
/**
|
||||
* @throws ReadPacketInternalException
|
||||
*/
|
||||
actual suspend fun read(): ByteReadPacket {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
actual suspend fun read(): ByteReadPacket = suspendCancellableCoroutine { cont ->
|
||||
thread.submit {
|
||||
kotlin.runCatching {
|
||||
readChannel.readPacketAtMost(Long.MAX_VALUE)
|
||||
} catch (e: IOException) {
|
||||
throw ReadPacketInternalException(e)
|
||||
}.let {
|
||||
cont.resumeWith(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,9 @@ import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent
|
||||
import net.mamoe.mirai.event.events.MemberJoinRequestEvent
|
||||
import net.mamoe.mirai.event.events.NewFriendRequestEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.action.BotNudge
|
||||
import net.mamoe.mirai.message.action.MemberNudge
|
||||
import net.mamoe.mirai.message.action.Nudge
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.utils.*
|
||||
@ -216,6 +219,15 @@ public abstract class Bot internal constructor(
|
||||
@JvmSynthetic
|
||||
public abstract suspend fun recall(source: MessageSource)
|
||||
|
||||
/**
|
||||
* 创建一个 "戳一戳" 消息
|
||||
*
|
||||
* @see MemberNudge.sendTo 发送这个戳一戳消息
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
public fun nudge(): BotNudge = BotNudge(this)
|
||||
|
||||
/**
|
||||
* 获取图片下载链接
|
||||
*
|
||||
@ -339,6 +351,14 @@ public abstract class Bot internal constructor(
|
||||
@JvmSynthetic
|
||||
public abstract suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent)
|
||||
|
||||
@Deprecated(
|
||||
"use member function.",
|
||||
replaceWith = ReplaceWith("nudge.sendTo(contact)"),
|
||||
level = DeprecationLevel.ERROR
|
||||
)
|
||||
@SinceMirai("1.3.0")
|
||||
public abstract suspend fun sendNudge(nudge: Nudge, receiver: Contact): Boolean
|
||||
|
||||
// endregion
|
||||
|
||||
/**
|
||||
|
@ -13,6 +13,7 @@ package net.mamoe.mirai
|
||||
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.Context
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
@ -45,6 +46,9 @@ public expect interface BotFactory {
|
||||
passwordMd5: ByteArray,
|
||||
configuration: BotConfiguration = BotConfiguration.Default
|
||||
): Bot
|
||||
|
||||
@SinceMirai("1.3.0")
|
||||
public companion object INSTANCE : BotFactory
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,10 +18,13 @@ import net.mamoe.mirai.event.events.FriendMessagePostSendEvent
|
||||
import net.mamoe.mirai.event.events.FriendMessagePreSendEvent
|
||||
import net.mamoe.mirai.message.FriendMessageEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.action.FriendNudge
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import net.mamoe.mirai.message.data.isContentEmpty
|
||||
import net.mamoe.mirai.message.recall
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
/**
|
||||
@ -70,6 +73,15 @@ public abstract class Friend : User(), CoroutineScope {
|
||||
@JvmSynthetic
|
||||
abstract override suspend fun sendMessage(message: Message): MessageReceipt<Friend>
|
||||
|
||||
/**
|
||||
* 创建一个 "戳一戳" 消息
|
||||
*
|
||||
* @see FriendNudge.sendTo 发送这个戳一戳消息
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
public final override fun nudge(): FriendNudge = FriendNudge(this)
|
||||
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "VIRTUAL_MEMBER_HIDDEN", "OVERRIDE_BY_INLINE")
|
||||
@kotlin.internal.InlineOnly
|
||||
@JvmSynthetic
|
||||
|
@ -16,10 +16,14 @@ import net.mamoe.mirai.JavaFriendlyAPI
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.getFriendOrNull
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.action.MemberNudge
|
||||
import net.mamoe.mirai.message.action.Nudge
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import net.mamoe.mirai.message.data.isContentEmpty
|
||||
import net.mamoe.mirai.message.recall
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import net.mamoe.mirai.utils.WeakRefProperty
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
import kotlin.time.Duration
|
||||
@ -157,6 +161,15 @@ public abstract class Member : MemberJavaFriendlyAPI, User() {
|
||||
@JvmSynthetic
|
||||
public abstract override suspend fun sendMessage(message: Message): MessageReceipt<Member>
|
||||
|
||||
/**
|
||||
* 创建一个 "戳一戳" 消息
|
||||
*
|
||||
* @see MemberNudge.sendTo 发送这个戳一戳消息
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
public final override fun nudge(): Nudge = MemberNudge(this)
|
||||
|
||||
/**
|
||||
* @see sendMessage
|
||||
*/
|
||||
|
@ -15,11 +15,17 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.action.FriendNudge
|
||||
import net.mamoe.mirai.message.action.Nudge
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import net.mamoe.mirai.message.data.isContentEmpty
|
||||
import net.mamoe.mirai.message.recall
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.OverFileSizeMaxException
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
/**
|
||||
@ -67,6 +73,15 @@ public abstract class User : Contact(), CoroutineScope {
|
||||
@JvmSynthetic
|
||||
public abstract override suspend fun sendMessage(message: Message): MessageReceipt<User>
|
||||
|
||||
/**
|
||||
* 创建一个 "戳一戳" 消息
|
||||
*
|
||||
* @see FriendNudge.sendTo 发送这个戳一戳消息
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
public abstract fun nudge(): Nudge
|
||||
|
||||
/**
|
||||
* @see sendMessage
|
||||
*/
|
||||
|
@ -0,0 +1,237 @@
|
||||
package net.mamoe.mirai.data
|
||||
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.jvm.JvmStatic
|
||||
|
||||
/**
|
||||
* 群荣誉信息
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
public enum class GroupHonorType(public val value: Int) {
|
||||
TALKATIVE(1), // 龙王
|
||||
PERFORMER(2), // 群聊之火
|
||||
LEGEND(3), // 群聊炽焰
|
||||
STRONG_NEWBIE(5), // 冒尖小春笋
|
||||
EMOTION(6), // 快乐源泉
|
||||
ACTIVE(7), // 活跃头衔
|
||||
EXCLUSIVE(8), // 特殊头衔
|
||||
MANAGE(9); // 管理头衔
|
||||
internal companion object {
|
||||
@JvmStatic
|
||||
internal fun deserializeFromInt(value: Int): GroupHonorType = values().first { it.value == value }
|
||||
}
|
||||
}
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
@Serializable
|
||||
public data class GroupHonorListData(
|
||||
@SerialName("acceptLanguages")
|
||||
val acceptLanguages: List<Language?>? = null,
|
||||
|
||||
@SerialName("gc")
|
||||
val gc: String?,
|
||||
|
||||
@Serializable(with = GroupHonorTypeSerializer::class)
|
||||
@SerialName("type")
|
||||
val type: GroupHonorType?,
|
||||
|
||||
@SerialName("uin")
|
||||
val uin: String?,
|
||||
|
||||
@SerialName("talkativeList")
|
||||
val talkativeList: List<Talkative?>? = null,
|
||||
|
||||
@SerialName("currentTalkative")
|
||||
val currentTalkative: CurrentTalkative? = null,
|
||||
|
||||
@SerialName("actorList")
|
||||
val actorList: List<Actor?>? = null,
|
||||
|
||||
@SerialName("legendList")
|
||||
val legendList: List<Actor?>? = null,
|
||||
|
||||
@SerialName("newbieList")
|
||||
val newbieList: List<Actor?>? = null,
|
||||
|
||||
@SerialName("strongnewbieList")
|
||||
val strongNewbieList: List<Actor?>? = null,
|
||||
|
||||
@SerialName("emotionList")
|
||||
val emotionList: List<Actor?>? = null,
|
||||
|
||||
@SerialName("levelname")
|
||||
val levelName: LevelName? = null,
|
||||
|
||||
@SerialName("manageList")
|
||||
val manageList: List<Tag?>? = null,
|
||||
|
||||
@SerialName("exclusiveList")
|
||||
val exclusiveList: List<Tag?>? = null,
|
||||
|
||||
@SerialName("activeObj")
|
||||
val activeObj: Map<String, List<Tag?>?>? = null, // Key为活跃等级名, 如`冒泡`
|
||||
|
||||
@SerialName("showActiveObj")
|
||||
val showActiveObj: Map<String, Boolean?>? = null,
|
||||
|
||||
@SerialName("myTitle")
|
||||
val myTitle: String?,
|
||||
|
||||
@SerialName("myIndex")
|
||||
val myIndex: Int? = 0,
|
||||
|
||||
@SerialName("myAvatar")
|
||||
val myAvatar: String?,
|
||||
|
||||
@SerialName("hasServerError")
|
||||
val hasServerError: Boolean?,
|
||||
|
||||
@SerialName("hwExcellentList")
|
||||
val hwExcellentList: List<Actor?>? = null
|
||||
) {
|
||||
@Serializable
|
||||
public data class Language(
|
||||
@SerialName("code")
|
||||
val code: String? = null,
|
||||
|
||||
@SerialName("script")
|
||||
val script: String? = null,
|
||||
|
||||
@SerialName("region")
|
||||
val region: String? = null,
|
||||
|
||||
@SerialName("quality")
|
||||
val quality: Double? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class Actor(
|
||||
@SerialName("uin")
|
||||
val uin: Long? = 0,
|
||||
|
||||
@SerialName("avatar")
|
||||
val avatar: String? = null,
|
||||
|
||||
@SerialName("name")
|
||||
val name: String? = null,
|
||||
|
||||
@SerialName("desc")
|
||||
val desc: String? = null,
|
||||
|
||||
@SerialName("btnText")
|
||||
val btnText: String? = null,
|
||||
|
||||
@SerialName("text")
|
||||
val text: String? = null,
|
||||
|
||||
@SerialName("icon")
|
||||
val icon: Int?
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class Talkative(
|
||||
@SerialName("uin")
|
||||
val uin: Long? = 0,
|
||||
|
||||
@SerialName("avatar")
|
||||
val avatar: String? = null,
|
||||
|
||||
@SerialName("name")
|
||||
val name: String? = null,
|
||||
|
||||
@SerialName("desc")
|
||||
val desc: String? = null,
|
||||
|
||||
@SerialName("btnText")
|
||||
val btnText: String? = null,
|
||||
|
||||
@SerialName("text")
|
||||
val text: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class CurrentTalkative(
|
||||
@SerialName("uin")
|
||||
val uin: Long? = 0,
|
||||
|
||||
@SerialName("day_count")
|
||||
val dayCount: Int? = null,
|
||||
|
||||
@SerialName("avatar")
|
||||
val avatar: String? = null,
|
||||
|
||||
@SerialName("avatar_size")
|
||||
val avatarSize: Int? = null,
|
||||
|
||||
@SerialName("nick")
|
||||
val nick: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class LevelName(
|
||||
@SerialName("lvln1")
|
||||
val lv1: String? = null,
|
||||
|
||||
@SerialName("lvln2")
|
||||
val lv2: String? = null,
|
||||
|
||||
@SerialName("lvln3")
|
||||
val lv3: String? = null,
|
||||
|
||||
@SerialName("lvln4")
|
||||
val lv4: String? = null,
|
||||
|
||||
@SerialName("lvln5")
|
||||
val lv5: String? = null,
|
||||
|
||||
@SerialName("lvln6")
|
||||
val lv6: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public data class Tag(
|
||||
@SerialName("uin")
|
||||
val uin: Long? = 0,
|
||||
|
||||
@SerialName("avatar")
|
||||
val avatar: String? = null,
|
||||
|
||||
@SerialName("name")
|
||||
val name: String? = null,
|
||||
|
||||
@SerialName("btnText")
|
||||
val btnText: String? = null,
|
||||
|
||||
@SerialName("text")
|
||||
val text: String? = null,
|
||||
|
||||
@SerialName("tag")
|
||||
val tag: String? = null, // 头衔
|
||||
|
||||
@SerialName("tagColor")
|
||||
val tagColor: String? = null
|
||||
)
|
||||
|
||||
@Serializer(forClass = GroupHonorType::class)
|
||||
public object GroupHonorTypeSerializer : KSerializer<GroupHonorType> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor("GroupHonorTypeSerializer", PrimitiveKind.INT)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: GroupHonorType) {
|
||||
encoder.encodeInt(value.value)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): GroupHonorType {
|
||||
return GroupHonorType.deserializeFromInt(decoder.decodeInt())
|
||||
}
|
||||
}
|
||||
}
|
@ -9,13 +9,10 @@
|
||||
|
||||
package net.mamoe.mirai.data
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
|
||||
/**
|
||||
* 群资料.
|
||||
*
|
||||
* 通过 [Bot._lowLevelQueryGroupInfo] 得到
|
||||
*/
|
||||
@LowLevelAPI
|
||||
public interface GroupInfo {
|
||||
|
@ -21,6 +21,8 @@
|
||||
- 服务器主动要求更换另一个服务器: RequireReconnect
|
||||
- Bot 重新登录: BotReloginEvent
|
||||
- Bot 头像改变: BotAvatarChangedEvent
|
||||
- (`1.2.0+`) Bot 昵称改变: BotNickChangedEvent
|
||||
- (`1.3.0+`) Bot 被戳: BotNudgedEvent
|
||||
|
||||
### [消息](message.kt)
|
||||
- (`1.1.0-`) 主动发送消息: MessageSendEvent
|
||||
@ -81,13 +83,17 @@
|
||||
##### 成员权限
|
||||
- 成员权限改变: MemberPermissionChangeEvent
|
||||
|
||||
##### 禁言
|
||||
##### 动作
|
||||
- 群成员被禁言: MemberMuteEvent
|
||||
- 群成员被取消禁言: MemberUnmuteEvent
|
||||
- (`1.3.0+`) 群员被戳: MemberNudgedEvent
|
||||
|
||||
### [好友](friend.kt)
|
||||
- 好友昵称改变: FriendRemarkChangeEvent
|
||||
- 成功添加了一个新好友: FriendAddEvent
|
||||
- 好友已被删除: FriendDeleteEvent
|
||||
- 一个账号请求添加机器人为好友: NewFriendRequestEvent
|
||||
- 好友头像改变: FriendAvatarChangedEvent
|
||||
- 好友头像改变: FriendAvatarChangedEvent
|
||||
- (`1.2.0+`) 好友昵称改变: FriendNickChangedEvent
|
||||
- (`1.2.0+`) 好友输入状态改变: FriendInputStatusChangedEvent
|
||||
- (`1.3.0+`) 好友被戳: FriendNudgedEvent
|
||||
|
@ -14,7 +14,9 @@
|
||||
package net.mamoe.mirai.event.events
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.User
|
||||
import net.mamoe.mirai.event.AbstractEvent
|
||||
import net.mamoe.mirai.message.action.Nudge
|
||||
import net.mamoe.mirai.qqandroid.network.Packet
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
@ -124,6 +126,31 @@ public data class BotNickChangedEvent(
|
||||
public val to: String
|
||||
) : BotEvent, Packet, AbstractEvent()
|
||||
|
||||
/**
|
||||
* [Bot] 被 [戳][Nudge] 的事件.
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
public data class BotNudgedEvent internal constructor(
|
||||
/**
|
||||
* 戳一戳的发起人,为 [Bot] 的某一好友, 或某一群员
|
||||
*/
|
||||
public val from: User,
|
||||
/**
|
||||
* 戳一戳的动作名称
|
||||
*/
|
||||
public val action: String,
|
||||
/**
|
||||
* 戳一戳中设置的自定义后缀
|
||||
*/
|
||||
public val suffix: String,
|
||||
) : BotEvent, Packet, AbstractEvent() {
|
||||
/**
|
||||
* 戳一戳的目标
|
||||
*/
|
||||
public override val bot: Bot get() = from.bot
|
||||
}
|
||||
|
||||
// region 图片
|
||||
|
||||
// endregion
|
||||
|
@ -112,8 +112,6 @@ public data class FriendAvatarChangedEvent internal constructor(
|
||||
public override val friend: Friend
|
||||
) : FriendEvent, Packet, AbstractEvent()
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* [Friend] 昵称改变事件, 在此事件广播时好友已经完成改名
|
||||
* @see BotNickChangedEvent
|
||||
@ -124,7 +122,7 @@ public data class FriendNickChangedEvent internal constructor(
|
||||
public val from: String,
|
||||
public val to: String
|
||||
) : FriendEvent, Packet, AbstractEvent()
|
||||
|
||||
|
||||
/**
|
||||
* 好友输入状态改变的事件,当开始输入文字、退出聊天窗口或清空输入框时会触发此事件
|
||||
*/
|
||||
|
@ -22,8 +22,10 @@ import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.event.AbstractEvent
|
||||
import net.mamoe.mirai.event.BroadcastControllable
|
||||
import net.mamoe.mirai.event.internal.MiraiAtomicBoolean
|
||||
import net.mamoe.mirai.message.action.Nudge
|
||||
import net.mamoe.mirai.qqandroid.network.Packet
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import net.mamoe.mirai.utils.internal.runBlocking
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import kotlin.jvm.*
|
||||
@ -130,6 +132,18 @@ public sealed class BotJoinGroupEvent : GroupEvent, BotPassiveEvent, Packet, Abs
|
||||
return "BotJoinGroupEvent.Invite(invitor=$invitor)"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 原群主通过 https://huifu.qq.com/ 恢复原来群主身份并入群,
|
||||
* [Bot] 是原群主
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
public data class Retrieve internal constructor(
|
||||
public override val group: Group
|
||||
) : BotJoinGroupEvent() {
|
||||
override fun toString(): String = "MemberJoinEvent.Retrieve(group=${group.id})"
|
||||
}
|
||||
}
|
||||
|
||||
// region 群设置
|
||||
@ -260,6 +274,17 @@ public sealed class MemberJoinEvent(
|
||||
) : MemberJoinEvent(member) {
|
||||
public override fun toString(): String = "MemberJoinEvent.Active(member=${member.id})"
|
||||
}
|
||||
|
||||
/**
|
||||
* 原群主通过 https://huifu.qq.com/ 恢复原来群主身份并入群,
|
||||
* 此时 [member] 的 [Member.permission] 肯定是 [MemberPermission.OWNER]
|
||||
*/
|
||||
@SinceMirai("1.3.0")
|
||||
public data class Retrieve internal constructor(
|
||||
public override val member: Member
|
||||
) : MemberJoinEvent(member) {
|
||||
override fun toString(): String = "MemberJoinEvent.Retrieve(member=${member.id})"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -487,4 +512,33 @@ public data class MemberUnmuteEvent internal constructor(
|
||||
|
||||
// endregion
|
||||
|
||||
// region 戳一戳
|
||||
|
||||
|
||||
/**
|
||||
* [Member] 被 [戳][Nudge] 的事件.
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
public data class MemberNudgedEvent internal constructor(
|
||||
/**
|
||||
* 戳一戳的发起人, 不可能是 bot
|
||||
*/
|
||||
public val from: Member,
|
||||
/**
|
||||
* 戳一戳的目标 (被戳的群员), 不可能是 bot
|
||||
*/
|
||||
public override val member: Member,
|
||||
/**
|
||||
* 戳一戳的动作名称
|
||||
*/
|
||||
public val action: String,
|
||||
/**
|
||||
* 戳一戳中设置的自定义后缀
|
||||
*/
|
||||
public val suffix: String,
|
||||
) : GroupMemberEvent, BotPassiveEvent, Packet, AbstractEvent()
|
||||
|
||||
// endregion
|
||||
|
||||
// endregion
|
||||
|
@ -55,13 +55,6 @@ public interface LowLevelBotAPIAccessor {
|
||||
@LowLevelAPI
|
||||
public suspend fun _lowLevelQueryGroupList(): Sequence<Long>
|
||||
|
||||
/**
|
||||
* 向服务器查询群资料. 获得的仅为当前时刻的资料.
|
||||
* 请优先使用 [Bot.getGroup] 然后查看群资料.
|
||||
*/
|
||||
@LowLevelAPI
|
||||
public suspend fun _lowLevelQueryGroupInfo(groupCode: Long): GroupInfo
|
||||
|
||||
/**
|
||||
* 向服务器查询群成员列表.
|
||||
* 请优先使用 [Bot.getGroup], [Group.members] 查看群成员.
|
||||
@ -122,6 +115,15 @@ public interface LowLevelBotAPIAccessor {
|
||||
public suspend fun _lowLevelGetGroupActiveData(groupId: Long, page: Int = -1): GroupActiveData
|
||||
|
||||
|
||||
/**
|
||||
* 获取群荣誉信息
|
||||
*/
|
||||
@SinceMirai("1.3.0")
|
||||
@LowLevelAPI
|
||||
@MiraiExperimentalAPI
|
||||
public suspend fun _lowLevelGetGroupHonorListData(groupId: Long, type: GroupHonorType): GroupHonorListData?
|
||||
|
||||
|
||||
/**
|
||||
* 处理一个账号请求添加机器人为好友的事件
|
||||
*/
|
||||
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2020 Mamoe Technologies and contributors.
|
||||
* *
|
||||
* * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
* *
|
||||
* * https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.message.action
|
||||
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.event.events.BotNudgedEvent
|
||||
import net.mamoe.mirai.event.events.MemberNudgedEvent
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
|
||||
/**
|
||||
* 一个 "戳一戳" 消息.
|
||||
*
|
||||
* 仅在手机 QQ 8.4.0 左右版本才受支持. 其他客户端会忽略这些消息.
|
||||
*
|
||||
* @see User.nudge 创建 [Nudge] 对象
|
||||
* @see Bot.nudge 创建 [Nudge] 对象
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
public sealed class Nudge {
|
||||
/**
|
||||
* 戳的对象. 即 "A 戳了 B" 中的 "B".
|
||||
*/
|
||||
public abstract val target: ContactOrBot // User or Bot
|
||||
|
||||
/**
|
||||
* 发送戳一戳该成员的消息.
|
||||
*
|
||||
* 需要 [使用协议][BotConfiguration.protocol] [MiraiProtocol.ANDROID_PHONE].
|
||||
*
|
||||
* @param receiver 这条 "戳一戳" 消息的接收对象. (不是 "戳" 动作的对象, 而是接收 "A 戳了 B" 这条消息的对象)
|
||||
*
|
||||
* @see MemberNudgedEvent 成员被戳事件
|
||||
* @see BotNudgedEvent [Bot] 被戳事件
|
||||
*
|
||||
* @throws UnsupportedOperationException 当未使用 [安卓协议][MiraiProtocol.ANDROID_PHONE] 时抛出
|
||||
*
|
||||
* @return 成功发送时为 `true`. 若对方禁用 "戳一戳" 功能, 返回 `false`.
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
@MiraiExperimentalAPI
|
||||
public suspend fun sendTo(receiver: Contact): Boolean {
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
return receiver.bot.sendNudge(this, receiver)
|
||||
}
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 发送戳一戳该成员的消息.
|
||||
*
|
||||
* 需要 [使用协议][BotConfiguration.protocol] [MiraiProtocol.ANDROID_PHONE].
|
||||
*
|
||||
* @see MemberNudgedEvent 成员被戳事件
|
||||
* @see BotNudgedEvent [Bot] 被戳事件
|
||||
*
|
||||
* @throws UnsupportedOperationException 当未使用 [安卓协议][MiraiProtocol.ANDROID_PHONE] 时抛出
|
||||
*
|
||||
* @return 成功发送时为 `true`. 若对方禁用 "戳一戳" 功能, 返回 `false`.
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@JvmBlockingBridge
|
||||
public suspend fun Contact.sendNudge(nudge: Nudge): Boolean = nudge.sendTo(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Bot.nudge
|
||||
* @see Nudge
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
public data class BotNudge(
|
||||
public override val target: Bot
|
||||
) : Nudge()
|
||||
|
||||
/**
|
||||
* @see User.nudge
|
||||
* @see Nudge
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
public sealed class UserNudge : Nudge() {
|
||||
public abstract override val target: User
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Member.nudge
|
||||
* @see Nudge
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
public data class MemberNudge(
|
||||
public override val target: Member
|
||||
) : UserNudge()
|
||||
|
||||
/**
|
||||
* @see Friend.nudge
|
||||
* @see Nudge
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("1.3.0")
|
||||
public data class FriendNudge(
|
||||
public override val target: Friend
|
||||
) : UserNudge()
|
@ -47,9 +47,11 @@ public class Voice @MiraiInternalAPI constructor(
|
||||
}
|
||||
|
||||
public val url: String?
|
||||
get() =
|
||||
if (_url.startsWith("http")) _url
|
||||
else null
|
||||
get() = when {
|
||||
_url.isBlank() -> null
|
||||
_url.startsWith("http") -> _url
|
||||
else -> "http://grouptalk.c2c.qq.com$_url"
|
||||
}
|
||||
|
||||
private var _stringValue: String? = null
|
||||
get() = field ?: kotlin.run {
|
||||
|
@ -15,6 +15,7 @@ package net.mamoe.mirai
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.Context
|
||||
import net.mamoe.mirai.utils.ContextImpl
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
|
||||
/**
|
||||
* 构造 [Bot] 的工厂. 这是 [Bot] 唯一的构造方式.
|
||||
@ -52,6 +53,16 @@ public actual interface BotFactory {
|
||||
configuration: BotConfiguration
|
||||
): Bot
|
||||
|
||||
@SinceMirai("1.3.0")
|
||||
public actual companion object INSTANCE : BotFactory {
|
||||
override fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration): Bot {
|
||||
return factory.Bot(context, qq, password, configuration)
|
||||
}
|
||||
|
||||
override fun Bot(context: Context, qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration): Bot {
|
||||
return factory.Bot(context, qq, passwordMd5, configuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,7 +72,12 @@ public actual interface BotFactory {
|
||||
*/
|
||||
@JvmName("newBot")
|
||||
@JvmOverloads
|
||||
public fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot =
|
||||
public fun Bot(
|
||||
context: Context,
|
||||
qq: Long,
|
||||
password: String,
|
||||
configuration: BotConfiguration = BotConfiguration.Default
|
||||
): Bot =
|
||||
factory.Bot(context, qq, password, configuration)
|
||||
|
||||
/**
|
||||
@ -109,7 +125,12 @@ public fun Bot(
|
||||
* 自动加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline fun Bot(context: Context, qq: Long, passwordMd5: ByteArray, configuration: (BotConfiguration.() -> Unit)): Bot =
|
||||
public inline fun Bot(
|
||||
context: Context,
|
||||
qq: Long,
|
||||
passwordMd5: ByteArray,
|
||||
configuration: (BotConfiguration.() -> Unit)
|
||||
): Bot =
|
||||
factory.Bot(context, qq, passwordMd5, BotConfiguration().apply(configuration))
|
||||
|
||||
|
||||
|
@ -59,7 +59,7 @@ public actual open class PlatformLogger @JvmOverloads constructor(
|
||||
*/
|
||||
@SinceMirai("1.1.0")
|
||||
protected open fun printLog(message: String?, priority: SimpleLogger.LogPriority) {
|
||||
if (isColored) output("${priority.color}$currentTimeFormatted ${priority.simpleName}/$identity: $message")
|
||||
if (isColored) output("${priority.color}$currentTimeFormatted ${priority.simpleName}/$identity: $message${Color.RESET}")
|
||||
else output("$currentTimeFormatted ${priority.simpleName}/$identity: $message")
|
||||
}
|
||||
|
||||
@ -143,4 +143,4 @@ internal val Throwable.stackTraceString
|
||||
get() = ByteArrayOutputStream().run {
|
||||
printStackTrace(PrintStream(this))
|
||||
String(this.toByteArray())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user