Merge branch 'dev'

This commit is contained in:
Him188 2020-09-16 12:36:04 +08:00
commit 9eed2b94ae
38 changed files with 1264 additions and 238 deletions

View File

@ -1,5 +1,35 @@
# Version 1.x # 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 ## `1.2.3` 2020/9/11
- 在同步事件失败时添加重试, 改善 #249, #482, #542, #567, #590 - 在同步事件失败时添加重试, 改善 #249, #482, #542, #567, #590
- 修复不断重连同一个服务器的问题 (#589) - 修复不断重连同一个服务器的问题 (#589)

View File

@ -9,7 +9,7 @@
object Versions { object Versions {
object Mirai { object Mirai {
const val version = "1.2.3" const val version = "1.3.0"
} }
object Kotlin { object Kotlin {

View File

@ -27,6 +27,7 @@ import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.network.ForceOfflineException import net.mamoe.mirai.network.ForceOfflineException
import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.qqandroid.network.BotNetworkHandler 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.qqandroid.network.closeAndJoin
import net.mamoe.mirai.supervisorJob import net.mamoe.mirai.supervisorJob
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
@ -91,7 +92,11 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
} }
bot.logger.info { "Connection lost, retrying login" } 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 var failed = false
val time = measureTime { val time = measureTime {

View File

@ -32,6 +32,7 @@ import net.mamoe.mirai.event.events.NewFriendRequestEvent
import net.mamoe.mirai.event.internal.MiraiAtomicBoolean import net.mamoe.mirai.event.internal.MiraiAtomicBoolean
import net.mamoe.mirai.getGroupOrNull import net.mamoe.mirai.getGroupOrNull
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.action.Nudge
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.qqandroid.contact.FriendImpl 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.QQAndroidBotNetworkHandler
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper 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.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.LongMsg import net.mamoe.mirai.qqandroid.network.protocol.data.proto.LongMsg
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.* import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.*
@ -226,6 +228,30 @@ internal class QQAndroidBot constructor(
accept = accept 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()) override val friends: ContactList<Friend> = ContactList(LockFreeLinkedList())
@JvmField internal var cachedNick: String? = null override lateinit var nick: String
override val nick: String get() = cachedNick ?: selfInfo.nick.also { cachedNick = it }
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 { override val selfQQ: Friend by lazy {
@OptIn(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
@ -311,12 +342,22 @@ internal abstract class QQAndroidBotBase constructor(
}.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode } }.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode }
} }
@Suppress(
"DeprecatedCallableAddReplaceWith",
"FunctionName",
"RedundantSuspendModifier",
"unused",
"unused_parameter"
)
@Deprecated("")
@OptIn(LowLevelAPI::class) @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( TroopManagement.GetGroupInfo(
client = bot.client, client = bot.client,
groupCode = groupCode groupCode = groupCode
).sendAndExpect<GroupInfoImpl>(retry = 3) ).sendAndExpect<GroupInfoImpl>(retry = 3).also { it.stTroopNum = stTroopNum }*/
} }
@OptIn(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
@ -608,6 +649,30 @@ internal abstract class QQAndroidBotBase constructor(
return json.decodeFromString(GroupActiveData.serializer(), rep) 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 @JvmSynthetic
@LowLevelAPI @LowLevelAPI
@MiraiExperimentalAPI @MiraiExperimentalAPI

View File

@ -124,6 +124,9 @@ internal class MemberImpl constructor(
@Suppress("PropertyName") @Suppress("PropertyName")
var _muteTimestamp: Int = memberInfo.muteTimestamp var _muteTimestamp: Int = memberInfo.muteTimestamp
@Suppress("PropertyName")
var _nudgeTimestamp: Long = 0L
override val muteTimeRemaining: Int override val muteTimeRemaining: Int
get() = if (_muteTimestamp == 0 || _muteTimestamp == 0xFFFFFFFF.toInt()) { get() = if (_muteTimestamp == 0 || _muteTimestamp == 0xFFFFFFFF.toInt()) {
0 0
@ -197,7 +200,7 @@ internal class MemberImpl constructor(
private fun checkBotPermissionHigherThanThis(operationName: String) { private fun checkBotPermissionHigherThanThis(operationName: String) {
check(group.botPermission > this.permission) { check(group.botPermission > this.permission) {
throw PermissionDeniedException( throw PermissionDeniedException(
"`$operationName` operation requires a higher permission, while" + "`$operationName` operation requires a higher permission, while " +
"${group.botPermission} < ${this.permission}" "${group.botPermission} < ${this.permission}"
) )
} }
@ -219,7 +222,6 @@ internal class MemberImpl constructor(
net.mamoe.mirai.event.events.MemberUnmuteEvent(this@MemberImpl, null).broadcast() net.mamoe.mirai.event.events.MemberUnmuteEvent(this@MemberImpl, null).broadcast()
} }
@JvmSynthetic @JvmSynthetic
override suspend fun kick(message: String) { override suspend fun kick(message: String) {
checkBotPermissionHigherThanThis("kick") checkBotPermissionHigherThanThis("kick")

View File

@ -113,7 +113,9 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
} }
is At -> { is At -> {
elements.add(ImMsgBody.Elem(text = it.toJceData())) 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 -> { is PokeMessage -> {
elements.add( elements.add(
@ -151,7 +153,9 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
when (val source = it.source) { when (val source = it.source) {
is OnlineMessageSource.Incoming.FromGroup -> { is OnlineMessageSource.Incoming.FromGroup -> {
transformOneMessage(At(source.sender)) 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 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) add(element)
last = element last = element
} }
@ -316,7 +331,7 @@ internal val MIRAI_CUSTOM_ELEM_TYPE = "mirai".hashCode() // 103904510
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: Bot, list: MessageChainBuilder) { internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: Bot, list: MessageChainBuilder) {
// (this._miraiContentToString()) // (this._miraiContentToString().soutv())
this.forEach { element -> this.forEach { element ->
when { when {
element.srcMsg != null -> { element.srcMsg != null -> {

View File

@ -246,7 +246,6 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
// self info // self info
data.selfInfo?.run { data.selfInfo?.run {
bot.cachedNick = null
bot.selfInfo = this bot.selfInfo = this
// bot.remark = remark ?: "" // bot.remark = remark ?: ""
// bot.sex = sex // bot.sex = sex
@ -272,28 +271,11 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
suspend fun StTroopNum.reloadGroup() { suspend fun StTroopNum.reloadGroup() {
retryCatching(3) { retryCatching(3) {
bot.groups.delegate.addLast( bot.groups.delegate.addLast(
@Suppress("DuplicatedCode")
GroupImpl( GroupImpl(
bot = bot, bot = bot,
coroutineContext = bot.coroutineContext, coroutineContext = bot.coroutineContext,
id = groupCode, id = groupCode,
groupInfo = bot._lowLevelQueryGroupInfo(groupCode).apply { groupInfo = GroupInfoImpl(this),
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
},
members = bot._lowLevelQueryGroupMemberList( members = bot._lowLevelQueryGroupMemberList(
groupUin, groupUin,
groupCode, groupCode,

View File

@ -20,6 +20,7 @@ import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.network.NoServerAvailableException import net.mamoe.mirai.network.NoServerAvailableException
import net.mamoe.mirai.qqandroid.BotAccount import net.mamoe.mirai.qqandroid.BotAccount
import net.mamoe.mirai.qqandroid.QQAndroidBot 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.data.jce.FileStoragePushFSSvcListFuckKotlin
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
@ -191,9 +192,6 @@ internal open class QQAndroidClient(
private val highwayDataTransSequenceIdForApplyUp: AtomicInt = atomic(77918) private val highwayDataTransSequenceIdForApplyUp: AtomicInt = atomic(77918)
internal fun nextHighwayDataTransSequenceIdForApplyUp(): Int = highwayDataTransSequenceIdForApplyUp.getAndAdd(2) 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 val appClientVersion: Int = 0
var networkType: NetworkType = NetworkType.WIFI var networkType: NetworkType = NetworkType.WIFI
@ -205,16 +203,49 @@ internal open class QQAndroidClient(
*/ */
val protocolVersion: Short = 8001 val protocolVersion: Short = 8001
class C2cMessageSyncData { class MessageSvcSyncData {
val firstNotify: AtomicBoolean = atomic(true) val firstNotify: AtomicBoolean = atomic(true)
@Volatile @Volatile
var syncCookie: ByteArray? = null var syncCookie: ByteArray? = null
var pubAccountCookie = EMPTY_BYTE_ARRAY var pubAccountCookie = EMPTY_BYTE_ARRAY
var msgCtrlBuf: ByteArray = 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()
/* /*
* 以下登录使用 * 以下登录使用

View File

@ -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
}
}

View File

@ -172,13 +172,13 @@ internal class Oidb0x88d : ProtoBuf {
@ProtoNumber(12) @JvmField val groupDefaultPage: Int? = null, @ProtoNumber(12) @JvmField val groupDefaultPage: Int? = null,
@ProtoNumber(13) @JvmField val groupInfoSeq: Int? = null, @ProtoNumber(13) @JvmField val groupInfoSeq: Int? = null,
@ProtoNumber(14) @JvmField val groupRoamingTime: Int? = null, @ProtoNumber(14) @JvmField val groupRoamingTime: Int? = null,
@ProtoNumber(15) var groupName: String? = null, @ProtoNumber(15) @JvmField val groupName: String? = null,
@ProtoNumber(16) var groupMemo: String? = null, @ProtoNumber(16) @JvmField val groupMemo: String? = null,
@ProtoNumber(17) @JvmField val ingGroupFingerMemo: String? = null, @ProtoNumber(17) @JvmField val ingGroupFingerMemo: String? = null,
@ProtoNumber(18) @JvmField val ingGroupClassText: String? = null, @ProtoNumber(18) @JvmField val ingGroupClassText: String? = null,
@ProtoNumber(19) @JvmField val groupAllianceCode: List<Int>? = null, @ProtoNumber(19) @JvmField val groupAllianceCode: List<Int>? = null,
@ProtoNumber(20) @JvmField val groupExtraAdmNum: 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(22) @JvmField val groupCurMsgSeq: Int? = null,
@ProtoNumber(23) @JvmField val groupLastMsgTime: Int? = null, @ProtoNumber(23) @JvmField val groupLastMsgTime: Int? = null,
@ProtoNumber(24) @JvmField val ingGroupQuestion: String? = null, @ProtoNumber(24) @JvmField val ingGroupQuestion: String? = null,
@ -258,7 +258,6 @@ internal class Oidb0x88d : ProtoBuf {
@ProtoNumber(98) @JvmField val cmduinRingtoneId: Int? = null, @ProtoNumber(98) @JvmField val cmduinRingtoneId: Int? = null,
@ProtoNumber(99) @JvmField val groupFlagext4: Int? = null, @ProtoNumber(99) @JvmField val groupFlagext4: Int? = null,
@ProtoNumber(100) @JvmField val groupFreezeReason: Int? = null, @ProtoNumber(100) @JvmField val groupFreezeReason: Int? = null,
@ProtoNumber(101) var groupCode: Long? = null // mirai 添加
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
@ -2423,3 +2422,17 @@ internal class Cmd0x6ce : ProtoBuf {
) : 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
}

View File

@ -13,10 +13,7 @@ import kotlinx.io.core.*
import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.Event
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.Packet 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.*
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.image.ImgStore 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.image.LongConn
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.* import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.*
@ -148,9 +145,10 @@ internal object KnownPacketFactories {
TroopManagement.EditSpecialTitle, TroopManagement.EditSpecialTitle,
TroopManagement.Mute, TroopManagement.Mute,
TroopManagement.GroupOperation, TroopManagement.GroupOperation,
TroopManagement.GetGroupInfo, // TroopManagement.GetGroupInfo,
TroopManagement.EditGroupNametag, TroopManagement.EditGroupNametag,
TroopManagement.Kick, TroopManagement.Kick,
NudgePacket,
Heartbeat.Alive, Heartbeat.Alive,
PbMessageSvc.PbMsgWithDraw, PbMessageSvc.PbMsgWithDraw,
MultiMsg.ApplyUp, MultiMsg.ApplyUp,

View File

@ -58,7 +58,7 @@ internal class NewContact {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): NewFriendRequestEvent? { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): NewFriendRequestEvent? {
readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()).run { readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()).run {
val struct = friendmsgs?.firstOrNull() val struct = friendmsgs?.firstOrNull()// 会有重复且无法过滤, 不要用 map
return struct?.msg?.run { return struct?.msg?.run {
NewFriendRequestEvent( NewFriendRequestEvent(
bot, bot,
@ -145,9 +145,16 @@ internal class NewContact {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Packet? { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Packet? {
readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()).run { 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") //this.soutv("SystemMsg")
when (subType) { when (subType) {
1 -> { // 处理被邀请入群 或 处理成员入群申请 1 -> { // 处理被邀请入群 或 处理成员入群申请

View File

@ -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())
)
)
}
}
}

View File

@ -14,13 +14,13 @@ import kotlinx.io.core.buildPacket
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.QQAndroidClient 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.ModifyGroupCardReq
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket 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.jce.stUinInfo
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.* import net.mamoe.mirai.qqandroid.network.protocol.data.proto.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
@ -32,19 +32,19 @@ import net.mamoe.mirai.data.GroupInfo as MiraiGroupInfo
@OptIn(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
internal class GroupInfoImpl( internal class GroupInfoImpl(
internal val delegate: Oidb0x88d.GroupInfo private val stTroopNum: StTroopNum
) : MiraiGroupInfo, Packet, Packet.NoLog { ) : MiraiGroupInfo, Packet, Packet.NoLog {
override val uin: Long get() = delegate.groupUin ?: error("cannot find groupUin") override val uin: Long get() = stTroopNum.groupUin
override val owner: Long get() = delegate.groupOwner ?: error("cannot find groupOwner") override val owner: Long get() = stTroopNum.dwGroupOwnerUin
override val groupCode: Long get() = Group.calculateGroupCodeByGroupUin(uin) override val groupCode: Long get() = stTroopNum.groupCode
override val memo: String get() = delegate.groupMemo ?: error("cannot find groupMemo") override val memo: String get() = stTroopNum.groupMemo
override val name: String get() = delegate.groupName ?: delegate.longGroupName ?: error("cannot find groupName") override val name: String get() = stTroopNum.groupName
override val allowMemberInvite get() = delegate.groupFlagExt?.and(0x000000c0) != 0 override val allowMemberInvite get() = stTroopNum.dwGroupFlagExt?.and(0x000000c0) != 0L
override val allowAnonymousChat get() = delegate.groupFlagExt?.and(0x40000000) == 0 override val allowAnonymousChat get() = stTroopNum.dwGroupFlagExt?.and(0x40000000) == 0L
override val autoApprove get() = delegate.groupFlagext3?.and(0x00100000) == 0 override val autoApprove get() = stTroopNum.dwGroupFlagExt3?.and(0x00100000) == 0L
override val confessTalk get() = delegate.groupFlagext3?.and(0x00002000) == 0 override val confessTalk get() = stTroopNum.dwGroupFlagExt3?.and(0x00002000) == 0L
override val muteAll: Boolean get() = delegate.shutupTimestamp != 0 override val muteAll: Boolean get() = stTroopNum.dwShutUpTimestamp != 0L
override val botMuteTimestamp: Int get() = delegate.shutupTimestampMe ?: 0 override val botMuteTimestamp: Int get() = stTroopNum.dwMyShutUpTimestamp?.toInt() ?: 0
} }
internal class TroopManagement { internal class TroopManagement {
@ -88,6 +88,7 @@ internal class TroopManagement {
internal object GetGroupInfo : OutgoingPacketFactory<GroupInfoImpl>("OidbSvc.0x88d_7") { internal object GetGroupInfo : OutgoingPacketFactory<GroupInfoImpl>("OidbSvc.0x88d_7") {
@Deprecated("")
operator fun invoke( operator fun invoke(
client: QQAndroidClient, client: QQAndroidClient,
groupCode: Long groupCode: Long
@ -107,8 +108,8 @@ internal class TroopManagement {
groupFlagExt = 0, groupFlagExt = 0,
groupFlagext4 = 0, groupFlagext4 = 0,
groupFlag = 0, groupFlag = 0,
groupFlagext3 = 0,//获取confess groupFlagext3 = 1,//获取confess
noFingerOpenFlag = 0, noFingerOpenFlag = 1,
cmduinFlagEx2 = 0, cmduinFlagEx2 = 0,
groupTypeFlag = 0, groupTypeFlag = 0,
appPrivilegeFlag = 0, appPrivilegeFlag = 0,
@ -135,12 +136,14 @@ internal class TroopManagement {
} }
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GroupInfoImpl { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GroupInfoImpl {
error("deprecated")
/*
with( with(
this.readBytes() this.readBytes()
.loadAs(OidbSso.OIDBSSOPkg.serializer()).bodybuffer.loadAs(Oidb0x88d.RspBody.serializer()).stzrspgroupinfo!![0].stgroupinfo!! .loadAs(OidbSso.OIDBSSOPkg.serializer()).bodybuffer.loadAs(Oidb0x88d.RspBody.serializer()).stzrspgroupinfo!![0].stgroupinfo!!
) { ) {
return GroupInfoImpl(this) return GroupInfoImpl()
} }*/
} }
} }

View File

@ -17,7 +17,6 @@ import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
@ -63,10 +62,6 @@ import kotlin.random.Random
internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Response>("MessageSvc.PbGetMsg") { internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Response>("MessageSvc.PbGetMsg") {
private val msgUidQueue = ArrayDeque<Long>()
private val msgUidSet = hashSetOf<Long>()
private val msgQueueMutex = Mutex()
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
operator fun invoke( operator fun invoke(
client: QQAndroidClient, client: QQAndroidClient,
@ -88,7 +83,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
whisperSessionId = 0, whisperSessionId = 0,
syncFlag = syncFlag, syncFlag = syncFlag,
// serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY, // serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY,
syncCookie = syncCookie ?: client.c2cMessageSync.syncCookie syncCookie = syncCookie ?: client.syncingController.syncCookie
?: byteArrayOf()//.also { client.c2cMessageSync.syncCookie = it }, ?: byteArrayOf()//.also { client.c2cMessageSync.syncCookie = it },
// syncFlag = client.c2cMessageSync.syncFlag, // syncFlag = client.c2cMessageSync.syncFlag,
//msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf, //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) @OptIn(FlowPreview::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { 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 // 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) { when (resp.msgRspType) {
0 -> { 0 -> {
bot.client.c2cMessageSync.syncCookie = resp.syncCookie bot.client.syncingController.syncCookie = resp.syncCookie
bot.client.c2cMessageSync.pubAccountCookie = resp.pubAccountCookie bot.client.syncingController.pubAccountCookie = resp.pubAccountCookie
} }
1 -> { 1 -> {
bot.client.c2cMessageSync.syncCookie = resp.syncCookie bot.client.syncingController.syncCookie = resp.syncCookie
} }
2 -> { 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.msgRspType._miraiContentToString())
// bot.logger.debug(resp.syncCookie._miraiContentToString()) // bot.logger.debug(resp.syncCookie._miraiContentToString())
bot.client.c2cMessageSync.msgCtrlBuf = resp.msgCtrlBuf bot.client.syncingController.msgCtrlBuf = resp.msgCtrlBuf
if (resp.uinPairMsgs == null) { if (resp.uinPairMsgs == null) {
return EmptyResponse return EmptyResponse
@ -184,37 +190,22 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
}.also { }.also {
MessageSvcPbDeleteMsg.delete(bot, it) // 删除消息 MessageSvcPbDeleteMsg.delete(bot, it) // 删除消息
} }
.mapNotNull<MsgComm.Msg, Packet> { msg -> .mapNotNull { msg ->
if (!bot.client.syncingController.pbGetMessageCacheList.addCache(
msgQueueMutex.lock() QQAndroidClient.MessageSvcSyncData.PbGetMessageSyncId(
val msgUid = msg.msgHead.msgUid uid = msg.msgHead.msgUid,
if (msgUidSet.size > 50) { sequence = msg.msgHead.msgSeq,
msgUidSet.remove(msgUidQueue.removeFirst()) time = msg.msgHead.msgTime
} )
if (!msgUidSet.add(msgUid)) { )
msgQueueMutex.unlock() ) return@mapNotNull null
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)
}
}
when (msg.msgHead.msgType) { when (msg.msgHead.msgType) {
33 -> bot.groupListModifyLock.withLock { 33 -> bot.groupListModifyLock.withLock {
if (msg.msgHead.authUin == bot.id) { 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: 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 // package: groupUin + 01 CA CC 69 8B 83 + invitorUin + length(06) + string + magicKey
val invitorUin = msg.msgBody.msgContent.sliceArray(10..13).toInt().toLong() val invitorUin = msg.msgBody.msgContent.sliceArray(10..13).toInt().toLong()
@ -256,7 +247,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
} }
38 -> bot.groupListModifyLock.withLock { // 建群 38 -> bot.groupListModifyLock.withLock { // 建群
return@mapNotNull createGroupForBot(msg.msgHead.fromUin) return@mapNotNull bot.createGroupForBot(msg.msgHead.fromUin)
?.let { BotJoinGroupEvent.Active(it) } ?.let { BotJoinGroupEvent.Active(it) }
} }
@ -265,7 +256,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
// msg.msgHead.authUin: 处理人 // msg.msgHead.authUin: 处理人
return@mapNotNull if (msg.msgHead.toUin == bot.id) { return@mapNotNull if (msg.msgHead.toUin == bot.id) {
createGroupForBot(msg.msgHead.fromUin) bot.createGroupForBot(msg.msgHead.fromUin)
?.let { BotJoinGroupEvent.Active(it) } ?.let { BotJoinGroupEvent.Active(it) }
} else { } else {
null null
@ -427,7 +418,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
MessageSvcPbGetMsg( MessageSvcPbGetMsg(
client, client,
MsgSvc.SyncFlag.CONTINUE, MsgSvc.SyncFlag.CONTINUE,
bot.client.c2cMessageSync.syncCookie bot.client.syncingController.syncCookie
).sendAndExpect<Packet>() ).sendAndExpect<Packet>()
} }
return return
@ -438,7 +429,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
MessageSvcPbGetMsg( MessageSvcPbGetMsg(
client, client,
MsgSvc.SyncFlag.CONTINUE, MsgSvc.SyncFlag.CONTINUE,
bot.client.c2cMessageSync.syncCookie bot.client.syncingController.syncCookie
).sendAndExpect<Packet>() ).sendAndExpect<Packet>()
} }
return return
@ -454,28 +445,11 @@ internal suspend fun QQAndroidBot.getNewGroup(groupCode: Long): Group? {
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(timeoutMillis = 10_000, retry = 5) .sendAndExpect<FriendList.GetTroopListSimplify.Response>(timeoutMillis = 10_000, retry = 5)
}.groups.firstOrNull { it.groupCode == groupCode } ?: return null }.groups.firstOrNull { it.groupCode == groupCode } ?: return null
@Suppress("DuplicatedCode")
return GroupImpl( return GroupImpl(
bot = this, bot = this,
coroutineContext = coroutineContext, coroutineContext = coroutineContext,
id = groupCode, id = groupCode,
groupInfo = _lowLevelQueryGroupInfo(troopNum.groupCode).apply { groupInfo = GroupInfoImpl(troopNum),
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
},
members = _lowLevelQueryGroupMemberList( members = _lowLevelQueryGroupMemberList(
troopNum.groupUin, troopNum.groupUin,
troopNum.groupCode, troopNum.groupCode,

View File

@ -80,7 +80,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
), ),
msgSeq = source.sequenceId, msgSeq = source.sequenceId,
msgRand = source.internalId, msgRand = source.internalId,
syncCookie = client.c2cMessageSync.syncCookie ?: byteArrayOf() syncCookie = client.syncingController.syncCookie ?: byteArrayOf()
// msgVia = 1 // msgVia = 1
) )
) )
@ -110,7 +110,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
), ),
msgSeq = source.sequenceId, msgSeq = source.sequenceId,
msgRand = source.internalId, msgRand = source.internalId,
syncCookie = client.c2cMessageSync.syncCookie ?: byteArrayOf() syncCookie = client.syncingController.syncCookie ?: byteArrayOf()
) )
) )
} }

View File

@ -31,13 +31,13 @@ internal object MessageSvcPushNotify : IncomingPacketFactory<RequestPushNotify>(
override suspend fun QQAndroidBot.handle(packet: RequestPushNotify, sequenceId: Int): OutgoingPacket? { override suspend fun QQAndroidBot.handle(packet: RequestPushNotify, sequenceId: Int): OutgoingPacket? {
client.c2cMessageSync.firstNotify.loop { firstNotify -> client.syncingController.firstNotify.loop { firstNotify ->
network.run { network.run {
return MessageSvcPbGetMsg( return MessageSvcPbGetMsg(
client, client,
MsgSvc.SyncFlag.START, MsgSvc.SyncFlag.START,
if (firstNotify) { if (firstNotify) {
if (!client.c2cMessageSync.firstNotify.compareAndSet(firstNotify, false)) { if (!client.syncingController.firstNotify.compareAndSet(firstNotify, false)) {
return@loop return@loop
} }
null null

View File

@ -20,15 +20,16 @@ import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt import kotlinx.io.core.readUInt
import net.mamoe.mirai.JavaFriendlyAPI import net.mamoe.mirai.JavaFriendlyAPI
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.event.events.BotGroupPermissionChangeEvent import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.event.events.MemberLeaveEvent import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.event.events.MemberPermissionChangeEvent
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.contact.GroupImpl import net.mamoe.mirai.qqandroid.contact.GroupImpl
import net.mamoe.mirai.qqandroid.contact.MemberImpl import net.mamoe.mirai.qqandroid.contact.MemberImpl
import net.mamoe.mirai.qqandroid.contact.checkIsMemberImpl import net.mamoe.mirai.qqandroid.contact.checkIsMemberImpl
import net.mamoe.mirai.qqandroid.message.contextualBugReportException 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.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.data.proto.OnlinePushTrans
import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket 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? { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
val content = this.readProtoBuf(OnlinePushTrans.PbMsgInfo.serializer()) val content = this.readProtoBuf(OnlinePushTrans.PbMsgInfo.serializer())
if (!bot.client.syncingController.pbPushTransMsgCacheList.addCache(
if (!bot.client.pbPushTransMsgCacheList.ensureNoDuplication(content.msgSeq)) { content.run {
QQAndroidClient.MessageSvcSyncData.PbPushTransMsgSyncId(msgUid, msgSeq, msgTime)
}
)
) {
return null return null
} }
// bot.network.logger.debug { content._miraiContentToString() }
content.msgData.read<Unit> { content.msgData.read<Unit> {
when (content.msgType) { when (content.msgType) {
44 -> { 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) this.discardExact(5)
val var4 = readByte().toInt() when (val mode = readUByte().toInt()) {
var var5 = 0L 0xFF -> {
val target = readUInt().toLong() // 群转让 / huifu.qq.com
if (var4 != 0 && var4 != 1) { // From -> to
var5 = readUInt().toLong() val from = readUInt().toLong()
} val to = readUInt().toLong()
val results = ArrayList<Packet>()
val group = bot.getGroupByUin(content.fromUin) as GroupImpl // println("$from -> $to")
if (to == bot.id) {
if (var5 == 0L && this.remaining == 1L) {//管理员变更 if (bot.getGroupByUinOrNull(content.fromUin) == null) {
val newPermission = MessageSvcPbGetMsg.run {
if (this.readByte().toInt() == 1) MemberPermission.ADMINISTRATOR results.add(
else MemberPermission.MEMBER BotJoinGroupEvent.Retrieve(
bot.createGroupForBot(content.fromUin)!!
if (target == bot.id) { )
if (group.botPermission == newPermission) { )
return null }
}
}
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( val group = bot.getGroupByUin(content.fromUin) as GroupImpl
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( if (var5 == 0L && this.remaining == 1L) {//管理员变更
member, val newPermission =
member.permission.also { member.permission = newPermission }, if (this.readByte().toInt() == 1) MemberPermission.ADMINISTRATOR
newPermission 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 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 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 ? readUInt().toLong() // groupUin
readByte().toInt() // follow type
discardExact(1)
val target = readUInt().toLong() val target = readUInt().toLong()
val type = readUByte().toInt() val type = readUByte().toInt()
val operator = readUInt().toLong() val operator = readUInt().toLong()
val groupUin = content.fromUin val groupUin = content.fromUin
when (type) { when (type) {
0x82 -> bot.getGroupByUinOrNull(groupUin)?.let { group -> 2, 0x82 -> bot.getGroupByUinOrNull(groupUin)?.let { group ->
val member = group.getOrNull(target) as? MemberImpl ?: return null if (target == bot.id) {
return MemberLeaveEvent.Quit(member.also { return BotLeaveEvent.Active(group).also {
member.cancel(CancellationException("Leaved actively")) group.cancel(CancellationException("Leaved actively"))
group.members.delegate.remove(member) 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 -> 3, 0x83 -> bot.getGroupByUin(groupUin).let { group ->
val member = group.getOrNull(target) as? MemberImpl ?: return null if (target == bot.id) {
return MemberLeaveEvent.Kick(member.also { return BotLeaveEvent.Kick(group.members[operator]).also {
member.cancel(CancellationException("Leaved actively")) group.cancel(CancellationException("Being kicked"))
group.members.delegate.remove(member) bot.groups.delegate.remove(group)
}, group.members[operator]) }
} 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])
}
} }
} }
} }

View File

@ -14,8 +14,6 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive 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.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
@ -23,6 +21,8 @@ import kotlinx.io.core.readUInt
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import net.mamoe.mirai.JavaFriendlyAPI 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.data.FriendInfo
import net.mamoe.mirai.event.events.* import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.getFriendOrNull 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.OnlinePushPack
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket 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.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.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.Submsgtype0xb3
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.TroopTips0x857 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.TroopTips0x857
import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory 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> mapper: ByteReadPacket.(msgInfo: MsgInfo) -> Sequence<Packet>
): Sequence<Packet> { ): Sequence<Packet> {
return asSequence().filter { msg -> 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) } } }.flatMap { it.vMsg.read { mapper(it) } }
} }
@ -101,7 +106,7 @@ internal object OnlinePushReqPush : IncomingPacketFactory<OnlinePushReqPush.ReqP
528 -> { 528 -> {
val notifyMsgBody = readJceStruct(MsgType0x210.serializer()) val notifyMsgBody = readJceStruct(MsgType0x210.serializer())
Transformers528[notifyMsgBody.uSubMsgType] Transformers528[notifyMsgBody.uSubMsgType]
?.let { processor -> processor(notifyMsgBody, bot) } ?.let { processor -> processor(notifyMsgBody, bot, msgInfo) }
?: kotlin.run { ?: kotlin.run {
bot.network.logger.debug { bot.network.logger.debug {
"unknown group 528 type 0x${notifyMsgBody.uSubMsgType.toUHexString("")}, data: " + notifyMsgBody.vProtobuf.toUHexString() "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)) 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 -> 0x10 to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
val dataBytes = readBytes(26) 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 { 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 { internal inline fun lambda528(crossinline block: MsgType0x210.(QQAndroidBot) -> Sequence<Packet>): Lambda528 {
return object : 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) 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 -> 0x8AL to lambda528 { bot ->
@Serializable @Serializable
data class Sub8AMsgInfo( class Sub8AMsgInfo(
@ProtoNumber(1) val fromUin: Long, @ProtoNumber(1) val fromUin: Long,
@ProtoNumber(2) val botUin: Long, @ProtoNumber(2) val botUin: Long,
@ProtoNumber(3) val srcId: Int, @ProtoNumber(3) val srcId: Int,
@ -367,7 +421,7 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
data class Sub8A( class Sub8A(
@ProtoNumber(1) val msgInfo: List<Sub8AMsgInfo>, @ProtoNumber(1) val msgInfo: List<Sub8AMsgInfo>,
@ProtoNumber(2) val appId: Int, // 1 @ProtoNumber(2) val appId: Int, // 1
@ProtoNumber(3) val instId: 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) bot.friends.delegate.addLast(new)
return@lambda528 sequenceOf(FriendAddEvent(new)) return@lambda528 sequenceOf(FriendAddEvent(new))
}, },
0xE2L to lambda528 { 0xE2L to lambda528 { _ ->
// TODO: unknown. maybe messages. // 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 // 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()) // vProtobuf.loadAs(Msgtype0x210.serializer())
@ -411,7 +465,7 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
return@lambda528 emptySequence() return@lambda528 emptySequence()
}, },
0x44L to lambda528 { bot -> 0x44L to lambda528 { bot ->
val msg = vProtobuf.loadAs(Submsgtype0x44.Submsgtype0x44.MsgBody.serializer()) val msg = vProtobuf.loadAs(Submsgtype0x44.MsgBody.serializer())
when { when {
msg.msgCleanCountMsg != null -> { msg.msgCleanCountMsg != null -> {
@ -426,20 +480,55 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
return@lambda528 emptySequence() return@lambda528 emptySequence()
}, },
// bot 在其他客户端被踢或主动退出而同步情况 // bot 在其他客户端被踢或主动退出而同步情况
0xD4L to lambda528 { bot -> 0xD4L to lambda528 { _ ->
// this.soutv("0x210") // this.soutv("0x210")
@Serializable /* @Serializable
data class SubD4( data class SubD4(
// ok // ok
val uin: Long val uin: Long
) : ProtoBuf ) : ProtoBuf
val uin = vProtobuf.loadAs(SubD4.serializer()).uin val uin = vProtobuf.loadAs(SubD4.serializer()).uin
val group = bot.getGroupByUinOrNull(uin) ?: bot.getGroupOrNull(uin) val group = bot.getGroupByUinOrNull(uin) ?: bot.getGroupOrNull(uin)
return@lambda528 if (group != null && bot.groups.delegate.remove(group)) { return@lambda528 if (group != null && bot.groups.delegate.remove(group)) {
group.cancel(CancellationException("Being kicked")) group.cancel(CancellationException("Being kicked"))
sequenceOf(BotLeaveEvent.Active(group)) sequenceOf(BotLeaveEvent.Active(group))
} else emptySequence() } 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 -> 0x115L to lambda528 { bot ->
@ -579,12 +668,10 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
val to = value.encodeToString() val to = value.encodeToString()
if (uin == bot.id) { if (uin == bot.id) {
val from = bot.nick val from = bot.nick
bot.cachedNick = to if (from != to) {
add( bot.nick = to
BotNickChangedEvent( add(BotNickChangedEvent(bot, from, to))
bot, from, to }
)
)
} else { } else {
val friend = (bot.getFriendOrNull(uin) ?: return@forEach) as FriendImpl val friend = (bot.getFriendOrNull(uin) ?: return@forEach) as FriendImpl
val info = friend.friendInfo val info = friend.friendInfo

View File

@ -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>
}

View File

@ -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>

View File

@ -10,6 +10,7 @@
package net.mamoe.mirai.qqandroid.utils package net.mamoe.mirai.qqandroid.utils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable import kotlinx.io.core.Closeable
@ -20,6 +21,7 @@ import java.io.BufferedInputStream
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.net.Socket import java.net.Socket
import java.net.SocketException import java.net.SocketException
import java.util.concurrent.Executors
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/** /**
@ -38,6 +40,7 @@ internal actual class PlatformSocket : Closeable {
if (::socket.isInitialized) { if (::socket.isInitialized) {
socket.close() socket.close()
} }
thread.shutdownNow()
} }
@PublishedApi @PublishedApi
@ -67,15 +70,17 @@ internal actual class PlatformSocket : Closeable {
} }
} }
private val thread = Executors.newSingleThreadExecutor()
/** /**
* @throws ReadPacketInternalException * @throws ReadPacketInternalException
*/ */
actual suspend fun read(): ByteReadPacket { actual suspend fun read(): ByteReadPacket = suspendCancellableCoroutine { cont ->
return withContext(Dispatchers.IO) { thread.submit {
try { kotlin.runCatching {
readChannel.readPacketAtMost(Long.MAX_VALUE) readChannel.readPacketAtMost(Long.MAX_VALUE)
} catch (e: IOException) { }.let {
throw ReadPacketInternalException(e) cont.resumeWith(it)
} }
} }
} }

View File

@ -20,6 +20,9 @@ import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent
import net.mamoe.mirai.event.events.MemberJoinRequestEvent import net.mamoe.mirai.event.events.MemberJoinRequestEvent
import net.mamoe.mirai.event.events.NewFriendRequestEvent import net.mamoe.mirai.event.events.NewFriendRequestEvent
import net.mamoe.mirai.message.MessageReceipt 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.message.data.*
import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
@ -216,6 +219,15 @@ public abstract class Bot internal constructor(
@JvmSynthetic @JvmSynthetic
public abstract suspend fun recall(source: MessageSource) 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 @JvmSynthetic
public abstract suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) 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 // endregion
/** /**

View File

@ -13,6 +13,7 @@ package net.mamoe.mirai
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.SinceMirai
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
@ -45,6 +46,9 @@ public expect interface BotFactory {
passwordMd5: ByteArray, passwordMd5: ByteArray,
configuration: BotConfiguration = BotConfiguration.Default configuration: BotConfiguration = BotConfiguration.Default
): Bot ): Bot
@SinceMirai("1.3.0")
public companion object INSTANCE : BotFactory
} }
/** /**

View File

@ -18,10 +18,13 @@ import net.mamoe.mirai.event.events.FriendMessagePostSendEvent
import net.mamoe.mirai.event.events.FriendMessagePreSendEvent import net.mamoe.mirai.event.events.FriendMessagePreSendEvent
import net.mamoe.mirai.message.FriendMessageEvent import net.mamoe.mirai.message.FriendMessageEvent
import net.mamoe.mirai.message.MessageReceipt 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.Message
import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.isContentEmpty import net.mamoe.mirai.message.data.isContentEmpty
import net.mamoe.mirai.message.recall import net.mamoe.mirai.message.recall
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
/** /**
@ -70,6 +73,15 @@ public abstract class Friend : User(), CoroutineScope {
@JvmSynthetic @JvmSynthetic
abstract override suspend fun sendMessage(message: Message): MessageReceipt<Friend> 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") @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "VIRTUAL_MEMBER_HIDDEN", "OVERRIDE_BY_INLINE")
@kotlin.internal.InlineOnly @kotlin.internal.InlineOnly
@JvmSynthetic @JvmSynthetic

View File

@ -16,10 +16,14 @@ import net.mamoe.mirai.JavaFriendlyAPI
import net.mamoe.mirai.event.events.* import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.getFriendOrNull import net.mamoe.mirai.getFriendOrNull
import net.mamoe.mirai.message.MessageReceipt 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.Message
import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.isContentEmpty import net.mamoe.mirai.message.data.isContentEmpty
import net.mamoe.mirai.message.recall 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 net.mamoe.mirai.utils.WeakRefProperty
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
import kotlin.time.Duration import kotlin.time.Duration
@ -157,6 +161,15 @@ public abstract class Member : MemberJavaFriendlyAPI, User() {
@JvmSynthetic @JvmSynthetic
public abstract override suspend fun sendMessage(message: Message): MessageReceipt<Member> 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 * @see sendMessage
*/ */

View File

@ -15,11 +15,17 @@ import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.* import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.message.MessageReceipt 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.Image
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.PlainText 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.ExternalImage
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.OverFileSizeMaxException import net.mamoe.mirai.utils.OverFileSizeMaxException
import net.mamoe.mirai.utils.SinceMirai
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
/** /**
@ -67,6 +73,15 @@ public abstract class User : Contact(), CoroutineScope {
@JvmSynthetic @JvmSynthetic
public abstract override suspend fun sendMessage(message: Message): MessageReceipt<User> 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 * @see sendMessage
*/ */

View File

@ -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())
}
}
}

View File

@ -9,13 +9,10 @@
package net.mamoe.mirai.data package net.mamoe.mirai.data
import net.mamoe.mirai.Bot
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
/** /**
* 群资料. * 群资料.
*
* 通过 [Bot._lowLevelQueryGroupInfo] 得到
*/ */
@LowLevelAPI @LowLevelAPI
public interface GroupInfo { public interface GroupInfo {

View File

@ -21,6 +21,8 @@
- 服务器主动要求更换另一个服务器: RequireReconnect - 服务器主动要求更换另一个服务器: RequireReconnect
- Bot 重新登录: BotReloginEvent - Bot 重新登录: BotReloginEvent
- Bot 头像改变: BotAvatarChangedEvent - Bot 头像改变: BotAvatarChangedEvent
- (`1.2.0+`) Bot 昵称改变: BotNickChangedEvent
- (`1.3.0+`) Bot 被戳: BotNudgedEvent
### [消息](message.kt) ### [消息](message.kt)
- (`1.1.0-`) 主动发送消息: MessageSendEvent - (`1.1.0-`) 主动发送消息: MessageSendEvent
@ -81,9 +83,10 @@
##### 成员权限 ##### 成员权限
- 成员权限改变: MemberPermissionChangeEvent - 成员权限改变: MemberPermissionChangeEvent
##### 禁言 ##### 动作
- 群成员被禁言: MemberMuteEvent - 群成员被禁言: MemberMuteEvent
- 群成员被取消禁言: MemberUnmuteEvent - 群成员被取消禁言: MemberUnmuteEvent
- (`1.3.0+`) 群员被戳: MemberNudgedEvent
### [好友](friend.kt) ### [好友](friend.kt)
- 好友昵称改变: FriendRemarkChangeEvent - 好友昵称改变: FriendRemarkChangeEvent
@ -91,3 +94,6 @@
- 好友已被删除: FriendDeleteEvent - 好友已被删除: FriendDeleteEvent
- 一个账号请求添加机器人为好友: NewFriendRequestEvent - 一个账号请求添加机器人为好友: NewFriendRequestEvent
- 好友头像改变: FriendAvatarChangedEvent - 好友头像改变: FriendAvatarChangedEvent
- (`1.2.0+`) 好友昵称改变: FriendNickChangedEvent
- (`1.2.0+`) 好友输入状态改变: FriendInputStatusChangedEvent
- (`1.3.0+`) 好友被戳: FriendNudgedEvent

View File

@ -14,7 +14,9 @@
package net.mamoe.mirai.event.events package net.mamoe.mirai.event.events
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.User
import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.message.action.Nudge
import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
@ -124,6 +126,31 @@ public data class BotNickChangedEvent(
public val to: String public val to: String
) : BotEvent, Packet, AbstractEvent() ) : 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 图片 // region 图片
// endregion // endregion

View File

@ -112,8 +112,6 @@ public data class FriendAvatarChangedEvent internal constructor(
public override val friend: Friend public override val friend: Friend
) : FriendEvent, Packet, AbstractEvent() ) : FriendEvent, Packet, AbstractEvent()
/** /**
* [Friend] 昵称改变事件, 在此事件广播时好友已经完成改名 * [Friend] 昵称改变事件, 在此事件广播时好友已经完成改名
* @see BotNickChangedEvent * @see BotNickChangedEvent

View File

@ -22,8 +22,10 @@ import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.internal.MiraiAtomicBoolean 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.qqandroid.network.Packet
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai
import net.mamoe.mirai.utils.internal.runBlocking import net.mamoe.mirai.utils.internal.runBlocking
import kotlin.internal.LowPriorityInOverloadResolution import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.jvm.* import kotlin.jvm.*
@ -130,6 +132,18 @@ public sealed class BotJoinGroupEvent : GroupEvent, BotPassiveEvent, Packet, Abs
return "BotJoinGroupEvent.Invite(invitor=$invitor)" 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 群设置 // region 群设置
@ -260,6 +274,17 @@ public sealed class MemberJoinEvent(
) : MemberJoinEvent(member) { ) : MemberJoinEvent(member) {
public override fun toString(): String = "MemberJoinEvent.Active(member=${member.id})" 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 // 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 // endregion

View File

@ -55,13 +55,6 @@ public interface LowLevelBotAPIAccessor {
@LowLevelAPI @LowLevelAPI
public suspend fun _lowLevelQueryGroupList(): Sequence<Long> public suspend fun _lowLevelQueryGroupList(): Sequence<Long>
/**
* 向服务器查询群资料. 获得的仅为当前时刻的资料.
* 请优先使用 [Bot.getGroup] 然后查看群资料.
*/
@LowLevelAPI
public suspend fun _lowLevelQueryGroupInfo(groupCode: Long): GroupInfo
/** /**
* 向服务器查询群成员列表. * 向服务器查询群成员列表.
* 请优先使用 [Bot.getGroup], [Group.members] 查看群成员. * 请优先使用 [Bot.getGroup], [Group.members] 查看群成员.
@ -122,6 +115,15 @@ public interface LowLevelBotAPIAccessor {
public suspend fun _lowLevelGetGroupActiveData(groupId: Long, page: Int = -1): GroupActiveData 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?
/** /**
* 处理一个账号请求添加机器人为好友的事件 * 处理一个账号请求添加机器人为好友的事件
*/ */

View File

@ -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()

View File

@ -47,9 +47,11 @@ public class Voice @MiraiInternalAPI constructor(
} }
public val url: String? public val url: String?
get() = get() = when {
if (_url.startsWith("http")) _url _url.isBlank() -> null
else null _url.startsWith("http") -> _url
else -> "http://grouptalk.c2c.qq.com$_url"
}
private var _stringValue: String? = null private var _stringValue: String? = null
get() = field ?: kotlin.run { get() = field ?: kotlin.run {

View File

@ -15,6 +15,7 @@ package net.mamoe.mirai
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.ContextImpl import net.mamoe.mirai.utils.ContextImpl
import net.mamoe.mirai.utils.SinceMirai
/** /**
* 构造 [Bot] 的工厂. 这是 [Bot] 唯一的构造方式. * 构造 [Bot] 的工厂. 这是 [Bot] 唯一的构造方式.
@ -52,6 +53,16 @@ public actual interface BotFactory {
configuration: BotConfiguration configuration: BotConfiguration
): Bot ): 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") @JvmName("newBot")
@JvmOverloads @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) factory.Bot(context, qq, password, configuration)
/** /**
@ -109,7 +125,12 @@ public fun Bot(
* 自动加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 * 自动加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例
*/ */
@JvmSynthetic @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)) factory.Bot(context, qq, passwordMd5, BotConfiguration().apply(configuration))

View File

@ -59,7 +59,7 @@ public actual open class PlatformLogger @JvmOverloads constructor(
*/ */
@SinceMirai("1.1.0") @SinceMirai("1.1.0")
protected open fun printLog(message: String?, priority: SimpleLogger.LogPriority) { 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") else output("$currentTimeFormatted ${priority.simpleName}/$identity: $message")
} }