Conflicts:
	mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
This commit is contained in:
jiahua.liu 2020-02-12 16:10:47 +08:00
commit 71c8609916
52 changed files with 799 additions and 898 deletions

View File

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

View File

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

View File

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

View File

@ -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, // ?

View File

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

View File

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

View File

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

View File

@ -9,6 +9,4 @@
package net.mamoe.mirai.qqandroid.io
interface JceStruct {
fun writeTo(output: JceOutput) = Unit
}
interface JceStruct

View File

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

View File

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

View File

@ -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", "")}")

View File

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

View File

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

View File

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

View File

@ -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.
}
}
*/
}

View File

@ -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 ?: "")
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@ package net.mamoe.mirai.data
/**
* 从服务器收到的包解析之后的结构化数据.
* 它是一个数据包工厂的处理的返回值.
*/
interface Packet {
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 // 自己会收到自己发的消息
}

View File

@ -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 {
/**
* 接受到这条消息的
*/

View File

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

View File

@ -23,6 +23,21 @@ interface MessageSource : Message {
*/
val messageUid: Long
/**
* 发送人号码
*/
val senderId: Long
/**
* 群号码
*/
val groupId: Long
/**
* 原消息内容
*/
val sourceMessage: MessageChain
/**
* 固定返回空字符串 ("")
*/

View File

@ -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) }*/
}

View File

@ -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}), 请尝试查看以上字符图片")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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