Commit 23adcf6d authored by Him188's avatar Him188

Quote reply support

parent 33867d04
......@@ -10,10 +10,8 @@
package net.mamoe.mirai.api.http.data.common
import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.utils.MiraiExperimentalAPI
@Serializable
abstract class ContactDTO : DTO {
......@@ -38,8 +36,8 @@ data class MemberDTO(
val permission: MemberPermission,
val group: GroupDTO
) : ContactDTO() {
constructor(member: Member) : this (
member.id, member.groupCard, member.permission,
constructor(member: Member) : this(
member.id, member.groupCardOrNick, member.permission,
GroupDTO(member.group)
)
}
......@@ -50,5 +48,6 @@ data class GroupDTO(
val name: String,
val permission: MemberPermission
) : ContactDTO() {
@UseExperimental(MiraiExperimentalAPI::class)
constructor(group: Group) : this(group.id, group.name, group.botPermission)
}
......@@ -48,6 +48,8 @@ internal abstract class ContactImpl : Contact {
internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), QQ {
override val bot: QQAndroidBot by bot.unsafeWeakRef()
override lateinit var nick: String
override suspend fun sendMessage(message: MessageChain) {
bot.network.run {
check(
......@@ -186,20 +188,10 @@ internal class MemberImpl(
override val bot: QQAndroidBot get() = qq.bot
override suspend fun mute(durationSeconds: Int): Boolean {
if (bot.uin == this.qq.id) {
return false
}
//判断有无禁言权限
val myPermission = group.botPermission
val targetPermission = this.permission
if (myPermission != MemberPermission.OWNER) {
if (targetPermission == MemberPermission.OWNER || targetPermission == MemberPermission.ADMINISTRATOR) {
return false
}
} else if (myPermission == MemberPermission.MEMBER) {
if (group.botPermission != MemberPermission.OWNER && (!group.botPermission.isOperator() || this.isOperator())) {
return false
}
return try {
bot.network.run {
TroopManagement.Mute(
client = bot.client,
......@@ -208,17 +200,30 @@ internal class MemberImpl(
timeInSecond = durationSeconds
).sendAndExpect<TroopManagement.Mute.Response>()
}
true
} catch (e: Exception) {
false
}
return true
}
override suspend fun unmute(): Boolean {
return mute(0)
if (group.botPermission != MemberPermission.OWNER && (!group.botPermission.isOperator() || this.isOperator())) {
return false
}
bot.network.run {
TroopManagement.Mute(
client = bot.client,
groupCode = group.id,
memberUin = this@MemberImpl.id,
timeInSecond = 0
).sendAndExpect<TroopManagement.Mute.Response>()
}
return true
}
override suspend fun kick(message: String): Boolean {
if (group.botPermission != MemberPermission.OWNER && (!group.botPermission.isOperator() || this.isOperator())) {
return false
}
bot.network.run {
return TroopManagement.Kick(
client = bot.client,
......
......@@ -18,10 +18,7 @@ import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.*
import kotlin.coroutines.CoroutineContext
@UseExperimental(MiraiInternalAPI::class)
......@@ -37,7 +34,8 @@ internal abstract class QQAndroidBotBase constructor(
account: BotAccount,
configuration: BotConfiguration
) : BotImpl<QQAndroidBotNetworkHandler>(account, configuration) {
val client: QQAndroidClient = QQAndroidClient(context, account, bot = @Suppress("LeakingThis") this as QQAndroidBot)
val client: QQAndroidClient =
QQAndroidClient(context, account, bot = @Suppress("LeakingThis") this as QQAndroidBot, device = configuration.deviceInfo?.invoke(context) ?: SystemDeviceInfo(context))
internal var firstLoginSucceed: Boolean = false
override val uin: Long get() = client.uin
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
......
/*
* Copyright 2020 Mamoe Technologies and contributors.
* Copyright 2017-2019 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 license.
*
* 此源代码的使用受 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
*/
/*
* Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
* Some code changed by Mamoe is annotated around "MIRAI MODIFY START" and "MIRAI MODIFY END"
*/
package net.mamoe.mirai.qqandroid.io.serialization
......@@ -73,7 +67,7 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
override fun SerialDescriptor.getTag(index: Int) = this.getProtoDesc(index)
// MIRAI MODIFY START:
// MIRAI MODIFY START
override fun encodeTaggedNull(tag: ProtoDesc) {
}
......
......@@ -20,6 +20,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.utils.firstValue
import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.toUHexString
fun <T : JceStruct> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset = JceCharset.UTF8): T {
......
......@@ -189,10 +189,8 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
if (this.any<QuoteReply>()) {
when (val source = this[QuoteReply].source) {
is MessageSourceFromServer -> {
elements.add(ImMsgBody.Elem(srcMsg = source.delegate))
}
is MessageSourceFromMsg -> { elements.add(ImMsgBody.Elem(srcMsg = source.toJceData())) }
is MessageSourceFromServer -> elements.add(ImMsgBody.Elem(srcMsg = source.delegate))
is MessageSourceFromMsg -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData()))
else -> error("unsupported MessageSource implementation: ${source::class.simpleName}")
}
}
......@@ -204,16 +202,19 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
is CustomFaceFromFile -> elements.add(ImMsgBody.Elem(customFace = it.toJceData()))
is CustomFaceFromServer -> elements.add(ImMsgBody.Elem(customFace = it.delegate))
is NotOnlineImageFromServer -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate))
is NotOnlineImageFromFile -> elements.add(
ImMsgBody.Elem(
notOnlineImage = it.toJceData(), generalFlags = ImMsgBody.GeneralFlags(
pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes()
)
)
)
is NotOnlineImageFromFile -> elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData()))
is QuoteReply,
is MessageSource -> {
}
else -> error("unsupported message type: ${it::class.simpleName}")
}
}
// if(this.any<QuoteReply>()){
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes())))
// }
return elements
}
......@@ -275,12 +276,12 @@ internal class NotOnlineImageFromServer(
internal fun MsgComm.Msg.toMessageChain(): MessageChain {
val elems = this.msgBody.richText.elems
val message = MessageChain(initialCapacity = elems.size)
val message = MessageChain(initialCapacity = elems.size + 1)
message.add(MessageSourceFromMsg(delegate = this))
elems.forEach {
when {
it.srcMsg != null -> message.add(QuoteReplyImpl(MessageSourceFromServer(it.srcMsg)))
it.srcMsg != null -> message.add(QuoteReply(MessageSourceFromServer(it.srcMsg)))
it.notOnlineImage != null -> message.add(NotOnlineImageFromServer(it.notOnlineImage))
it.customFace != null -> message.add(CustomFaceFromServer(it.customFace))
it.text != null -> {
......
......@@ -9,22 +9,19 @@
package net.mamoe.mirai.qqandroid.message
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg
import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.jvm.JvmStatic
internal inline class MessageSourceFromServer(
val delegate: ImMsgBody.SourceMsg
) : MessageSource {
override val originalSeq: Int get() = delegate.origSeqs!!.first()
override val senderId: Long get() = delegate.senderUin
override val groupId: Long get() = delegate.toUin
override val time: Int get() = delegate.time
override val messageUid: Long
get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!
override fun toString(): String = ""
}
......@@ -32,52 +29,46 @@ internal inline class MessageSourceFromServer(
internal inline class MessageSourceFromMsg(
val delegate: MsgComm.Msg
) : MessageSource {
override val originalSeq: Int get() = delegate.msgHead.msgSeq
override val senderId: Long get() = delegate.msgHead.fromUin
override val groupId: Long get() = delegate.msgHead.toUin
override val time: Int get() = delegate.msgHead.msgTime
override val messageUid: Long
get() = delegate.msgBody.richText.attr!!.random.toLong()
fun toJceData(): ImMsgBody.SourceMsg {
val groupUin = Group.calculateGroupIdByGroupCode(delegate.msgHead.groupInfo!!.groupCode)
return ImMsgBody.SourceMsg(
origSeqs = listOf(delegate.msgHead.msgSeq),
senderUin = delegate.msgHead.fromUin,
toUin = delegate.msgHead.groupInfo!!.groupCode,
toUin = groupUin,
flag = 1,
elems = delegate.toMessageChain().toRichTextElems(),
elems = delegate.msgBody.richText.elems,
type = 0,
time = delegate.msgHead.msgTime,
pbReserve = SourceMsg.ResvAttr(
origUids = delegate.msgBody.richText.attr!!.random.toLong()//,
//oriMsgtype = delegate.msgHead.msgType
).toByteArray(SourceMsg.ResvAttr.serializer()).also { println("pbReserve=" + it.toUHexString()) },// PbReserve(delegate.msgBody.richText.attr!!.random.toLong()).toByteArray(PbReserve.serializer()).also { println("pbReserve=" + it.toUHexString()) },
origUids = messageUid
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = delegate.msgHead.fromUin, // qq
toUin = delegate.msgHead.groupInfo.groupCode, // group
msgType = delegate.msgHead.msgType.also { println("msgType=$it") }, // 82?
toUin = groupUin, // group
msgType = delegate.msgHead.msgType, // 82?
c2cCmd = delegate.msgHead.c2cCmd,
msgSeq = delegate.msgHead.msgSeq,
msgTime = delegate.msgHead.msgTime,
msgUid = delegate.msgBody.richText.attr.random.toLong(), // ok
msgUid = messageUid, // ok
groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = delegate.toMessageChain().toRichTextElems().apply { add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2())) }
elems = delegate.msgBody.richText.elems.also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
)
)
).toByteArray(MsgComm.Msg.serializer())
)
).toByteArray(MsgComm.Msg.serializer()).also { println("srcMsg=" + it.toUHexString()) } // fucking slow
).also { println(it.contentToString()) }
}
override fun toString(): String = ""
companion object {
@JvmStatic
val PB_RESERVE_HEAD = byteArrayOf(0x18)
@JvmStatic
val PB_RESERVE_TAIL = byteArrayOf(1)
}
}
\ No newline at end of file
/*
* 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.message
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.QuoteReply
class QuoteReplyImpl(override val source: MessageSource) : QuoteReply {
override fun toString(): kotlin.String = ""
}
\ No newline at end of file
......@@ -43,7 +43,6 @@ import net.mamoe.mirai.utils.io.*
import kotlin.coroutines.CoroutineContext
import kotlin.jvm.Volatile
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
@Suppress("MemberVisibilityCanBePrivate")
@UseExperimental(MiraiInternalAPI::class)
......@@ -131,8 +130,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
bot.qqs.delegate.clear()
bot.groups.delegate.clear()
val friendListLoadTime = async {
measureTime {
val friendListJob = launch {
try {
bot.logger.info("开始加载好友信息")
var currentFriendCount = 0
......@@ -164,34 +162,21 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
bot.logger.error("加载好友列表失败|一般这是由于加载过于频繁导致/将以热加载方式加载好友列表")
}
}
}
val groupInfo = mutableMapOf<Long, Int>()
val groupTime = async {
measureTime {
val groupJob = launch {
try {
bot.logger.info("开始加载群组列表与群成员列表")
val troopListData = FriendList.GetTroopListSimplify(bot.client)
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(timeoutMillis = 5000, retry = 2)
// println("获取到群数量" + troopData.groups.size)
val toGet: MutableMap<GroupImpl, ContactList<Member>> = mutableMapOf()
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
troopListData.groups.forEach { troopNum ->
val contactList = ContactList(LockFreeLinkedList<Member>())
val groupInfoResponse = try {
val groupInfoResponse =
TroopManagement.GetGroupOperationInfo(
client = bot.client,
groupCode = troopNum.groupCode
).sendAndExpect<TroopManagement.GetGroupOperationInfo.Response>()
} catch (e: Exception) {
bot.logger.info("获取" + troopNum.groupCode + "的群设置失败")
TroopManagement.GetGroupOperationInfo.Response(
allowAnonymousChat = false,
allowMemberInvite = false,
autoApprove = false,
confessTalk = false
)
}
val group =
GroupImpl(
bot = bot,
......@@ -207,15 +192,12 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
_anonymousChat = groupInfoResponse.allowAnonymousChat,
members = contactList
)
toGet[group] = contactList
bot.groups.delegate.addLast(group)
launch {
try {
fillTroopMemberList(group, contactList, troopNum.dwGroupOwnerUin)
groupInfo[troopNum.groupCode] = contactList.size
} catch (e: Exception) {
groupInfo[troopNum.groupCode] = -1
bot.logger.warning("群${troopNum.groupCode}的列表拉取失败, 将采用动态加入")
bot.logger.error("群${troopNum.groupCode}的列表拉取失败, 一段时间后将会重试")
bot.logger.error(e)
}
}
......@@ -226,38 +208,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
bot.logger.error(e)
}
}
}
//===log===//
fun fillUntil(long: Number, size: Int): String {
val x = long.toString()
return x + " ".repeat(
if (size - x.length > 0) {
size - x.length
} else {
0
}
)
}
joinAll(friendListLoadTime, groupTime)
bot.logger.info("====================Mirai Bot List初始化完毕====================")
bot.logger.info("好友数量: ${fillUntil(bot.qqs.size, 9)}\t\t\t 加载时间: ${friendListLoadTime.await().inMilliseconds}ms")
bot.logger.info("加入群组: ${fillUntil(bot.groups.size, 9)}\t\t\t 加载时间: ${groupTime.await().inMilliseconds}ms")
groupInfo.forEach {
if (it.value == -1) {
bot.logger.error("群组号码: ${fillUntil(it.key, 9)}\t 成员数量加载失败")
} else {
bot.logger.info(
"群组号码: ${fillUntil(it.key, 9)}\t 成员数量: ${fillUntil(
it.value,
4
)}\t BOT权限: " + bot.groups[it.key].botPermission.toString() + ""
)
}
}
bot.logger.info("====================Mirai Bot List初始化完毕====================")
joinAll(friendListJob, groupJob)
this@QQAndroidBotNetworkHandler.launch(CoroutineName("Heartbeat")) {
while (this.isActive) {
......@@ -302,23 +254,23 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
targetGroupCode = group.id,
nextUin = nextUin
).sendAndExpect<FriendList.GetTroopMemberList.Response>(timeoutMillis = 3000)
data.members.forEach {
data.members.forEach { troopMemberInfo ->
val member = MemberImpl(
qq = bot.QQ(it.memberUin) as QQImpl,
_groupCard = it.autoRemark ?: it.nick,
_specialTitle = it.sSpecialTitle ?: "",
qq = (bot.QQ(troopMemberInfo.memberUin) as QQImpl).also { it.nick = troopMemberInfo.nick },
_groupCard = troopMemberInfo.sName ?: "",
_specialTitle = troopMemberInfo.sSpecialTitle ?: "",
group = group,
coroutineContext = group.coroutineContext,
permission = when {
it.memberUin == owner -> MemberPermission.OWNER
it.dwFlag == 1L -> MemberPermission.ADMINISTRATOR
troopMemberInfo.memberUin == owner -> MemberPermission.OWNER
troopMemberInfo.dwFlag == 1L -> MemberPermission.ADMINISTRATOR
else -> MemberPermission.MEMBER
}
)
if (member.permission == MemberPermission.OWNER) {
group.owner = member
}
if (it.memberUin != bot.uin) {
if (troopMemberInfo.memberUin != bot.uin) {
list.delegate.addLast(member)
} else {
group.botPermission = member.permission
......
......@@ -20,12 +20,11 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot
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.Tlv
import net.mamoe.mirai.qqandroid.utils.DeviceInfo
import net.mamoe.mirai.utils.DeviceInfo
import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.qqandroid.utils.SystemDeviceInfo
import net.mamoe.mirai.utils.SystemDeviceInfo
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.cryptor.ECDH
import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.cryptor.decryptBy
import net.mamoe.mirai.utils.io.*
......@@ -105,7 +104,7 @@ internal open class QQAndroidClient(
val apkVersionName: ByteArray get() = "8.2.0".toByteArray()
val buildVer: String get() = "8.2.0.1296"
private val messageSequenceId: AtomicInt = atomic(0)
private val messageSequenceId: AtomicInt = atomic(22911)
internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2)
private val requestPacketRequestId: AtomicInt = atomic(1921334513)
......
......@@ -80,7 +80,7 @@ object Highway {
writeFully(head)
check(body.copyTo(this).toInt() == bodySize) { "bad body size" }
writeByte(41)
}.also { println(it.remaining) }
}
}
}
}
\ No newline at end of file
......@@ -356,7 +356,7 @@ internal class ImMsgBody : ProtoBuf {
@SerialId(26) val conferenceTipsInfo: ConferenceTipsInfo? = null,
@SerialId(27) val redbagInfo: RedBagInfo? = null,
@SerialId(28) val lowVersionTips: LowVersionTips? = null,
@SerialId(29) val bankcodeCtrlInfo: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(29) val bankcodeCtrlInfo: ByteArray? = null,
@SerialId(30) val nearByMsg: NearByMessageType? = null,
@SerialId(31) val customElem: CustomElem? = null,
@SerialId(32) val locationInfo: LocationInfo? = null,
......@@ -1142,7 +1142,7 @@ internal class ImReceipt : ProtoBuf {
) : ProtoBuf
@Serializable
data internal class ReceiptResp(
internal data class ReceiptResp(
@SerialId(1) val command: Int /* enum */ = 1,
@SerialId(2) val receiptInfo: ReceiptInfo? = null
) : ProtoBuf
......@@ -1194,10 +1194,10 @@ internal class Submsgtype0xc7 : ProtoBuf {
@SerialId(2) val srcUin: Long = 0L,
@SerialId(3) val dstUin: Long = 0L,
@SerialId(4) val changeType: Int /* enum */ = 1,
@SerialId(5) val msgRelationalChainInfoOld: Submsgtype0xc7.RelationalChainInfo? = null,
@SerialId(6) val msgRelationalChainInfoNew: Submsgtype0xc7.RelationalChainInfo? = null,
@SerialId(7) val msgToDegradeInfo: Submsgtype0xc7.ToDegradeInfo? = null,
@SerialId(20) val relationalChainInfos: List<Submsgtype0xc7.RelationalChainInfos>? = null,
@SerialId(5) val msgRelationalChainInfoOld: RelationalChainInfo? = null,
@SerialId(6) val msgRelationalChainInfoNew: RelationalChainInfo? = null,
@SerialId(7) val msgToDegradeInfo: ToDegradeInfo? = null,
@SerialId(20) val relationalChainInfos: List<RelationalChainInfos>? = null,
@SerialId(100) val uint32FeatureId: List<Int>? = null
) : ProtoBuf
......@@ -1235,8 +1235,8 @@ internal class Submsgtype0xc7 : ProtoBuf {
internal class ForwardBody(
@SerialId(1) val notifyType: Int = 0,
@SerialId(2) val opType: Int = 0,
@SerialId(3000) val msgHotFriendNotify: Submsgtype0xc7.HotFriendNotify? = null,
@SerialId(4000) val msgRelationalChainChange: Submsgtype0xc7.RelationalChainChange? = null
@SerialId(3000) val msgHotFriendNotify: HotFriendNotify? = null,
@SerialId(4000) val msgRelationalChainChange: RelationalChainChange? = null
) : ProtoBuf
@Serializable
......@@ -1277,24 +1277,24 @@ internal class Submsgtype0xc7 : ProtoBuf {
@SerialId(303) val lastBoatTime: Int = 0,
@SerialId(304) val boatWording: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(400) val notifyType: Int = 0,
@SerialId(401) val msgFriendshipFlagNotify: Submsgtype0xc7.FriendShipFlagNotify? = null
@SerialId(401) val msgFriendshipFlagNotify: FriendShipFlagNotify? = null
) : ProtoBuf
@Serializable
internal class RelationalChainInfos(
@SerialId(1) val msgRelationalChainInfoOld: Submsgtype0xc7.RelationalChainInfo? = null,
@SerialId(2) val msgRelationalChainInfoNew: Submsgtype0xc7.RelationalChainInfo? = null
@SerialId(1) val msgRelationalChainInfoOld: RelationalChainInfo? = null,
@SerialId(2) val msgRelationalChainInfoNew: RelationalChainInfo? = null
) : ProtoBuf
@Serializable
internal class ToDegradeInfo(
@SerialId(1) val toDegradeItem: List<Submsgtype0xc7.ToDegradeItem>? = null,
@SerialId(1) val toDegradeItem: List<ToDegradeItem>? = null,
@SerialId(2) val nick: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val notifyTime: Long = 0L
) : ProtoBuf
@Serializable
internal class MsgBody(
@SerialId(1) val msgModInfos: List<Submsgtype0xc7.ForwardBody>? = null
@SerialId(1) val msgModInfos: List<ForwardBody>? = null
) : ProtoBuf
}
......@@ -22,6 +22,8 @@ import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf
import net.mamoe.mirai.qqandroid.message.toMessageChain
import net.mamoe.mirai.qqandroid.message.toRichTextElems
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify
......@@ -30,8 +32,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.message.toMessageChain
import net.mamoe.mirai.qqandroid.message.toRichTextElems
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.currentTimeSeconds
import kotlin.math.absoluteValue
......@@ -184,8 +184,8 @@ internal class MessageSvc {
override fun toString(): String = "MessageSvc.PbSendMsg.Response.SUCCESS"
}
data class Failed(val errorCode: Int, val errorMessage: String) : Response() {
override fun toString(): String = "MessageSvc.PbSendMsg.Response.Failed(errorCode=$errorCode, errorMessage=$errorMessage)"
data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() {
override fun toString(): String = "MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)"
}
}
......@@ -236,21 +236,16 @@ internal class MessageSvc {
writeProtoBuf(
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
routingHead = MsgSvc.RoutingHead(grp = MsgSvc.Grp(groupCode = groupCode)),
contentHead = MsgComm.ContentHead(pkgNum = 1, divSeq = seq),
contentHead = MsgComm.ContentHead(pkgNum = 1),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = message.toRichTextElems()
)
),
//
//
//
msgSeq = seq,
msgRand = Random.nextInt().absoluteValue,
syncCookie = EMPTY_BYTE_ARRAY
// ?: SyncCookie(time = currentTimeSeconds + client.timeDifference).toByteArray(SyncCookie.serializer()),
, msgVia = 1
syncCookie = EMPTY_BYTE_ARRAY,
msgVia = 1
)
)
}
......@@ -260,7 +255,7 @@ internal class MessageSvc {
return if (response.result == 0) {
Response.SUCCESS
} else {
Response.Failed(response.errtype, response.errmsg)
Response.Failed(response.result, response.errtype, response.errmsg)
}
}
}
......
......@@ -75,8 +75,3 @@ internal fun guidFlag(
flag = flag or (macOrAndroidIdChangeFlag.value shl 8 and 0xFF00)
return flag
}
\ No newline at end of file
/**
* Defaults "%4;7t>;28<fc.5*6".toByteArray()
*/
fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray = md5(androidId + macAddress)
\ No newline at end of file
......@@ -106,6 +106,28 @@ interface Group : Contact, CoroutineScope {
suspend fun quit(): Boolean
companion object {
/**
* by @kar98k
*/
fun calculateGroupIdByGroupCode(groupCode: Long): Long {
var left: Long = groupCode / 1000000L
when {
left <= 10 -> left += 202
left <= 19 -> left += 480 - 11
left <= 66 -> left += 2100 - 20
left <= 156 -> left += 2010 - 67
left <= 209 -> left += 2147 - 157
left <= 309 -> left += 4100 - 210
left <= 499 -> left += 3800 - 310
}
return left * 1000000L + groupCode % 1000000L
}
}
@MiraiExperimentalAPI
fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})"
}
\ No newline at end of file
......@@ -11,7 +11,8 @@
package net.mamoe.mirai.contact
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.WeakRefProperty
import kotlin.jvm.JvmName
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
......@@ -31,9 +32,11 @@ interface Member : QQ, Contact {
val permission: MemberPermission
/**
* 群名片
* 群名片. 可能为空.
*
* 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException]
*
* @see [groupCardOrNick] 获取非空群名片或昵称
*/
var groupCard: String
......@@ -48,7 +51,7 @@ interface Member : QQ, Contact {
* 禁言
*
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
* @return 当机器人无权限禁言这个群成员时返回 `false`
* @return 当机器人无权限禁言这个群成员时返回 `false`
*
* @see Int.minutesToSeconds
* @see Int.hoursToSeconds
......@@ -57,26 +60,31 @@ interface Member : QQ, Contact {
suspend fun mute(durationSeconds: Int): Boolean
/**
* 解除禁言. 在没有权限时会返回 `false`. 否则均返回 `true`.
* 解除禁言. 在没有权限时会返回 `false`.
*/
suspend fun unmute(): Boolean
/**
* 踢出该成员. 机器人无权限时返回 `false`
*/
suspend fun kick(message: String = ""): Boolean
/**
* 当且仅当 `[other] is [Member] && [other].id == this.id && [other].group == this.group` 时为 true
*/
override fun equals(other: Any?): Boolean
}
/**
* 获取非空群名片或昵称.
*
* 若 [群名片][Member.groupCard] 不为空则返回群名片, 为空则返回 [QQ.nick]
*/
val Member.groupCardOrNick: String get() = this.groupCard.takeIf { it.isNotEmpty() } ?: this.nick
@ExperimentalTime
suspend inline fun Member.mute(duration: Duration): Boolean {
require(duration.inDays <= 30) { "duration must be at most 1 month" }
require(duration.inSeconds > 0) { "duration must be greater than 0 second" }
return this.mute(duration.inSeconds.toInt())
}
\ No newline at end of file
@ExperimentalUnsignedTypes
suspend inline fun Member.mute(durationSeconds: UInt): Boolean {
require(durationSeconds.toInt() <= 30 * 24 * 3600) { "duration must be at most 1 month" }
return this.mute(durationSeconds.toInt()) // same bin rep.
}
\ No newline at end of file
......@@ -62,7 +62,7 @@ inline fun Member.isOwner(): Boolean = this.permission.isOwner()
inline fun Member.isAdministrator(): Boolean = this.permission.isAdministrator()
/**
* 管理员或群主
* 管理员或群主
*/
@Suppress("NOTHING_TO_INLINE")
inline fun Member.isOperator(): Boolean = this.permission.isOperator()
......
......@@ -35,6 +35,11 @@ interface QQ : Contact, CoroutineScope {
*/
override val id: Long
/**
* 昵称
*/
val nick: String
/**
* 请求头像下载链接
*/
......
......@@ -14,7 +14,11 @@ package net.mamoe.mirai.data
*/
interface Packet
object NoPacket : Packet
object NoPacket : Packet{
override fun toString(): String {
return "NoPacket"
}
}
/**
* PacketFactory 可以一次解析多个包出来. 它们将会被分别广播.
......
......@@ -89,6 +89,15 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
// endregion
// region
/**
* 引用这个消息.
*/
inline fun MessageChain.quote(): MessageChain = this.quote(sender as? Member ?: error("only group message can be quoted"))
// endregion
// region 上传图片
suspend inline fun ExternalImage.upload(): Image = this.upload(subject)
// endregion
......
......@@ -12,6 +12,7 @@
package net.mamoe.mirai.message.data
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.groupCardOrNick
import net.mamoe.mirai.utils.MiraiInternalAPI
......@@ -20,7 +21,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
*/
class At @MiraiInternalAPI constructor(val target: Long, val display: String) : Message {
@UseExperimental(MiraiInternalAPI::class)
constructor(member: Member) : this(member.id, "@${member.groupCard}")
constructor(member: Member) : this(member.id, "@${member.groupCardOrNick}")
override fun toString(): String = display
......
......@@ -135,18 +135,18 @@ inline fun List<Message>.toMessageChain(): MessageChain = MessageChain(this)
/**
* 获取第一个 [M] 类型的 [Message] 实例
*/
inline fun <reified M : Message> MessageChain.firstOrNull(): Message? = this.firstOrNull { it is M }
inline fun <reified M : Message> MessageChain.firstOrNull(): M? = this.firstOrNull { it is M } as M?
/**
* 获取第一个 [M] 类型的 [Message] 实例
* @throws [NoSuchElementException] 如果找不到该类型的实例
*/
inline fun <reified M : Message> MessageChain.first(): Message = this.first { it is M }
inline fun <reified M : Message> MessageChain.first(): M = this.first { it is M } as M
/**
* 获取第一个 [M] 类型的 [Message] 实例
*/
inline fun <reified M : Message> MessageChain.any(): Boolean = this.firstOrNull { it is M } !== null
inline fun <reified M : Message> MessageChain.any(): Boolean = this.any { it is M }
/**
......
......@@ -10,32 +10,18 @@
package net.mamoe.mirai.message.data
/**
* 消息源, 用于被引用. 它将由协议模块实现为 `MessageSourceImpl`
* 消息源, 用于被引用. 它将由协议模块实现.
* 消息源只用于 [QuoteReply]
*
* `mirai-core-qqandroid`: `net.mamoe.mirai.qqandroid.message.MessageSourceFromMsg`
*/
interface MessageSource : Message {
companion object : Message.Key<MessageSource>
/* 以下属性均无 backing field, 即都是以 `get() =` 实现 */
/**
* 原消息序列号
*/
val originalSeq: Int
/**
* 发送人 id
*/
val senderId: Long
/**
* 群 id
*/
val groupId: Long
/**
* in seconds
* 实际上是个随机数, 但服务器确实是用它当做 uid
*/
val time: Int
val messageUid: Long
/**
* 固定返回空字符串 ("")
......
......@@ -9,12 +9,28 @@
package net.mamoe.mirai.message.data
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.utils.MiraiInternalAPI
/**
* 群内的引用回复. 它将由协议模块实现为 `QuoteReplyImpl`
* 群内的引用回复.
* 总是使用 [quote] 来构造实例.
*/
interface QuoteReply : Message {
val source: MessageSource
class QuoteReply @MiraiInternalAPI constructor(val source: MessageSource) : Message {
companion object Key : Message.Key<QuoteReply>
override fun toString(): String = ""
}
/**
* 引用这条消息.
* 返回 `[QuoteReply] + [At] + [PlainText]`(必要的结构)
*/
fun MessageChain.quote(sender: Member): MessageChain {
this.firstOrNull<MessageSource>()?.let {
@UseExperimental(MiraiInternalAPI::class)
return QuoteReply(it) + sender.at() + " " // required
}
error("cannot find MessageSource")
}
\ No newline at end of file
......@@ -50,6 +50,10 @@ class ExternalImage(
data: ByteReadPacket,
filename: String
): ExternalImage = ExternalImage(width, height, md5, format, data, data.remaining, filename)
fun generateUUID(md5: ByteArray): String{
return "${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}"
}
}
val format: String =
......@@ -83,7 +87,7 @@ class ExternalImage(
override fun toString(): String = "[ExternalImage(${width}x$height $format)]"
fun calculateImageResourceId(): String {
return "{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format"
return "{${generateUUID(md5)}}.$format"
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment