Commit 23adcf6d authored by Him188's avatar Him188

Quote reply support

parent 33867d04
...@@ -10,10 +10,8 @@ ...@@ -10,10 +10,8 @@
package net.mamoe.mirai.api.http.data.common package net.mamoe.mirai.api.http.data.common
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.QQ
@Serializable @Serializable
abstract class ContactDTO : DTO { abstract class ContactDTO : DTO {
...@@ -38,8 +36,8 @@ data class MemberDTO( ...@@ -38,8 +36,8 @@ data class MemberDTO(
val permission: MemberPermission, val permission: MemberPermission,
val group: GroupDTO val group: GroupDTO
) : ContactDTO() { ) : ContactDTO() {
constructor(member: Member) : this ( constructor(member: Member) : this(
member.id, member.groupCard, member.permission, member.id, member.groupCardOrNick, member.permission,
GroupDTO(member.group) GroupDTO(member.group)
) )
} }
...@@ -50,5 +48,6 @@ data class GroupDTO( ...@@ -50,5 +48,6 @@ data class GroupDTO(
val name: String, val name: String,
val permission: MemberPermission val permission: MemberPermission
) : ContactDTO() { ) : ContactDTO() {
@UseExperimental(MiraiExperimentalAPI::class)
constructor(group: Group) : this(group.id, group.name, group.botPermission) constructor(group: Group) : this(group.id, group.name, group.botPermission)
} }
...@@ -48,6 +48,8 @@ internal abstract class ContactImpl : Contact { ...@@ -48,6 +48,8 @@ internal abstract class ContactImpl : Contact {
internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), QQ { internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), QQ {
override val bot: QQAndroidBot by bot.unsafeWeakRef() override val bot: QQAndroidBot by bot.unsafeWeakRef()
override lateinit var nick: String
override suspend fun sendMessage(message: MessageChain) { override suspend fun sendMessage(message: MessageChain) {
bot.network.run { bot.network.run {
check( check(
...@@ -186,39 +188,42 @@ internal class MemberImpl( ...@@ -186,39 +188,42 @@ internal class MemberImpl(
override val bot: QQAndroidBot get() = qq.bot override val bot: QQAndroidBot get() = qq.bot
override suspend fun mute(durationSeconds: Int): Boolean { override suspend fun mute(durationSeconds: Int): Boolean {
if (bot.uin == this.qq.id) { if (group.botPermission != MemberPermission.OWNER && (!group.botPermission.isOperator() || this.isOperator())) {
return false return false
} }
//判断有无禁言权限
val myPermission = group.botPermission bot.network.run {
val targetPermission = this.permission TroopManagement.Mute(
if (myPermission != MemberPermission.OWNER) { client = bot.client,
if (targetPermission == MemberPermission.OWNER || targetPermission == MemberPermission.ADMINISTRATOR) { groupCode = group.id,
return false memberUin = this@MemberImpl.id,
} timeInSecond = durationSeconds
} else if (myPermission == MemberPermission.MEMBER) { ).sendAndExpect<TroopManagement.Mute.Response>()
return false
}
return try {
bot.network.run {
TroopManagement.Mute(
client = bot.client,
groupCode = group.id,
memberUin = this@MemberImpl.id,
timeInSecond = durationSeconds
).sendAndExpect<TroopManagement.Mute.Response>()
}
true
} catch (e: Exception) {
false
} }
return true
} }
override suspend fun unmute(): Boolean { 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 { override suspend fun kick(message: String): Boolean {
if (group.botPermission != MemberPermission.OWNER && (!group.botPermission.isOperator() || this.isOperator())) {
return false
}
bot.network.run { bot.network.run {
return TroopManagement.Kick( return TroopManagement.Kick(
client = bot.client, client = bot.client,
......
...@@ -18,10 +18,7 @@ import net.mamoe.mirai.event.events.BotEvent ...@@ -18,10 +18,7 @@ import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
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.utils.BotConfiguration import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
...@@ -37,7 +34,8 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -37,7 +34,8 @@ internal abstract class QQAndroidBotBase constructor(
account: BotAccount, account: BotAccount,
configuration: BotConfiguration configuration: BotConfiguration
) : BotImpl<QQAndroidBotNetworkHandler>(account, configuration) { ) : 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 internal var firstLoginSucceed: Boolean = false
override val uin: Long get() = client.uin override val uin: Long get() = client.uin
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList()) 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 许可证的约束, 可以在以下链接找到该许可证. * Some code changed by Mamoe is annotated around "MIRAI MODIFY START" and "MIRAI MODIFY END"
* 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.
*/ */
package net.mamoe.mirai.qqandroid.io.serialization package net.mamoe.mirai.qqandroid.io.serialization
...@@ -73,7 +67,7 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac ...@@ -73,7 +67,7 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
override fun SerialDescriptor.getTag(index: Int) = this.getProtoDesc(index) override fun SerialDescriptor.getTag(index: Int) = this.getProtoDesc(index)
// MIRAI MODIFY START: // MIRAI MODIFY START
override fun encodeTaggedNull(tag: ProtoDesc) { override fun encodeTaggedNull(tag: ProtoDesc) {
} }
......
...@@ -20,6 +20,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3 ...@@ -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.qqandroid.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.utils.firstValue import net.mamoe.mirai.utils.firstValue
import net.mamoe.mirai.utils.io.read 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 { fun <T : JceStruct> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset = JceCharset.UTF8): T {
......
...@@ -189,10 +189,8 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> { ...@@ -189,10 +189,8 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
if (this.any<QuoteReply>()) { if (this.any<QuoteReply>()) {
when (val source = this[QuoteReply].source) { when (val source = this[QuoteReply].source) {
is MessageSourceFromServer -> { is MessageSourceFromServer -> elements.add(ImMsgBody.Elem(srcMsg = source.delegate))
elements.add(ImMsgBody.Elem(srcMsg = source.delegate)) is MessageSourceFromMsg -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData()))
}
is MessageSourceFromMsg -> { elements.add(ImMsgBody.Elem(srcMsg = source.toJceData())) }
else -> error("unsupported MessageSource implementation: ${source::class.simpleName}") else -> error("unsupported MessageSource implementation: ${source::class.simpleName}")
} }
} }
...@@ -204,16 +202,19 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> { ...@@ -204,16 +202,19 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
is CustomFaceFromFile -> elements.add(ImMsgBody.Elem(customFace = it.toJceData())) is CustomFaceFromFile -> elements.add(ImMsgBody.Elem(customFace = it.toJceData()))
is CustomFaceFromServer -> elements.add(ImMsgBody.Elem(customFace = it.delegate)) is CustomFaceFromServer -> elements.add(ImMsgBody.Elem(customFace = it.delegate))
is NotOnlineImageFromServer -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate)) is NotOnlineImageFromServer -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate))
is NotOnlineImageFromFile -> elements.add( is NotOnlineImageFromFile -> elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData()))
ImMsgBody.Elem( is QuoteReply,
notOnlineImage = it.toJceData(), generalFlags = ImMsgBody.GeneralFlags( is MessageSource -> {
pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes()
) }
) 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 return elements
} }
...@@ -275,12 +276,12 @@ internal class NotOnlineImageFromServer( ...@@ -275,12 +276,12 @@ internal class NotOnlineImageFromServer(
internal fun MsgComm.Msg.toMessageChain(): MessageChain { internal fun MsgComm.Msg.toMessageChain(): MessageChain {
val elems = this.msgBody.richText.elems val elems = this.msgBody.richText.elems
val message = MessageChain(initialCapacity = elems.size) val message = MessageChain(initialCapacity = elems.size + 1)
message.add(MessageSourceFromMsg(delegate = this)) message.add(MessageSourceFromMsg(delegate = this))
elems.forEach { elems.forEach {
when { 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.notOnlineImage != null -> message.add(NotOnlineImageFromServer(it.notOnlineImage))
it.customFace != null -> message.add(CustomFaceFromServer(it.customFace)) it.customFace != null -> message.add(CustomFaceFromServer(it.customFace))
it.text != null -> { it.text != null -> {
......
...@@ -9,22 +9,19 @@ ...@@ -9,22 +9,19 @@
package net.mamoe.mirai.qqandroid.message package net.mamoe.mirai.qqandroid.message
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.message.data.MessageSource 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.io.serialization.toByteArray
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.MsgComm import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg 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( internal inline class MessageSourceFromServer(
val delegate: ImMsgBody.SourceMsg val delegate: ImMsgBody.SourceMsg
) : MessageSource { ) : MessageSource {
override val originalSeq: Int get() = delegate.origSeqs!!.first() override val messageUid: Long
override val senderId: Long get() = delegate.senderUin get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!
override val groupId: Long get() = delegate.toUin
override val time: Int get() = delegate.time
override fun toString(): String = "" override fun toString(): String = ""
} }
...@@ -32,52 +29,46 @@ internal inline class MessageSourceFromServer( ...@@ -32,52 +29,46 @@ internal inline class MessageSourceFromServer(
internal inline class MessageSourceFromMsg( internal inline class MessageSourceFromMsg(
val delegate: MsgComm.Msg val delegate: MsgComm.Msg
) : MessageSource { ) : MessageSource {
override val originalSeq: Int get() = delegate.msgHead.msgSeq override val messageUid: Long
override val senderId: Long get() = delegate.msgHead.fromUin get() = delegate.msgBody.richText.attr!!.random.toLong()
override val groupId: Long get() = delegate.msgHead.toUin
override val time: Int get() = delegate.msgHead.msgTime
fun toJceData(): ImMsgBody.SourceMsg { fun toJceData(): ImMsgBody.SourceMsg {
val groupUin = Group.calculateGroupIdByGroupCode(delegate.msgHead.groupInfo!!.groupCode)
return ImMsgBody.SourceMsg( return ImMsgBody.SourceMsg(
origSeqs = listOf(delegate.msgHead.msgSeq), origSeqs = listOf(delegate.msgHead.msgSeq),
senderUin = delegate.msgHead.fromUin, senderUin = delegate.msgHead.fromUin,
toUin = delegate.msgHead.groupInfo!!.groupCode, toUin = groupUin,
flag = 1, flag = 1,
elems = delegate.toMessageChain().toRichTextElems(), elems = delegate.msgBody.richText.elems,
type = 0, type = 0,
time = delegate.msgHead.msgTime, time = delegate.msgHead.msgTime,
pbReserve = SourceMsg.ResvAttr( pbReserve = SourceMsg.ResvAttr(
origUids = delegate.msgBody.richText.attr!!.random.toLong()//, origUids = messageUid
//oriMsgtype = delegate.msgHead.msgType ).toByteArray(SourceMsg.ResvAttr.serializer()),
).toByteArray(SourceMsg.ResvAttr.serializer()).also { println("pbReserve=" + it.toUHexString()) },// PbReserve(delegate.msgBody.richText.attr!!.random.toLong()).toByteArray(PbReserve.serializer()).also { println("pbReserve=" + it.toUHexString()) },
srcMsg = MsgComm.Msg( srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead( msgHead = MsgComm.MsgHead(
fromUin = delegate.msgHead.fromUin, // qq fromUin = delegate.msgHead.fromUin, // qq
toUin = delegate.msgHead.groupInfo.groupCode, // group toUin = groupUin, // group
msgType = delegate.msgHead.msgType.also { println("msgType=$it") }, // 82? msgType = delegate.msgHead.msgType, // 82?
c2cCmd = delegate.msgHead.c2cCmd, c2cCmd = delegate.msgHead.c2cCmd,
msgSeq = delegate.msgHead.msgSeq, msgSeq = delegate.msgHead.msgSeq,
msgTime = delegate.msgHead.msgTime, msgTime = delegate.msgHead.msgTime,
msgUid = delegate.msgBody.richText.attr.random.toLong(), // ok msgUid = messageUid, // ok
groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode), groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode),
isSrcMsg = true isSrcMsg = true
), ),
msgBody = ImMsgBody.MsgBody( msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText( 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()).also { println("srcMsg=" + it.toUHexString()) } // fucking slow ).toByteArray(MsgComm.Msg.serializer())
).also { println(it.contentToString()) } )
} }
override fun toString(): String = "" 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
...@@ -20,12 +20,11 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot ...@@ -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.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv 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.NetworkType
import net.mamoe.mirai.qqandroid.utils.SystemDeviceInfo import net.mamoe.mirai.utils.SystemDeviceInfo
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.cryptor.ECDH 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.cryptor.decryptBy
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
...@@ -105,7 +104,7 @@ internal open class QQAndroidClient( ...@@ -105,7 +104,7 @@ internal open class QQAndroidClient(
val apkVersionName: ByteArray get() = "8.2.0".toByteArray() val apkVersionName: ByteArray get() = "8.2.0".toByteArray()
val buildVer: String get() = "8.2.0.1296" 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) internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2)
private val requestPacketRequestId: AtomicInt = atomic(1921334513) private val requestPacketRequestId: AtomicInt = atomic(1921334513)
......
...@@ -80,7 +80,7 @@ object Highway { ...@@ -80,7 +80,7 @@ object Highway {
writeFully(head) writeFully(head)
check(body.copyTo(this).toInt() == bodySize) { "bad body size" } check(body.copyTo(this).toInt() == bodySize) { "bad body size" }
writeByte(41) writeByte(41)
}.also { println(it.remaining) } }
} }
} }
} }
\ No newline at end of file
...@@ -356,7 +356,7 @@ internal class ImMsgBody : ProtoBuf { ...@@ -356,7 +356,7 @@ internal class ImMsgBody : ProtoBuf {
@SerialId(26) val conferenceTipsInfo: ConferenceTipsInfo? = null, @SerialId(26) val conferenceTipsInfo: ConferenceTipsInfo? = null,
@SerialId(27) val redbagInfo: RedBagInfo? = null, @SerialId(27) val redbagInfo: RedBagInfo? = null,
@SerialId(28) val lowVersionTips: LowVersionTips? = 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(30) val nearByMsg: NearByMessageType? = null,
@SerialId(31) val customElem: CustomElem? = null, @SerialId(31) val customElem: CustomElem? = null,
@SerialId(32) val locationInfo: LocationInfo? = null, @SerialId(32) val locationInfo: LocationInfo? = null,
...@@ -1142,7 +1142,7 @@ internal class ImReceipt : ProtoBuf { ...@@ -1142,7 +1142,7 @@ internal class ImReceipt : ProtoBuf {
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
data internal class ReceiptResp( internal data class ReceiptResp(
@SerialId(1) val command: Int /* enum */ = 1, @SerialId(1) val command: Int /* enum */ = 1,
@SerialId(2) val receiptInfo: ReceiptInfo? = null @SerialId(2) val receiptInfo: ReceiptInfo? = null
) : ProtoBuf ) : ProtoBuf
...@@ -1194,10 +1194,10 @@ internal class Submsgtype0xc7 : ProtoBuf { ...@@ -1194,10 +1194,10 @@ internal class Submsgtype0xc7 : ProtoBuf {
@SerialId(2) val srcUin: Long = 0L, @SerialId(2) val srcUin: Long = 0L,
@SerialId(3) val dstUin: Long = 0L, @SerialId(3) val dstUin: Long = 0L,
@SerialId(4) val changeType: Int /* enum */ = 1, @SerialId(4) val changeType: Int /* enum */ = 1,
@SerialId(5) val msgRelationalChainInfoOld: Submsgtype0xc7.RelationalChainInfo? = null, @SerialId(5) val msgRelationalChainInfoOld: RelationalChainInfo? = null,
@SerialId(6) val msgRelationalChainInfoNew: Submsgtype0xc7.RelationalChainInfo? = null, @SerialId(6) val msgRelationalChainInfoNew: RelationalChainInfo? = null,
@SerialId(7) val msgToDegradeInfo: Submsgtype0xc7.ToDegradeInfo? = null, @SerialId(7) val msgToDegradeInfo: ToDegradeInfo? = null,
@SerialId(20) val relationalChainInfos: List<Submsgtype0xc7.RelationalChainInfos>? = null, @SerialId(20) val relationalChainInfos: List<RelationalChainInfos>? = null,
@SerialId(100) val uint32FeatureId: List<Int>? = null @SerialId(100) val uint32FeatureId: List<Int>? = null
) : ProtoBuf ) : ProtoBuf
...@@ -1235,8 +1235,8 @@ internal class Submsgtype0xc7 : ProtoBuf { ...@@ -1235,8 +1235,8 @@ internal class Submsgtype0xc7 : ProtoBuf {
internal class ForwardBody( internal class ForwardBody(
@SerialId(1) val notifyType: Int = 0, @SerialId(1) val notifyType: Int = 0,
@SerialId(2) val opType: Int = 0, @SerialId(2) val opType: Int = 0,
@SerialId(3000) val msgHotFriendNotify: Submsgtype0xc7.HotFriendNotify? = null, @SerialId(3000) val msgHotFriendNotify: HotFriendNotify? = null,
@SerialId(4000) val msgRelationalChainChange: Submsgtype0xc7.RelationalChainChange? = null @SerialId(4000) val msgRelationalChainChange: RelationalChainChange? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
...@@ -1277,24 +1277,24 @@ internal class Submsgtype0xc7 : ProtoBuf { ...@@ -1277,24 +1277,24 @@ internal class Submsgtype0xc7 : ProtoBuf {
@SerialId(303) val lastBoatTime: Int = 0, @SerialId(303) val lastBoatTime: Int = 0,
@SerialId(304) val boatWording: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(304) val boatWording: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(400) val notifyType: Int = 0, @SerialId(400) val notifyType: Int = 0,
@SerialId(401) val msgFriendshipFlagNotify: Submsgtype0xc7.FriendShipFlagNotify? = null @SerialId(401) val msgFriendshipFlagNotify: FriendShipFlagNotify? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class RelationalChainInfos( internal class RelationalChainInfos(
@SerialId(1) val msgRelationalChainInfoOld: Submsgtype0xc7.RelationalChainInfo? = null, @SerialId(1) val msgRelationalChainInfoOld: RelationalChainInfo? = null,
@SerialId(2) val msgRelationalChainInfoNew: Submsgtype0xc7.RelationalChainInfo? = null @SerialId(2) val msgRelationalChainInfoNew: RelationalChainInfo? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class ToDegradeInfo( 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(2) val nick: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val notifyTime: Long = 0L @SerialId(3) val notifyTime: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class MsgBody( internal class MsgBody(
@SerialId(1) val msgModInfos: List<Submsgtype0xc7.ForwardBody>? = null @SerialId(1) val msgModInfos: List<ForwardBody>? = null
) : ProtoBuf ) : ProtoBuf
} }
...@@ -22,6 +22,8 @@ import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket ...@@ -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.readProtoBuf
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf 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.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify
...@@ -30,8 +32,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm ...@@ -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.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie
import net.mamoe.mirai.qqandroid.network.protocol.packet.* 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.MiraiInternalAPI
import net.mamoe.mirai.utils.currentTimeSeconds import net.mamoe.mirai.utils.currentTimeSeconds
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
...@@ -184,8 +184,8 @@ internal class MessageSvc { ...@@ -184,8 +184,8 @@ internal class MessageSvc {
override fun toString(): String = "MessageSvc.PbSendMsg.Response.SUCCESS" override fun toString(): String = "MessageSvc.PbSendMsg.Response.SUCCESS"
} }
data class Failed(val errorCode: Int, val errorMessage: String) : Response() { data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() {
override fun toString(): String = "MessageSvc.PbSendMsg.Response.Failed(errorCode=$errorCode, errorMessage=$errorMessage)" override fun toString(): String = "MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)"
} }
} }
...@@ -236,21 +236,16 @@ internal class MessageSvc { ...@@ -236,21 +236,16 @@ internal class MessageSvc {
writeProtoBuf( writeProtoBuf(
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq( MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
routingHead = MsgSvc.RoutingHead(grp = MsgSvc.Grp(groupCode = groupCode)), routingHead = MsgSvc.RoutingHead(grp = MsgSvc.Grp(groupCode = groupCode)),
contentHead = MsgComm.ContentHead(pkgNum = 1, divSeq = seq), contentHead = MsgComm.ContentHead(pkgNum = 1),
msgBody = ImMsgBody.MsgBody( msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText( richText = ImMsgBody.RichText(
elems = message.toRichTextElems() elems = message.toRichTextElems()
) )
), ),
//
//
//
msgSeq = seq, msgSeq = seq,
msgRand = Random.nextInt().absoluteValue, msgRand = Random.nextInt().absoluteValue,
syncCookie = EMPTY_BYTE_ARRAY syncCookie = EMPTY_BYTE_ARRAY,
// ?: SyncCookie(time = currentTimeSeconds + client.timeDifference).toByteArray(SyncCookie.serializer()), msgVia = 1
, msgVia = 1
) )
) )
} }
...@@ -260,7 +255,7 @@ internal class MessageSvc { ...@@ -260,7 +255,7 @@ internal class MessageSvc {
return if (response.result == 0) { return if (response.result == 0) {
Response.SUCCESS Response.SUCCESS
} else { } else {
Response.Failed(response.errtype, response.errmsg) Response.Failed(response.result, response.errtype, response.errmsg)
} }
} }
} }
......
...@@ -74,9 +74,4 @@ internal fun guidFlag( ...@@ -74,9 +74,4 @@ internal fun guidFlag(
flag = flag or (guidSource.id shl 24 and 0xFF000000) flag = flag or (guidSource.id shl 24 and 0xFF000000)
flag = flag or (macOrAndroidIdChangeFlag.value shl 8 and 0xFF00) flag = flag or (macOrAndroidIdChangeFlag.value shl 8 and 0xFF00)
return flag 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 { ...@@ -106,6 +106,28 @@ interface Group : Contact, CoroutineScope {
suspend fun quit(): Boolean 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 @MiraiExperimentalAPI
fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})" 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 @@ ...@@ -11,7 +11,8 @@
package net.mamoe.mirai.contact 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.Duration
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
...@@ -31,9 +32,11 @@ interface Member : QQ, Contact { ...@@ -31,9 +32,11 @@ interface Member : QQ, Contact {
val permission: MemberPermission val permission: MemberPermission
/** /**
* 群名片 * 群名片. 可能为空.
* *
* 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException] * 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException]
*
* @see [groupCardOrNick] 获取非空群名片或昵称
*/ */
var groupCard: String var groupCard: String
...@@ -48,7 +51,7 @@ interface Member : QQ, Contact { ...@@ -48,7 +51,7 @@ interface Member : QQ, Contact {
* 禁言 * 禁言
* *
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
* @return 当机器人无权限禁言这个群成员时返回 `false` * @return 当机器人无权限禁言这个群成员时返回 `false`
* *
* @see Int.minutesToSeconds * @see Int.minutesToSeconds
* @see Int.hoursToSeconds * @see Int.hoursToSeconds
...@@ -57,26 +60,31 @@ interface Member : QQ, Contact { ...@@ -57,26 +60,31 @@ interface Member : QQ, Contact {
suspend fun mute(durationSeconds: Int): Boolean suspend fun mute(durationSeconds: Int): Boolean
/** /**
* 解除禁言. 在没有权限时会返回 `false`. 否则均返回 `true`. * 解除禁言. 在没有权限时会返回 `false`.
*/ */
suspend fun unmute(): Boolean suspend fun unmute(): Boolean
/**
* 踢出该成员. 机器人无权限时返回 `false`
*/
suspend fun kick(message: String = ""): Boolean suspend fun kick(message: String = ""): Boolean
/** /**
* 当且仅当 `[other] is [Member] && [other].id == this.id && [other].group == this.group` 时为 true * 当且仅当 `[other] is [Member] && [other].id == this.id && [other].group == this.group` 时为 true
*/ */
override fun equals(other: Any?): Boolean override fun equals(other: Any?): Boolean
} }
/**
* 获取非空群名片或昵称.
*
* 若 [群名片][Member.groupCard] 不为空则返回群名片, 为空则返回 [QQ.nick]
*/
val Member.groupCardOrNick: String get() = this.groupCard.takeIf { it.isNotEmpty() } ?: this.nick
@ExperimentalTime @ExperimentalTime
suspend inline fun Member.mute(duration: Duration): Boolean { suspend inline fun Member.mute(duration: Duration): Boolean {
require(duration.inDays <= 30) { "duration must be at most 1 month" } require(duration.inDays <= 30) { "duration must be at most 1 month" }
require(duration.inSeconds > 0) { "duration must be greater than 0 second" } require(duration.inSeconds > 0) { "duration must be greater than 0 second" }
return this.mute(duration.inSeconds.toInt()) return this.mute(duration.inSeconds.toInt())
}
@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() ...@@ -62,7 +62,7 @@ inline fun Member.isOwner(): Boolean = this.permission.isOwner()
inline fun Member.isAdministrator(): Boolean = this.permission.isAdministrator() inline fun Member.isAdministrator(): Boolean = this.permission.isAdministrator()
/** /**
* 管理员或群主 * 管理员或群主
*/ */
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun Member.isOperator(): Boolean = this.permission.isOperator() inline fun Member.isOperator(): Boolean = this.permission.isOperator()
......
...@@ -35,6 +35,11 @@ interface QQ : Contact, CoroutineScope { ...@@ -35,6 +35,11 @@ interface QQ : Contact, CoroutineScope {
*/ */
override val id: Long override val id: Long
/**
* 昵称
*/
val nick: String
/** /**
* 请求头像下载链接 * 请求头像下载链接
*/ */
......
...@@ -14,7 +14,11 @@ package net.mamoe.mirai.data ...@@ -14,7 +14,11 @@ package net.mamoe.mirai.data
*/ */
interface Packet interface Packet
object NoPacket : Packet object NoPacket : Packet{
override fun toString(): String {
return "NoPacket"
}
}
/** /**
* PacketFactory 可以一次解析多个包出来. 它们将会被分别广播. * PacketFactory 可以一次解析多个包出来. 它们将会被分别广播.
......
...@@ -89,6 +89,15 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) : ...@@ -89,6 +89,15 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
// endregion // endregion
// region
/**
* 引用这个消息.
*/
inline fun MessageChain.quote(): MessageChain = this.quote(sender as? Member ?: error("only group message can be quoted"))
// endregion
// region 上传图片 // region 上传图片
suspend inline fun ExternalImage.upload(): Image = this.upload(subject) suspend inline fun ExternalImage.upload(): Image = this.upload(subject)
// endregion // endregion
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.groupCardOrNick
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
...@@ -20,7 +21,7 @@ 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 { class At @MiraiInternalAPI constructor(val target: Long, val display: String) : Message {
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
constructor(member: Member) : this(member.id, "@${member.groupCard}") constructor(member: Member) : this(member.id, "@${member.groupCardOrNick}")
override fun toString(): String = display override fun toString(): String = display
......
...@@ -135,18 +135,18 @@ inline fun List<Message>.toMessageChain(): MessageChain = MessageChain(this) ...@@ -135,18 +135,18 @@ inline fun List<Message>.toMessageChain(): MessageChain = MessageChain(this)
/** /**
* 获取第一个 [M] 类型的 [Message] 实例 * 获取第一个 [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] 实例 * 获取第一个 [M] 类型的 [Message] 实例
* @throws [NoSuchElementException] 如果找不到该类型的实例 * @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] 实例 * 获取第一个 [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 @@ ...@@ -10,32 +10,18 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
/** /**
* 消息源, 用于被引用. 它将由协议模块实现为 `MessageSourceImpl` * 消息源, 用于被引用. 它将由协议模块实现.
* 消息源只用于 [QuoteReply]
*
* `mirai-core-qqandroid`: `net.mamoe.mirai.qqandroid.message.MessageSourceFromMsg`
*/ */
interface MessageSource : Message { interface MessageSource : Message {
companion object : Message.Key<MessageSource> 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 @@ ...@@ -9,12 +9,28 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.utils.MiraiInternalAPI
/** /**
* 群内的引用回复. 它将由协议模块实现为 `QuoteReplyImpl` * 群内的引用回复.
* 总是使用 [quote] 来构造实例.
*/ */
interface QuoteReply : Message { class QuoteReply @MiraiInternalAPI constructor(val source: MessageSource) : Message {
val source: MessageSource
companion object Key : Message.Key<QuoteReply> 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( ...@@ -50,6 +50,10 @@ class ExternalImage(
data: ByteReadPacket, data: ByteReadPacket,
filename: String filename: String
): ExternalImage = ExternalImage(width, height, md5, format, data, data.remaining, filename) ): 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 = val format: String =
...@@ -83,7 +87,7 @@ class ExternalImage( ...@@ -83,7 +87,7 @@ class ExternalImage(
override fun toString(): String = "[ExternalImage(${width}x$height $format)]" override fun toString(): String = "[ExternalImage(${width}x$height $format)]"
fun calculateImageResourceId(): String { 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