Commit 2b477b3b authored by Him188's avatar Him188

Redesign events

parent 745200f6
......@@ -68,6 +68,8 @@ kotlin {
dependsOn(commonMain)
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
implementation("io.ktor:ktor-client-android:$ktorVersion")
}
......
......@@ -9,4 +9,4 @@ import java.util.concurrent.Executors
*
* JVM: 独立的 4 thread 调度器
*/
actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
\ No newline at end of file
internal actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
\ No newline at end of file
......@@ -8,44 +8,44 @@ actual typealias PlatformLogger = AndroidLogger
* Android 平台的默认的日志记录器, 使用 [Log]
* 不应该直接构造这个类的实例. 需使用 [DefaultLogger]
*/
open class AndroidLogger(override val identity: String?) : MiraiLoggerPlatformBase() {
open class AndroidLogger(override val identity: String? = "Mirai") : MiraiLoggerPlatformBase() {
override fun verbose0(any: Any?) {
Log.v(identity, any.toString())
Log.v(identity, any?.toString() ?: "")
}
override fun verbose0(message: String?, e: Throwable?) {
Log.v(identity, message.toString(), e)
Log.v(identity, message ?: "", e)
}
override fun debug0(any: Any?) {
Log.d(identity, any.toString())
Log.d(identity, any?.toString() ?: "")
}
override fun debug0(message: String?, e: Throwable?) {
Log.d(identity, message.toString(), e)
Log.d(identity, message ?: "", e)
}
override fun info0(any: Any?) {
Log.i(identity, any.toString())
Log.i(identity, any?.toString() ?: "")
}
override fun info0(message: String?, e: Throwable?) {
Log.i(identity, message.toString(), e)
Log.i(identity, message ?: "", e)
}
override fun warning0(any: Any?) {
Log.w(identity, any.toString())
Log.w(identity, any?.toString() ?: "")
}
override fun warning0(message: String?, e: Throwable?) {
Log.w(identity, message.toString(), e)
Log.w(identity, message ?: "", e)
}
override fun error0(any: Any?) {
Log.e(identity, any.toString())
Log.e(identity, any?.toString() ?: "")
}
override fun error0(message: String?, e: Throwable?) {
Log.e(identity, message.toString(), e)
Log.e(identity, message ?: "", e)
}
}
\ No newline at end of file
......@@ -9,10 +9,13 @@ import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.singleChain
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.handler.EventPacketHandler
import net.mamoe.mirai.network.protocol.tim.packet.action.RequestProfileDetailsPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.RequestProfileDetailsResponse
import net.mamoe.mirai.network.protocol.tim.packet.action.SendFriendMessagePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.SendGroupMessagePacket
import net.mamoe.mirai.network.sessionKey
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.sendPacket
import net.mamoe.mirai.utils.SuspendLazy
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
import net.mamoe.mirai.withSession
......@@ -89,7 +92,7 @@ class Group internal constructor(bot: Bot, val groupId: GroupId) : Contact(bot,
get() = TODO("Implementing group members is less important")
override suspend fun sendMessage(message: MessageChain) {
bot.network[EventPacketHandler].sendGroupMessage(this, message)
bot.sendPacket(SendGroupMessagePacket(bot.qqAccount, internalId, bot.sessionKey, message))
}
companion object
......@@ -114,7 +117,7 @@ open class QQ internal constructor(bot: Bot, id: UInt) : Contact(bot, id) {
val profile: Deferred<Profile> by bot.network.SuspendLazy { updateProfile() }
override suspend fun sendMessage(message: MessageChain) {
bot.network[EventPacketHandler].sendFriendMessage(this, message)
bot.sendPacket(SendFriendMessagePacket(bot.qqAccount, id, bot.sessionKey, message))
}
/**
......@@ -154,6 +157,24 @@ class Member internal constructor(bot: Bot, id: UInt, val group: Group) : QQ(bot
}
}
/**
* 群成员的权限
*/
enum class MemberPermission {
/**
* 群主
*/
OWNER,
/**
* 管理员
*/
OPERATOR,
/**
* 一般群成员
*/
MEMBER;
}
/**
* 个人资料
*/
......
......@@ -3,83 +3,21 @@
package net.mamoe.mirai.event
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.event.events.FriendMessageEvent
import net.mamoe.mirai.event.events.GroupMessageEvent
import net.mamoe.mirai.message.*
import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.sendTo
import net.mamoe.mirai.utils.upload
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.any
import net.mamoe.mirai.network.protocol.tim.packet.event.FriendMessage
import net.mamoe.mirai.network.protocol.tim.packet.event.GroupMessage
import net.mamoe.mirai.network.protocol.tim.packet.event.MessageEventPacket
import kotlin.jvm.JvmName
/**
* 消息事件时创建的临时容器.
*/
abstract class SenderAndMessage<TContact : Contact>(
/**
* 发送这条消息的用户.
*/
val sender: QQ,
/**
* 消息事件主体. 对于好友消息, 这个属性为 [QQ] 的实例; 对于群消息, 这个属性为 [Group] 的实例
*/
val subject: TContact,
val message: MessageChain
) {
/**
* 给这个消息事件的主体发送消息
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/
suspend inline fun reply(message: MessageChain) = subject.sendMessage(message)
suspend fun reply(message: Message) = subject.sendMessage(message.singleChain())
suspend fun reply(plain: String) = subject.sendMessage(plain.toMessage())
// region Send to subject
suspend inline fun ExternalImage.send() = this.sendTo(subject)
suspend inline fun ExternalImage.upload(): Image = this.upload(subject)
suspend inline fun Image.send() = this.sendTo(subject)
suspend inline fun ImageId.send() = this.sendTo(subject)
suspend inline fun Message.send() = this.sendTo(subject)
suspend inline fun String.send() = this.toMessage().sendTo(subject)
// endregion
}
/**
* [subject] = [sender] = [QQ]
*/
class FriendSenderAndMessage(
sender: QQ,
message: MessageChain
) : SenderAndMessage<QQ>(sender, sender, message)
/**
* [subject] = [group] = [Group]
*/
class GroupSenderAndMessage(
val group: Group,
sender: QQ,
message: MessageChain
) : SenderAndMessage<Group>(sender, group, message)
/**
* 订阅来自所有 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话.
*/
@MessageDsl
suspend inline fun subscribeMessages(crossinline listeners: suspend MessageSubscribersBuilder<SenderAndMessage<*>>.() -> Unit) {
MessageSubscribersBuilder<SenderAndMessage<*>> { listener ->
subscribeAlways<BotEvent> {
when (it) {
is FriendMessageEvent -> listener(FriendSenderAndMessage(it.sender, it.message))
is GroupMessageEvent -> listener(GroupSenderAndMessage(it.group, it.sender, it.message))
}
suspend inline fun subscribeMessages(crossinline listeners: suspend MessageSubscribersBuilder<MessageEventPacket<*>>.() -> Unit) {
MessageSubscribersBuilder<MessageEventPacket<*>> { listener ->
subscribeAlways<MessageEventPacket<*>> {
listener(it)
}
}.apply { listeners() }
}
......@@ -88,10 +26,10 @@ suspend inline fun subscribeMessages(crossinline listeners: suspend MessageSubsc
* 订阅来自所有 [Bot] 的所有群消息事件
*/
@MessageDsl
suspend inline fun subscribeGroupMessages(crossinline listeners: suspend MessageSubscribersBuilder<GroupSenderAndMessage>.() -> Unit) {
MessageSubscribersBuilder<GroupSenderAndMessage> { listener ->
subscribeAlways<GroupMessageEvent> {
listener(GroupSenderAndMessage(it.group, it.sender, it.message))
suspend inline fun subscribeGroupMessages(crossinline listeners: suspend MessageSubscribersBuilder<GroupMessage>.() -> Unit) {
MessageSubscribersBuilder<GroupMessage> { listener ->
subscribeAlways<GroupMessage> {
listener(it)
}
}.apply { listeners() }
}
......@@ -100,10 +38,10 @@ suspend inline fun subscribeGroupMessages(crossinline listeners: suspend Message
* 订阅来自所有 [Bot] 的所有好友消息事件
*/
@MessageDsl
suspend inline fun subscribeFriendMessages(crossinline listeners: suspend MessageSubscribersBuilder<FriendSenderAndMessage>.() -> Unit) {
MessageSubscribersBuilder<FriendSenderAndMessage> { listener ->
subscribeAlways<FriendMessageEvent> {
listener(FriendSenderAndMessage(it.sender, it.message))
suspend inline fun subscribeFriendMessages(crossinline listeners: suspend MessageSubscribersBuilder<FriendMessage>.() -> Unit) {
MessageSubscribersBuilder<FriendMessage> { listener ->
subscribeAlways<FriendMessage> {
listener(it)
}
}.apply { listeners() }
}
......@@ -112,13 +50,10 @@ suspend inline fun subscribeFriendMessages(crossinline listeners: suspend Messag
* 订阅来自这个 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话.
*/
@MessageDsl
suspend inline fun Bot.subscribeMessages(crossinline listeners: suspend MessageSubscribersBuilder<SenderAndMessage<*>>.() -> Unit) {
MessageSubscribersBuilder<SenderAndMessage<*>> { listener ->
this.subscribeAlways<BotEvent> {
when (it) {
is FriendMessageEvent -> listener(FriendSenderAndMessage(it.sender, it.message))
is GroupMessageEvent -> listener(GroupSenderAndMessage(it.group, it.sender, it.message))
}
suspend inline fun Bot.subscribeMessages(crossinline listeners: suspend MessageSubscribersBuilder<MessageEventPacket<*>>.() -> Unit) {
MessageSubscribersBuilder<MessageEventPacket<*>> { listener ->
this.subscribeAlways<MessageEventPacket<*>> {
listener(it)
}
}.apply { listeners() }
}
......@@ -127,10 +62,10 @@ suspend inline fun Bot.subscribeMessages(crossinline listeners: suspend MessageS
* 订阅来自这个 [Bot] 的所有群消息事件
*/
@MessageDsl
suspend inline fun Bot.subscribeGroupMessages(crossinline listeners: suspend MessageSubscribersBuilder<GroupSenderAndMessage>.() -> Unit) {
MessageSubscribersBuilder<GroupSenderAndMessage> { listener ->
this.subscribeAlways<GroupMessageEvent> {
listener(GroupSenderAndMessage(it.group, it.sender, it.message))
suspend inline fun Bot.subscribeGroupMessages(crossinline listeners: suspend MessageSubscribersBuilder<GroupMessage>.() -> Unit) {
MessageSubscribersBuilder<GroupMessage> { listener ->
this.subscribeAlways<GroupMessage> {
listener(it)
}
}.apply { listeners() }
}
......@@ -139,10 +74,10 @@ suspend inline fun Bot.subscribeGroupMessages(crossinline listeners: suspend Mes
* 订阅来自这个 [Bot] 的所有好友消息事件.
*/
@MessageDsl
suspend inline fun Bot.subscribeFriendMessages(crossinline listeners: suspend MessageSubscribersBuilder<FriendSenderAndMessage>.() -> Unit) {
MessageSubscribersBuilder<FriendSenderAndMessage> { listener ->
this.subscribeAlways<FriendMessageEvent> {
listener(FriendSenderAndMessage(it.sender, it.message))
suspend inline fun Bot.subscribeFriendMessages(crossinline listeners: suspend MessageSubscribersBuilder<FriendMessage>.() -> Unit) {
MessageSubscribersBuilder<FriendMessage> { listener ->
this.subscribeAlways<FriendMessage> {
listener(it)
}
}.apply { listeners() }
}
......@@ -151,11 +86,11 @@ internal typealias MessageReplier<T> = @MessageDsl suspend T.(String) -> Message
internal typealias StringReplier<T> = @MessageDsl suspend T.(String) -> String
internal suspend inline operator fun <T : SenderAndMessage<*>> (@MessageDsl suspend T.(String) -> Unit).invoke(t: T) =
internal suspend inline operator fun <T : MessageEventPacket<*>> (@MessageDsl suspend T.(String) -> Unit).invoke(t: T) =
this.invoke(t, t.message.stringValue)
@JvmName("invoke1") //Avoid Platform declaration clash
internal suspend inline operator fun <T : SenderAndMessage<*>> StringReplier<T>.invoke(t: T): String =
internal suspend inline operator fun <T : MessageEventPacket<*>> StringReplier<T>.invoke(t: T): String =
this.invoke(t, t.message.stringValue)
/**
......@@ -166,7 +101,7 @@ internal suspend inline operator fun <T : SenderAndMessage<*>> StringReplier<T>.
*/
@Suppress("unused")
@MessageDsl
class MessageSubscribersBuilder<T : SenderAndMessage<*>>(
class MessageSubscribersBuilder<T : MessageEventPacket<*>>(
inline val subscriber: suspend (@MessageDsl suspend T.(String) -> Unit) -> Unit
) {
/**
......@@ -221,7 +156,7 @@ class MessageSubscribersBuilder<T : SenderAndMessage<*>>(
* 如果是来自这个群的消息, 就执行 [onEvent]
*/
suspend fun sentFrom(id: UInt, onEvent: @MessageDsl suspend T.(String) -> Unit) =
content({ if (this is GroupSenderAndMessage) group.id == id else false }, onEvent)
content({ if (this is GroupMessage) group.id == id else false }, onEvent)
/**
* 如果是来自这个群的消息, 就执行 [onEvent]
......
......@@ -10,6 +10,13 @@ import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmOverloads
/**
* 可被监听的.
*
* 可以是任何 class 或 object.
*/
interface Subscribable
/**
* 所有事件的基类.
* 若监听这个类, 监听器将会接收所有事件的广播.
......@@ -17,7 +24,7 @@ import kotlin.jvm.JvmOverloads
* @see [broadcast] 广播事件
* @see [subscribe] 监听事件
*/
abstract class Event {
abstract class Event : Subscribable {
/**
* 事件是否已取消. 事件需实现 [Cancellable] 才可以被取消, 否则这个字段为常量值 false
......@@ -51,9 +58,9 @@ private val EventDebuggingFlag: Boolean by lazy {
}
/**
* 实现这个接口的事件可以被取消.
* 实现这个接口的事件可以被取消. 在广播中取消不会影响广播过程.
*/
interface Cancellable {
interface Cancellable : Subscribable {
val cancelled: Boolean
fun cancel()
......@@ -67,7 +74,7 @@ interface Cancellable {
*/
@Suppress("UNCHECKED_CAST")
@JvmOverloads
suspend fun <E : Event> E.broadcast(context: CoroutineContext = EmptyCoroutineContext): E {
suspend fun <E : Subscribable> E.broadcast(context: CoroutineContext = EmptyCoroutineContext): E {
if (EventDebuggingFlag) {
EventLogger.debug(this::class.simpleName + " pre broadcast")
}
......
......@@ -26,22 +26,26 @@ enum class ListeningStatus {
* 订阅所有 [E] 及其子类事件.
* 在
*/
suspend inline fun <reified E : Event> subscribe(noinline handler: suspend (E) -> ListeningStatus) = E::class.subscribe(handler)
suspend inline fun <reified E : Subscribable> subscribe(noinline handler: suspend (E) -> ListeningStatus) = E::class.subscribe(handler)
suspend inline fun <reified E : Event> subscribeAlways(noinline listener: suspend (E) -> Unit) = E::class.subscribeAlways(listener)
suspend inline fun <reified E : Subscribable> subscribeAlways(noinline listener: suspend (E) -> Unit) = E::class.subscribeAlways(listener)
suspend inline fun <reified E : Event> subscribeOnce(noinline listener: suspend (E) -> Unit) = E::class.subscribeOnce(listener)
suspend inline fun <reified E : Subscribable> subscribeOnce(noinline listener: suspend (E) -> Unit) = E::class.subscribeOnce(listener)
suspend inline fun <reified E : Event, T> subscribeUntil(valueIfStop: T, noinline listener: suspend (E) -> T) = E::class.subscribeUntil(valueIfStop, listener)
suspend inline fun <reified E : Event> subscribeUntilFalse(noinline listener: suspend (E) -> Boolean) = E::class.subscribeUntilFalse(listener)
suspend inline fun <reified E : Event> subscribeUntilTrue(noinline listener: suspend (E) -> Boolean) = E::class.subscribeUntilTrue(listener)
suspend inline fun <reified E : Event> subscribeUntilNull(noinline listener: suspend (E) -> Any?) = E::class.subscribeUntilNull(listener)
suspend inline fun <reified E : Subscribable, T> subscribeUntil(valueIfStop: T, noinline listener: suspend (E) -> T) =
E::class.subscribeUntil(valueIfStop, listener)
suspend inline fun <reified E : Subscribable> subscribeUntilFalse(noinline listener: suspend (E) -> Boolean) = E::class.subscribeUntilFalse(listener)
suspend inline fun <reified E : Subscribable> subscribeUntilTrue(noinline listener: suspend (E) -> Boolean) = E::class.subscribeUntilTrue(listener)
suspend inline fun <reified E : Subscribable> subscribeUntilNull(noinline listener: suspend (E) -> Any?) = E::class.subscribeUntilNull(listener)
suspend inline fun <reified E : Event, T> subscribeWhile(valueIfContinue: T, noinline listener: suspend (E) -> T) = E::class.subscribeWhile(valueIfContinue, listener)
suspend inline fun <reified E : Event> subscribeWhileFalse(noinline listener: suspend (E) -> Boolean) = E::class.subscribeWhileFalse(listener)
suspend inline fun <reified E : Event> subscribeWhileTrue(noinline listener: suspend (E) -> Boolean) = E::class.subscribeWhileTrue(listener)
suspend inline fun <reified E : Event> subscribeWhileNull(noinline listener: suspend (E) -> Any?) = E::class.subscribeWhileNull(listener)
suspend inline fun <reified E : Subscribable, T> subscribeWhile(valueIfContinue: T, noinline listener: suspend (E) -> T) =
E::class.subscribeWhile(valueIfContinue, listener)
suspend inline fun <reified E : Subscribable> subscribeWhileFalse(noinline listener: suspend (E) -> Boolean) = E::class.subscribeWhileFalse(listener)
suspend inline fun <reified E : Subscribable> subscribeWhileTrue(noinline listener: suspend (E) -> Boolean) = E::class.subscribeWhileTrue(listener)
suspend inline fun <reified E : Subscribable> subscribeWhileNull(noinline listener: suspend (E) -> Any?) = E::class.subscribeWhileNull(listener)
// endregion
......@@ -49,40 +53,42 @@ suspend inline fun <reified E : Event> subscribeWhileNull(noinline listener: sus
// region KClass 的扩展方法 (不推荐)
@PublishedApi
internal suspend fun <E : Event> KClass<E>.subscribe(handler: suspend (E) -> ListeningStatus) = this.subscribeInternal(Handler(handler))
internal suspend fun <E : Subscribable> KClass<E>.subscribe(handler: suspend (E) -> ListeningStatus) = this.subscribeInternal(Handler(handler))
@PublishedApi
internal suspend fun <E : Event> KClass<E>.subscribeAlways(listener: suspend (E) -> Unit) = this.subscribeInternal(Handler { listener(it); ListeningStatus.LISTENING })
internal suspend fun <E : Subscribable> KClass<E>.subscribeAlways(listener: suspend (E) -> Unit) =
this.subscribeInternal(Handler { listener(it); ListeningStatus.LISTENING })
@PublishedApi
internal suspend fun <E : Event> KClass<E>.subscribeOnce(listener: suspend (E) -> Unit) = this.subscribeInternal(Handler { listener(it); ListeningStatus.STOPPED })
internal suspend fun <E : Subscribable> KClass<E>.subscribeOnce(listener: suspend (E) -> Unit) =
this.subscribeInternal(Handler { listener(it); ListeningStatus.STOPPED })
@PublishedApi
internal suspend fun <E : Event, T> KClass<E>.subscribeUntil(valueIfStop: T, listener: suspend (E) -> T) =
internal suspend fun <E : Subscribable, T> KClass<E>.subscribeUntil(valueIfStop: T, listener: suspend (E) -> T) =
subscribeInternal(Handler { if (listener(it) === valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
@PublishedApi
internal suspend fun <E : Event> KClass<E>.subscribeUntilFalse(listener: suspend (E) -> Boolean) = subscribeUntil(false, listener)
internal suspend fun <E : Subscribable> KClass<E>.subscribeUntilFalse(listener: suspend (E) -> Boolean) = subscribeUntil(false, listener)
@PublishedApi
internal suspend fun <E : Event> KClass<E>.subscribeUntilTrue(listener: suspend (E) -> Boolean) = subscribeUntil(true, listener)
internal suspend fun <E : Subscribable> KClass<E>.subscribeUntilTrue(listener: suspend (E) -> Boolean) = subscribeUntil(true, listener)
@PublishedApi
internal suspend fun <E : Event> KClass<E>.subscribeUntilNull(listener: suspend (E) -> Any?) = subscribeUntil(null, listener)
internal suspend fun <E : Subscribable> KClass<E>.subscribeUntilNull(listener: suspend (E) -> Any?) = subscribeUntil(null, listener)
@PublishedApi
internal suspend fun <E : Event, T> KClass<E>.subscribeWhile(valueIfContinue: T, listener: suspend (E) -> T) =
internal suspend fun <E : Subscribable, T> KClass<E>.subscribeWhile(valueIfContinue: T, listener: suspend (E) -> T) =
subscribeInternal(Handler { if (listener(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
@PublishedApi
internal suspend fun <E : Event> KClass<E>.subscribeWhileFalse(listener: suspend (E) -> Boolean) = subscribeWhile(false, listener)
internal suspend fun <E : Subscribable> KClass<E>.subscribeWhileFalse(listener: suspend (E) -> Boolean) = subscribeWhile(false, listener)
@PublishedApi
internal suspend fun <E : Event> KClass<E>.subscribeWhileTrue(listener: suspend (E) -> Boolean) = subscribeWhile(true, listener)
internal suspend fun <E : Subscribable> KClass<E>.subscribeWhileTrue(listener: suspend (E) -> Boolean) = subscribeWhile(true, listener)
@PublishedApi
internal suspend fun <E : Event> KClass<E>.subscribeWhileNull(listener: suspend (E) -> Any?) = subscribeWhile(null, listener)
internal suspend fun <E : Subscribable> KClass<E>.subscribeWhileNull(listener: suspend (E) -> Any?) = subscribeWhile(null, listener)
// endregion
......@@ -94,7 +100,7 @@ internal suspend fun <E : Event> KClass<E>.subscribeWhileNull(listener: suspend
*/
@ListenersBuilderDsl
@PublishedApi
internal suspend fun <E : Event> KClass<E>.subscribeAll(listeners: suspend ListenerBuilder<E>.() -> Unit) {
internal suspend fun <E : Subscribable> KClass<E>.subscribeAll(listeners: suspend ListenerBuilder<E>.() -> Unit) {
with(ListenerBuilder<E> { this.subscribeInternal(it) }) {
listeners()
}
......@@ -105,7 +111,7 @@ internal suspend fun <E : Event> KClass<E>.subscribeAll(listeners: suspend Liste
* @see ListenerBuilder
*/
@ListenersBuilderDsl
suspend inline fun <reified E : Event> subscribeAll(noinline listeners: suspend ListenerBuilder<E>.() -> Unit) = E::class.subscribeAll(listeners)
suspend inline fun <reified E : Subscribable> subscribeAll(noinline listeners: suspend ListenerBuilder<E>.() -> Unit) = E::class.subscribeAll(listeners)
/**
* 监听构建器. 可同时进行多种方式的监听
......@@ -125,7 +131,7 @@ suspend inline fun <reified E : Event> subscribeAll(noinline listeners: suspend
*/
@ListenersBuilderDsl
@Suppress("MemberVisibilityCanBePrivate", "unused")
class ListenerBuilder<out E : Event>(
class ListenerBuilder<out E : Subscribable>(
@PublishedApi
internal val handlerConsumer: suspend (Listener<E>) -> Unit
) {
......
package net.mamoe.mirai.event.events
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.Cancellable
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.message.ImageId
abstract class BotEvent(val bot: Bot) : Event()
abstract class BotEvent : Event {
private lateinit var _bot: Bot
open val bot: Bot get() = _bot
class BotLoginSucceedEvent(bot: Bot) : BotEvent(bot)
constructor(bot: Bot) : super() {
this._bot = bot
}
/**
* 上传好友图片时, 服务器返回好友图片 ID 事件.
*
* 在这之后图片将会被上传到服务器.
*/
class FriendImageIdObtainedEvent(bot: Bot, val imageId: ImageId) : BotEvent(bot), Cancellable
\ No newline at end of file
constructor() : super()
}
class BotLoginSucceedEvent(bot: Bot) : BotEvent(bot)
\ No newline at end of file
@file:Suppress("unused")
package net.mamoe.mirai.event.events
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.utils.OnlineStatus
/**
* 好友事件
*/
sealed class FriendEvent(bot: Bot, val sender: QQ) : BotEvent(bot)
/**
* 接受好友消息事件
*
* @author Him188moe
*/
class FriendMessageEvent(bot: Bot, sender: QQ, val message: MessageChain) : FriendEvent(bot, sender) {
suspend inline fun reply(message: Message) = sender.sendMessage(message)
suspend inline fun reply(message: String) = sender.sendMessage(message)
suspend inline fun reply(message: MessageChain) = sender.sendMessage(message)//shortcut
}
/**
* 好友发起会话事件. 即好友在消息输入框内输入任意内容.
*/
class FriendConversationInitializedEvent(bot: Bot, sender: QQ) : FriendEvent(bot, sender)
/**
* 好友在线状态改变事件
*/
class FriendOnlineStatusChangedEvent(bot: Bot, sender: QQ, val newStatus: OnlineStatus) : FriendEvent(bot, sender)
/**
* 机器人账号被 [sender] 删除好友
*/
class DeletedByFriendEvent(bot: Bot, qq: QQ) : FriendEvent(bot, qq)
\ No newline at end of file
@file:Suppress("unused")
package net.mamoe.mirai.event.events
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.network.protocol.tim.packet.event.SenderPermission
abstract class GroupEvent(bot: Bot, val group: Group) : BotEvent(bot)
/**
* 群消息
*/
class GroupMessageEvent(
bot: Bot,
group: Group,
val sender: QQ,
val message: MessageChain,
val senderPermission: SenderPermission,
val senderName: String//若他有群名片就是群名片, 没有就是昵称
) : GroupEvent(bot, group) {
suspend inline fun reply(message: Message) = group.sendMessage(message)
suspend inline fun reply(message: String) = group.sendMessage(message)
suspend inline fun reply(message: MessageChain) = group.sendMessage(message)
}
/**
* 群成员权限改变
*/
class MemberPermissionChangedEvent(
bot: Bot,
group: Group,
val member: QQ,
val kind: Kind
) : GroupEvent(bot, group) {
enum class Kind {
/**
* 变成管理员
*/
BECOME_OPERATOR,
/**
* 不再是管理员
*/
NO_LONGER_OPERATOR,
} // TODO: 2019/11/2 变成群主的情况
}
\ No newline at end of file
......@@ -47,5 +47,4 @@ sealed class ServerPacketEvent<P : Packet>(bot: Bot, packet: P) : PacketEvent<P>
/**
* 服务器数据包接收事件. 此时包已经解密完成.
*/
class ServerPacketReceivedEvent<P : Packet>(bot: Bot, packet: P) : ServerPacketEvent<P>(bot, packet),
Cancellable
\ No newline at end of file
class ServerPacketReceivedEvent<P : Packet>(bot: Bot, packet: P) : ServerPacketEvent<P>(bot, packet), Cancellable
\ No newline at end of file
......@@ -5,10 +5,10 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.EventLogger
import net.mamoe.mirai.event.EventScope
import net.mamoe.mirai.event.ListeningStatus
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.utils.internal.inlinedRemoveIf
import kotlin.jvm.JvmField
......@@ -23,7 +23,7 @@ import kotlin.reflect.KFunction
*
* @author Him188moe
*/
internal suspend fun <E : Event> KClass<E>.subscribeInternal(listener: Listener<E>): Unit = with(this.listeners()) {
internal suspend fun <E : Subscribable> KClass<E>.subscribeInternal(listener: Listener<E>): Unit = with(this.listeners()) {
if (mainMutex.tryLock(listener)) {//能锁则代表这个事件目前没有正在广播.
try {
add(listener)//直接修改主监听者列表
......@@ -60,13 +60,13 @@ internal suspend fun <E : Event> KClass<E>.subscribeInternal(listener: Listener<
*
* @author Him188moe
*/
sealed class Listener<in E : Event> {
sealed class Listener<in E : Subscribable> {
internal val lock = Mutex()
abstract suspend fun onEvent(event: E): ListeningStatus
}
@PublishedApi
internal class Handler<in E : Event>(@JvmField val handler: suspend (E) -> ListeningStatus) : Listener<E>() {
internal class Handler<in E : Subscribable>(@JvmField val handler: suspend (E) -> ListeningStatus) : Listener<E>() {
override suspend fun onEvent(event: E): ListeningStatus = handler.invoke(event)
}
......@@ -76,7 +76,7 @@ internal class Handler<in E : Event>(@JvmField val handler: suspend (E) -> Liste
* 所有的 [BotEvent.bot] `!==` `bot` 的事件都不会被处理
*/
@PublishedApi
internal class HandlerWithBot<E : Event>(val bot: Bot, @JvmField val handler: suspend Bot.(E) -> ListeningStatus) :
internal class HandlerWithBot<E : Subscribable>(val bot: Bot, @JvmField val handler: suspend Bot.(E) -> ListeningStatus) :
Listener<E>() {
override suspend fun onEvent(event: E): ListeningStatus = with(bot) {
if (event !is BotEvent || event.bot !== this) {
......@@ -94,9 +94,9 @@ internal class HandlerWithBot<E : Event>(val bot: Bot, @JvmField val handler: su
/**
* 这个事件类的监听器 list
*/
internal suspend fun <E : Event> KClass<out E>.listeners(): EventListeners<E> = EventListenerManger.get(this)
internal suspend fun <E : Subscribable> KClass<out E>.listeners(): EventListeners<E> = EventListenerManger.get(this)
internal class EventListeners<E : Event> : MutableList<Listener<E>> by mutableListOf() {
internal class EventListeners<E : Subscribable> : MutableList<Listener<E>> by mutableListOf() {
/**
* 主监听者列表.
* 广播事件时使用这个锁.
......@@ -125,11 +125,11 @@ internal class EventListeners<E : Event> : MutableList<Listener<E>> by mutableLi
* [EventListeners] 是 lazy 的: 它们只会在被需要的时候才创建和存储.
*/
internal object EventListenerManger {
private val registries: MutableMap<KClass<out Event>, EventListeners<*>> = mutableMapOf()
private val registries: MutableMap<KClass<out Subscribable>, EventListeners<*>> = mutableMapOf()
private val registriesMutex = Mutex()
@Suppress("UNCHECKED_CAST")
internal suspend fun <E : Event> get(clazz: KClass<out E>): EventListeners<E> = registriesMutex.withLock {
internal suspend fun <E : Subscribable> get(clazz: KClass<out E>): EventListeners<E> = registriesMutex.withLock {
if (registries.containsKey(clazz)) {
return registries[clazz] as EventListeners<E>
} else {
......@@ -141,7 +141,7 @@ internal object EventListenerManger {
}
}
internal suspend fun <E : Event> E.broadcastInternal(): E {
internal suspend fun <E : Subscribable> E.broadcastInternal(): E {
suspend fun callListeners(listeners: EventListeners<in E>) {
suspend fun callAndRemoveIfRequired() = listeners.inlinedRemoveIf {
if (it.lock.tryLock()) {
......@@ -177,12 +177,12 @@ internal suspend fun <E : Event> E.broadcastInternal(): E {
/**
* apply [block] to all the [EventListeners] in [clazz]'s superclasses
*/
private tailrec suspend fun <E : Event> applySuperListeners(
private tailrec suspend fun <E : Subscribable> applySuperListeners(
clazz: KClass<out E>,
block: suspend (EventListeners<in E>) -> Unit
) {
val superEventClass =
clazz.supertypes.map { it.classifier }.filterIsInstance<KClass<out Event>>().firstOrNull() ?: return
clazz.supertypes.map { it.classifier }.filterIsInstance<KClass<out Subscribable>>().firstOrNull() ?: return
@Suppress("UNCHECKED_CAST")
block(superEventClass.listeners() as EventListeners<in E>)
applySuperListeners(superEventClass, block)
......
......@@ -6,6 +6,7 @@ import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket
import net.mamoe.mirai.utils.ExternalImage
import kotlin.jvm.Volatile
// region Message Base
/**
......@@ -210,20 +211,24 @@ inline class Face(val id: FaceID) : Message {
* 构造无初始元素的可修改的 [MessageChain]. 初始大小将会被设定为 8
*/
@Suppress("FunctionName")
fun MessageChain(): MessageChain = MessageChainImpl(ArrayList(8))
fun MessageChain(): MessageChain = EmptyMessageChain()
/**
* 构造无初始元素的可修改的 [MessageChain]. 初始大小将会被设定为 [initialCapacity]
*/
@Suppress("FunctionName")
fun MessageChain(initialCapacity: Int): MessageChain = MessageChainImpl(ArrayList(initialCapacity))
fun MessageChain(initialCapacity: Int): MessageChain =
if (initialCapacity == 0) EmptyMessageChain()
else MessageChainImpl(ArrayList(initialCapacity))
/**
* 构造 [MessageChain]
* 若仅提供一个参数, 请考虑使用 [Message.toChain] 以优化性能
*/
@Suppress("FunctionName")
fun MessageChain(vararg messages: Message): MessageChain = MessageChainImpl(messages.toMutableList())
fun MessageChain(vararg messages: Message): MessageChain =
if (messages.isEmpty()) EmptyMessageChain()
else MessageChainImpl(messages.toMutableList())
/**
* 构造 [MessageChain]
......@@ -351,15 +356,17 @@ interface MessageChain : Message, MutableList<Message> {
/**
* 空的 [Message].
*
* 它不包含任何元素, 但维护一个 'lazy' 的 [delegate].
* 它不包含任何元素, 但维护一个 'lazy' 的 [MessageChainImpl].
*
* 只有在必要的时候才会创建 list, 如迭代([iterator]), 插入([add]), 连接([concat], [plus], [plusAssign])时.
* 只有在必要的时候(如迭代([iterator]), 插入([add]), 连接([concat], [plus], [plusAssign]))才会创建这个对象代表的 list
*
* 它是一个正常的 [Message] 和 [Message]. 可以做所有 [Message] 能做的事.
* 它是一个正常的 [Message] 和 [MessageChain]. 可以做所有 [Message] 能做的事.
*/
class EmptyMessageChain : MessageChain {
private val delegate: MessageChainImpl by lazy { MessageChainImpl() }
private inline val initialized: Boolean get() = (::delegate as Lazy<*>).isInitialized()
private val delegate: MessageChain by lazy { MessageChainImpl().also { initialized = true } }
@Volatile
private var initialized: Boolean = false
override fun subList(fromIndex: Int, toIndex: Int): MutableList<Message> =
if (initialized) delegate.subList(
......@@ -498,8 +505,9 @@ internal inline class MessageChainImpl constructor(
*/
private val delegate: MutableList<Message>
) : Message, MutableList<Message>, MessageChain {
constructor() : this(ArrayList(8))
//constructor() : this(ArrayList(8))
constructor(initialCapacity: Int) : this(ArrayList(initialCapacity))
constructor(vararg messages: Message) : this(messages.toMutableList())
constructor(messages: Iterable<Message>) : this(messages.toMutableList())
......
......@@ -168,6 +168,7 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
is Image -> buildPacket {
when (id.value.length) {
// "{F61593B5-5B98-1798-3F47-2A91D32ED2FC}.jpg"
42 -> {
writeUByte(MessageType.GROUP_IMAGE.value)
......@@ -188,6 +189,7 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
}
}
// "/01ee6426-5ff1-4cf0-8278-e8634d2909ef"
37 -> {
writeUByte(MessageType.FRIEND_IMAGE.value)
......@@ -225,8 +227,7 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
writeByte(0x02)
//"46 52 25 46 60 30 59 4F 4A 5A 51 48 31 46 4A 53 4C 51 4C 4A 33 46 31 2E 6A 70 67".hexToBytes().stringOfWitch()
// writeShortLVString(filename)//图片文件名 FR%F`0YOJZQH1FJSLQLJ3F1.jpg
require(id.value.length == 37) { "Illegal ImageId: ${id.value}" }
writeShortLVString(id.value.substring(1..24) + ".gif")//图片文件名. 后缀不影响. 但无后缀会导致 PC QQ 无法显示这个图片
writeShortLVString(id.value.substring(1..24) + ".gif")// 图片文件名. 后缀不影响. 但无后缀会导致 PC QQ 无法显示这个图片
writeHex("03 00 04 00 00 02 A2 04")
writeShortLVString(id.value)
writeHex("14 00 04 03 00 00 00 0B 00 00 18")
......@@ -234,10 +235,10 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
writeHex("19 00 04 00 00 00 4E 1A 00 04 00 00 00 23 FF 00 63 16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 20 ")
writeStringUtf8("674e")//没有 "e" 服务器就不回复
writeStringUtf8("674e")// 没有 "e" 服务器就不回复
writeStringUtf8(id.value.substring(1..id.value.lastIndex - 4))//这一串文件名决定手机 QQ 保存的图片. 可以随意
writeStringUtf8(".gif")//后缀要有
writeStringUtf8(".gif")// 后缀似乎必须要有
writeUByte(0x66u)
//有时候 PC QQ 看不到发这些消息, 但手机可以. 可能是 ID 过期了, 手机有缓存而电脑没有
......@@ -256,7 +257,7 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
writeUByte(0x41u)
}
}
else -> error("Illegal ImageId ${id.value}")
else -> error("Illegal ImageId: ${id.value}")
}
}
......
......@@ -8,13 +8,11 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.ListeningStatus
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.*
import net.mamoe.mirai.event.events.BeforePacketSendEvent
import net.mamoe.mirai.event.events.BotLoginSucceedEvent
import net.mamoe.mirai.event.events.PacketSentEvent
import net.mamoe.mirai.event.events.ServerPacketReceivedEvent
import net.mamoe.mirai.event.subscribe
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.handler.*
......@@ -33,7 +31,7 @@ import kotlin.properties.Delegates
*
* JVM: 独立的 4 thread 调度器
*/
expect val NetworkDispatcher: CoroutineDispatcher
internal expect val NetworkDispatcher: CoroutineDispatcher
/**
* [BotNetworkHandler] 的 TIM PC 协议实现
......@@ -75,7 +73,6 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
socket.resendTouch().takeIf { it != LoginResult.TIMEOUT }?.let { return@withContext it }
println()
bot.logger.warning("Timeout. Retrying next server")
socket.close()
......@@ -91,7 +88,6 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
require(size == 0) { "Already logged in" }
val session = BotSession(bot, sessionKey, socket)
add(EventPacketHandler(session).asNode(EventPacketHandler))
add(ActionPacketHandler(session).asNode(ActionPacketHandler))
bot.logger.info("Successfully logged in")
}
......@@ -144,6 +140,8 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
} catch (e: ReadPacketInternalException) {
bot.logger.error("Socket channel read failed: ${e.message}")
continue
} catch (e: CancellationException) {
return
} catch (e: Throwable) {
bot.logger.error("Caught unexpected exceptions", e)
continue
......@@ -154,6 +152,7 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
continue
}// sometimes exceptions are thrown without this `if` clause
}
//buffer.resetForRead()
launch {
// `.use`: Ensure that the packet is consumed **totally**
......@@ -164,9 +163,27 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
buffer.resetForWrite()
buffer.writeFully(it, 0, length)
}
ByteReadPacket(buffer, IoBuffer.Pool).use {
ByteReadPacket(buffer, IoBuffer.Pool).use { input ->
try {
processPacket(it)
input.discardExact(3)
val id = matchPacketId(input.readUShort())
val sequenceId = input.readUShort()
input.discardExact(7)//4 for qq number, 3 for 0x00 0x00 0x00
val packet = try {
with(id.factory) {
loginHandler.provideDecrypter(id.factory)
.decrypt(input)
.decode(id, sequenceId, this@TIMBotNetworkHandler)
}
} finally {
input.close()
}
handlePacket0(sequenceId, packet, id.factory)
} catch (e: Exception) {
bot.logger.error(e)
}
......@@ -205,25 +222,19 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
return receiving
}
private suspend inline fun processPacket(input: ByteReadPacket) = with(input) {
discardExact(3)
val id = PacketId(readUShort())
val sequenceId = readUShort()
discardExact(7)//4 for qq number, 3 for 0x00 0x00 0x00. 但更可能是应该 discard 8
val packet: Packet = with(id.factory) {
try {
loginHandler.provideDecrypter(id.factory)
.decrypt(input)
.decode(id, sequenceId, this@TIMBotNetworkHandler)
} finally {
input.close()
}
private suspend fun <TPacket : Packet> handlePacket0(
sequenceId: UShort,
packet: TPacket,
factory: PacketFactory<TPacket, *>
) {
if (!packet::class.annotations.filterIsInstance<NoLog>().any()) {
bot.logger.verbose("Packet received: $packet")
}
bot.logger.verbose("Packet received: $packet")
when (packet) {
is Cancellable -> if ((packet as Cancellable).broadcast(coroutineContext).cancelled) return
is Subscribable -> packet.broadcast(coroutineContext)
}
// Remove first to release the lock
handlersLock.withLock {
......@@ -236,6 +247,12 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
if (ServerPacketReceivedEvent(bot, packet).broadcast().cancelled)
return
if (factory is SessionPacketFactory<*>) {
with(factory as SessionPacketFactory<TPacket>) {
processPacket(packet)
}
}
// They should be called in sequence because packet is lock-free
loginHandler.onPacketReceived(packet)
this@TIMBotNetworkHandler.forEach {
......@@ -270,7 +287,7 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
it::class.annotations.filterIsInstance<NoLog>().any()
}
}?.let {
bot.logger.verbose("Packet sent: $it")
bot.logger.verbose("Packet sent: ${it::class.simpleName ?: "[OutgoingPacket]"}")
}
PacketSentEvent(bot, packet).broadcast()
......@@ -455,7 +472,7 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
//是ClientPasswordSubmissionPacket之后服务器回复的可能之一
is SubmitPasswordPacket.LoginResponse.KeyExchange -> {
this.privateKey = packet.privateKeyUpdate!!
this.privateKey = packet.privateKeyUpdate
socket.sendPacket(
SubmitPasswordPacket(
......
package net.mamoe.mirai.network.protocol.tim.handler
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.GroupId
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.groupId
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.packet.EventPacket
import net.mamoe.mirai.network.protocol.tim.packet.FriendStatusChanged
import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.SendFriendMessagePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.SendGroupMessagePacket
import net.mamoe.mirai.network.qqAccount
/**
* 处理消息事件, 承担消息发送任务.
*
* @author Him188moe
*/
@Suppress("EXPERIMENTAL_API_USAGE")
class EventPacketHandler(session: BotSession) : PacketHandler(session) {
companion object Key : PacketHandler.Key<EventPacketHandler>
override suspend fun onPacketReceived(packet: Packet): Unit = with(session) {
when (packet) {
is EventPacket.FriendMessage -> {
if (!packet.isPrevious) FriendMessageEvent(bot, bot.getQQ(packet.qq), packet.message) else null
}
is EventPacket.GroupMessage -> {
if (packet.qq == bot.account.id) return
GroupMessageEvent(
bot, bot.getGroup(GroupId(packet.groupNumber)), bot.getQQ(packet.qq), packet.message, packet.senderPermission, packet.senderName
)
}
is EventPacket.FriendConversationInitialize -> FriendConversationInitializedEvent(bot, bot.getQQ(packet.qq))
is FriendStatusChanged -> FriendOnlineStatusChangedEvent(bot, bot.getQQ(packet.qq), packet.status)
is FriendImageIdRequestPacket.Response -> packet.imageId?.let { FriendImageIdObtainedEvent(bot, it) }
is EventPacket.MemberPermissionChange ->
MemberPermissionChangedEvent(bot, bot.getGroup(packet.groupId.groupId()), bot.getQQ(packet.qq), packet.kind)
else -> null
}?.broadcast()
}
suspend fun sendFriendMessage(qq: QQ, message: MessageChain) {
session.socket.sendPacket(SendFriendMessagePacket(session.qqAccount, qq.id, session.sessionKey, message))
}
suspend fun sendGroupMessage(group: Group, message: MessageChain) {
session.socket.sendPacket(
SendGroupMessagePacket(
session.qqAccount,
group.internalId,
session.sessionKey,
message
)
)
}
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
package net.mamoe.mirai.network.protocol.tim.packet
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.Subscribable
import kotlin.reflect.KClass
......@@ -27,11 +27,11 @@ inline val AnnotatedId.value: UShort get() = id.value
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS)
annotation class CorrespondingEvent(
val eventClass: KClass<out Event>
val eventClass: KClass<out Subscribable>
)
/**
* 版本信息
* 包的最后一次修改时间, 和分析时使用的 TIM 版本
*/
@MustBeDocumented
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
......
......@@ -2,6 +2,7 @@ package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.utils.decryptBy
/**
......
......@@ -6,6 +6,7 @@ import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.use
import kotlinx.io.core.writeUShort
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeHex
......@@ -24,21 +25,19 @@ class OutgoingPacket(
private val name: String by lazy {
name ?: packetId.toString()
}
constructor(id: PacketId, sequenceId: UShort, delegate: ByteReadPacket) : this(null, id, sequenceId, delegate)
constructor(annotation: AnnotatedId, sequenceId: UShort, delegate: ByteReadPacket) :
this(annotation.toString(), annotation.id, sequenceId, delegate)
override fun toString(): String = packetToString(packetId.value, sequenceId, name)
}
/**
* 登录完成建立 session 之后发出的包.
* 均使用 sessionKey 加密
*
* @param TPacket invariant
*/
abstract class SessionPacketFactory<out TPacket : Packet> : PacketFactory<TPacket, SessionKey>(SessionKey) {
final override fun decrypt(input: ByteReadPacket, decrypter: SessionKey): ByteReadPacket = decrypter.decrypt(input)
abstract class SessionPacketFactory<TPacket : Packet> : PacketFactory<TPacket, SessionKey>(SessionKey) {
/**
* 在 [BotNetworkHandler] 下处理这个包. 广播事件等.
*/
open suspend fun BotNetworkHandler<*>.processPacket(packet: TPacket) {}
}
/**
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.atomicfu.atomic
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toUHexString
object PacketFactoryList : MutableList<PacketFactory<*, *>> by mutableListOf()
/**
* 一种数据包的处理工厂. 它可以解密解码服务器发来的这个包, 也可以编码加密要发送给服务器的这个包
* 应由一个 `object` 实现, 且实现 `operator fun invoke`
*
* @param TPacket 服务器回复包解析结果
* @param TDecrypter 服务器回复包解密器
*/
abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypter>(internal val decrypterType: DecrypterType<TDecrypter>) {
/**
* 2 Ubyte.
* 读取注解 [AnnotatedId]
*/
private val annotatedId: AnnotatedId
get() = (this::class.annotations.firstOrNull { it is AnnotatedId } as? AnnotatedId)
?: error("Annotation AnnotatedId not found")
/**
* 包 ID.
*/
open val id: PacketId by lazy { annotatedId.id }
init {
@Suppress("LeakingThis")
PacketFactoryList.add(this)
}
/**
* **解码**服务器的回复数据包
*/
abstract suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): TPacket
companion object {
private val sequenceIdInternal = atomic(1)
@PublishedApi
internal fun atomicNextSequenceId(): UShort = sequenceIdInternal.getAndIncrement().toUShort()
}
}
object UnknownPacketFactory : SessionPacketFactory<UnknownPacket>() {
override suspend fun BotNetworkHandler<*>.processPacket(packet: UnknownPacket) {
ByteArrayPool.useInstance {
packet.body.readAvailable(it)
bot.logger.debug("Unknown packet(${packet.id.value}) body = " + it.toUHexString())
}
packet.body.close()
}
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): UnknownPacket {
return UnknownPacket(id, this)
}
}
object IgnoredPacketFactory : SessionPacketFactory<IgnoredPacket>() {
override suspend fun BotNetworkHandler<*>.processPacket(packet: IgnoredPacket) {
}
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): IgnoredPacket = IgnoredPacket
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet
import net.mamoe.mirai.network.protocol.tim.packet.action.*
import net.mamoe.mirai.network.protocol.tim.packet.event.EventPacketFactory
import net.mamoe.mirai.network.protocol.tim.packet.event.FriendOnlineStatusChangedPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.*
/**
* 通过 [value] 匹配一个 [IgnoredPacketId] 或 [KnownPacketId], 无匹配则返回一个 [UnknownPacketId].
*/
fun matchPacketId(value: UShort): PacketId =
IgnoredPacketIds.firstOrNull { it.value == value } ?: KnownPacketId.values().firstOrNull { it.value == value } ?: UnknownPacketId(value)
/**
* 包 ID.
*/
interface PacketId {
val value: UShort
val factory: PacketFactory<*, *>
}
/**
* 用于代表 `null`. 调用任何属性时都将会得到一个 [error]
*/
@Suppress("unused")
object NullPacketId : PacketId {
override val factory: PacketFactory<*, *> get() = error("uninitialized")
override val value: UShort get() = error("uninitialized")
}
/**
* 未知的 [PacketId]
*/
inline class UnknownPacketId(override inline val value: UShort) : PacketId {
override val factory: PacketFactory<*, *> get() = UnknownPacketFactory
}
object IgnoredPacketIds : List<IgnoredPacketId> by {
listOf<UShort>(
).map { IgnoredPacketId(it.toUShort()) }
}()
inline class IgnoredPacketId constructor(override val value: UShort) : PacketId {
override val factory: PacketFactory<*, *> get() = IgnoredPacketFactory
}
/**
* 已知的 [matchPacketId]. 所有在 Mirai 中实现过的包都会使用这些 Id
*/
@Suppress("unused")
enum class KnownPacketId(override inline val value: UShort, override inline val factory: PacketFactory<*, *>) :
PacketId {
inline TOUCH(0x0825u, TouchPacket),
inline SESSION_KEY(0x0828u, RequestSessionPacket),
inline LOGIN(0x0836u, SubmitPasswordPacket),
inline CAPTCHA(0x00BAu, CaptchaPacket),
inline SERVER_EVENT_1(0x00CEu, EventPacketFactory),
inline SERVER_EVENT_2(0x0017u, EventPacketFactory),
inline FRIEND_ONLINE_STATUS_CHANGE(0x0081u, FriendOnlineStatusChangedPacket),
inline CHANGE_ONLINE_STATUS(0x00ECu, ChangeOnlineStatusPacket),
inline HEARTBEAT(0x0058u, HeartbeatPacket),
inline S_KEY(0x001Du, RequestSKeyPacket),
inline ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket),
inline SEND_GROUP_MESSAGE(0x0002u, SendGroupMessagePacket),
inline SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket),
inline CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket),
inline GROUP_IMAGE_ID(0x0388u, GroupImageIdRequestPacket),
inline FRIEND_IMAGE_ID(0x0352u, FriendImageIdRequestPacket),
inline REQUEST_PROFILE_AVATAR(0x0031u, RequestProfilePicturePacket),
inline REQUEST_PROFILE_DETAILS(0x003Cu, RequestProfilePicturePacket),
// @Suppress("DEPRECATION")
// inline SUBMIT_IMAGE_FILE_NAME(0x01BDu, SubmitImageFilenamePacket),
;
override fun toString(): String = factory.let { it::class.simpleName } ?: this.name
}
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.decryptBy
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.hexToBytes
fun ByteReadPacket.decryptBy(key: ByteArray): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
fun ByteReadPacket.decryptBy(key: IoBuffer): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
fun ByteReadPacket.decryptBy(keyHex: String): ByteReadPacket = decryptBy(keyHex.hexToBytes())
inline fun <R> ByteReadPacket.decryptAsByteArray(key: ByteArray, consumer: (ByteArray) -> R): R =
ByteArrayPool.useInstance {
val length = remaining.toInt()
readFully(it, 0, length)
consumer(it.decryptBy(key, length))
}.also { close() }
inline fun <R> ByteReadPacket.decryptAsByteArray(keyHex: String, consumer: (ByteArray) -> R): R =
this.decryptAsByteArray(keyHex.hexToBytes(), consumer)
inline fun <R> ByteReadPacket.decryptAsByteArray(key: IoBuffer, consumer: (ByteArray) -> R): R =
ByteArrayPool.useInstance {
val length = remaining.toInt()
readFully(it, 0, length)
consumer(it.decryptBy(key, length))
}.also { close() }
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
/*
ID: 00 17
长度 95
76 E4 B8 DD //1994701021
76 E4 B8 DD //1994701021
00 0B B9 A9 09 90 BB 54 1F //类似Event的uniqueId?
40 02
10 00 00 00
18 00
08 00
02 00
01 00
09 00
06 41 4B DA 4C 00 00
00 0A
00 04
01 00 00 00 00 00
00 06 00 00
00 0E
08 02
1A 02 08 49 0A 0C 08 A2 FF 8C F0
03 10 CA EB 8B ED 05
或者
长度63
00 00 27 10 76 E4 B8 DD
00 09 ED 26 64 73 0E CA 1F 40
00 12 00 00
00 08
00 0A
00 04
01 00 00
00 02
值都是一样的.
*/
......@@ -162,6 +162,11 @@ object AddFriendPacket : SessionPacketFactory<AddFriendPacket.AddFriendResponse>
}
}
override suspend fun BotNetworkHandler<*>.processPacket(packet: AddFriendResponse) {
}
class AddFriendResponse : Packet
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
import net.mamoe.mirai.utils.io.readBoolean
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
data class AndroidDeviceStatusChangePacket(val kind: Kind) : Packet {
enum class Kind {
ONLINE,
OFFLINE
}
}
/**
* Android 客户端在线状态改变
*/
@PacketVersion(date = "2019.10.31", timVersion = "2.3.2.21173")
object AndroidDeviceOnlineStatusChangedEventFactory : KnownEventParserAndHandler<AndroidDeviceStatusChangePacket>(0x00C4u) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): AndroidDeviceStatusChangePacket {
discardExact(13)
return AndroidDeviceStatusChangePacket(
if (readBoolean()) AndroidDeviceStatusChangePacket.Kind.OFFLINE else AndroidDeviceStatusChangePacket.Kind.ONLINE
)
}
}
package net.mamoe.mirai.network.protocol.tim.packet.event
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.network.protocol.tim.packet.Packet
interface EventPacket : Subscribable, Packet
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.sessionKey
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.readIoBuffer
import net.mamoe.mirai.utils.io.writeHex
import net.mamoe.mirai.utils.io.writeQQ
/**
* 事件的识别 ID. 在 ACK 时使用
*/
class EventPacketIdentity(
val from: UInt,//对于好友消息, 这个是发送人
val to: UInt,//对于好友消息, 这个是bot
internal val uniqueId: IoBuffer//8
) {
override fun toString(): String = "($from->$to)"
}
fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = with(identity) {
writeUInt(from)
writeUInt(to)
writeFully(uniqueId)
}
@Suppress("FunctionName")
fun matchEventPacketFactory(value: UShort): EventParserAndHandler<*> =
KnownEventParserAndHandler.firstOrNull { it.id == value } ?: IgnoredEventIds.firstOrNull { it.id == value } ?: UnknownEventParserAndHandler(value)
/**
* 事件包, 它将会分析事件 ID 并解析事件为 [Packet]
*/
@Suppress("FunctionName")
object EventPacketFactory : PacketFactory<Packet, SessionKey>(SessionKey) {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Packet {
val eventIdentity = EventPacketIdentity(
from = readUInt(),
to = readUInt(),
uniqueId = readIoBuffer(8)
)
handler.sendPacket(EventPacketFactory(id, sequenceId, handler.bot.qqAccount, handler.sessionKey, eventIdentity))
discardExact(2)
return with(matchEventPacketFactory(readUShort())) { parse(handler.bot, eventIdentity) }.also {
if (it is MessageEventPacket<*>) {
it.botVar = handler.bot
}
if (it is EventParserAndHandler<*>) {
@Suppress("UNCHECKED_CAST")
with(it as EventParserAndHandler<in Packet>) {
with(handler) {
handlePacket(it)
}
}
}
}
}
operator fun invoke(
id: PacketId,
sequenceId: UShort,
bot: UInt,
sessionKey: SessionKey,
identity: EventPacketIdentity
): OutgoingPacket = buildOutgoingPacket(name = "EventPacket", id = id, sequenceId = sequenceId) {
writeQQ(bot)
writeHex(TIMProtocol.fixVer2)
encryptAndWrite(sessionKey) {
writeEventPacketIdentity(identity)
}
}
}
interface EventParserAndHandler<TPacket : Packet> {
val id: UShort
suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): TPacket
/**
* 在 [BotNetworkHandler] 下处理这个包. 广播事件等.
*/
suspend fun BotNetworkHandler<*>.handlePacket(packet: TPacket) {}
}
abstract class KnownEventParserAndHandler<TPacket : Packet>(override val id: UShort) : EventParserAndHandler<TPacket> {
companion object FactoryList : MutableList<KnownEventParserAndHandler<*>> by mutableListOf(
AndroidDeviceOnlineStatusChangedEventFactory,
FriendConversationInitializedEventParserAndHandler,
GroupFileUploadEventFactory,
GroupMemberPermissionChangedEventFactory,
GroupMessageEventParserAndHandler,
FriendMessageEventParserAndHandler
)
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
data class FriendConversationInitialize(
val qq: UInt
) : EventPacket
object FriendConversationInitializedEventParserAndHandler : KnownEventParserAndHandler<FriendConversationInitialize>(0x0079u) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): FriendConversationInitialize {
discardExact(4)// 00 00 00 00
return FriendConversationInitialize(readUInt())
}
}
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "JoinDeclarationAndAssignment")
package net.mamoe.mirai.network.protocol.tim.packet
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt
import net.mamoe.mirai.event.events.FriendOnlineStatusChangedEvent
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.AnnotatedId
import net.mamoe.mirai.network.protocol.tim.packet.KnownPacketId
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.SessionPacketFactory
import net.mamoe.mirai.utils.OnlineStatus
@CorrespondingEvent(FriendOnlineStatusChangedEvent::class)
abstract class FriendStatusChanged : Packet {
abstract val qq: UInt
abstract val status: OnlineStatus
}
data class FriendStatusChanged(
val qq: QQ,
val status: OnlineStatus
) : EventPacket
/**
* 好友在线状态改变
......@@ -22,17 +26,12 @@ abstract class FriendStatusChanged : Packet {
@AnnotatedId(KnownPacketId.FRIEND_ONLINE_STATUS_CHANGE)
object FriendOnlineStatusChangedPacket : SessionPacketFactory<FriendStatusChanged>() {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged =
object : FriendStatusChanged() {
override val qq: UInt
override val status: OnlineStatus
init {
qq = readUInt()
discardExact(8)
val id = readUByte()
status = OnlineStatus.ofId(id) ?: error("Unknown online status id $id")
}
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged {
val qq = readUInt()
discardExact(8)
val statusId = readUByte()
val status = OnlineStatus.ofId(statusId) ?: error("Unknown online status id $statusId")
return FriendStatusChanged(handler.bot.getQQ(qq), status)
}
//在线 XX XX XX XX 01 00 00 00 00 00 00 00 0A 15 E3 10 00 01 2E 01 00 00 00 00 00 00 00 00 00 00 00 13 08 02 C2 76 E4 B8 DD 00 00 00 00 00 00 00 00 00 00 00
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
import net.mamoe.mirai.utils.io.readString
data class GroupFileUploadPacket(inline val xmlMessage: String) : EventPacket
@PacketVersion(date = "2019.7.1", timVersion = "2.3.2.21173")
object GroupFileUploadEventFactory : KnownEventParserAndHandler<GroupFileUploadPacket>(0x002Du) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): GroupFileUploadPacket {
discardExact(60)
val size = readShort().toInt()
discardExact(3)
return GroupFileUploadPacket(xmlMessage = readString(size))
}
}
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.Bot
object IgnoredEventPacket : EventPacket
object IgnoredEventIds : List<IgnoredEventParserAndHandler> by {
listOf(
0x0021u
).map { IgnoredEventParserAndHandler(it.toUShort()) }
}()
inline class IgnoredEventParserAndHandler(override val id: UShort) : EventParserAndHandler<IgnoredEventPacket> {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): IgnoredEventPacket = IgnoredEventPacket
}
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
data class MemberPermissionChangePacket(
val groupId: UInt,
val qq: UInt,
val kind: Kind
) : Packet {
enum class Kind {
/**
* 变成管理员
*/
BECOME_OPERATOR,
/**
* 不再是管理员
*/
NO_LONGER_OPERATOR,
} // TODO: 2019/11/2 变成群主的情况
}
@PacketVersion(date = "2019.11.1", timVersion = "2.3.2.21173")
object GroupMemberPermissionChangedEventFactory : KnownEventParserAndHandler<MemberPermissionChangePacket>(0x002Cu) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): MemberPermissionChangePacket {
// 群里一个人变成管理员:
// 00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 01 76 E4 B8 DD 01
// 取消管理员
// 00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 00 76 E4 B8 DD 00
discardExact(remaining - 5)
val qq = readUInt()
val kind = when (readByte().toInt()) {
0x00 -> MemberPermissionChangePacket.Kind.NO_LONGER_OPERATOR
0x01 -> MemberPermissionChangePacket.Kind.BECOME_OPERATOR
else -> error("Could not determine permission change kind")
}
return MemberPermissionChangePacket(identity.from, qq, kind)
}
}
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.message.*
import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.io.printTLVMap
import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readTLVMap
import net.mamoe.mirai.utils.io.readUShortLVByteArray
import net.mamoe.mirai.utils.sendTo
import net.mamoe.mirai.utils.upload
sealed class MessageEventPacket<TSubject : Contact> : EventPacket, BotEvent() {
internal lateinit var botVar: Bot
override val bot: Bot get() = botVar
/**
* 消息事件主体.
*
* 对于好友消息, 这个属性为 [QQ] 的实例;
* 对于群消息, 这个属性为 [Group] 的实例
*
* 在回复消息时, 可通过 [subject] 作为回复对象
*/
abstract val subject: TSubject
/**
* 发送人
*/
abstract val sender: QQ
abstract val message: MessageChain
// region Send to subject
/**
* 给这个消息事件的主体发送消息
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/
suspend inline fun reply(message: MessageChain) = subject.sendMessage(message)
suspend fun reply(message: Message) = subject.sendMessage(message.singleChain())
suspend fun reply(plain: String) = subject.sendMessage(plain.toMessage())
suspend inline fun ExternalImage.send() = this.sendTo(subject)
suspend inline fun ExternalImage.upload(): Image = this.upload(subject)
suspend inline fun Image.send() = this.sendTo(subject)
suspend inline fun ImageId.send() = this.sendTo(subject)
suspend inline fun Message.send() = this.sendTo(subject)
suspend inline fun String.send() = this.toMessage().sendTo(subject)
// endregion
}
// region group message
data class GroupMessage(
val group: Group,
val senderName: String,
/**
* 发送方权限.
*/
val permission: MemberPermission,
override val sender: QQ,
override val message: MessageChain = NullMessageChain
) : MessageEventPacket<Group>() {
override val subject: Group get() = group
}
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
object GroupMessageEventParserAndHandler : KnownEventParserAndHandler<GroupMessage>(0x0052u) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): GroupMessage {
discardExact(31)
val groupNumber = readUInt()
discardExact(1)
val qq = readUInt()
discardExact(48)
readUShortLVByteArray()
discardExact(2)//2个0x00
val message = readMessageChain()
var senderPermission: MemberPermission = MemberPermission.MEMBER
var senderName = ""
val map = readTLVMap(true)
if (map.containsKey(18u)) {
map.getValue(18u).read {
val tlv = readTLVMap(true)
senderPermission = when (tlv.takeIf { it.containsKey(0x04u) }?.get(0x04u)?.getOrNull(3)?.toUInt()) {
null -> MemberPermission.MEMBER
0x08u -> MemberPermission.OWNER
0x10u -> MemberPermission.OPERATOR
else -> {
tlv.printTLVMap("TLV(tag=18) Map")
MiraiLogger.warning("Could not determine member permission, default permission MEMBER is being used")
MemberPermission.MEMBER
}
}
senderName = when {
tlv.containsKey(0x01u) -> kotlinx.io.core.String(tlv.getValue(0x01u))//这个人的qq昵称
tlv.containsKey(0x02u) -> kotlinx.io.core.String(tlv.getValue(0x02u))//这个人的群名片
else -> {
tlv.printTLVMap("TLV(tag=18) Map")
MiraiLogger.warning("Could not determine senderName")
"null"
}
}
}
}
return GroupMessage(bot.getGroup(groupNumber), senderName, senderPermission, bot.getQQ(qq), message)
}
}
// endregion
// region friend message
data class FriendMessage(
/**
* 是否是在这次登录之前的消息, 即消息记录
*/
val isPrevious: Boolean,
override val sender: QQ,
override val message: MessageChain
) : MessageEventPacket<QQ>() {
override val subject: QQ get() = sender
}
@Suppress("unused")
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
object FriendMessageEventParserAndHandler : KnownEventParserAndHandler<FriendMessage>(0x00A6u) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): FriendMessage {
discardExact(2)
val l1 = readShort()
discardExact(1)//0x00
val previous = readByte().toInt() == 0x08
discardExact(l1.toInt() - 2)
//java.io.EOFException: Only 49 bytes were discarded of 69 requested
//抖动窗口消息
discardExact(69)
readUShortLVByteArray()//font
discardExact(2)//2个0x00
val message = readMessageChain()
return FriendMessage(
isPrevious = previous,
sender = bot.getQQ(identity.from),
message = message
)
}
}
// endregion
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.*
import net.mamoe.mirai.event.events.MemberPermissionChangedEvent
import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.sessionKey
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.io.*
/**
* 事件的识别 ID. 在 [事件确认包][ServerEventPacket.EventResponse] 中被使用.
*/
class EventPacketIdentity(
val from: UInt,//对于好友消息, 这个是发送人
val to: UInt,//对于好友消息, 这个是bot
internal val uniqueId: IoBuffer//8
) {
override fun toString(): String = "($from->$to)"
}
enum class SenderPermission {
OWNER,
OPERATOR,
MEMBER;
}
object IgnoredEventPacket : Packet
class UnknownEventPacket(
val id: UnknownEventId
// TODO: 2019/11/5 补充包数据 , 用于输出
) : Packet
/**
* 事件包, 它将会分析 [事件ID][KnownEventId] 并解析事件为 [Packet]
*/
@NoLog
@Suppress("FunctionName")
object EventPacketFactory : PacketFactory<Packet, SessionKey>(SessionKey) {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Packet {
val eventIdentity = EventPacketIdentity(
from = readUInt(),
to = readUInt(),
uniqueId = readIoBuffer(8)
)
handler.sendPacket(EventPacketFactory(id, sequenceId, handler.bot.qqAccount, handler.sessionKey, eventIdentity))
discardExact(2)
return when (val type = EventId(readUShort())) {
is KnownEventId -> type.parser(this, eventIdentity)
is UnknownEventId -> UnknownEventPacket(type)
is IgnoredEventId -> IgnoredEventPacket
else -> throw AssertionError("Unknown EventId type")
}
}
operator fun invoke(
id: PacketId,
sequenceId: UShort,
bot: UInt,
sessionKey: SessionKey,
identity: EventPacketIdentity
): OutgoingPacket = buildOutgoingPacket(name = "EventPacket", id = id, sequenceId = sequenceId) {
writeQQ(bot)
writeHex(TIMProtocol.fixVer2)
encryptAndWrite(sessionKey) {
writeEventPacketIdentity(identity)
}
}
}
fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = with(identity) {
writeUInt(from)
writeUInt(to)
writeFully(uniqueId)
}
typealias EventPacketParser = ByteReadPacket.(EventPacketIdentity) -> Packet
interface EventId {
val value: UShort
val parser: EventPacketParser?
}
// TODO: 2019/11/5 整理文件
@Suppress("FunctionName")
fun EventId(value: UShort): EventId =
KnownEventId.ofValueOrNull(value) ?: IgnoredEventIds.firstOrNull { it.value == value } ?: UnknownEventId(value)
object IgnoredEventIds : List<IgnoredEventId> by {
listOf(
0x0021u
).map { IgnoredEventId(it.toUShort()) }
}()
inline class IgnoredEventId(override val value: UShort) : EventId {
override val parser: EventPacketParser get() = { IgnoredPacket }
}
inline class UnknownEventId(override val value: UShort) : EventId {
override val parser: EventPacketParser
get() = {
MiraiLogger.debug("UnknownEventPacket type = ${value.toUHexString()}")
MiraiLogger.debug("UnknownEventPacket data = ${readBytes().toUHexString()}")
UnknownEventPacket(UnknownEventId(value))
}
}
/**
*
* @param parser 解析器. 解析 [数据包][ByteReadPacket] 为 [Packet]
*/
@Suppress("unused")
enum class KnownEventId(override inline val value: UShort, override val parser: EventPacketParser) : EventId {
/**
* Android 客户端在线状态改变
*/
ANDROID_DEVICE_ONLINE_STATUS_CHANGE(0x00C4u, {
discardExact(13)
EventPacket.AndroidDeviceStatusChange(
if (readBoolean()) EventPacket.AndroidDeviceStatusChange.Kind.OFFLINE else EventPacket.AndroidDeviceStatusChange.Kind.ONLINE
)
}),
GROUP_FILE_UPLOAD(0x002Du, {
discardExact(60)
val size = readShort().toInt()
discardExact(3)
EventPacket.GroupFileUpload(xmlMessage = readString(size))
}),
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
GROUP_MEMBER_PERMISSION_CHANGE(0x002Cu, {
// 群里一个人变成管理员:
// 00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 01 76 E4 B8 DD 01
// 取消管理员
// 00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 00 76 E4 B8 DD 00
discardExact(remaining - 5)
EventPacket.MemberPermissionChange().apply {
groupId = it.from
qq = readUInt()
kind = when (readByte().toInt()) {
0x00 -> MemberPermissionChangedEvent.Kind.NO_LONGER_OPERATOR
0x01 -> MemberPermissionChangedEvent.Kind.BECOME_OPERATOR
else -> error("Could not determine permission change kind")
}
}
}),
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
GROUP_MESSAGE(0x0052u, {
EventPacket.GroupMessage().apply {
discardExact(31)
groupNumber = readUInt()
discardExact(1)
qq = readUInt()
discardExact(48)
readUShortLVByteArray()
discardExact(2)//2个0x00
message = readMessageChain()
val map = readTLVMap(true)
if (map.containsKey(18u)) {
map.getValue(18u).read {
val tlv = readTLVMap(true)
senderPermission = when (tlv.takeIf { it.containsKey(0x04u) }?.get(0x04u)?.getOrNull(3)?.toUInt()) {
null -> SenderPermission.MEMBER
0x08u -> SenderPermission.OWNER
0x10u -> SenderPermission.OPERATOR
else -> {
tlv.printTLVMap("TLV(tag=18) Map")
MiraiLogger.warning("Could not determine member permission, default permission MEMBER is being used")
SenderPermission.MEMBER
}
}
senderName = when {
tlv.containsKey(0x01u) -> String(tlv.getValue(0x01u))//这个人的qq昵称
tlv.containsKey(0x02u) -> String(tlv.getValue(0x02u))//这个人的群名片
else -> {
tlv.printTLVMap("TLV(tag=18) Map")
MiraiLogger.warning("Could not determine senderName")
"null"
}
}
}
}
}
}),
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
FRIEND_MESSAGE(0x00A6u, {
discardExact(2)
val l1 = readShort()
discardExact(1)//0x00
val previous = readByte().toInt() == 0x08
discardExact(l1.toInt() - 2)
//java.io.EOFException: Only 49 bytes were discarded of 69 requested
//抖动窗口消息
discardExact(69)
readUShortLVByteArray()//font
discardExact(2)//2个0x00
EventPacket.FriendMessage(
isPrevious = previous,
qq = it.from,
message = readMessageChain()
)
}),
FRIEND_CONVERSATION_INITIALIZE(0x0079u, {
discardExact(4)// 00 00 00 00
EventPacket.FriendConversationInitialize().apply {
qq = readUInt()
}
}),
;
companion object {
fun ofValueOrNull(value: UShort): KnownEventId? = values().firstOrNull { it.value == value }
}
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toUHexString
data class UnknownEventPacket(
val id: UShort,
val body: ByteReadPacket
) : EventPacket
//TODO This class should be declared with `inline`, but a CompilationException will be thrown
class UnknownEventParserAndHandler(override val id: UShort) : EventParserAndHandler<UnknownEventPacket> {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): UnknownEventPacket {
MiraiLogger.debug("UnknownEventPacket type = ${id.toUHexString()}")
MiraiLogger.debug("UnknownEventPacket data = ${readBytes().toUHexString()}")
return UnknownEventPacket(id, this) //TODO the cause is that `this` reference.
}
override suspend fun BotNetworkHandler<*>.handlePacket(packet: UnknownEventPacket) {
ByteArrayPool.useInstance {
packet.body.readAvailable(it)
bot.logger.debug("Unknown packet(${packet.id}) data = " + it.toUHexString())
}
}
}
......@@ -110,7 +110,7 @@ object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, CaptchaKey>(
}
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): CaptchaResponse =
when (val id = readByte().toUInt()) {
when (val flag = readByte().toUInt()) {
0x14u -> {//00 05 00 00 00 00 00 00 38
CaptchaResponse.Correct().apply {
discardExact(9)
......@@ -134,7 +134,7 @@ object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, CaptchaKey>(
}
}
else -> error("Unable to analyze RequestCaptchaTransmissionPacket, unknown id: $id")
else -> error("Unable to analyze RequestCaptchaTransmissionPacket, unknown id: $flag")
}
}
/*
......
......@@ -31,7 +31,9 @@ object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacket.ChangeO
}
}
object ChangeOnlineStatusResponse : Packet
object ChangeOnlineStatusResponse : Packet {
override fun toString(): String = this::class.simpleName!!
}
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): ChangeOnlineStatusResponse =
ChangeOnlineStatusResponse
......
......@@ -12,7 +12,6 @@ import net.mamoe.mirai.utils.decryptBy
import net.mamoe.mirai.utils.encryptBy
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.writeCRC32
import kotlin.properties.Delegates
object ShareKey : DecrypterByteArray, DecrypterType<ShareKey> {
override val value: ByteArray = TIMProtocol.shareKey.hexToBytes(withCache = false)
......@@ -27,9 +26,14 @@ inline class SubmitPasswordResponseDecrypter(private val privateKey: PrivateKey)
var decrypted = ShareKey.decrypt(packet)
(decrypted.remaining).let {
if (it.toInt() % 8 == 0 && it >= 16) {
decrypted = privateKey.decrypt(decrypted)
decrypted = try {
privateKey.decrypt(decrypted)
} catch (e: Exception) {
// 某些情况不需要这次解密
decrypted
}
}
} // TODO: 2019/11/5 优化: 某些情况下并不需要这次解密. 根据长度判断会导致一些问题
}
return decrypted
}
......@@ -59,7 +63,7 @@ object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse,
writeZero(2)
writeShort(16); writeHex(TIMProtocol.key0836)//=16
//TODO shareKey 极大可能为 publicKey, key0836 计算得到
// shareKey 极大可能为 publicKey, key0836 计算得到
encryptAndWrite(TIMProtocol.shareKey) {
writePart1(bot, password, loginTime, loginIP, privateKey, token0825, randomDeviceName, tlv0006)
if (token00BA != null) {
......@@ -73,71 +77,112 @@ object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse,
}
sealed class LoginResponse : Packet {
class KeyExchange : LoginResponse() {
lateinit var tlv0006: IoBuffer//120bytes
var tokenUnknown: ByteArray? = null
data class KeyExchange(
val tlv0006: IoBuffer,//120bytes
val tokenUnknown: ByteArray?,
val privateKeyUpdate: PrivateKey//16bytes
) : LoginResponse() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is KeyExchange) return false
if (tlv0006 != other.tlv0006) return false
if (tokenUnknown != null) {
if (other.tokenUnknown == null) return false
if (!tokenUnknown.contentEquals(other.tokenUnknown)) return false
} else if (other.tokenUnknown != null) return false
if (privateKeyUpdate != other.privateKeyUpdate) return false
return true
}
var privateKeyUpdate: PrivateKey? = null//16bytes
override fun hashCode(): Int {
var result = tlv0006.hashCode()
result = 31 * result + (tokenUnknown?.contentHashCode() ?: 0)
result = 31 * result + privateKeyUpdate.hashCode()
return result
}
}
class CaptchaInit : LoginResponse() {
lateinit var captchaPart1: IoBuffer
lateinit var token00BA: ByteArray
var unknownBoolean: Boolean by Delegates.notNull()
}
data class CaptchaInit(
val captchaPart1: IoBuffer,
val token00BA: ByteArray,
val unknownBoolean: Boolean
) : LoginResponse() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is CaptchaInit) return false
class Success : LoginResponse() {
var sessionResponseDecryptionKey: SessionResponseDecryptionKey by Delegates.notNull()//16 bytes|
if (captchaPart1 != other.captchaPart1) return false
if (!token00BA.contentEquals(other.token00BA)) return false
if (unknownBoolean != other.unknownBoolean) return false
lateinit var token38: IoBuffer//56
lateinit var token88: IoBuffer//136
lateinit var encryptionKey: IoBuffer//16
return true
}
lateinit var nickname: String
var age: Short by Delegates.notNull()
lateinit var gender: Gender
override fun hashCode(): Int {
var result = captchaPart1.hashCode()
result = 31 * result + token00BA.contentHashCode()
result = 31 * result + unknownBoolean.hashCode()
return result
}
}
class Failed(val result: LoginResult) : LoginResponse()
data class Success(
val sessionResponseDecryptionKey: SessionResponseDecryptionKey,
val token38: IoBuffer,//56
val token88: IoBuffer,//136
val encryptionKey: IoBuffer,//16
val nickname: String,
val age: Short,
val gender: Gender
) : LoginResponse()
data class Failed(val result: LoginResult) : LoginResponse()
}
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): LoginResponse {
val size = remaining.toInt()
return when {
size == 229 || size == 271 || size == 207 -> LoginResponse.KeyExchange().apply {
size == 229 || size == 271 || size == 207 -> {
discardExact(5)//01 00 1E 00 10
privateKeyUpdate = PrivateKey(readBytes(0x10))
val privateKeyUpdate = PrivateKey(readBytes(0x10))
discardExact(4)//00 06 00 78
tlv0006 = readIoBuffer(0x78)
val tlv0006 = readIoBuffer(0x78)
try {
return try {
discardExact(8)//01 10 00 3C 00 01 00 38
tokenUnknown = readBytes(56)
LoginResponse.KeyExchange(tlv0006, readBytes(56), privateKeyUpdate)
} catch (e: EOFException) {
//什么都不做. 因为有的包就是没有这个数据.
LoginResponse.KeyExchange(tlv0006, null, privateKeyUpdate)
}
}
size == 844 || size == 871 -> LoginResponse.CaptchaInit().apply {
size == 844 || size == 871 -> {
discardExact(78)
//println(readRemainingBytes().toUHexString())
val captchaLength = readShort()//2bytes
this.captchaPart1 = readIoBuffer(captchaLength)
val captchaPart1 = readIoBuffer(captchaLength)
discardExact(1)
this.unknownBoolean = readByte().toInt() == 1
val unknownBoolean = readByte().toInt() == 1
discardExact(remaining - 60)
this.token00BA = readBytes(40)
return LoginResponse.CaptchaInit(captchaPart1, readBytes(40), unknownBoolean)
}
size > 650 -> LoginResponse.Success().apply {
size > 650 -> {
discardExact(7)//00 01 09 00 70 00 01
//FB 01 04 03 33
encryptionKey = readIoBuffer(16)//C6 72 C7 73 70 01 46 A2 11 88 AC E4 92 7B BF 90
val encryptionKey = readIoBuffer(16)//C6 72 C7 73 70 01 46 A2 11 88 AC E4 92 7B BF 90
discardExact(2)//00 38
token38 = readIoBuffer(56)
val token38 = readIoBuffer(56)
discardExact(60)//00 20 01 60 C5 A1 39 7A 12 8E BC 34 C3 56 70 E3 1A ED 20 67 ED A9 DB 06 C1 70 81 3C 01 69 0D FF 63 DA 00 00 01 03 00 14 00 01 00 10 60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6
......@@ -159,24 +204,26 @@ object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse,
discardExact(23 + 3)//01 D3 00 01 00 16 00 00 00 01 00 00 00 64 00 00 0D DE 00 09 3A 80 00
discardExact(2)//00 02
sessionResponseDecryptionKey = SessionResponseDecryptionKey(readIoBuffer(16))
val sessionResponseDecryptionKey = SessionResponseDecryptionKey(readIoBuffer(16))
discardExact(2)
token88 = readIoBuffer(136)
val token88 = readIoBuffer(136)
discardExact(299)//2E 72 7A 50 41 54 5B 62 7D 47 5D 37 41 53 47 51 00 78 00 01 5D A2 DB 79 00 70 72 E7 D3 4E 6F D8 D1 DD F2 67 04 1D 23 4D E9 A7 AB 89 7A B7 E6 4B C0 79 60 3B 4F AA 31 C5 24 51 C1 4B 4F A4 32 74 BA FE 8E 06 DB 54 25 A2 56 91 E8 66 BB 23 29 EB F7 13 7B 94 1E AF B2 40 4E 69 5C 8C 35 04 D1 25 1F 60 93 F3 40 71 0B 61 60 F1 B6 A9 7A E8 B1 DA 0E 16 A2 F1 2D 69 5A 01 20 7A AB A7 37 68 D2 1A B0 4D 35 D1 E1 35 64 F6 90 2B 00 83 01 24 5B 4E 69 3D 45 54 6B 29 5E 73 23 2D 4E 42 3F 00 70 00 01 5D A2 DB 79 00 68 FD 10 8A 39 51 09 C6 69 CE 09 A4 52 8C 53 D3 B6 87 E1 7B 7E 4E 52 6D BA 9C C4 6E 6D DE 09 99 67 B4 BD 56 71 14 5A 54 01 68 1C 3C AA 0D 76 0B 86 5A C1 F1 BC 5E 0A ED E3 8C 57 86 35 D8 A5 F8 16 01 24 8B 57 56 8C A6 31 6F 65 73 03 DA ED 21 FA 6B 79 32 2B 09 01 E8 D2 D8 F0 7B F1 60 C2 7F 53 5D F6 53 50 8A 43 E2 23 2E 52 7B 60 39 56 67 2D 6A 23 43 4B 60 55 68 35 01 08 00 23 00 01 00 1F 00 17 02 5B
val nickLength = readUByte().toInt()
nickname = readString(nickLength)
val nickname = readString(nickLength)
//后文
//00 05 00 04 00 00 00 01 01 15 00 10 49 83 5C D9 93 6C 8D FE 09 18 99 37 99 80 68 92
discardExact(4)//02 13 80 02
age = readShort()//00 05
val age = readShort()//00 05
discardExact(4)//00 04 00 00
discardExact(2)//00 01
gender = if (readBoolean()) Gender.FEMALE else Gender.MALE
val gender = if (readBoolean()) Gender.FEMALE else Gender.MALE
return LoginResponse.Success(sessionResponseDecryptionKey, token38, token88, encryptionKey, nickname, age, gender)
}
else -> LoginResponse.Failed(when (size) {
......
......@@ -57,7 +57,7 @@ object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey
}
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): TouchResponse = TouchResponse().apply {
when (val id = readByte().toUByte().toInt()) {
when (val flag = readByte().toUByte().toInt()) {
0xFE -> {
discardExact(94)
serverIP = readIP()
......@@ -71,7 +71,7 @@ object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey
loginIP = readIP()
}
else -> throw IllegalStateException(id.toByte().toUHexString())
else -> throw IllegalStateException(flag.toByte().toUHexString())
}
}
}
\ No newline at end of file
......@@ -19,11 +19,14 @@ internal fun getGTK(sKey: String): Int {
}
@Tested
fun BytePacketBuilder.writeCRC32() = writeCRC32(getRandomByteArray(16))
@PublishedApi
internal fun BytePacketBuilder.writeCRC32() = writeCRC32(getRandomByteArray(16))
fun BytePacketBuilder.writeCRC32(key: ByteArray) {
@PublishedApi
internal fun BytePacketBuilder.writeCRC32(key: ByteArray) {
writeFully(key)//key
writeInt(crc32(key))
}
fun md5(str: String): ByteArray = md5(str.toByteArray())
\ No newline at end of file
@PublishedApi
internal fun md5(str: String): ByteArray = md5(str.toByteArray())
\ No newline at end of file
......@@ -25,6 +25,9 @@ fun ExternalImage(
/**
* 外部图片. 图片数据还没有读取到内存.
*
* 在 JVM, 请查看 'ExternalImageJvm.kt'
*
* @see ExternalImage.sendTo 上传图片并以纯图片消息发送给联系人
* @See ExternalImage.upload 上传图片并得到 [Image] 消息
*/
......
......@@ -17,7 +17,7 @@ var DefaultLogger: (identity: String?) -> MiraiLogger = { PlatformLogger() }
*
* 不应该直接构造这个类的实例. 请使用 [DefaultLogger]
*/
expect open class PlatformLogger @JvmOverloads internal constructor(identity: String? = null) : MiraiLoggerPlatformBase
expect open class PlatformLogger @JvmOverloads internal constructor(identity: String? = "Mirai") : MiraiLoggerPlatformBase
/**
......@@ -32,7 +32,7 @@ interface MiraiLogger {
/**
* 顶层日志记录器
*/
companion object : MiraiLogger by DefaultLogger("TOP Level")
companion object : MiraiLogger by DefaultLogger("Mirai")
val identity: String?
......
......@@ -11,7 +11,7 @@ import net.mamoe.mirai.contact.QQ
/**
* 创建一个在当前 [CoroutineScope] 下执行的 [SuspendLazy]
*/
fun <R> CoroutineScope.SuspendLazy(initializer: suspend () -> R): SuspendLazy<R> = SuspendLazy(this, initializer)
fun <R> CoroutineScope.SuspendLazy(initializer: suspend () -> R): Lazy<Deferred<R>> = SuspendLazy(this, initializer)
/**
* 挂起初始化的 [lazy], 是属性不能 `suspend` 的替代品.
......@@ -26,7 +26,8 @@ fun <R> CoroutineScope.SuspendLazy(initializer: suspend () -> R): SuspendLazy<R>
*
* @sample QQ.profile
*/
class SuspendLazy<R>(scope: CoroutineScope, val initializer: suspend () -> R) : Lazy<Deferred<R>> {
@PublishedApi
internal class SuspendLazy<R>(scope: CoroutineScope, val initializer: suspend () -> R) : Lazy<Deferred<R>> {
private val valueUpdater: Deferred<R> by lazy { scope.async { initializer() } }
@Suppress("EXPERIMENTAL_API_USAGE")
......
......@@ -25,9 +25,11 @@ class DecryptionFailedException : Exception()
* @param key 长度至少为 16
* @throws DecryptionFailedException 解密错误时
*/
fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key, sourceLength = length)
@PublishedApi
internal fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key, sourceLength = length)
fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key.value, sourceLength = length)
@PublishedApi
internal fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key.value, sourceLength = length)
/**
* 通过 [String.hexToBytes] 将 [keyHex] 转为 [ByteArray] 后用它解密 [this].
......@@ -36,7 +38,8 @@ fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.size): ByteA
* @param keyHex 长度至少为 16 bytes
* @throws DecryptionFailedException 解密错误时
*/
fun ByteArray.encryptBy(keyHex: String, length: Int = this.size): ByteArray = encryptBy(keyHex.hexToBytes(withCache = true), length = length)
@PublishedApi
internal fun ByteArray.encryptBy(keyHex: String, length: Int = this.size): ByteArray = encryptBy(keyHex.hexToBytes(withCache = true), length = length)
/**
* 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 加密.
......@@ -45,7 +48,8 @@ fun ByteArray.encryptBy(keyHex: String, length: Int = this.size): ByteArray = en
* @consumer 由于缓存需要被回收, 需在方法内执行解密后明文的消耗过程
* @throws DecryptionFailedException 解密错误时
*/
inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int = remaining.toInt() - offset, consumer: (ByteArray) -> Unit) {
@PublishedApi
internal inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int = remaining.toInt() - offset, consumer: (ByteArray) -> Unit) {
ByteArrayPool.useInstance {
this.readFully(it, offset, length)
consumer(it.encryptBy(key, length = length))
......@@ -63,7 +67,9 @@ inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int
* @param key 固定长度 16
* @throws DecryptionFailedException 解密错误时
*/
fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.decrypt(checkDataLengthAndReturnSelf(length), key, sourceLength = length)
@PublishedApi
internal fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray =
TEA.decrypt(checkDataLengthAndReturnSelf(length), key, sourceLength = length)
/**
* 使用 [key] 解密 [this].
......@@ -73,7 +79,8 @@ fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray = TE
* @param key 长度至少为 16
* @throws DecryptionFailedException 解密错误时
*/
fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray {
@PublishedApi
internal fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray {
checkDataLengthAndReturnSelf(length)
return ByteArrayPool.useInstance { keyBuffer ->
key.readFully(keyBuffer, 0, key.readRemaining)
......@@ -88,7 +95,8 @@ fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray {
* @param keyHex 长度至少为 16 bytes
* @throws DecryptionFailedException 解密错误时
*/
fun ByteArray.decryptBy(keyHex: String, length: Int = this.size): ByteArray = decryptBy(keyHex.hexToBytes(withCache = true), length = length)
@PublishedApi
internal fun ByteArray.decryptBy(keyHex: String, length: Int = this.size): ByteArray = decryptBy(keyHex.hexToBytes(withCache = true), length = length)
/**
* 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 解密.
......@@ -96,7 +104,8 @@ fun ByteArray.decryptBy(keyHex: String, length: Int = this.size): ByteArray = de
* @param key 长度至少为 16
* @throws DecryptionFailedException 解密错误时
*/
fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray {
@PublishedApi
internal fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray {
return ByteArrayPool.useInstance {
this.readFully(it, offset, length)
it.checkDataLengthAndReturnSelf(length)
......@@ -110,12 +119,45 @@ fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemain
* @param keyHex 长度至少为 16
* @throws DecryptionFailedException 解密错误时
*/
fun IoBuffer.decryptBy(keyHex: String, offset: Int = 0, length: Int = readRemaining - offset): ByteArray =
@PublishedApi
internal fun IoBuffer.decryptBy(keyHex: String, offset: Int = 0, length: Int = readRemaining - offset): ByteArray =
decryptBy(keyHex.hexToBytes(withCache = true), offset = offset, length = length)
// endregion
// region ByteReadPacket extension
@PublishedApi
internal fun ByteReadPacket.decryptBy(key: ByteArray): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
@PublishedApi
internal fun ByteReadPacket.decryptBy(key: IoBuffer): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
@PublishedApi
internal fun ByteReadPacket.decryptBy(keyHex: String): ByteReadPacket = decryptBy(keyHex.hexToBytes())
@PublishedApi
internal inline fun <R> ByteReadPacket.decryptAsByteArray(key: ByteArray, consumer: (ByteArray) -> R): R =
ByteArrayPool.useInstance {
val length = remaining.toInt()
readFully(it, 0, length)
consumer(it.decryptBy(key, length))
}.also { close() }
@PublishedApi
internal inline fun <R> ByteReadPacket.decryptAsByteArray(keyHex: String, consumer: (ByteArray) -> R): R =
this.decryptAsByteArray(keyHex.hexToBytes(), consumer)
@PublishedApi
internal inline fun <R> ByteReadPacket.decryptAsByteArray(key: IoBuffer, consumer: (ByteArray) -> R): R =
ByteArrayPool.useInstance {
val length = remaining.toInt()
readFully(it, 0, length)
consumer(it.decryptBy(key, length))
}.also { close() }
// endregion
private object TEA {
private const val UINT32_MASK = 0xffffffffL
......@@ -362,9 +404,4 @@ private object TEA {
private fun ByteArray.checkDataLengthAndReturnSelf(length: Int = this.size): ByteArray {
require(length % 8 == 0 && length >= 16) { "data must len % 8 == 0 && len >= 16 but given (length=$length) ${this.toUHexString()}" }
return this
}
fun main() {
println("A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D".hexToBytes().encryptBy("A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D").decryptBy("A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D").toUHexString() == "A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D")
}
\ No newline at end of file
......@@ -23,9 +23,9 @@ expect class ClosedChannelException : IOException
/**
* 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
*/
class SendPacketInternalException(cause: Throwable?) : Exception(cause)
internal class SendPacketInternalException(cause: Throwable?) : Exception(cause)
/**
* 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
*/
class ReadPacketInternalException(cause: Throwable?) : Exception(cause)
\ No newline at end of file
internal class ReadPacketInternalException(cause: Throwable?) : Exception(cause)
\ No newline at end of file
......@@ -92,7 +92,8 @@ fun String.hexToUBytes(withCache: Boolean = true): UByteArray =
/**
* 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray]
*/
fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
@PublishedApi
internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
/**
* 随机生成长度为 [length] 的 [String].
......
......@@ -9,6 +9,6 @@ import kotlinx.coroutines.runBlocking
*/
object Events {
@JvmStatic
fun <E : Event> subscribe(type: Class<E>, handler: suspend (E) -> ListeningStatus) =
fun <E : Subscribable> subscribe(type: Class<E>, handler: suspend (E) -> ListeningStatus) =
runBlocking { type.kotlin.subscribe(handler) }
}
......@@ -7,5 +7,4 @@ import java.util.concurrent.Executors
/**
* 独立的 4 thread 调度器
*/
actual val NetworkDispatcher: CoroutineDispatcher
get() = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
\ No newline at end of file
internal actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
\ No newline at end of file
package net.mamoe.mirai.utils
import io.ktor.util.KtorExperimentalAPI
import io.ktor.util.cio.writeChannel
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.io.ByteWriteChannel
import kotlinx.coroutines.io.jvm.nio.copyTo
import kotlinx.coroutines.io.reader
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlinx.io.core.use
import java.awt.Image
import java.awt.image.BufferedImage
import java.io.File
import java.io.RandomAccessFile
import javax.imageio.ImageIO
import kotlin.coroutines.CoroutineContext
/**
* 平台默认的验证码识别器.
*
* 可被修改, 除覆盖配置外全局生效.
*/
@KtorExperimentalAPI
actual var DefaultCaptchaSolver: CaptchaSolver = {
captchaLock.withLock {
val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
......@@ -38,6 +43,16 @@ actual var DefaultCaptchaSolver: CaptchaSolver = {
}
}
// Copied from Ktor CIO
private fun File.writeChannel(
coroutineContext: CoroutineContext = Dispatchers.IO
): ByteWriteChannel = GlobalScope.reader(CoroutineName("file-writer") + coroutineContext, autoFlush = true) {
RandomAccessFile(this@writeChannel, "rw").use { file ->
val copied = channel.copyTo(file.channel)
file.setLength(copied) // truncate tail that could remain from the previously written data
}
}.channel
private val captchaLock = Mutex()
......
package net.mamoe.mirai.utils
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.text.SimpleDateFormat
import java.util.*
......@@ -11,42 +9,39 @@ actual typealias PlatformLogger = Console
* JVM 控制台日志实现
*/
open class Console @JvmOverloads internal constructor(
override val identity: String? = null
override val identity: String? = "Mirai"
) : MiraiLoggerPlatformBase() {
override fun verbose0(any: Any?) = println(any.toString(), LoggerTextFormat.RESET)
override fun verbose0(any: Any?) = println(any, LoggerTextFormat.RESET)
override fun verbose0(message: String?, e: Throwable?) {
verbose(message.toString())
if (message != null) verbose(message.toString())
e?.printStackTrace()
}
override fun info0(any: Any?) = println(any.toString(), LoggerTextFormat.LIGHT_GREEN)
override fun info0(any: Any?) = println(any, LoggerTextFormat.LIGHT_GREEN)
override fun info0(message: String?, e: Throwable?) {
info(message.toString())
if (message != null) info(message.toString())
e?.printStackTrace()
}
override fun warning0(any: Any?) = println(any.toString(), LoggerTextFormat.LIGHT_RED)
override fun warning0(any: Any?) = println(any, LoggerTextFormat.LIGHT_RED)
override fun warning0(message: String?, e: Throwable?) {
warning(message.toString())
if (message != null) warning(message.toString())
e?.printStackTrace()
}
override fun error0(any: Any?) = println(any.toString(), LoggerTextFormat.RED)
override fun error0(any: Any?) = println(any, LoggerTextFormat.RED)
override fun error0(message: String?, e: Throwable?) {
error(message.toString())
if (message != null) error(message.toString())
e?.printStackTrace()
}
override fun debug0(any: Any?) {
println(any.toString(), LoggerTextFormat.LIGHT_CYAN)
}
override fun debug0(any: Any?) = println(any, LoggerTextFormat.LIGHT_CYAN)
override fun debug0(message: String?, e: Throwable?) {
debug(message.toString())
if (message != null) debug(message.toString())
e?.printStackTrace()
}
private fun println(value: String?, color: LoggerTextFormat) {
private fun println(value: Any?, color: LoggerTextFormat) {
val time = SimpleDateFormat("HH:mm:ss", Locale.SIMPLIFIED_CHINESE).format(Date())
if (identity == null) {
......@@ -81,8 +76,4 @@ internal enum class LoggerTextFormat(private val format: String) {
;
override fun toString(): String = format
}
@Suppress("unused")
val Throwable.stacktraceString: String
get() = ByteArrayOutputStream().also { printStackTrace(PrintStream(it)) }.toString()
\ No newline at end of file
}
\ No newline at end of file
......@@ -8,10 +8,11 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.*
import net.mamoe.mirai.event.events.FriendMessageEvent
import net.mamoe.mirai.login
import net.mamoe.mirai.message.*
import net.mamoe.mirai.network.protocol.tim.packet.action.uploadImage
import net.mamoe.mirai.network.protocol.tim.packet.event.FriendMessage
import net.mamoe.mirai.network.protocol.tim.packet.event.GroupMessage
import net.mamoe.mirai.network.protocol.tim.packet.login.ifFail
import net.mamoe.mirai.utils.suspendToExternalImage
import java.io.File
......@@ -52,7 +53,7 @@ suspend fun main() {
directlySubscribe(bot)
//DSL 监听
subscribeAll<FriendMessageEvent> {
subscribeAll<FriendMessage> {
always {
//获取第一个纯文本消息
val firstText = it.message.firstOrNull<PlainText>()
......@@ -102,7 +103,7 @@ suspend fun Bot.messageDSL() {
// sender: QQ
// it: String (MessageChain.toString)
if (this is GroupSenderAndMessage) {
if (this is GroupMessage) {
//如果是群消息
// group: Group
this.group.sendMessage("你在一个群里")
......@@ -196,7 +197,7 @@ suspend fun directlySubscribe(bot: Bot) {
// 手动处理消息
// 使用 Bot 的扩展方法监听, 将在处理事件时得到一个 this: Bot.
// 这样可以调用 Bot 内的一些扩展方法如 UInt.qq():QQ
bot.subscribeAlways<FriendMessageEvent> {
bot.subscribeAlways<FriendMessage> {
// this: Bot
// it: FriendMessageEvent
......@@ -257,9 +258,9 @@ suspend fun directlySubscribe(bot: Bot) {
* 对机器人说 "停止", 机器人停止
*/
suspend fun demo2() {
subscribeAlways<FriendMessageEvent> { event ->
subscribeAlways<FriendMessage> { event ->
if (event.message eq "记笔记") {
subscribeUntilFalse<FriendMessageEvent> {
subscribeUntilFalse<FriendMessage> {
it.reply("你发送了 ${it.message}")
it.message eq "停止"
......
......@@ -4,6 +4,7 @@ apply plugin: "java"
dependencies {
api project(":mirai-core")
runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // mpp targeting android limitation
//runtime files("../../mirai-core/build/classes/atomicfu/jvm/main") // mpp targeting android limitation
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutines_version
......
......@@ -7,7 +7,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.login
......@@ -41,7 +41,7 @@ suspend fun main() {
/**
* 监听所有事件
*/
subscribeAlways<Event> {
subscribeAlways<Subscribable> {
//bot.logger.verbose("收到了一个事件: ${it::class.simpleName}")
}
bot.subscribeMessages {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment