mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-09 19:50:27 +08:00
Merge branch 'master' of https://github.com/mamoe/mirai
Conflicts: mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
This commit is contained in:
commit
71c8609916
@ -8,12 +8,12 @@ kotlin.parallel.tasks.in.project=true
|
||||
kotlinVersion=1.3.61
|
||||
# kotlin libraries
|
||||
serializationVersion=0.14.0
|
||||
coroutinesVersion=1.3.2
|
||||
coroutinesVersion=1.3.3
|
||||
atomicFuVersion=0.14.1
|
||||
kotlinXIoVersion=0.1.16
|
||||
coroutinesIoVersion=0.1.16
|
||||
# utility
|
||||
ktorVersion=1.2.6
|
||||
ktorVersion=1.3.1
|
||||
klockVersion=1.7.0
|
||||
# gradle plugin
|
||||
protobufJavaVersion=3.10.0
|
@ -4,7 +4,8 @@ import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.routing.routing
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.api.http.data.*
|
||||
import net.mamoe.mirai.api.http.data.PermissionDeniedException
|
||||
import net.mamoe.mirai.api.http.data.StateCode
|
||||
import net.mamoe.mirai.api.http.data.common.DTO
|
||||
import net.mamoe.mirai.api.http.data.common.VerifyDTO
|
||||
import net.mamoe.mirai.contact.Group
|
||||
@ -63,7 +64,7 @@ fun Application.groupManageModule() {
|
||||
val group = dto.session.bot.getGroup(dto.target)
|
||||
with(dto.config) {
|
||||
name?.let { group.name = it }
|
||||
announcement?.let { group.announcement = it }
|
||||
announcement?.let { group.entranceAnnouncement = it }
|
||||
confessTalk?.let { group.confessTalk = it }
|
||||
allowMemberInvite?.let { group.allowMemberInvite = it }
|
||||
// TODO: 待core接口实现设置可改
|
||||
@ -84,7 +85,7 @@ fun Application.groupManageModule() {
|
||||
miraiVerify<MemberInfoDTO>("/memberInfo") { dto ->
|
||||
val member = dto.session.bot.getGroup(dto.target)[dto.memberId]
|
||||
with(dto.info) {
|
||||
name?.let { member.groupCard = it }
|
||||
name?.let { member.nameCard = it }
|
||||
specialTitle?.let { member.specialTitle = it }
|
||||
}
|
||||
call.respondStateCode(StateCode.Success)
|
||||
@ -127,7 +128,7 @@ private data class GroupDetailDTO(
|
||||
val anonymousChat: Boolean? = null
|
||||
) : DTO {
|
||||
constructor(group: Group) : this(
|
||||
group.name, group.announcement, group.confessTalk, group.allowMemberInvite,
|
||||
group.name, group.entranceAnnouncement, group.confessTalk, group.allowMemberInvite,
|
||||
group.autoApprove, group.anonymousChat
|
||||
)
|
||||
}
|
||||
@ -145,5 +146,5 @@ private data class MemberDetailDTO(
|
||||
val name: String? = null,
|
||||
val specialTitle: String? = null
|
||||
) : DTO {
|
||||
constructor(member: Member) : this(member.groupCard, member.specialTitle)
|
||||
constructor(member: Member) : this(member.nameCard, member.specialTitle)
|
||||
}
|
||||
|
@ -93,10 +93,10 @@ kotlin {
|
||||
|
||||
val androidTest by getting {
|
||||
dependencies {
|
||||
api(kotlin("test", kotlinVersion))
|
||||
api(kotlin("test-junit", kotlinVersion))
|
||||
api(kotlin("test-annotations-common"))
|
||||
api(kotlin("test-common"))
|
||||
implementation(kotlin("test", kotlinVersion))
|
||||
implementation(kotlin("test-junit", kotlinVersion))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
implementation(kotlin("test-common"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,10 @@ import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.data.FriendNameRemark
|
||||
import net.mamoe.mirai.data.PreviousNameList
|
||||
import net.mamoe.mirai.data.Profile
|
||||
import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.data.CustomFaceFromFile
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
@ -51,18 +55,25 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin
|
||||
override lateinit var nick: String
|
||||
|
||||
override suspend fun sendMessage(message: MessageChain) {
|
||||
val event = FriendMessageSendEvent(this, message).broadcast()
|
||||
if (event.isCancelled) {
|
||||
throw EventCancelledException("cancelled by FriendMessageSendEvent")
|
||||
}
|
||||
bot.network.run {
|
||||
check(
|
||||
MessageSvc.PbSendMsg.ToFriend(
|
||||
bot.client,
|
||||
id,
|
||||
message
|
||||
event.message
|
||||
).sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS
|
||||
) { "send message failed" }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun uploadImage(image: ExternalImage): Image = try {
|
||||
if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) {
|
||||
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
|
||||
}
|
||||
bot.network.run {
|
||||
val response = LongConn.OffPicUp(
|
||||
bot.client, Cmd0x352.TryUpImgReq(
|
||||
@ -87,7 +98,9 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin
|
||||
height = response.imageInfo.fileHeight,
|
||||
width = response.imageInfo.fileWidth,
|
||||
resourceId = response.resourceId
|
||||
)
|
||||
).also {
|
||||
ImageUploadEvent.Succeed(this@QQImpl, image, it).broadcast()
|
||||
}
|
||||
is LongConn.OffPicUp.Response.RequireUpload -> {
|
||||
Http.postImage("0x6ff0070", bot.uin, null, imageInput = image.input, inputSize = image.inputSize, uKeyHex = response.uKey.toUHexString(""))
|
||||
// HighwayHelper.uploadImage(
|
||||
@ -108,9 +121,14 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin
|
||||
height = image.height,
|
||||
width = image.width,
|
||||
resourceId = response.resourceId
|
||||
)
|
||||
).also {
|
||||
ImageUploadEvent.Succeed(this@QQImpl, image, it).broadcast()
|
||||
}
|
||||
}
|
||||
is LongConn.OffPicUp.Response.Failed -> {
|
||||
ImageUploadEvent.Failed(this@QQImpl, image, -1, response.message).broadcast()
|
||||
error(response.message)
|
||||
}
|
||||
is LongConn.OffPicUp.Response.Failed -> error(response.message)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@ -144,16 +162,17 @@ internal class MemberImpl(
|
||||
var _specialTitle: String,
|
||||
group: GroupImpl,
|
||||
override val coroutineContext: CoroutineContext,
|
||||
override val permission: MemberPermission
|
||||
override var permission: MemberPermission
|
||||
) : ContactImpl(), Member, QQ by qq {
|
||||
override val group: GroupImpl by group.unsafeWeakRef()
|
||||
val qq: QQImpl by qq.unsafeWeakRef()
|
||||
|
||||
override var groupCard: String
|
||||
override var nameCard: String
|
||||
get() = _groupCard
|
||||
set(newValue) {
|
||||
group.checkBotPermissionOperator()
|
||||
if (_groupCard != newValue) {
|
||||
val oldValue = _groupCard
|
||||
_groupCard = newValue
|
||||
launch {
|
||||
bot.network.run {
|
||||
@ -163,6 +182,7 @@ internal class MemberImpl(
|
||||
newValue
|
||||
).sendWithoutExpect()
|
||||
}
|
||||
MemberCardChangeEvent(oldValue, newValue, this@MemberImpl, null).broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,8 +190,9 @@ internal class MemberImpl(
|
||||
override var specialTitle: String
|
||||
get() = _specialTitle
|
||||
set(newValue) {
|
||||
group.checkBotPermissionOperator()
|
||||
group.checkBotPermission(MemberPermission.OWNER)
|
||||
if (_specialTitle != newValue) {
|
||||
val oldValue = _specialTitle
|
||||
_specialTitle = newValue
|
||||
launch {
|
||||
bot.network.run {
|
||||
@ -181,6 +202,7 @@ internal class MemberImpl(
|
||||
newValue
|
||||
).sendWithoutExpect()
|
||||
}
|
||||
MemberSpecialTitleChangeEvent(oldValue, newValue, this@MemberImpl).broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -200,6 +222,8 @@ internal class MemberImpl(
|
||||
timeInSecond = durationSeconds
|
||||
).sendAndExpect<TroopManagement.Mute.Response>()
|
||||
}
|
||||
|
||||
MemberMuteEvent(this@MemberImpl, durationSeconds, null).broadcast()
|
||||
return true
|
||||
}
|
||||
|
||||
@ -216,6 +240,8 @@ internal class MemberImpl(
|
||||
timeInSecond = 0
|
||||
).sendAndExpect<TroopManagement.Mute.Response>()
|
||||
}
|
||||
|
||||
MemberUnmuteEvent(this@MemberImpl, null).broadcast()
|
||||
return true
|
||||
}
|
||||
|
||||
@ -229,7 +255,9 @@ internal class MemberImpl(
|
||||
client = bot.client,
|
||||
member = this@MemberImpl,
|
||||
message = message
|
||||
).sendAndExpect<TroopManagement.Kick.Response>().success
|
||||
).sendAndExpect<TroopManagement.Kick.Response>().success.also {
|
||||
MemberLeaveEvent.Kick(this@MemberImpl, null).broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,6 +294,7 @@ internal class GroupImpl(
|
||||
set(newValue) {
|
||||
this.checkBotPermissionOperator()
|
||||
if (_name != newValue) {
|
||||
val oldValue = _name
|
||||
_name = newValue
|
||||
launch {
|
||||
bot.network.run {
|
||||
@ -275,15 +304,17 @@ internal class GroupImpl(
|
||||
newName = newValue
|
||||
).sendWithoutExpect()
|
||||
}
|
||||
GroupNameChangeEvent(oldValue, newValue, this@GroupImpl, null).broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var announcement: String
|
||||
override var entranceAnnouncement: String
|
||||
get() = _announcement
|
||||
set(newValue) {
|
||||
this.checkBotPermissionOperator()
|
||||
if (_announcement != newValue) {
|
||||
val oldValue = _announcement
|
||||
_announcement = newValue
|
||||
launch {
|
||||
bot.network.run {
|
||||
@ -293,6 +324,7 @@ internal class GroupImpl(
|
||||
newMemo = newValue
|
||||
).sendWithoutExpect()
|
||||
}
|
||||
GroupEntranceAnnouncementChangeEvent(oldValue, newValue, this@GroupImpl, null).broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -303,6 +335,7 @@ internal class GroupImpl(
|
||||
set(newValue) {
|
||||
this.checkBotPermissionOperator()
|
||||
if (_allowMemberInvite != newValue) {
|
||||
val oldValue = _allowMemberInvite
|
||||
_allowMemberInvite = newValue
|
||||
launch {
|
||||
bot.network.run {
|
||||
@ -312,6 +345,7 @@ internal class GroupImpl(
|
||||
switch = newValue
|
||||
).sendWithoutExpect()
|
||||
}
|
||||
GroupAllowMemberInviteEvent(oldValue, newValue, this@GroupImpl, null).broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -333,6 +367,7 @@ internal class GroupImpl(
|
||||
set(newValue) {
|
||||
this.checkBotPermissionOperator()
|
||||
if (_confessTalk != newValue) {
|
||||
val oldValue = _confessTalk
|
||||
_confessTalk = newValue
|
||||
launch {
|
||||
bot.network.run {
|
||||
@ -342,6 +377,7 @@ internal class GroupImpl(
|
||||
switch = newValue
|
||||
).sendWithoutExpect()
|
||||
}
|
||||
GroupAllowConfessTalkEvent(oldValue, newValue, this@GroupImpl, null).broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -352,6 +388,7 @@ internal class GroupImpl(
|
||||
set(newValue) {
|
||||
this.checkBotPermissionOperator()
|
||||
if (_muteAll != newValue) {
|
||||
val oldValue = _muteAll
|
||||
_muteAll = newValue
|
||||
launch {
|
||||
bot.network.run {
|
||||
@ -361,6 +398,7 @@ internal class GroupImpl(
|
||||
switch = newValue
|
||||
).sendWithoutExpect()
|
||||
}
|
||||
GroupMuteAllEvent(oldValue, newValue, this@GroupImpl, null).broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -372,7 +410,7 @@ internal class GroupImpl(
|
||||
|
||||
override suspend fun quit(): Boolean {
|
||||
check(botPermission != MemberPermission.OWNER) { "An owner cannot quit from a owning group" }
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
|
||||
@ -391,11 +429,15 @@ internal class GroupImpl(
|
||||
override val bot: QQAndroidBot by bot.unsafeWeakRef()
|
||||
|
||||
override suspend fun sendMessage(message: MessageChain) {
|
||||
val event = GroupMessageSendEvent(this, message).broadcast()
|
||||
if (event.isCancelled) {
|
||||
throw EventCancelledException("cancelled by FriendMessageSendEvent")
|
||||
}
|
||||
bot.network.run {
|
||||
val response = MessageSvc.PbSendMsg.ToGroup(
|
||||
bot.client,
|
||||
id,
|
||||
message
|
||||
event.message
|
||||
).sendAndExpect<MessageSvc.PbSendMsg.Response>()
|
||||
check(
|
||||
response is MessageSvc.PbSendMsg.Response.SUCCESS
|
||||
@ -404,6 +446,9 @@ internal class GroupImpl(
|
||||
}
|
||||
|
||||
override suspend fun uploadImage(image: ExternalImage): Image = try {
|
||||
if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) {
|
||||
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
|
||||
}
|
||||
bot.network.run {
|
||||
val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp(
|
||||
bot.client,
|
||||
@ -418,7 +463,10 @@ internal class GroupImpl(
|
||||
).sendAndExpect()
|
||||
|
||||
when (response) {
|
||||
is ImgStore.GroupPicUp.Response.Failed -> error("upload group image failed with reason ${response.message}")
|
||||
is ImgStore.GroupPicUp.Response.Failed -> {
|
||||
ImageUploadEvent.Failed(this@GroupImpl, image, response.resultCode, response.message).broadcast()
|
||||
error("upload group image failed with reason ${response.message}")
|
||||
}
|
||||
is ImgStore.GroupPicUp.Response.FileExists -> {
|
||||
val resourceId = image.calculateImageResourceId()
|
||||
// return NotOnlineImageFromFile(
|
||||
@ -435,10 +483,9 @@ internal class GroupImpl(
|
||||
return CustomFaceFromFile(
|
||||
md5 = image.md5,
|
||||
filepath = resourceId
|
||||
)
|
||||
).also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() }
|
||||
}
|
||||
is ImgStore.GroupPicUp.Response.RequireUpload -> {
|
||||
|
||||
HighwayHelper.uploadImage(
|
||||
client = bot.client,
|
||||
serverIp = response.uploadIpList.first().toIpV4AddressString(),
|
||||
@ -463,7 +510,7 @@ internal class GroupImpl(
|
||||
return CustomFaceFromFile(
|
||||
md5 = image.md5,
|
||||
filepath = resourceId
|
||||
)
|
||||
).also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() }
|
||||
/*
|
||||
fileId = response.fileId.toInt(),
|
||||
fileType = 0, // ?
|
||||
|
@ -12,7 +12,10 @@ package net.mamoe.mirai.qqandroid
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import net.mamoe.mirai.BotAccount
|
||||
import net.mamoe.mirai.BotImpl
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.contact.ContactList
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.contact.filteringGetOrNull
|
||||
import net.mamoe.mirai.data.AddFriendResult
|
||||
import net.mamoe.mirai.event.events.BotEvent
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
@ -40,12 +43,7 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
override val uin: Long get() = client.uin
|
||||
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
|
||||
|
||||
val selfQQ: QQ by lazy { QQ(uin) }
|
||||
|
||||
override fun getFriend(id: Long): QQ {
|
||||
if (id == uin) return selfQQ
|
||||
return qqs.delegate[id]
|
||||
}
|
||||
override val selfQQ: QQ by lazy { QQ(uin) }
|
||||
|
||||
override fun QQ(id: Long): QQ {
|
||||
return QQImpl(this as QQAndroidBot, coroutineContext, id)
|
||||
@ -57,15 +55,11 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
|
||||
override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
|
||||
|
||||
// internally visible only
|
||||
fun getGroupByUin(uin: Long): Group {
|
||||
return groups.delegate.filteringGetOrNull { (it as GroupImpl).uin == uin } ?: throw NoSuchElementException("Can not found group with ID=${uin}")
|
||||
}
|
||||
|
||||
override fun getGroup(id: Long): Group {
|
||||
return groups.delegate.getOrNull(id)
|
||||
?: throw NoSuchElementException("Can not found group with GroupCode=${id}")
|
||||
}
|
||||
|
||||
override fun onEvent(event: BotEvent): Boolean {
|
||||
return firstLoginSucceed
|
||||
}
|
||||
|
@ -10,10 +10,10 @@
|
||||
package net.mamoe.mirai.qqandroid.event
|
||||
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.event.Cancellable
|
||||
import net.mamoe.mirai.event.AbstractCancellableEvent
|
||||
import net.mamoe.mirai.event.Event
|
||||
|
||||
/**
|
||||
* 接收到数据包
|
||||
*/
|
||||
class PacketReceivedEvent(val packet: Packet) : Event(), Cancellable
|
||||
data class PacketReceivedEvent(val packet: Packet) : Event, AbstractCancellableEvent()
|
@ -1,301 +0,0 @@
|
||||
/*
|
||||
* 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.io
|
||||
|
||||
import kotlinx.io.charsets.Charset
|
||||
import kotlinx.io.core.*
|
||||
import kotlin.experimental.or
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@PublishedApi
|
||||
internal val CharsetGBK = Charset.forName("GBK")
|
||||
@PublishedApi
|
||||
internal val CharsetUTF8 = Charset.forName("UTF8")
|
||||
|
||||
inline fun buildJcePacket(stringCharset: Charset = CharsetGBK, block: JceOutput.() -> Unit): ByteReadPacket {
|
||||
return JceOutput(stringCharset).apply(block).build()
|
||||
}
|
||||
|
||||
inline fun BytePacketBuilder.writeJcePacket(stringCharset: Charset = CharsetGBK, block: JceOutput.() -> Unit) {
|
||||
return this.writePacket(buildJcePacket(stringCharset, block))
|
||||
}
|
||||
|
||||
fun jceStruct(tag: Int, struct: JceStruct): ByteArray{
|
||||
return buildJcePacket {
|
||||
writeJceStruct(struct, tag)
|
||||
}.readBytes()
|
||||
}
|
||||
|
||||
fun <K, V> jceMap(tag: Int, vararg entries: Pair<K, V>): ByteArray {
|
||||
return buildJcePacket {
|
||||
writeMap(mapOf(*entries), tag)
|
||||
}.readBytes()
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* From: com.qq.taf.jce.JceOutputStream
|
||||
*/
|
||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
@UseExperimental(ExperimentalIoApi::class)
|
||||
class JceOutput(
|
||||
private val stringCharset: Charset = CharsetGBK
|
||||
) {
|
||||
private val output: BytePacketBuilder = BytePacketBuilder()
|
||||
|
||||
fun build(): ByteReadPacket = output.build()
|
||||
|
||||
fun close() = output.close()
|
||||
fun flush() = output.flush()
|
||||
|
||||
fun writeByte(v: Byte, tag: Int) {
|
||||
if (v.toInt() == 0) {
|
||||
writeHead(ZERO_TYPE, tag)
|
||||
} else {
|
||||
writeHead(BYTE, tag)
|
||||
output.writeByte(v)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeDouble(v: Double, tag: Int) {
|
||||
writeHead(DOUBLE, tag)
|
||||
output.writeDouble(v)
|
||||
}
|
||||
|
||||
fun writeFloat(v: Float, tag: Int) {
|
||||
writeHead(FLOAT, tag)
|
||||
output.writeFloat(v)
|
||||
}
|
||||
|
||||
fun writeFully(src: ByteArray, tag: Int) {
|
||||
writeHead(SIMPLE_LIST, tag)
|
||||
writeHead(BYTE, 0)
|
||||
writeInt(src.size, 0)
|
||||
output.writeFully(src)
|
||||
}
|
||||
|
||||
fun writeFully(src: DoubleArray, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeDouble(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeFully(src: FloatArray, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeFloat(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeFully(src: IntArray, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeInt(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeFully(src: LongArray, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeLong(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeFully(src: ShortArray, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeShort(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeFully(src: BooleanArray, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeBoolean(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> writeFully(src: Array<T>, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeObject(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeInt(v: Int, tag: Int) {
|
||||
if (v in Short.MIN_VALUE..Short.MAX_VALUE) {
|
||||
writeShort(v.toShort(), tag)
|
||||
} else {
|
||||
writeHead(INT, tag)
|
||||
output.writeInt(v)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeLong(v: Long, tag: Int) {
|
||||
if (v in Int.MIN_VALUE..Int.MAX_VALUE) {
|
||||
writeInt(v.toInt(), tag)
|
||||
} else {
|
||||
writeHead(LONG, tag)
|
||||
output.writeLong(v)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeShort(v: Short, tag: Int) {
|
||||
if (v in Byte.MIN_VALUE..Byte.MAX_VALUE) {
|
||||
writeByte(v.toByte(), tag)
|
||||
} else {
|
||||
writeHead(SHORT, tag)
|
||||
output.writeShort(v)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeBoolean(v: Boolean, tag: Int) {
|
||||
this.writeByte(if (v) 1 else 0, tag)
|
||||
}
|
||||
|
||||
fun writeString(v: String, tag: Int) {
|
||||
val array = v.toByteArray(stringCharset)
|
||||
if (array.size > 255) {
|
||||
writeHead(STRING4, tag)
|
||||
output.writeInt(array.size)
|
||||
output.writeFully(array)
|
||||
} else {
|
||||
writeHead(STRING1, tag)
|
||||
output.writeByte(array.size.toByte())
|
||||
output.writeFully(array)
|
||||
}
|
||||
}
|
||||
|
||||
fun <K, V> writeMap(map: Map<K, V>, tag: Int) {
|
||||
writeHead(MAP, tag)
|
||||
if (map.isEmpty()) {
|
||||
writeInt(0, 0)
|
||||
} else {
|
||||
writeInt(map.size, 0)
|
||||
map.forEach { (key, value) ->
|
||||
writeObject(key, 0)
|
||||
writeObject(value, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun writeCollection(collection: Collection<*>?, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
if (collection == null || collection.isEmpty()) {
|
||||
writeInt(0, 0)
|
||||
} else {
|
||||
writeInt(collection.size, 0)
|
||||
collection.forEach {
|
||||
writeObject(it, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun writeJceStruct(v: JceStruct, tag: Int) {
|
||||
writeHead(STRUCT_BEGIN, tag)
|
||||
v.writeTo(this)
|
||||
writeHead(STRUCT_END, 0)
|
||||
}
|
||||
|
||||
fun <T> writeObject(v: T, tag: Int) {
|
||||
when (v) {
|
||||
is Byte -> writeByte(v, tag)
|
||||
is Short -> writeShort(v, tag)
|
||||
is Int -> writeInt(v, tag)
|
||||
is Long -> writeLong(v, tag)
|
||||
is Float -> writeFloat(v, tag)
|
||||
is Double -> writeDouble(v, tag)
|
||||
is JceStruct -> writeJceStruct(v, tag)
|
||||
is ByteArray -> writeFully(v, tag)
|
||||
is Collection<*> -> writeCollection(v, tag)
|
||||
is Boolean -> writeBoolean(v, tag)
|
||||
is Map<*, *> -> writeMap(v, tag)
|
||||
is IntArray -> writeFully(v, tag)
|
||||
is ShortArray -> writeFully(v, tag)
|
||||
is BooleanArray -> writeFully(v, tag)
|
||||
is LongArray -> writeFully(v, tag)
|
||||
is FloatArray -> writeFully(v, tag)
|
||||
is DoubleArray -> writeFully(v, tag)
|
||||
is Array<*> -> writeFully(v, tag)
|
||||
is String -> writeString(v, tag)
|
||||
|
||||
//
|
||||
// is ByteReadPacket -> ByteArrayPool.useInstance {
|
||||
// v.readAvailable(it)
|
||||
// writeFully(it, tag)
|
||||
// }
|
||||
else -> error("unsupported type: ${v.getClassName()}")
|
||||
}
|
||||
}
|
||||
|
||||
fun write(v: Int, tag: Int) = writeInt(v, tag)
|
||||
fun write(v: Byte, tag: Int) = writeByte(v, tag)
|
||||
fun write(v: Short, tag: Int) = writeShort(v, tag)
|
||||
fun write(v: Long, tag: Int) = writeLong(v, tag)
|
||||
fun write(v: Float, tag: Int) = writeFloat(v, tag)
|
||||
fun write(v: Double, tag: Int) = writeDouble(v, tag)
|
||||
fun write(v: String, tag: Int) = writeString(v, tag)
|
||||
fun write(v: Boolean, tag: Int) = writeBoolean(v, tag)
|
||||
fun write(v: Collection<*>, tag: Int) = writeCollection(v, tag)
|
||||
fun write(v: Map<*, *>, tag: Int) = writeMap(v, tag)
|
||||
fun write(v: ByteArray, tag: Int) = writeFully(v, tag)
|
||||
fun write(v: IntArray, tag: Int) = writeFully(v, tag)
|
||||
fun write(v: BooleanArray, tag: Int) = writeFully(v, tag)
|
||||
fun write(v: LongArray, tag: Int) = writeFully(v, tag)
|
||||
fun write(v: ShortArray, tag: Int) = writeFully(v, tag)
|
||||
fun write(v: Array<*>, tag: Int) = writeFully(v, tag)
|
||||
fun write(v: FloatArray, tag: Int) = writeFully(v, tag)
|
||||
fun write(v: DoubleArray, tag: Int) = writeFully(v, tag)
|
||||
|
||||
@PublishedApi
|
||||
internal companion object {
|
||||
const val BYTE: Int = 0
|
||||
const val DOUBLE: Int = 5
|
||||
const val FLOAT: Int = 4
|
||||
const val INT: Int = 2
|
||||
const val JCE_MAX_STRING_LENGTH = 104857600
|
||||
const val LIST: Int = 9
|
||||
const val LONG: Int = 3
|
||||
const val MAP: Int = 8
|
||||
const val SHORT: Int = 1
|
||||
const val SIMPLE_LIST: Int = 13
|
||||
const val STRING1: Int = 6
|
||||
const val STRING4: Int = 7
|
||||
const val STRUCT_BEGIN: Int = 10
|
||||
const val STRUCT_END: Int = 11
|
||||
const val ZERO_TYPE: Int = 12
|
||||
|
||||
private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun writeHead(type: Int, tag: Int) {
|
||||
if (tag < 15) {
|
||||
this.output.writeByte(((tag shl 4) or type).toByte())
|
||||
return
|
||||
}
|
||||
if (tag < 256) {
|
||||
this.output.writeByte((type.toByte() or 0xF0.toByte()))
|
||||
this.output.writeByte(tag.toByte())
|
||||
return
|
||||
}
|
||||
throw JceEncodeException("tag is too large: $tag")
|
||||
}
|
||||
}
|
||||
|
||||
class JceEncodeException(message: String) : RuntimeException(message)
|
@ -9,6 +9,4 @@
|
||||
|
||||
package net.mamoe.mirai.qqandroid.io
|
||||
|
||||
interface JceStruct {
|
||||
fun writeTo(output: JceOutput) = Unit
|
||||
}
|
||||
interface JceStruct
|
@ -274,12 +274,30 @@ internal class NotOnlineImageFromServer(
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
||||
internal fun MsgComm.Msg.toMessageChain(): MessageChain {
|
||||
val elems = this.msgBody.richText.elems
|
||||
val elements = this.msgBody.richText.elems
|
||||
|
||||
val message = MessageChain(initialCapacity = elems.size + 1)
|
||||
val message = MessageChain(initialCapacity = elements.size + 1)
|
||||
message.add(MessageSourceFromMsg(delegate = this))
|
||||
elements.joinToMessageChain(message)
|
||||
return message
|
||||
}
|
||||
|
||||
elems.forEach {
|
||||
// These two functions are not the same.
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
||||
internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain {
|
||||
val elements = this.elems!!
|
||||
|
||||
val message = MessageChain(initialCapacity = elements.size + 1)
|
||||
message.add(MessageSourceFromServer(delegate = this))
|
||||
elements.joinToMessageChain(message)
|
||||
return message
|
||||
}
|
||||
|
||||
|
||||
@UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class)
|
||||
internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChain) {
|
||||
this.forEach {
|
||||
when {
|
||||
it.srcMsg != null -> message.add(QuoteReply(MessageSourceFromServer(it.srcMsg)))
|
||||
it.notOnlineImage != null -> message.add(NotOnlineImageFromServer(it.notOnlineImage))
|
||||
@ -296,5 +314,4 @@ internal fun MsgComm.Msg.toMessageChain(): MessageChain {
|
||||
}
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
package net.mamoe.mirai.qqandroid.message
|
||||
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
|
||||
@ -20,8 +21,10 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg
|
||||
internal inline class MessageSourceFromServer(
|
||||
val delegate: ImMsgBody.SourceMsg
|
||||
) : MessageSource {
|
||||
override val messageUid: Long
|
||||
get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!
|
||||
override val messageUid: Long get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!
|
||||
override val sourceMessage: MessageChain get() = delegate.toMessageChain()
|
||||
override val senderId: Long get() = delegate.senderUin
|
||||
override val groupId: Long get() = Group.calculateGroupCodeByGroupUin(delegate.toUin)
|
||||
|
||||
override fun toString(): String = ""
|
||||
}
|
||||
@ -29,12 +32,14 @@ internal inline class MessageSourceFromServer(
|
||||
internal inline class MessageSourceFromMsg(
|
||||
val delegate: MsgComm.Msg
|
||||
) : MessageSource {
|
||||
override val messageUid: Long
|
||||
get() = delegate.msgBody.richText.attr!!.random.toLong()
|
||||
override val messageUid: Long get() = delegate.msgBody.richText.attr!!.random.toLong()
|
||||
override val sourceMessage: MessageChain get() = delegate.toMessageChain()
|
||||
override val senderId: Long get() = delegate.msgHead.fromUin
|
||||
override val groupId: Long get() = delegate.msgHead.groupInfo!!.groupCode
|
||||
|
||||
fun toJceData(): ImMsgBody.SourceMsg {
|
||||
|
||||
val groupUin = Group.calculateGroupIdByGroupCode(delegate.msgHead.groupInfo!!.groupCode)
|
||||
val groupUin = Group.calculateGroupUinByGroupCode(delegate.msgHead.groupInfo!!.groupCode)
|
||||
|
||||
return ImMsgBody.SourceMsg(
|
||||
origSeqs = listOf(delegate.msgHead.msgSeq),
|
||||
|
@ -21,10 +21,10 @@ import kotlinx.io.core.use
|
||||
import net.mamoe.mirai.contact.ContactList
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.event.events.ForceOfflineEvent
|
||||
import net.mamoe.mirai.data.MultiPacket
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.event.*
|
||||
import net.mamoe.mirai.event.events.BotOfflineEvent
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.qqandroid.GroupImpl
|
||||
import net.mamoe.mirai.qqandroid.MemberImpl
|
||||
@ -119,7 +119,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
|
||||
@UseExperimental(MiraiExperimentalAPI::class, ExperimentalTime::class)
|
||||
override suspend fun init(): Unit = coroutineScope {
|
||||
this@QQAndroidBotNetworkHandler.subscribeAlways<ForceOfflineEvent> {
|
||||
this@QQAndroidBotNetworkHandler.subscribeAlways<BotOfflineEvent> {
|
||||
if (this@QQAndroidBotNetworkHandler.bot == this.bot) {
|
||||
this.bot.logger.error("被挤下线")
|
||||
close()
|
||||
@ -344,20 +344,20 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
}
|
||||
|
||||
// check top-level cancelling
|
||||
if (PacketReceivedEvent(packet).broadcast().cancelled) {
|
||||
if (PacketReceivedEvent(packet).broadcast().isCancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// broadcast
|
||||
if (packet is Subscribable) {
|
||||
if (packet is Event) {
|
||||
if (packet is BroadcastControllable) {
|
||||
if (packet.shouldBroadcast) packet.broadcast()
|
||||
} else {
|
||||
packet.broadcast()
|
||||
}
|
||||
|
||||
if (packet is Cancellable && packet.cancelled) return
|
||||
if (packet is CancellableEvent && packet.isCancelled) return
|
||||
}
|
||||
|
||||
bot.logger.info("Received packet: ${packet.toString().replace("\n", """\n""").replace("\r", "")}")
|
||||
|
@ -16,7 +16,6 @@ import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.URLProtocol
|
||||
import io.ktor.http.content.OutgoingContent
|
||||
import io.ktor.http.userAgent
|
||||
import kotlinx.coroutines.io.ByteWriteChannel
|
||||
import kotlinx.io.core.Input
|
||||
import kotlinx.io.core.readAvailable
|
||||
import kotlinx.io.core.use
|
||||
@ -64,7 +63,7 @@ internal suspend inline fun HttpClient.postImage(
|
||||
override val contentType: ContentType = ContentType.Image.Any
|
||||
override val contentLength: Long = inputSize
|
||||
|
||||
override suspend fun writeTo(channel: ByteWriteChannel) {
|
||||
override suspend fun writeTo(channel: io.ktor.utils.io.ByteWriteChannel) {
|
||||
ByteArrayPool.useInstance { buffer: ByteArray ->
|
||||
var size: Int
|
||||
while (imageInput.readAvailable(buffer).also { size = it } != 0) {
|
||||
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
* 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.http
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.headers
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.response.HttpResponse
|
||||
import io.ktor.http.URLProtocol
|
||||
import io.ktor.http.setCookie
|
||||
import io.ktor.http.userAgent
|
||||
import kotlinx.coroutines.io.readRemaining
|
||||
import kotlinx.io.core.readBytes
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
import net.mamoe.mirai.utils.currentTimeMillis
|
||||
import net.mamoe.mirai.utils.io.toUHexString
|
||||
|
||||
/**
|
||||
* 好像不需要了
|
||||
*/
|
||||
object HttpRequest {
|
||||
private lateinit var cookie: String
|
||||
}
|
||||
|
||||
|
||||
internal suspend fun HttpClient.getPTLoginCookies(
|
||||
client: QQAndroidClient
|
||||
): String {
|
||||
//$"https://ssl.ptlogin2.qq.com/jump?pt_clientver=5593&pt_src=1&keyindex=9&ptlang=2052&clientuin={QQ}&clientkey={Util.ToHex(TXProtocol.BufServiceTicketHttp, "", "{0}")}&u1=https:%2F%2Fuser.qzone.qq.com%2F417085811%3FADUIN=417085811%26ADSESSION={Util.GetTimeMillis(DateTime.Now)}%26ADTAG=CLIENT.QQ.5593_MyTip.0%26ADPUBNO=26841&source=namecardhoverstar"
|
||||
// "https://ssl.ptlogin2.qq.com/jump?pt_clientver=5509&pt_src=1&keyindex=9&clientuin={0}&clientkey={1}&u1=http%3A%2F%2Fqun.qq.com%2Fmember.html%23gid%3D168209441",
|
||||
val res = post<HttpResponse> {
|
||||
println(client.wLoginSigInfo.userStWebSig.data.toUHexString().replace(" ", "").toLowerCase())
|
||||
url {
|
||||
protocol = URLProtocol.HTTPS
|
||||
host = "ssl.ptlogin2.qq.com"
|
||||
path(
|
||||
"/jump?pt_clientver=5509&pt_src=1&keyindex=9&clientuin=${client.uin}&clientkey=${client.wLoginSigInfo.userStWebSig.data.toUHexString().replace(
|
||||
" ",
|
||||
""
|
||||
)}&u1=http%3A%2F%2Fqun.qq.com%2Fmember.html%23gid%3D168209441&FADUIN=417085811&ADSESSION=${currentTimeMillis}&source=namecardhoverstar"
|
||||
)
|
||||
}
|
||||
headers {
|
||||
userAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36")
|
||||
}
|
||||
}
|
||||
|
||||
println(res.status)
|
||||
println(res.setCookie())
|
||||
println(res.content.readRemaining().readBytes().toUHexString())
|
||||
return "done";
|
||||
}
|
||||
|
||||
|
||||
internal suspend fun HttpClient.getGroupList(
|
||||
client: QQAndroidClient
|
||||
): String {
|
||||
// "https://ssl.ptlogin2.qq.com/jump?pt_clientver=5509&pt_src=1&keyindex=9&clientuin={0}&clientkey={1}&u1=http%3A%2F%2Fqun.qq.com%2Fmember.html%23gid%3D168209441",
|
||||
val res = get<HttpResponse> {
|
||||
url {
|
||||
protocol = URLProtocol.HTTPS
|
||||
host = "ssl.ptlogin2.qq.com"
|
||||
path("jump")
|
||||
parameters["pt_clientver"] = "5509"
|
||||
parameters["pt_src"] = "1"
|
||||
parameters["keyindex"] = "9"
|
||||
parameters["u1"] = "http%3A%2F%2Fqun.qq.com%2Fmember.html%23gid%3D168209441"
|
||||
parameters["clientuin"] = client.uin.toString()
|
||||
parameters["clientkey"] = client.wLoginSigInfo.userStWebSig.data.toUHexString()
|
||||
}
|
||||
headers {
|
||||
userAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36")
|
||||
}
|
||||
}
|
||||
|
||||
println(res.status)
|
||||
println(res.setCookie())
|
||||
return "done";
|
||||
}
|
@ -12,7 +12,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet
|
||||
import kotlinx.io.core.*
|
||||
import kotlinx.io.pool.useInstance
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.event.Subscribable
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
|
||||
@ -20,7 +20,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.*
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.ConfigPushSvc
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
|
||||
@ -59,7 +58,7 @@ internal abstract class OutgoingPacketFactory<TPacket : Packet>(
|
||||
final override val receivingCommandName: String get() = commandName
|
||||
|
||||
/**
|
||||
* **解码**服务器的回复数据包. 返回的包若是 [Subscribable], 则会 broadcast.
|
||||
* **解码**服务器的回复数据包. 返回的包若是 [Event], 则会 broadcast.
|
||||
*/
|
||||
abstract suspend fun ByteReadPacket.decode(bot: QQAndroidBot): TPacket
|
||||
|
||||
@ -85,7 +84,7 @@ internal abstract class IncomingPacketFactory<TPacket : Packet>(
|
||||
val responseCommandName: String = ""
|
||||
) : PacketFactory<TPacket>() {
|
||||
/**
|
||||
* **解码**服务器的回复数据包. 返回的包若是 [Subscribable], 则会 broadcast.
|
||||
* **解码**服务器的回复数据包. 返回的包若是 [Event], 则会 broadcast.
|
||||
*/
|
||||
abstract suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): TPacket
|
||||
|
||||
|
@ -27,7 +27,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
|
||||
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.network.protocol.packet.chat.TroopManagement.GetGroupOperationInfo.decode
|
||||
import net.mamoe.mirai.utils.daysToSeconds
|
||||
import net.mamoe.mirai.utils.io.encodeToString
|
||||
|
||||
@ -66,9 +65,7 @@ internal class TroopManagement {
|
||||
}
|
||||
|
||||
object Response : Packet {
|
||||
override fun toString(): String {
|
||||
return "Response(Mute)"
|
||||
}
|
||||
override fun toString(): String = "Response(Mute)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,9 +77,7 @@ internal class TroopManagement {
|
||||
val autoApprove: Boolean,
|
||||
val confessTalk: Boolean
|
||||
) : Packet {
|
||||
override fun toString(): String {
|
||||
return "Response(GroupInfo)"
|
||||
}
|
||||
override fun toString(): String = "Response(GroupInfo)"
|
||||
}
|
||||
|
||||
operator fun invoke(
|
||||
@ -146,9 +141,7 @@ internal class TroopManagement {
|
||||
class Response(
|
||||
val success: Boolean
|
||||
) : Packet {
|
||||
override fun toString(): String {
|
||||
return "Response(Kick Member)"
|
||||
}
|
||||
override fun toString(): String = "Response(Kick Member)"
|
||||
}
|
||||
|
||||
operator fun invoke(
|
||||
@ -424,13 +417,4 @@ internal class TroopManagement {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
internal object Recall: OutgoingPacketFactory<WtLogin.Login.LoginPacketResponse>("wtlogin.login"){
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): WtLogin.Login.LoginPacketResponse {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
@ -11,10 +11,10 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.discardExact
|
||||
import net.mamoe.mirai.event.events.ForceOfflineEvent
|
||||
import net.mamoe.mirai.data.MultiPacket
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.event.BroadcastControllable
|
||||
import net.mamoe.mirai.event.events.BotOfflineEvent
|
||||
import net.mamoe.mirai.message.FriendMessage
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
@ -178,10 +178,10 @@ internal class MessageSvc {
|
||||
/**
|
||||
* 被挤下线
|
||||
*/
|
||||
internal object PushForceOffline : OutgoingPacketFactory<ForceOfflineEvent>("MessageSvc.PushForceOffline") {
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): ForceOfflineEvent {
|
||||
internal object PushForceOffline : OutgoingPacketFactory<BotOfflineEvent.Force>("MessageSvc.PushForceOffline") {
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): BotOfflineEvent.Force {
|
||||
val struct = this.decodeUniPacket(RequestPushForceOffline.serializer())
|
||||
return ForceOfflineEvent(bot, title = struct.title ?: "", tips = struct.tips ?: "")
|
||||
return BotOfflineEvent.Force(bot, title = struct.title ?: "", message = struct.tips ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,9 @@ import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.data.NoPacket
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.message.GroupMessage
|
||||
import net.mamoe.mirai.qqandroid.MemberImpl
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
|
||||
@ -103,26 +105,33 @@ internal class OnlinePush {
|
||||
this.discardExact(5)
|
||||
val var4 = readByte().toInt()
|
||||
var var5 = 0L
|
||||
val var7 = readUInt().toLong()
|
||||
val target = readUInt().toLong()
|
||||
if (var4 != 0 && var4 != 1) {
|
||||
var5 = readUInt().toLong()
|
||||
}
|
||||
if (var5 == 0L && this.remaining == 1L) {//管理员变更
|
||||
val groupUin = content.fromUin
|
||||
val target = var7
|
||||
if (this.readByte().toInt() == 1) {
|
||||
println("群uin" + groupUin + "新增管理员" + target)
|
||||
|
||||
val member = bot.getGroupByUin(groupUin)[target] as MemberImpl
|
||||
val old = member.permission
|
||||
return if (this.readByte().toInt() == 1) {
|
||||
member.permission = MemberPermission.ADMINISTRATOR
|
||||
MemberPermissionChangeEvent(member, old, MemberPermission.ADMINISTRATOR)
|
||||
} else {
|
||||
println("群uin" + groupUin + "减少管理员" + target)
|
||||
member.permission = MemberPermission.MEMBER
|
||||
MemberPermissionChangeEvent(member, old, MemberPermission.ADMINISTRATOR)
|
||||
}
|
||||
}
|
||||
}
|
||||
34 -> {
|
||||
var groupUinOrCode = readUInt().toLong()
|
||||
readUInt().toLong() // uin or code ?
|
||||
if (readByte().toInt() == 1) {
|
||||
val target = readUInt().toLong()
|
||||
val groupUin = content.fromUin
|
||||
println("群uin" + groupUin + "t掉了" + target)
|
||||
|
||||
val member = bot.getGroupByUin(groupUin)[target] as MemberImpl
|
||||
|
||||
return MemberLeaveEvent.Kick(member, TODO("踢出时获取管理员"))
|
||||
}
|
||||
|
||||
}
|
||||
@ -144,79 +153,86 @@ internal class OnlinePush {
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet {
|
||||
val reqPushMsg = decodeUniPacket(OnlinePushPack.SvcReqPushMsg.serializer(), "req")
|
||||
reqPushMsg.vMsgInfos.forEach { msgInfo: MsgInfo ->
|
||||
var debug = ""
|
||||
msgInfo.vMsg!!.read {
|
||||
if (msgInfo.shMsgType.toInt() == 732) {
|
||||
val groupCode = this.readUInt().toLong()
|
||||
debug = "群 $groupCode "
|
||||
when (val internalType = this.readShort().toInt()) {
|
||||
3073 -> {
|
||||
val operatorUin = this.readUInt().toLong()
|
||||
debug += " 管理员 $operatorUin"
|
||||
val operationTime = this.readUInt().toLong()
|
||||
debug += " 禁言 "
|
||||
this.discardExact(2)
|
||||
val target = this.readUInt().toLong()
|
||||
val time = this.readUInt().toLong()
|
||||
if (target == 0L) {
|
||||
debug += "全群"
|
||||
} else {
|
||||
debug += target
|
||||
}
|
||||
when {
|
||||
msgInfo.shMsgType.toInt() == 732 -> {
|
||||
val group = bot.getGroup(this.readUInt().toLong())
|
||||
|
||||
if (time == 0L) {
|
||||
debug += " 解除 "
|
||||
} else {
|
||||
debug += " " + time + "s"
|
||||
}
|
||||
}
|
||||
3585 -> {
|
||||
val operatorUin = this.readUInt().toLong()
|
||||
debug += " 管理员 $operatorUin"
|
||||
debug += " 匿名聊天 "
|
||||
if (this.readInt() == 0) {
|
||||
debug += " 开启 "
|
||||
} else {
|
||||
debug += " 关闭 "
|
||||
}
|
||||
}
|
||||
4096 -> {
|
||||
val dataBytes = this.readBytes(26)
|
||||
val message = this.readString(this.readByte().toInt())
|
||||
if (dataBytes[0].toInt() != 59) {
|
||||
println("更改群名为$message")
|
||||
} else {
|
||||
println(message + ":" + dataBytes.toUHexString())
|
||||
debug += message
|
||||
when (message) {
|
||||
"管理员已关闭群聊坦白说" -> {
|
||||
when (val internalType = this.readShort().toInt()) {
|
||||
3073 -> { // mute
|
||||
val operator = group[this.readUInt().toLong()]
|
||||
this.readUInt().toLong() // time
|
||||
this.discardExact(2)
|
||||
val target = this.readUInt().toLong()
|
||||
val time = this.readInt()
|
||||
|
||||
return if (target == 0L) {
|
||||
if (time == 0) {
|
||||
GroupMuteAllEvent(origin = true, new = false, operator = operator, group = group)
|
||||
} else {
|
||||
GroupMuteAllEvent(origin = false, new = true, operator = operator, group = group)
|
||||
}
|
||||
"管理员已开启群聊坦白说" -> {
|
||||
|
||||
}
|
||||
else -> {
|
||||
println("Unknown server messages $message")
|
||||
} else {
|
||||
val member = group[target]
|
||||
if (time == 0) {
|
||||
MemberUnmuteEvent(operator = operator, member = member)
|
||||
} else {
|
||||
MemberMuteEvent(operator = operator, member = member, durationSeconds = time)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4352 -> {
|
||||
println(msgInfo.contentToString())
|
||||
println(msgInfo.vMsg.toUHexString())
|
||||
}
|
||||
else -> {
|
||||
println("unknown group internal type $internalType , data: " + this.readBytes().toUHexString() + " ")
|
||||
3585 -> { // 匿名
|
||||
val operator = group[this.readUInt().toLong()]
|
||||
return GroupAllowAnonymousChatEvent(
|
||||
origin = group.anonymousChat,
|
||||
new = this.readInt() == 0,
|
||||
operator = operator,
|
||||
group = group
|
||||
)
|
||||
}
|
||||
4096 -> {
|
||||
val dataBytes = this.readBytes(26)
|
||||
val message = this.readString(this.readByte().toInt())
|
||||
|
||||
TODO("读取操作人")
|
||||
|
||||
/*
|
||||
return if (dataBytes[0].toInt() != 59) {
|
||||
GroupNameChangeEvent(origin = group.name, new = )
|
||||
} else {
|
||||
println(message + ":" + dataBytes.toUHexString())
|
||||
when (message) {
|
||||
"管理员已关闭群聊坦白说" -> {
|
||||
GroupAllowConfessTalkEvent(group.confessTalk, false, ope)
|
||||
}
|
||||
"管理员已开启群聊坦白说" -> {
|
||||
|
||||
}
|
||||
else -> {
|
||||
println("Unknown server messages $message")
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
4352 -> {
|
||||
println(msgInfo.contentToString())
|
||||
println(msgInfo.vMsg.toUHexString())
|
||||
}
|
||||
else -> {
|
||||
println("unknown group internal type $internalType , data: " + this.readBytes().toUHexString() + " ")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (msgInfo.shMsgType.toInt() == 528) {
|
||||
val content = msgInfo.vMsg.loadAs(OnlinePushPack.MsgType0x210.serializer())
|
||||
println(content.contentToString())
|
||||
} else {
|
||||
println("unknown shtype ${msgInfo.shMsgType.toInt()}")
|
||||
msgInfo.shMsgType.toInt() == 528 -> {
|
||||
val content = msgInfo.vMsg.loadAs(OnlinePushPack.MsgType0x210.serializer())
|
||||
println(content.contentToString())
|
||||
}
|
||||
else -> {
|
||||
println("unknown shtype ${msgInfo.shMsgType.toInt()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
println(debug)
|
||||
}
|
||||
|
||||
return NoPacket
|
||||
|
@ -248,6 +248,7 @@ fun ByteReadPacket.analysisOneFullPacket(): ByteReadPacket = debugIfFail("Failed
|
||||
|
||||
val encryptedBody = readBytes((remaining - 1).toInt())
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val decrypted = kotlin.runCatching {
|
||||
encryptedBody.decryptBy(encrypt).also { println("first by calculatedShareKey or sessionKey(method=7)") }
|
||||
}.getOrElse {
|
||||
|
@ -75,7 +75,7 @@ private fun processFullPacketWithoutLength(packet: ByteReadPacket) {
|
||||
|
||||
val flag3 = readByte().toInt()
|
||||
|
||||
val uinAccount = readString(readInt() - 4)//uin
|
||||
readString(readInt() - 4)//uin
|
||||
|
||||
//debugPrint("remaining")
|
||||
|
||||
@ -163,17 +163,17 @@ private fun Map<Int, ByteArray>.getOrEmpty(key: Int): ByteArray {
|
||||
|
||||
var randomKey: ByteArray = byteArrayOf()
|
||||
private fun ByteReadPacket.parseOicqResponse(body: ByteReadPacket.() -> Unit) {
|
||||
val qq: Long
|
||||
readIoBuffer(readInt() - 4).withUse {
|
||||
check(readByte().toInt() == 2)
|
||||
this.discardExact(2) // 27 + 2 + body.size
|
||||
this.discardExact(2) // const, =8001
|
||||
this.readUShort() // commandId
|
||||
this.readShort() // const, =0x0001
|
||||
qq = this.readUInt().toLong()
|
||||
this.readUInt().toLong() // qq
|
||||
val encryptionMethod = this.readUShort().toInt()
|
||||
|
||||
this.discardExact(1) // const = 0
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val packet = when (encryptionMethod) {
|
||||
4 -> { // peer public key, ECDH
|
||||
var data = this.decryptBy(shareKeyCalculatedByConstPubKey, 0, this.readRemaining - 1)
|
||||
@ -229,7 +229,7 @@ private fun parseSsoFrame(flag3: Int, input: ByteReadPacket): KnownPacketFactori
|
||||
|
||||
commandName = readString(readInt() - 4)
|
||||
DebugLogger.warning("commandName=$commandName")
|
||||
val unknown = readBytes(readInt() - 4)
|
||||
readBytes(readInt() - 4) // unknown, sessionId?
|
||||
//if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}")
|
||||
|
||||
check(readInt() == 0)
|
||||
@ -249,7 +249,7 @@ private fun parseSsoFrame(flag3: Int, input: ByteReadPacket): KnownPacketFactori
|
||||
} else {
|
||||
|
||||
}
|
||||
return KnownPacketFactories.IncomingPacket(packetFactory, ssoSequenceId, data.toReadPacket())
|
||||
return KnownPacketFactories.IncomingPacket(packetFactory, ssoSequenceId, data.toReadPacket(), commandName)
|
||||
}
|
||||
|
||||
|
||||
@ -278,7 +278,7 @@ private fun parseUniFrame(input: ByteReadPacket): KnownPacketFactories.IncomingP
|
||||
|
||||
commandName = readString(readInt() - 4)
|
||||
DebugLogger.warning("commandName=$commandName")
|
||||
val unknown = readBytes(readInt() - 4)
|
||||
readBytes(readInt() - 4) // unknown
|
||||
//if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}")
|
||||
|
||||
check(readInt() == 0)
|
||||
@ -291,7 +291,7 @@ private fun parseUniFrame(input: ByteReadPacket): KnownPacketFactories.IncomingP
|
||||
println("找不到包 PacketFactory")
|
||||
PacketLogger.verbose("传递给 PacketFactory 的数据 = ${input.readBytes().toUHexString()}")
|
||||
}
|
||||
return KnownPacketFactories.IncomingPacket(packetFactory, ssoSequenceId, input)
|
||||
return KnownPacketFactories.IncomingPacket(packetFactory, ssoSequenceId, input, commandName)
|
||||
}
|
||||
|
||||
private inline fun <R> inline(block: () -> R): R = block()
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
package net.mamoe.mirai.qqandroid.io.serialization
|
||||
|
||||
/*
|
||||
|
||||
import kotlinx.io.core.readBytes
|
||||
import kotlinx.serialization.SerialId
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -310,4 +312,6 @@ class JceDecoderTest {
|
||||
OuterStruct(listOf(TestSimpleJceStruct(), TestSimpleJceStruct())).contentToString()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
@ -61,13 +61,13 @@ kotlin {
|
||||
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
|
||||
|
||||
dependencies {
|
||||
api(kotlin("stdlib", kotlinVersion))
|
||||
api(kotlin("serialization", kotlinVersion))
|
||||
implementation(kotlin("stdlib", kotlinVersion))
|
||||
implementation(kotlin("serialization", kotlinVersion))
|
||||
|
||||
api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
|
||||
api(kotlinx("io", kotlinXIoVersion))
|
||||
api(kotlinx("coroutines-io", coroutinesIoVersion))
|
||||
api(kotlinx("coroutines-core", coroutinesVersion))
|
||||
implementation("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
|
||||
implementation(kotlinx("io", kotlinXIoVersion))
|
||||
implementation(kotlinx("coroutines-io", coroutinesIoVersion))
|
||||
implementation(kotlinx("coroutines-core", coroutinesVersion))
|
||||
}
|
||||
}
|
||||
commonMain {
|
||||
@ -90,8 +90,8 @@ kotlin {
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
api(kotlin("test-annotations-common"))
|
||||
api(kotlin("test-common"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
implementation(kotlin("test-common"))
|
||||
|
||||
//runtimeOnly(files("build/classes/kotlin/metadata/test")) // classpath is not properly set by IDE
|
||||
}
|
||||
@ -102,6 +102,8 @@ kotlin {
|
||||
dependencies {
|
||||
api(kotlin("reflect", kotlinVersion))
|
||||
|
||||
api(kotlinx("io", kotlinXIoVersion))
|
||||
api(kotlinx("io-jvm", kotlinXIoVersion))
|
||||
api(kotlinx("serialization-runtime", serializationVersion))
|
||||
api(kotlinx("coroutines-android", coroutinesVersion))
|
||||
|
||||
@ -111,10 +113,10 @@ kotlin {
|
||||
|
||||
val androidTest by getting {
|
||||
dependencies {
|
||||
api(kotlin("test", kotlinVersion))
|
||||
api(kotlin("test-junit", kotlinVersion))
|
||||
api(kotlin("test-annotations-common"))
|
||||
api(kotlin("test-common"))
|
||||
implementation(kotlin("test", kotlinVersion))
|
||||
implementation(kotlin("test-junit", kotlinVersion))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
implementation(kotlin("test-common"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,6 +130,9 @@ kotlin {
|
||||
api(ktor("client-core-jvm", ktorVersion))
|
||||
api(kotlinx("io-jvm", kotlinXIoVersion))
|
||||
api(kotlinx("serialization-runtime", serializationVersion))
|
||||
api(kotlinx("coroutines-io", coroutinesIoVersion))
|
||||
api(kotlinx("coroutines-io-jvm", coroutinesIoVersion))
|
||||
api(kotlinx("io-jvm", coroutinesIoVersion))
|
||||
|
||||
api("org.bouncycastle:bcprov-jdk15on:1.64")
|
||||
runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE
|
||||
@ -136,8 +141,8 @@ kotlin {
|
||||
|
||||
val jvmTest by getting {
|
||||
dependencies {
|
||||
api(kotlin("test", kotlinVersion))
|
||||
api(kotlin("test-junit", kotlinVersion))
|
||||
implementation(kotlin("test", kotlinVersion))
|
||||
implementation(kotlin("test-junit", kotlinVersion))
|
||||
implementation("org.pcap4j:pcap4j-distribution:1.8.2")
|
||||
|
||||
runtimeOnly(files("build/classes/kotlin/jvm/test")) // classpath is not properly set by IDE
|
||||
|
@ -12,7 +12,6 @@
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import io.ktor.util.asStream
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.io.core.Input
|
||||
@ -100,7 +99,7 @@ suspend fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO
|
||||
fun Input.toExternalImage(): ExternalImage {
|
||||
val file = createTempFile().apply { deleteOnExit() }
|
||||
file.outputStream().asOutput().use {
|
||||
this.asStream().asInput().copyTo(it)
|
||||
this.copyTo(it)
|
||||
}
|
||||
return file.toExternalImage()
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName")
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai
|
||||
|
||||
@ -17,15 +17,15 @@ import kotlinx.io.OutputStream
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.IoBuffer
|
||||
import kotlinx.io.core.use
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.contact.ContactList
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.data.AddFriendResult
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.WeakRef
|
||||
import net.mamoe.mirai.utils.io.transferTo
|
||||
import net.mamoe.mirai.utils.toList
|
||||
|
||||
/**
|
||||
* 机器人对象. 一个机器人实例登录一个 QQ 账号.
|
||||
@ -35,8 +35,8 @@ import net.mamoe.mirai.utils.io.transferTo
|
||||
*
|
||||
* @see Contact
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
abstract class Bot : CoroutineScope {
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
companion object {
|
||||
/**
|
||||
* 复制一份此时的 [Bot] 实例列表.
|
||||
@ -72,6 +72,8 @@ abstract class Bot : CoroutineScope {
|
||||
|
||||
// region contacts
|
||||
|
||||
abstract val selfQQ: QQ
|
||||
|
||||
/**
|
||||
* 机器人的好友列表. 它将与服务器同步更新
|
||||
*/
|
||||
@ -102,7 +104,11 @@ abstract class Bot : CoroutineScope {
|
||||
/**
|
||||
* 获取一个好友对象. 若没有这个好友, 则会抛出异常 [NoSuchElementException]
|
||||
*/
|
||||
abstract fun getFriend(id: Long): QQ
|
||||
fun getFriend(id: Long): QQ {
|
||||
if (id == uin) return selfQQ
|
||||
return qqs.delegate.getOrNull(id)
|
||||
?: throw NoSuchElementException("No such friend $id for bot ${this.uin}")
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个 [QQ] 对象. 它持有对 [Bot] 的弱引用([WeakRef]).
|
||||
@ -120,7 +126,10 @@ abstract class Bot : CoroutineScope {
|
||||
/**
|
||||
* 获取一个机器人加入的群. 若没有这个群, 则会抛出异常 [NoSuchElementException]
|
||||
*/
|
||||
abstract fun getGroup(id: Long): Group
|
||||
fun getGroup(id: Long): Group {
|
||||
return groups.delegate.getOrNull(id)
|
||||
?: throw NoSuchElementException("No such group $id for bot ${this.uin}")
|
||||
}
|
||||
|
||||
// TODO 目前还不能构造群对象. 这将在以后支持
|
||||
|
||||
@ -186,6 +195,7 @@ abstract class Bot : CoroutineScope {
|
||||
|
||||
@Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("getFriend(this.toLong())"))
|
||||
fun Int.qq(): QQ = getFriend(this.toLong())
|
||||
|
||||
@Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("getFriend(this)"))
|
||||
fun Long.qq(): QQ = getFriend(this)
|
||||
|
||||
@ -200,4 +210,12 @@ abstract class Bot : CoroutineScope {
|
||||
download().use { input -> input.transferTo(output) }
|
||||
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Bot.containsFriend(id: Long): Boolean = this.qqs.contains(id)
|
||||
|
||||
inline fun Bot.containsGroup(id: Long): Boolean = this.groups.contains(id)
|
||||
|
||||
inline fun Bot.getFriendOrNull(id: Long): QQ? = this.qqs.getOrNull(id)
|
||||
|
||||
inline fun Bot.getGroupOrNull(id: Long): Group? = this.groups.getOrNull(id)
|
@ -107,7 +107,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
logger.info("Initializing BotNetworkHandler")
|
||||
try {
|
||||
if (::_network.isInitialized) {
|
||||
BotOfflineEvent(this).broadcast()
|
||||
BotOfflineEvent.Active(this, cause).broadcast()
|
||||
_network.closeAndJoin(cause)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
@ -13,6 +13,10 @@ package net.mamoe.mirai.contact
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.event.events.BeforeImageUploadEvent
|
||||
import net.mamoe.mirai.event.events.ImageUploadEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.WeakRefProperty
|
||||
@ -40,6 +44,9 @@ interface Contact : CoroutineScope {
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
suspend fun sendMessage(message: MessageChain)
|
||||
|
||||
@ -47,6 +54,9 @@ interface Contact : CoroutineScope {
|
||||
* 上传一个图片以备发送.
|
||||
* TODO: 群图片与好友图片之间是否通用还不确定.
|
||||
* TODO: 好友之间图片是否通用还不确定.
|
||||
*
|
||||
* @see BeforeImageUploadEvent 图片发送前事件, cancellable
|
||||
* @see ImageUploadEvent 图片发送完成事件
|
||||
*/
|
||||
suspend fun uploadImage(image: ExternalImage): Image
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
|
||||
/**
|
||||
@ -22,32 +23,41 @@ interface Group : Contact, CoroutineScope {
|
||||
* 群名称.
|
||||
*
|
||||
* 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException]
|
||||
* 频繁修改可能会被服务器拒绝.
|
||||
*
|
||||
* 注: 频繁修改可能会被服务器拒绝.
|
||||
* @see MemberPermissionChangeEvent
|
||||
*/
|
||||
var name: String
|
||||
/**
|
||||
* 入群公告, 没有时为空字符串.
|
||||
*
|
||||
* 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException]
|
||||
*
|
||||
* @see GroupEntranceAnnouncementChangeEvent
|
||||
*/
|
||||
var announcement: String
|
||||
var entranceAnnouncement: String
|
||||
/**
|
||||
* 全体禁言状态. `true` 为开启.
|
||||
*
|
||||
* 当前仅能修改状态.
|
||||
*
|
||||
* @see GroupMuteAllEvent
|
||||
*/// TODO: 2020/2/5 实现 muteAll 的查询
|
||||
var muteAll: Boolean
|
||||
/**
|
||||
* 坦白说状态. `true` 为允许.
|
||||
*
|
||||
* 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException]
|
||||
|
||||
* @see GroupAllowConfessTalkEvent
|
||||
*/
|
||||
var confessTalk: Boolean
|
||||
/**
|
||||
* 允许群员邀请好友入群的状态. `true` 为允许
|
||||
*
|
||||
* 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException]
|
||||
*
|
||||
* @see GroupAllowMemberInviteEvent
|
||||
*/
|
||||
var allowMemberInvite: Boolean
|
||||
/**
|
||||
@ -74,6 +84,8 @@ interface Group : Contact, CoroutineScope {
|
||||
* 机器人在这个群里的权限
|
||||
*
|
||||
* **MiraiExperimentalAPI**: 在未来可能会被修改
|
||||
*
|
||||
* @see BotGroupPermissionChangeEvent
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
val botPermission: MemberPermission
|
||||
@ -111,21 +123,37 @@ interface Group : Contact, CoroutineScope {
|
||||
/**
|
||||
* by @kar98k
|
||||
*/
|
||||
fun calculateGroupIdByGroupCode(groupCode: Long): Long {
|
||||
fun calculateGroupUinByGroupCode(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
|
||||
when (left) {
|
||||
in 0..10 -> left += 202
|
||||
in 11..19 -> left += 480 - 11
|
||||
in 20..66 -> left += 2100 - 20
|
||||
in 67..156 -> left += 2010 - 67
|
||||
in 157..209 -> left += 2147 - 157
|
||||
in 210..309 -> left += 4100 - 210
|
||||
in 310..499 -> left += 3800 - 310
|
||||
}
|
||||
|
||||
return left * 1000000L + groupCode % 1000000L
|
||||
}
|
||||
|
||||
fun calculateGroupCodeByGroupUin(groupUin: Long): Long {
|
||||
var left: Long = groupUin / 1000000L
|
||||
|
||||
when (left) {
|
||||
in 0 + 202..10 + 202 -> left -= 202
|
||||
in 11 + 480 - 11..19 + 480 - 11 -> left -= 480 - 11
|
||||
in 20 + 2100 - 20..66 + 2100 - 20 -> left -= 2100 - 20
|
||||
in 67 + 2010 - 67..156 + 2010 - 67 -> left -= 2010 - 67
|
||||
in 157 + 2147 - 157..209 + 2147 - 157 -> left -= 2147 - 157
|
||||
in 210 + 4100 - 210..309 + 4100 - 210 -> left -= 4100 - 210
|
||||
in 310 + 3800 - 310..499 + 3800 - 310 -> left -= 3800 - 310
|
||||
}
|
||||
|
||||
return left * 1000000L + groupUin % 1000000L
|
||||
}
|
||||
}
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
|
@ -11,8 +11,9 @@
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.utils.WeakRefProperty
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
@ -32,18 +33,22 @@ interface Member : QQ, Contact {
|
||||
val permission: MemberPermission
|
||||
|
||||
/**
|
||||
* 群名片. 可能为空.
|
||||
* 群名片. 可能为空. 修改时将会触发事件
|
||||
*
|
||||
* 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException]
|
||||
*
|
||||
* @see [groupCardOrNick] 获取非空群名片或昵称
|
||||
*
|
||||
* @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件
|
||||
*/
|
||||
var groupCard: String
|
||||
var nameCard: String
|
||||
|
||||
/**
|
||||
* 群头衔
|
||||
*
|
||||
* 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException]
|
||||
*
|
||||
* @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件
|
||||
*/
|
||||
var specialTitle: String
|
||||
|
||||
@ -51,21 +56,27 @@ interface Member : QQ, Contact {
|
||||
* 禁言
|
||||
*
|
||||
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
|
||||
* @return 当机器人无权限禁言这个群成员时返回 `false`
|
||||
* @return 机器人无权限时返回 `false`
|
||||
*
|
||||
* @see Int.minutesToSeconds
|
||||
* @see Int.hoursToSeconds
|
||||
* @see Int.daysToSeconds
|
||||
*
|
||||
* @see MemberMuteEvent 成员被禁言事件
|
||||
*/
|
||||
suspend fun mute(durationSeconds: Int): Boolean
|
||||
|
||||
/**
|
||||
* 解除禁言. 在没有权限时会返回 `false`.
|
||||
* 解除禁言. 机器人无权限时返回 `false`.
|
||||
*
|
||||
* @see MemberUnmuteEvent 成员被取消禁言事件.
|
||||
*/
|
||||
suspend fun unmute(): Boolean
|
||||
|
||||
/**
|
||||
* 踢出该成员. 机器人无权限时返回 `false`
|
||||
* 踢出该成员. 机器人无权限时返回 `false`.
|
||||
*
|
||||
* @see MemberLeaveEvent.Kick 成员被踢出事件.
|
||||
*/
|
||||
suspend fun kick(message: String = ""): Boolean
|
||||
|
||||
@ -78,9 +89,9 @@ interface Member : QQ, Contact {
|
||||
/**
|
||||
* 获取非空群名片或昵称.
|
||||
*
|
||||
* 若 [群名片][Member.groupCard] 不为空则返回群名片, 为空则返回 [QQ.nick]
|
||||
* 若 [群名片][Member.nameCard] 不为空则返回群名片, 为空则返回 [QQ.nick]
|
||||
*/
|
||||
val Member.groupCardOrNick: String get() = this.groupCard.takeIf { it.isNotEmpty() } ?: this.nick
|
||||
val Member.groupCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() } ?: this.nick
|
||||
|
||||
@ExperimentalTime
|
||||
suspend inline fun Member.mute(duration: Duration): Boolean {
|
||||
|
@ -16,6 +16,7 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.data.FriendNameRemark
|
||||
import net.mamoe.mirai.data.PreviousNameList
|
||||
import net.mamoe.mirai.data.Profile
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
|
||||
/**
|
||||
* QQ 对象.
|
||||
@ -49,6 +50,7 @@ interface QQ : Contact, CoroutineScope {
|
||||
/**
|
||||
* 查询用户资料
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
suspend fun queryProfile(): Profile
|
||||
|
||||
/**
|
||||
@ -58,10 +60,12 @@ interface QQ : Contact, CoroutineScope {
|
||||
* - 昵称
|
||||
* - 共同群内的群名片
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
suspend fun queryPreviousNameList(): PreviousNameList
|
||||
|
||||
/**
|
||||
* 查询机器人账号给这个人设置的备注
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
suspend fun queryRemark(): FriendNameRemark
|
||||
}
|
@ -9,11 +9,11 @@
|
||||
|
||||
package net.mamoe.mirai.data
|
||||
|
||||
import net.mamoe.mirai.event.Subscribable
|
||||
import net.mamoe.mirai.event.Event
|
||||
|
||||
/**
|
||||
* 事件包. 可被监听.
|
||||
*
|
||||
* @see Subscribable
|
||||
* @see Event
|
||||
*/
|
||||
interface EventPacket : Subscribable, Packet
|
||||
interface EventPacket : Event, Packet
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* 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.data
|
||||
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
|
||||
|
||||
// region mute
|
||||
/**
|
||||
* 某群成员被禁言事件
|
||||
*/
|
||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
class MemberMuteEvent(
|
||||
val member: Member,
|
||||
override val durationSeconds: Int,
|
||||
override val operator: Member
|
||||
) : MuteEvent() {
|
||||
override val group: Group get() = operator.group
|
||||
override fun toString(): String = "MemberMuteEvent(member=${member.id}, group=${group.id}, operator=${operator.id}, duration=${durationSeconds}s"
|
||||
}
|
||||
|
||||
/**
|
||||
* 机器人被禁言事件
|
||||
*/
|
||||
class BeingMutedEvent(
|
||||
override val durationSeconds: Int,
|
||||
override val operator: Member
|
||||
) : MuteEvent() {
|
||||
override val group: Group get() = operator.group
|
||||
override fun toString(): String = "BeingMutedEvent(group=${group.id}, operator=${operator.id}, duration=${durationSeconds}s"
|
||||
}
|
||||
|
||||
sealed class MuteEvent : EventOfMute() {
|
||||
abstract override val operator: Member
|
||||
abstract override val group: Group
|
||||
abstract val durationSeconds: Int
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region unmute
|
||||
/**
|
||||
* 某群成员被解除禁言事件
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class MemberUnmuteEvent(
|
||||
val member: Member,
|
||||
override val operator: Member
|
||||
) : UnmuteEvent() {
|
||||
override val group: Group get() = operator.group
|
||||
override fun toString(): String = "MemberUnmuteEvent(member=${member.id}, group=${group.id}, operator=${operator.id}"
|
||||
}
|
||||
|
||||
/**
|
||||
* 机器人被解除禁言事件
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
class BeingUnmutedEvent(
|
||||
override val operator: Member
|
||||
) : UnmuteEvent() {
|
||||
override val group: Group get() = operator.group
|
||||
override fun toString(): String = "BeingUnmutedEvent(group=${group.id}, operator=${operator.id}"
|
||||
}
|
||||
|
||||
sealed class UnmuteEvent : EventOfMute() {
|
||||
abstract override val operator: Member
|
||||
abstract override val group: Group
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
abstract class EventOfMute : EventPacket {
|
||||
abstract val operator: Member
|
||||
abstract val group: Group
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ package net.mamoe.mirai.data
|
||||
|
||||
/**
|
||||
* 从服务器收到的包解析之后的结构化数据.
|
||||
* 它是一个数据包工厂的处理的返回值.
|
||||
*/
|
||||
interface Packet {
|
||||
/**
|
||||
|
@ -17,61 +17,62 @@ import net.mamoe.mirai.event.internal.broadcastInternal
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
|
||||
/**
|
||||
* 可被监听的.
|
||||
* 可被监听的类, 可以是任何 class 或 object.
|
||||
*
|
||||
* 可以是任何 class 或 object.
|
||||
* 若监听这个类, 监听器将会接收所有事件的广播.
|
||||
*
|
||||
* @see subscribeAlways
|
||||
* @see subscribeWhile
|
||||
*
|
||||
* @see subscribeMessages
|
||||
*/
|
||||
interface Subscribable
|
||||
|
||||
/**
|
||||
* 所有事件的基类.
|
||||
* 若监听这个类, 监听器将会接收所有事件的广播.
|
||||
*
|
||||
* @see [broadcast] 广播事件
|
||||
* @see [subscribe] 监听事件
|
||||
*/
|
||||
abstract class Event : Subscribable {
|
||||
interface Event
|
||||
|
||||
/**
|
||||
* 可被取消的事件
|
||||
*/
|
||||
interface CancellableEvent {
|
||||
/**
|
||||
* 事件是否已取消.
|
||||
*/
|
||||
val isCancelled: Boolean
|
||||
|
||||
/**
|
||||
* 事件是否已取消. 事件需实现 [Cancellable] 才可以被取消, 否则这个字段为常量值 false
|
||||
* 取消这个事件.
|
||||
*/
|
||||
val cancelled: Boolean get() = _cancelled
|
||||
|
||||
private var _cancelled: Boolean = false
|
||||
get() = field.takeIf { this is Cancellable } ?: false
|
||||
private set(value) =
|
||||
if (this is Cancellable) field = value
|
||||
else throw UnsupportedOperationException()
|
||||
|
||||
/**
|
||||
* 取消事件. 事件需实现 [Cancellable] 才可以被取消
|
||||
*
|
||||
* @throws UnsupportedOperationException 如果事件没有实现 [Cancellable]
|
||||
*/
|
||||
fun cancel() {
|
||||
_cancelled = true
|
||||
}
|
||||
fun cancel()
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现这个接口的事件([Event])可以被取消. 在广播中取消不会影响广播过程.
|
||||
* 可被取消的事件的实现
|
||||
*/
|
||||
interface Cancellable : Subscribable {
|
||||
val cancelled: Boolean
|
||||
abstract class AbstractCancellableEvent : Event, CancellableEvent {
|
||||
/**
|
||||
* 事件是否已取消.
|
||||
*/
|
||||
override val isCancelled: Boolean get() = _cancelled
|
||||
|
||||
fun cancel()
|
||||
private var _cancelled: Boolean = false
|
||||
|
||||
/**
|
||||
* 取消事件.
|
||||
*/
|
||||
override fun cancel() {
|
||||
_cancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播一个事件的唯一途径.
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
suspend fun <E : Subscribable> E.broadcast(): E = apply {
|
||||
suspend fun <E : Event> E.broadcast(): E = apply {
|
||||
if (this is BroadcastControllable && !this.shouldBroadcast) {
|
||||
return@apply
|
||||
}
|
||||
if (this is BotEvent && !(this.bot as BotImpl<*>).onEvent(this)) {
|
||||
return@apply
|
||||
}
|
||||
@ -81,7 +82,7 @@ suspend fun <E : Subscribable> E.broadcast(): E = apply {
|
||||
/**
|
||||
* 可控制是否需要广播这个事件包
|
||||
*/
|
||||
interface BroadcastControllable : Subscribable {
|
||||
interface BroadcastControllable : Event {
|
||||
val shouldBroadcast: Boolean
|
||||
get() = true
|
||||
}
|
@ -39,7 +39,7 @@ enum class ListeningStatus {
|
||||
* 事件监听器.
|
||||
* 由 [subscribe] 等方法返回.
|
||||
*/
|
||||
interface Listener<in E : Subscribable> : CompletableJob {
|
||||
interface Listener<in E : Event> : CompletableJob {
|
||||
suspend fun onEvent(event: E): ListeningStatus
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ interface Listener<in E : Subscribable> : CompletableJob {
|
||||
|
||||
/**
|
||||
* 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
|
||||
* 每当 [事件广播][Subscribable.broadcast] 时, [handler] 都会被执行.
|
||||
* 每当 [事件广播][Event.broadcast] 时, [handler] 都会被执行.
|
||||
*
|
||||
* 当 [handler] 返回 [ListeningStatus.STOPPED] 时停止监听.
|
||||
* 或 [Listener] complete 时结束.
|
||||
@ -57,7 +57,7 @@ interface Listener<in E : Subscribable> : CompletableJob {
|
||||
* 例如:
|
||||
* ```kotlin
|
||||
* runBlocking { // this: CoroutineScope
|
||||
* subscribe<Subscribable> { /* 一些处理 */ } // 返回 Listener, 即 CompletableJob
|
||||
* subscribe<Event> { /* 一些处理 */ } // 返回 Listener, 即 CompletableJob
|
||||
* }
|
||||
* foo()
|
||||
* ```
|
||||
@ -66,7 +66,7 @@ interface Listener<in E : Subscribable> : CompletableJob {
|
||||
*
|
||||
* 要创建一个全局都存在的监听, 即守护协程, 请在 [GlobalScope] 下调用本函数:
|
||||
* ```kotlin
|
||||
* GlobalScope.subscribe<Subscribable> { /* 一些处理 */ }
|
||||
* GlobalScope.subscribe<Event> { /* 一些处理 */ }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
@ -75,52 +75,52 @@ interface Listener<in E : Subscribable> : CompletableJob {
|
||||
* bot.subscribe<Subscribe> { /* 一些处理 */ }
|
||||
* ```
|
||||
*/
|
||||
inline fun <reified E : Subscribable> CoroutineScope.subscribe(crossinline handler: suspend E.(E) -> ListeningStatus): Listener<E> =
|
||||
inline fun <reified E : Event> CoroutineScope.subscribe(crossinline handler: suspend E.(E) -> ListeningStatus): Listener<E> =
|
||||
E::class.subscribeInternal(Handler { it.handler(it) })
|
||||
|
||||
/**
|
||||
* 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
|
||||
* 每当 [事件广播][Subscribable.broadcast] 时, [listener] 都会被执行.
|
||||
* 每当 [事件广播][Event.broadcast] 时, [listener] 都会被执行.
|
||||
*
|
||||
* 仅当 [Listener] complete 时结束.
|
||||
*
|
||||
* @see subscribe 获取更多说明
|
||||
*/
|
||||
inline fun <reified E : Subscribable> CoroutineScope.subscribeAlways(crossinline listener: suspend E.(E) -> Unit): Listener<E> =
|
||||
inline fun <reified E : Event> CoroutineScope.subscribeAlways(crossinline listener: suspend E.(E) -> Unit): Listener<E> =
|
||||
E::class.subscribeInternal(Handler { it.listener(it); ListeningStatus.LISTENING })
|
||||
|
||||
/**
|
||||
* 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
|
||||
* 仅在第一次 [事件广播][Subscribable.broadcast] 时, [listener] 会被执行.
|
||||
* 仅在第一次 [事件广播][Event.broadcast] 时, [listener] 会被执行.
|
||||
*
|
||||
* 在这之前, 可通过 [Listener.complete] 来停止监听.
|
||||
*
|
||||
* @see subscribe 获取更多说明
|
||||
*/
|
||||
inline fun <reified E : Subscribable> CoroutineScope.subscribeOnce(crossinline listener: suspend E.(E) -> Unit): Listener<E> =
|
||||
inline fun <reified E : Event> CoroutineScope.subscribeOnce(crossinline listener: suspend E.(E) -> Unit): Listener<E> =
|
||||
E::class.subscribeInternal(Handler { it.listener(it); ListeningStatus.STOPPED })
|
||||
|
||||
/**
|
||||
* 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
|
||||
* 每当 [事件广播][Subscribable.broadcast] 时, [listener] 都会被执行, 直到 [listener] 的返回值 [equals] 于 [valueIfStop]
|
||||
* 每当 [事件广播][Event.broadcast] 时, [listener] 都会被执行, 直到 [listener] 的返回值 [equals] 于 [valueIfStop]
|
||||
*
|
||||
* 可在任意时刻通过 [Listener.complete] 来停止监听.
|
||||
*
|
||||
* @see subscribe 获取更多说明
|
||||
*/
|
||||
inline fun <reified E : Subscribable, T> CoroutineScope.subscribeUntil(valueIfStop: T, crossinline listener: suspend E.(E) -> T): Listener<E> =
|
||||
inline fun <reified E : Event, T> CoroutineScope.subscribeUntil(valueIfStop: T, crossinline listener: suspend E.(E) -> T): Listener<E> =
|
||||
E::class.subscribeInternal(Handler { if (it.listener(it) == valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
|
||||
|
||||
/**
|
||||
* 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
|
||||
* 每当 [事件广播][Subscribable.broadcast] 时, [listener] 都会被执行,
|
||||
* 每当 [事件广播][Event.broadcast] 时, [listener] 都会被执行,
|
||||
* 如果 [listener] 的返回值 [equals] 于 [valueIfContinue], 则继续监听, 否则停止
|
||||
*
|
||||
* 可在任意时刻通过 [Listener.complete] 来停止监听.
|
||||
*
|
||||
* @see subscribe 获取更多说明
|
||||
*/
|
||||
inline fun <reified E : Subscribable, T> CoroutineScope.subscribeWhile(valueIfContinue: T, crossinline listener: suspend E.(E) -> T): Listener<E> =
|
||||
inline fun <reified E : Event, T> CoroutineScope.subscribeWhile(valueIfContinue: T, crossinline listener: suspend E.(E) -> T): Listener<E> =
|
||||
E::class.subscribeInternal(Handler { if (it.listener(it) != valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
|
||||
|
||||
// endregion
|
||||
@ -146,7 +146,7 @@ inline fun <reified E : Subscribable, T> CoroutineScope.subscribeWhile(valueIfCo
|
||||
*/
|
||||
@ListenersBuilderDsl
|
||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
inline class ListenerBuilder<out E : Subscribable>(
|
||||
inline class ListenerBuilder<out E : Event>(
|
||||
@PublishedApi internal inline val handlerConsumer: CoroutineCoroutineScope.(Listener<E>) -> Unit
|
||||
) {
|
||||
fun CoroutineCoroutineScope.handler(listener: suspend E.(E) -> ListeningStatus) {
|
||||
|
@ -10,89 +10,327 @@
|
||||
package net.mamoe.mirai.event.events
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.utils.WeakRef
|
||||
import kotlin.properties.Delegates
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.event.AbstractCancellableEvent
|
||||
import net.mamoe.mirai.event.CancellableEvent
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
|
||||
|
||||
abstract class BotEvent : Event {
|
||||
private lateinit var _bot: Bot
|
||||
open val bot: Bot get() = _bot
|
||||
|
||||
constructor(bot: Bot) : super() {
|
||||
this._bot = bot
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
class EventCancelledException : RuntimeException {
|
||||
constructor() : super()
|
||||
constructor(message: String?) : super(message)
|
||||
constructor(message: String?, cause: Throwable?) : super(message, cause)
|
||||
constructor(cause: Throwable?) : super(cause)
|
||||
}
|
||||
|
||||
class BotLoginSucceedEvent(bot: Bot) : BotEvent(bot)
|
||||
|
||||
class BotOfflineEvent(bot: Bot) : BotEvent(bot)
|
||||
// region Bot 状态
|
||||
|
||||
class BotReadyEvent(bot: Bot) : BotEvent(bot)
|
||||
/**
|
||||
* [Bot] 登录完成, 好友列表, 群组列表初始化完成
|
||||
*/
|
||||
data class BotOnlineEvent(override val bot: Bot) : BotActiveEvent
|
||||
|
||||
interface GroupEvent {
|
||||
val group: Group
|
||||
/**
|
||||
* [Bot] 离线.
|
||||
*/
|
||||
sealed class BotOfflineEvent : BotActiveEvent {
|
||||
|
||||
/**
|
||||
* 主动离线
|
||||
*/
|
||||
data class Active(override val bot: Bot, val cause: Throwable?) : BotOfflineEvent()
|
||||
|
||||
/**
|
||||
* 被挤下线
|
||||
*/
|
||||
data class Force(override val bot: Bot, val title: String, val message: String) : BotOfflineEvent(), Packet
|
||||
}
|
||||
|
||||
class AddGroupEvent(bot: Bot, override val group: Group) : BotEvent(bot), GroupEvent
|
||||
// endregion
|
||||
|
||||
class RemoveGroupEvent(bot: Bot, override val group: Group) : BotEvent(bot), GroupEvent
|
||||
// region 消息
|
||||
|
||||
class BotGroupPermissionChangeEvent(
|
||||
bot: Bot,
|
||||
sealed class MessageSendEvent : BotEvent, BotActiveEvent, AbstractCancellableEvent() {
|
||||
abstract val target: Contact
|
||||
final override val bot: Bot
|
||||
get() = target.bot
|
||||
|
||||
data class GroupMessageSendEvent(
|
||||
override val target: Group,
|
||||
var message: MessageChain
|
||||
) : MessageSendEvent(), CancellableEvent
|
||||
|
||||
data class FriendMessageSendEvent(
|
||||
override val target: QQ,
|
||||
var message: MessageChain
|
||||
) : MessageSendEvent(), CancellableEvent
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region 图片
|
||||
|
||||
/**
|
||||
* 图片上传前. 可以阻止上传
|
||||
*/
|
||||
data class BeforeImageUploadEvent(
|
||||
val target: Contact,
|
||||
val source: ExternalImage
|
||||
) : BotEvent, BotActiveEvent, AbstractCancellableEvent() {
|
||||
override val bot: Bot
|
||||
get() = target.bot
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片上传完成
|
||||
*/
|
||||
sealed class ImageUploadEvent : BotEvent, BotActiveEvent, AbstractCancellableEvent() {
|
||||
abstract val target: Contact
|
||||
abstract val source: ExternalImage
|
||||
override val bot: Bot
|
||||
get() = target.bot
|
||||
|
||||
data class Succeed(
|
||||
override val target: Contact,
|
||||
override val source: ExternalImage,
|
||||
val image: Image
|
||||
) : ImageUploadEvent(), CancellableEvent
|
||||
|
||||
data class Failed(
|
||||
override val target: Contact,
|
||||
override val source: ExternalImage,
|
||||
val errno: Int,
|
||||
val message: String
|
||||
) : ImageUploadEvent(), CancellableEvent
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region 群
|
||||
|
||||
/**
|
||||
* Bot 在群里的权限被改变. 操作人一定是群主
|
||||
*/
|
||||
data class BotGroupPermissionChangeEvent(
|
||||
override val group: Group,
|
||||
val origin: MemberPermission,
|
||||
val new: MemberPermission
|
||||
) : BotEvent(bot), GroupEvent
|
||||
) : BotPassiveEvent, GroupEvent
|
||||
|
||||
// region 群设置
|
||||
|
||||
interface GroupSettingChangeEvent<T> : GroupEvent {
|
||||
/**
|
||||
* 群设置改变. 此事件广播前修改就已经完成.
|
||||
*/
|
||||
interface GroupSettingChangeEvent<T> : GroupEvent, BotPassiveEvent {
|
||||
val origin: T
|
||||
val new: T
|
||||
}
|
||||
|
||||
class GroupNameChangeEvent(
|
||||
bot: Bot,
|
||||
override val group: Group,
|
||||
/**
|
||||
* 群名改变. 此事件广播前修改就已经完成.
|
||||
*/
|
||||
data class GroupNameChangeEvent(
|
||||
override val origin: String,
|
||||
override val new: String
|
||||
) : BotEvent(bot), GroupSettingChangeEvent<String>
|
||||
|
||||
class GroupMuteAllEvent(
|
||||
bot: Bot,
|
||||
override val new: String,
|
||||
override val group: Group,
|
||||
override val origin: Boolean,
|
||||
override val new: Boolean
|
||||
) : BotEvent(bot), GroupSettingChangeEvent<Boolean>
|
||||
/**
|
||||
* 操作人. 为 null 时则是机器人操作
|
||||
*/
|
||||
val operator: Member?
|
||||
) : GroupSettingChangeEvent<String>, Packet
|
||||
|
||||
class GroupConfessTalkEvent(
|
||||
bot: Bot,
|
||||
/**
|
||||
* 入群公告改变. 此事件广播前修改就已经完成.
|
||||
*/
|
||||
data class GroupEntranceAnnouncementChangeEvent(
|
||||
override val origin: String,
|
||||
override val new: String,
|
||||
override val group: Group,
|
||||
override val origin: Boolean,
|
||||
override val new: Boolean
|
||||
) : BotEvent(bot), GroupSettingChangeEvent<Boolean>
|
||||
/**
|
||||
* 操作人. 为 null 时则是机器人操作
|
||||
*/
|
||||
val operator: Member?
|
||||
) : GroupSettingChangeEvent<String>, Packet
|
||||
|
||||
interface GroupMemberEvent : GroupEvent {
|
||||
val member: Member
|
||||
override val group: Group
|
||||
get() = member.group
|
||||
|
||||
/**
|
||||
* 群 "全员禁言" 功能状态改变. 此事件广播前修改就已经完成.
|
||||
*/
|
||||
data class GroupMuteAllEvent(
|
||||
override val origin: Boolean,
|
||||
override val new: Boolean,
|
||||
override val group: Group,
|
||||
/**
|
||||
* 操作人. 为 null 时则是机器人操作
|
||||
*/
|
||||
val operator: Member?
|
||||
) : GroupSettingChangeEvent<Boolean>, Packet
|
||||
|
||||
/**
|
||||
* 群 "匿名聊天" 功能状态改变. 此事件广播前修改就已经完成.
|
||||
*/
|
||||
data class GroupAllowAnonymousChatEvent(
|
||||
override val origin: Boolean,
|
||||
override val new: Boolean,
|
||||
override val group: Group,
|
||||
/**
|
||||
* 操作人. 为 null 时则是机器人操作
|
||||
*/
|
||||
val operator: Member?
|
||||
) : GroupSettingChangeEvent<Boolean>, Packet
|
||||
|
||||
/**
|
||||
* 群 "坦白说" 功能状态改变. 此事件广播前修改就已经完成.
|
||||
*/
|
||||
data class GroupAllowConfessTalkEvent(
|
||||
override val origin: Boolean,
|
||||
override val new: Boolean,
|
||||
override val group: Group,
|
||||
/**
|
||||
* 操作人. 为 null 时则是机器人操作
|
||||
*/
|
||||
val operator: Member?
|
||||
) : GroupSettingChangeEvent<Boolean>, Packet
|
||||
|
||||
/**
|
||||
* 群 "允许群员邀请好友加群" 功能状态改变. 此事件广播前修改就已经完成.
|
||||
*/
|
||||
data class GroupAllowMemberInviteEvent(
|
||||
override val origin: Boolean,
|
||||
override val new: Boolean,
|
||||
override val group: Group,
|
||||
/**
|
||||
* 操作人. 为 null 时则是机器人操作
|
||||
*/
|
||||
val operator: Member?
|
||||
) : GroupSettingChangeEvent<Boolean>, Packet
|
||||
|
||||
// endregion
|
||||
|
||||
|
||||
// region 群成员
|
||||
|
||||
// region 成员变更
|
||||
|
||||
/**
|
||||
* 成员加入群的事件
|
||||
*/
|
||||
data class MemberJoinEvent(override val member: Member) : GroupMemberEvent, BotPassiveEvent
|
||||
|
||||
/**
|
||||
* 成员离开群的事件
|
||||
*/
|
||||
sealed class MemberLeaveEvent : GroupMemberEvent {
|
||||
/**
|
||||
* 成员被踢出群. 成员不可能是机器人自己.
|
||||
*/
|
||||
data class Kick(
|
||||
override val member: Member,
|
||||
/**
|
||||
* 操作人. 为 null 则是机器人操作
|
||||
*/
|
||||
val operator: Member?
|
||||
) : MemberLeaveEvent(), Packet
|
||||
|
||||
/**
|
||||
* 成员主动离开
|
||||
*/
|
||||
data class Quit(override val member: Member) : MemberLeaveEvent()
|
||||
}
|
||||
|
||||
class MemberJoinEvent(bot: Bot, override val member: Member) : BotEvent(bot), GroupMemberEvent
|
||||
// endregion
|
||||
|
||||
class MemberLeftEvent(bot: Bot, override val member: Member) : BotEvent(bot), GroupMemberEvent
|
||||
// region 名片和头衔
|
||||
|
||||
class MemberMuteEvent(bot: Bot, override val member: Member) : BotEvent(bot), GroupMemberEvent
|
||||
/**
|
||||
* 群名片改动. 此事件广播前修改就已经完成.
|
||||
*/
|
||||
data class MemberCardChangeEvent(
|
||||
/**
|
||||
* 修改前
|
||||
*/
|
||||
val origin: String,
|
||||
|
||||
class MemberPermissionChangeEvent(
|
||||
bot: Bot,
|
||||
/**
|
||||
* 修改后
|
||||
*/
|
||||
val new: String,
|
||||
|
||||
override val member: Member,
|
||||
|
||||
/**
|
||||
* 操作人. 为 null 时则是机器人操作. 可能与 [member] 引用相同, 此时为群员自己修改.
|
||||
*/
|
||||
val operator: Member?
|
||||
) : GroupMemberEvent
|
||||
|
||||
/**
|
||||
* 群头衔改动. 一定为群主操作
|
||||
*/
|
||||
data class MemberSpecialTitleChangeEvent(
|
||||
/**
|
||||
* 修改前
|
||||
*/
|
||||
val origin: String,
|
||||
|
||||
/**
|
||||
* 修改后
|
||||
*/
|
||||
val new: String,
|
||||
|
||||
override val member: Member
|
||||
) : GroupMemberEvent
|
||||
|
||||
// endregion
|
||||
|
||||
|
||||
// region 成员权限
|
||||
|
||||
/**
|
||||
* 成员权限改变的事件. 成员不可能是机器人自己.
|
||||
*/
|
||||
data class MemberPermissionChangeEvent(
|
||||
override val member: Member,
|
||||
val origin: MemberPermission,
|
||||
val new: MemberPermission
|
||||
) : BotEvent(bot), GroupMemberEvent
|
||||
) : GroupMemberEvent, BotPassiveEvent, Packet
|
||||
|
||||
// endregion
|
||||
|
||||
|
||||
// region 禁言
|
||||
|
||||
/**
|
||||
* 群成员被禁言事件. 操作人和被禁言的成员都不可能是机器人本人
|
||||
*/
|
||||
data class MemberMuteEvent(
|
||||
override val member: Member,
|
||||
val durationSeconds: Int,
|
||||
/**
|
||||
* 操作人. 为 null 则为机器人操作
|
||||
*/
|
||||
val operator: Member?
|
||||
) : GroupMemberEvent, Packet
|
||||
|
||||
/**
|
||||
* 群成员被取消禁言事件. 操作人和被禁言的成员都不可能是机器人本人
|
||||
*/
|
||||
data class MemberUnmuteEvent(
|
||||
override val member: Member,
|
||||
/**
|
||||
* 操作人. 为 null 则为机器人操作
|
||||
*/
|
||||
val operator: Member?
|
||||
) : GroupMemberEvent, Packet
|
||||
|
||||
// endregion
|
||||
|
||||
// endregion
|
||||
|
||||
// endregion
|
@ -1,13 +0,0 @@
|
||||
package net.mamoe.mirai.event.events
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.data.Packet
|
||||
|
||||
/**
|
||||
* 被挤下线
|
||||
*/
|
||||
data class ForceOfflineEvent(
|
||||
override val bot: Bot,
|
||||
val title: String,
|
||||
val tips: String
|
||||
) : BotEvent(), Packet
|
@ -1,19 +0,0 @@
|
||||
/*
|
||||
* 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.event.events
|
||||
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.data.EventPacket
|
||||
import net.mamoe.mirai.data.OnlineStatus
|
||||
|
||||
data class FriendStatusChanged(
|
||||
val qq: QQ,
|
||||
val status: OnlineStatus
|
||||
) : EventPacket
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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.event.events
|
||||
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.data.EventPacket
|
||||
import net.mamoe.mirai.utils.getValue
|
||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
import kotlin.jvm.JvmOverloads
|
||||
|
||||
/**
|
||||
* 陌生人请求添加机器人账号为好友
|
||||
*/
|
||||
class ReceiveFriendAddRequestEvent(
|
||||
_qq: QQ,
|
||||
/**
|
||||
* 验证消息
|
||||
*/
|
||||
val message: String
|
||||
) : EventPacket {
|
||||
val qq: QQ by _qq.unsafeWeakRef()
|
||||
|
||||
/**
|
||||
* 同意这个请求
|
||||
*
|
||||
* @param remark 备注名, 不设置则需为 `null`
|
||||
*/
|
||||
@JvmOverloads
|
||||
suspend fun approve(remark: String? = null): Unit = qq.bot.approveFriendAddRequest(qq.id, remark)
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.event.events
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.event.Event
|
||||
|
||||
/**
|
||||
* 有关一个 [Bot] 的事件
|
||||
*/
|
||||
interface BotEvent : Event {
|
||||
val bot: Bot
|
||||
}
|
||||
|
||||
/**
|
||||
* [Bot] 被动接收的事件. 这些事件可能与机器人有关
|
||||
*/
|
||||
interface BotPassiveEvent : BotEvent
|
||||
|
||||
/**
|
||||
* 由 [Bot] 主动发起的动作的事件
|
||||
*/
|
||||
interface BotActiveEvent : BotEvent
|
||||
|
||||
|
||||
/**
|
||||
* 有关群的事件
|
||||
*/
|
||||
interface GroupEvent : BotEvent {
|
||||
val group: Group
|
||||
override val bot: Bot
|
||||
get() = group.bot
|
||||
}
|
||||
|
||||
/**
|
||||
* 有关群成员的事件
|
||||
*/
|
||||
interface GroupMemberEvent : GroupEvent {
|
||||
val member: Member
|
||||
override val group: Group
|
||||
get() = member.group
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 有关群的事件
|
||||
*/
|
||||
interface FriendEvent : BotEvent {
|
||||
val friend: QQ
|
||||
override val bot: Bot
|
||||
get() = friend.bot
|
||||
}
|
@ -10,9 +10,9 @@
|
||||
package net.mamoe.mirai.event.internal
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.event.Listener
|
||||
import net.mamoe.mirai.event.ListeningStatus
|
||||
import net.mamoe.mirai.event.Subscribable
|
||||
import net.mamoe.mirai.utils.LockFreeLinkedList
|
||||
import net.mamoe.mirai.utils.MiraiDebugAPI
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
@ -29,14 +29,14 @@ import kotlin.reflect.KClass
|
||||
var EventDisabled = false
|
||||
|
||||
@PublishedApi
|
||||
internal fun <L : Listener<E>, E : Subscribable> KClass<out E>.subscribeInternal(listener: L): L {
|
||||
internal fun <L : Listener<E>, E : Event> KClass<out E>.subscribeInternal(listener: L): L {
|
||||
this.listeners().addLast(listener)
|
||||
return listener
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
@Suppress("FunctionName")
|
||||
internal fun <E : Subscribable> CoroutineScope.Handler(handler: suspend (E) -> ListeningStatus): Handler<E> {
|
||||
internal fun <E : Event> CoroutineScope.Handler(handler: suspend (E) -> ListeningStatus): Handler<E> {
|
||||
return Handler(coroutineContext[Job], coroutineContext, handler)
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ private inline fun inline(block: () -> Unit) = block()
|
||||
* 事件处理器.
|
||||
*/
|
||||
@PublishedApi
|
||||
internal class Handler<in E : Subscribable>
|
||||
internal class Handler<in E : Event>
|
||||
@PublishedApi internal constructor(parentJob: Job?, private val subscriberContext: CoroutineContext, @JvmField val handler: suspend (E) -> ListeningStatus) :
|
||||
Listener<E>, CompletableJob by Job(parentJob) {
|
||||
|
||||
@ -79,21 +79,21 @@ internal class Handler<in E : Subscribable>
|
||||
/**
|
||||
* 这个事件类的监听器 list
|
||||
*/
|
||||
internal fun <E : Subscribable> KClass<out E>.listeners(): EventListeners<E> = EventListenerManager.get(this)
|
||||
internal fun <E : Event> KClass<out E>.listeners(): EventListeners<E> = EventListenerManager.get(this)
|
||||
|
||||
internal class EventListeners<E : Subscribable> : LockFreeLinkedList<Listener<E>>()
|
||||
internal class EventListeners<E : Event> : LockFreeLinkedList<Listener<E>>()
|
||||
|
||||
/**
|
||||
* 管理每个事件 class 的 [EventListeners].
|
||||
* [EventListeners] 是 lazy 的: 它们只会在被需要的时候才创建和存储.
|
||||
*/
|
||||
internal object EventListenerManager {
|
||||
private data class Registry<E : Subscribable>(val clazz: KClass<E>, val listeners: EventListeners<E>)
|
||||
private data class Registry<E : Event>(val clazz: KClass<E>, val listeners: EventListeners<E>)
|
||||
|
||||
private val registries = LockFreeLinkedList<Registry<*>>()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun <E : Subscribable> get(clazz: KClass<out E>): EventListeners<E> {
|
||||
internal fun <E : Event> get(clazz: KClass<out E>): EventListeners<E> {
|
||||
return registries.filteringGetOrAdd({ it.clazz == clazz }) {
|
||||
Registry(
|
||||
clazz,
|
||||
@ -105,7 +105,7 @@ internal object EventListenerManager {
|
||||
|
||||
// inline: NO extra Continuation
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal suspend inline fun Subscribable.broadcastInternal() {
|
||||
internal suspend inline fun Event.broadcastInternal() {
|
||||
if (EventDisabled) return
|
||||
|
||||
callAndRemoveIfRequired(this::class.listeners())
|
||||
@ -113,18 +113,18 @@ internal suspend inline fun Subscribable.broadcastInternal() {
|
||||
var supertypes = this::class.supertypes
|
||||
while (true) {
|
||||
val superSubscribableType = supertypes.firstOrNull {
|
||||
it.classifier as? KClass<out Subscribable> != null
|
||||
it.classifier as? KClass<out Event> != null
|
||||
}
|
||||
|
||||
superSubscribableType?.let {
|
||||
callAndRemoveIfRequired((it.classifier as KClass<out Subscribable>).listeners())
|
||||
callAndRemoveIfRequired((it.classifier as KClass<out Event>).listeners())
|
||||
}
|
||||
|
||||
supertypes = (superSubscribableType?.classifier as? KClass<*>)?.supertypes ?: return
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun <E : Subscribable> E.callAndRemoveIfRequired(listeners: EventListeners<E>) {
|
||||
private suspend inline fun <E : Event> E.callAndRemoveIfRequired(listeners: EventListeners<E>) {
|
||||
// atomic foreach
|
||||
listeners.forEach {
|
||||
if (it.onEvent(this) == ListeningStatus.STOPPED) {
|
||||
|
@ -13,7 +13,6 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.event.BroadcastControllable
|
||||
import net.mamoe.mirai.message.data.At
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.utils.getValue
|
||||
@ -30,21 +29,14 @@ class GroupMessage(
|
||||
val permission: MemberPermission,
|
||||
sender: Member,
|
||||
override val message: MessageChain
|
||||
) : MessagePacket<Member, Group>(bot), BroadcastControllable {
|
||||
) : MessagePacket<Member, Group>(bot) {
|
||||
val group: Group by group.unsafeWeakRef()
|
||||
override val sender: Member by sender.unsafeWeakRef()
|
||||
|
||||
/*
|
||||
01 00 09 01 00 06 66 61 69 6C 65 64 19 00 45 01 00 42 AA 02 3F 08 06 50 02 60 00 68 00 88 01 00 9A 01 31 08 0A 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 02 B0 03 00 C0 03 00 D0 03 00 E8 03 02 8A 04 04 08 02 08 01 90 04 80 C8 10 0E 00 0E 01 00 04 00 00 08 E4 07 00 04 00 00 00 01 12 00 1E 02 00 09 E9 85 B1 E9 87 8E E6 98 9F 03 00 01 02 05 00 04 00 00 00 03 08 00 04 00 00 00 04
|
||||
*/
|
||||
override val subject: Group get() = group
|
||||
|
||||
inline fun At.member(): Member = group[this.target]
|
||||
inline fun Long.member(): Member = group[this]
|
||||
override fun toString(): String =
|
||||
"GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)"
|
||||
|
||||
|
||||
override val shouldBroadcast: Boolean
|
||||
get() = bot.uin != sender.id // 自己会收到自己发的消息
|
||||
}
|
@ -37,7 +37,7 @@ expect abstract class MessagePacket<TSender : QQ, TSubject : Contact>(bot: Bot)
|
||||
*/ // Tips: 在 IntelliJ 中 (左侧边栏) 打开 `Structure`, 可查看类结构
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
@MiraiInternalAPI
|
||||
abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) : EventPacket, BotEvent() {
|
||||
abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) : EventPacket, BotEvent {
|
||||
/**
|
||||
* 接受到这条消息的
|
||||
*/
|
||||
|
@ -59,14 +59,14 @@ interface Message {
|
||||
infix fun eq(other: Message): Boolean = this == other
|
||||
|
||||
/**
|
||||
* 将 [stringValue] 与 [other] 比较
|
||||
* 将 [toString] 与 [other] 比较
|
||||
*/
|
||||
infix fun eq(other: String): Boolean = this.toString() == other
|
||||
|
||||
operator fun contains(sub: String): Boolean = false
|
||||
|
||||
/**
|
||||
* 把 [this] 连接到 [tail] 的头部. 类似于字符串相加.
|
||||
* 把 `this` 连接到 [tail] 的头部. 类似于字符串相加.
|
||||
*
|
||||
* 例:
|
||||
* ```kotlin
|
||||
@ -92,7 +92,7 @@ interface Message {
|
||||
override fun toString(): String
|
||||
|
||||
operator fun plus(another: Message): MessageChain = this.followedBy(another)
|
||||
operator fun plus(another: String): MessageChain = this.followedBy(another.toString().toMessage())
|
||||
operator fun plus(another: String): MessageChain = this.followedBy(another.toMessage())
|
||||
// `+ ""` will be resolved to `plus(String)` instead of `plus(CharSeq)`
|
||||
operator fun plus(another: CharSequence): MessageChain = this.followedBy(another.toString().toMessage())
|
||||
}
|
||||
|
@ -23,6 +23,21 @@ interface MessageSource : Message {
|
||||
*/
|
||||
val messageUid: Long
|
||||
|
||||
/**
|
||||
* 发送人号码
|
||||
*/
|
||||
val senderId: Long
|
||||
|
||||
/**
|
||||
* 群号码
|
||||
*/
|
||||
val groupId: Long
|
||||
|
||||
/**
|
||||
* 原消息内容
|
||||
*/
|
||||
val sourceMessage: MessageChain
|
||||
|
||||
/**
|
||||
* 固定返回空字符串 ("")
|
||||
*/
|
||||
|
@ -17,6 +17,6 @@ package net.mamoe.mirai.event
|
||||
object Events {
|
||||
/*
|
||||
@JvmStatic
|
||||
fun <E : Subscribable> subscribe(type: Class<E>, handler: suspend (E) -> ListeningStatus) =
|
||||
fun <E : Event> subscribe(type: Class<E>, handler: suspend (E) -> ListeningStatus) =
|
||||
runBlocking { type.kotlin.subscribe(handler) }*/
|
||||
}
|
||||
|
@ -9,11 +9,11 @@
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import io.ktor.util.cio.use
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.io.ByteWriteChannel
|
||||
import kotlinx.coroutines.io.close
|
||||
import kotlinx.coroutines.io.jvm.nio.copyTo
|
||||
import kotlinx.coroutines.io.reader
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@ -44,7 +44,7 @@ internal class DefaultLoginSolver : LoginSolver() {
|
||||
tempFile.createNewFile()
|
||||
bot.logger.info("需要图片验证码登录, 验证码为 4 字母")
|
||||
try {
|
||||
tempFile.writeChannel().use { writeFully(data) }
|
||||
tempFile.writeChannel().apply { writeFully(data); close() }
|
||||
bot.logger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}")
|
||||
} catch (e: Exception) {
|
||||
bot.logger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片")
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import io.ktor.util.asStream
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.io.core.Input
|
||||
@ -132,7 +131,7 @@ suspend inline fun InputStream.suspendToExternalImage(): ExternalImage = withCon
|
||||
fun Input.toExternalImage(): ExternalImage {
|
||||
val file = createTempFile().apply { deleteOnExit() }
|
||||
file.outputStream().asOutput().use {
|
||||
this.asStream().asInput().copyTo(it)
|
||||
this.copyTo(it)
|
||||
}
|
||||
return file.toExternalImage()
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import kotlin.system.exitProcess
|
||||
import kotlin.test.Test
|
||||
|
||||
|
||||
class TestEvent : Subscribable {
|
||||
class TestEvent : Event {
|
||||
var triggered = false
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ class EventTests {
|
||||
}
|
||||
|
||||
|
||||
open class ParentEvent : Subscribable {
|
||||
open class ParentEvent : Event {
|
||||
var triggered = false
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ android {
|
||||
exclude 'META-INF/ktor-http-cio.kotlin_module'
|
||||
exclude 'META-INF/ktor-client-core.kotlin_module'
|
||||
exclude "META-INF/kotlinx-serialization-runtime.kotlin_module"
|
||||
exclude 'META-INF/ktor-io.kotlin_module'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,8 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.alsoLogin
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.event.Subscribable
|
||||
import net.mamoe.mirai.event.events.ReceiveFriendAddRequestEvent
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.event.events.BotEvent
|
||||
import net.mamoe.mirai.event.subscribeAlways
|
||||
import net.mamoe.mirai.event.subscribeGroupMessages
|
||||
import net.mamoe.mirai.event.subscribeMessages
|
||||
@ -48,14 +48,14 @@ suspend fun main() {
|
||||
// override config here.
|
||||
}.alsoLogin()
|
||||
|
||||
// 任何可以监听的对象都继承 Subscribable, 因此这个订阅会订阅全部的事件.
|
||||
GlobalScope.subscribeAlways<Subscribable> {
|
||||
// 任何可以监听的对象都继承 Event, 因此这个订阅会订阅全部的事件.
|
||||
GlobalScope.subscribeAlways<Event> {
|
||||
//bot.logger.verbose("收到了一个事件: $this")
|
||||
}
|
||||
|
||||
// 全局范围订阅事件, 不受 bot 实例影响
|
||||
GlobalScope.subscribeAlways<ReceiveFriendAddRequestEvent> {
|
||||
it.approve()
|
||||
GlobalScope.subscribeAlways<BotEvent> {
|
||||
|
||||
}
|
||||
|
||||
// 订阅来自这个 bot 的群消息事件
|
||||
@ -81,14 +81,6 @@ suspend fun main() {
|
||||
|
||||
"你好" reply "你好!"
|
||||
|
||||
startsWith("profile", removePrefix = true) {
|
||||
val account = it.trim()
|
||||
if (account.isNotEmpty()) {
|
||||
bot.getFriend(account.toLong())
|
||||
} else {
|
||||
sender
|
||||
}.queryProfile().toString().reply()
|
||||
}
|
||||
"grouplist" reply {
|
||||
|
||||
//"https://ssl.ptlogin2.qq.com/jump?pt_clientver=5509&pt_src=1&keyindex=9&clientuin=" + bot.qqAccount + "&clientkey=" + com.tick_tock.pctim.utils.Util.byte2HexString(
|
||||
|
@ -52,7 +52,7 @@ internal class BlockingGroupImpl(private val delegate: Group) : BlockingGroup {
|
||||
override fun toFullString(): String = delegate.toFullString()
|
||||
override fun getMember(id: Long): BlockingMember = delegate[id].blocking()
|
||||
override fun getBot(): BlockingBot = delegate.bot.blocking()
|
||||
override fun getAnnouncement(): String = delegate.announcement
|
||||
override fun getAnnouncement(): String = delegate.entranceAnnouncement
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
override fun getMembers(): Map<Long, BlockingMember> =
|
||||
delegate.members.delegate.toList().associateBy { it.id }.mapValues { it.value.blocking() }
|
||||
|
@ -11,7 +11,7 @@ package net.mamoe.mirai.imageplugin
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import net.mamoe.mirai.event.events.BotLoginSucceedEvent
|
||||
import net.mamoe.mirai.event.events.BotOnlineEvent
|
||||
import net.mamoe.mirai.event.subscribeAlways
|
||||
import net.mamoe.mirai.event.subscribeMessages
|
||||
import net.mamoe.mirai.plugin.PluginBase
|
||||
@ -22,7 +22,7 @@ class ImageSenderMain : PluginBase() {
|
||||
@MiraiExperimentalAPI
|
||||
override fun onEnable() {
|
||||
logger.info("Image Sender plugin enabled")
|
||||
GlobalScope.subscribeAlways<BotLoginSucceedEvent> {
|
||||
GlobalScope.subscribeAlways<BotOnlineEvent> {
|
||||
logger.info("${this.bot.uin} login succeed, it will be controlled by Image Sender Plugin")
|
||||
this.bot.subscribeMessages {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user