Commit 3eab26a5 authored by jiahua.liu's avatar jiahua.liu

Merge remote-tracking branch 'origin/master'

parents b7fd77b6 35e9138c
...@@ -2,11 +2,34 @@ ...@@ -2,11 +2,34 @@
开发版本. 频繁更新, 不保证高稳定性 开发版本. 频繁更新, 不保证高稳定性
## `0.15.0` 2020/2/14
### mirai-core
- 新增事件: `BotReloginEvent``BotOfflineEvent.Dropped`
- `AtAll` 现在实现 `Message.Key`
- 新增 `BotConfiguration` DSL, 支持自动将设备信息存储在文件系统等
- 新增 `MessageSource.quote(Member)`
- 更好的网络层连接逻辑
- 密码错误后不再重试登录
- 掉线后尝试快速重连, 失败则普通重连 (#47)
- 有原因的登录失败时将抛出特定异常: `LoginFailedException`
- 默认心跳时间调整为 60s
### mirai-core-qqandroid
- 解决一些验证码无法识别的问题
- 忽略一些不需要处理的事件(机器人主动操作触发的事件)
## `0.14.0` 2020/2/13 ## `0.14.0` 2020/2/13
### mirai-core ### mirai-core
- **支持 at 全体成员: `AtAll`** - **支持 at 全体成员: `AtAll`**
### mirai-core-qqandroid ### mirai-core-qqandroid
- **支持 `AtAll` 的发送和解析** - **支持 `AtAll` 的发送和解析**
- **修复某些情况下禁言处理异常** - **修复某些情况下禁言处理异常**
......
# style guide # style guide
kotlin.code.style=official kotlin.code.style=official
# config # config
mirai_version=0.14.0 mirai_version=0.15.0
kotlin.incremental.multiplatform=true kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true kotlin.parallel.tasks.in.project=true
# kotlin # kotlin
......
#Thu Feb 06 14:10:33 CST 2020 #Thu Feb 06 14:10:33 CST 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
......
...@@ -220,6 +220,44 @@ fun main() { ...@@ -220,6 +220,44 @@ fun main() {
### 发送引用回复消息(仅支持群消息)
```
[POST] /sendQuoteMessage
```
使用此方法向指定的消息进行引用回复
#### 请求
```json5
{
"sessionKey": "YourSession",
"target": 987654321,
"messageChain": [
{ "type": "Plain", "text":"hello\n" },
{ "type": "Plain", "text":"world" }
]
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | -------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | false | 987654321 | 引用消息的Message Source的Uid |
| messageChain | Array | false | [] | 消息链,是一个消息对象构成的数组 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 发送图片消息(通过URL) ### 发送图片消息(通过URL)
``` ```
...@@ -308,6 +346,9 @@ Content-Type:multipart/form-data ...@@ -308,6 +346,9 @@ Content-Type:multipart/form-data
[{ [{
"type": "GroupMessage", // 消息类型:GroupMessage或FriendMessage "type": "GroupMessage", // 消息类型:GroupMessage或FriendMessage
"messageChain": [{ // 消息链,是一个消息对象构成的数组 "messageChain": [{ // 消息链,是一个消息对象构成的数组
"type": "Source",
"uid": 123456
},{
"type": "Plain", "type": "Plain",
"text": "Miral牛逼" "text": "Miral牛逼"
}], }],
...@@ -343,12 +384,26 @@ Content-Type:multipart/form-data ...@@ -343,12 +384,26 @@ Content-Type:multipart/form-data
#### 消息是构成消息链的基本对象,目前支持的消息类型有 #### 消息是构成消息链的基本对象,目前支持的消息类型有
+ [x] At,@消息 + [x] At,@消息
+ [x] AtAll,@全体成员
+ [x] Face,表情消息 + [x] Face,表情消息
+ [x] Plain,文字消息 + [x] Plain,文字消息
+ [ ] Image,图片消息 + [x] Image,图片消息
+ [ ] Xml,Xml卡片消息 + [ ] Xml,Xml卡片消息
+ [ ] 敬请期待 + [ ] 敬请期待
#### Source
```json5
{
"type": "Source",
"uid": 123456
}
```
| 名字 | 类型 | 说明 |
| ---- | ---- | ------------------------------------------------------------ |
| uid | Long | 消息的识别号,用于引用回复(Source类型只在群消息中返回,且永远为chain的第一个元素) |
#### At #### At
```json5 ```json5
...@@ -364,6 +419,18 @@ Content-Type:multipart/form-data ...@@ -364,6 +419,18 @@ Content-Type:multipart/form-data
| target | Long | 群员QQ号 | | target | Long | 群员QQ号 |
| display | String | @时显示的文本如:"@Mirai" | | display | String | @时显示的文本如:"@Mirai" |
#### AtAll
```json5
{
"type": "AtAll"
}
```
| 名字 | 类型 | 说明 |
| ------- | ------ | ------------------------- |
| - | - | - |
#### Face #### Face
```json5 ```json5
......
...@@ -36,9 +36,15 @@ data class UnKnownMessagePacketDTO(val msg: String) : MessagePacketDTO() ...@@ -36,9 +36,15 @@ data class UnKnownMessagePacketDTO(val msg: String) : MessagePacketDTO()
// Message // Message
@Serializable @Serializable
@SerialName("Source")
data class MessageSourceDTO(val uid: Long) : MessageDTO()
@Serializable
@SerialName("At") @SerialName("At")
data class AtDTO(val target: Long, val display: String) : MessageDTO() data class AtDTO(val target: Long, val display: String) : MessageDTO()
@Serializable @Serializable
@SerialName("AtAll")
data class AtAllDTO(val target: Long = 0) : MessageDTO() // target为保留字段
@Serializable
@SerialName("Face") @SerialName("Face")
data class FaceDTO(val faceId: Int) : MessageDTO() data class FaceDTO(val faceId: Int) : MessageDTO()
@Serializable @Serializable
...@@ -82,7 +88,9 @@ fun MessageChainDTO.toMessageChain() = ...@@ -82,7 +88,9 @@ fun MessageChainDTO.toMessageChain() =
@UseExperimental(ExperimentalUnsignedTypes::class) @UseExperimental(ExperimentalUnsignedTypes::class)
fun Message.toDTO() = when (this) { fun Message.toDTO() = when (this) {
is MessageSource -> MessageSourceDTO(messageUid)
is At -> AtDTO(target, display) is At -> AtDTO(target, display)
is AtAll -> AtAllDTO(0L)
is Face -> FaceDTO(id.value.toInt()) is Face -> FaceDTO(id.value.toInt())
is PlainText -> PlainDTO(stringValue) is PlainText -> PlainDTO(stringValue)
is Image -> ImageDTO(imageId) is Image -> ImageDTO(imageId)
...@@ -93,11 +101,12 @@ fun Message.toDTO() = when (this) { ...@@ -93,11 +101,12 @@ fun Message.toDTO() = when (this) {
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) @UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
fun MessageDTO.toMessage() = when (this) { fun MessageDTO.toMessage() = when (this) {
is AtDTO -> At(target, display) is AtDTO -> At(target, display)
is AtAllDTO -> AtAll
is FaceDTO -> Face(FaceId(faceId.toUByte())) is FaceDTO -> Face(FaceId(faceId.toUByte()))
is PlainDTO -> PlainText(text) is PlainDTO -> PlainText(text)
is ImageDTO -> Image(imageId) is ImageDTO -> Image(imageId)
is XmlDTO -> XMLMessage(xml) is XmlDTO -> XMLMessage(xml)
is UnknownMessageDTO -> PlainText("assert cannot reach") is MessageSourceDTO, is UnknownMessageDTO -> PlainText("assert cannot reach")
} }
......
...@@ -9,17 +9,32 @@ ...@@ -9,17 +9,32 @@
package net.mamoe.mirai.api.http.queue package net.mamoe.mirai.api.http.queue
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.MessagePacket import net.mamoe.mirai.message.MessagePacket
import net.mamoe.mirai.message.data.MessageSource
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedDeque import java.util.concurrent.ConcurrentLinkedDeque
class MessageQueue : ConcurrentLinkedDeque<MessagePacket<*, *>>() { class MessageQueue : ConcurrentLinkedDeque<MessagePacket<*, *>>() {
val quoteCache = ConcurrentHashMap<Long, GroupMessage>()
fun fetch(size: Int): List<MessagePacket<*, *>> { fun fetch(size: Int): List<MessagePacket<*, *>> {
var count = size var count = size
quoteCache.clear()
val ret = ArrayList<MessagePacket<*, *>>(count) val ret = ArrayList<MessagePacket<*, *>>(count)
while (!this.isEmpty() && count-- > 0) { while (!this.isEmpty() && count-- > 0) {
ret.add(this.pop()) val packet = pop()
ret.add(packet)
if (packet is GroupMessage) {
addCache(packet)
}
} }
return ret return ret
} }
private fun addCache(msg: GroupMessage) {
quoteCache[msg.message[MessageSource].messageUid] = msg
}
} }
\ No newline at end of file
...@@ -52,6 +52,12 @@ fun Application.messageModule() { ...@@ -52,6 +52,12 @@ fun Application.messageModule() {
call.respondStateCode(StateCode.Success) call.respondStateCode(StateCode.Success)
} }
miraiVerify<SendDTO>("/quoteMessage") {
it.session.messageQueue.quoteCache[it.target]?.quoteReply(it.messageChain.toMessageChain())
?: throw NoSuchElementException()
call.respondStateCode(StateCode.Success)
}
miraiVerify<SendImageDTO>("sendImageMessage") { miraiVerify<SendImageDTO>("sendImageMessage") {
val bot = it.session.bot val bot = it.session.bot
val contact = when { val contact = when {
...@@ -72,12 +78,14 @@ fun Application.messageModule() { ...@@ -72,12 +78,14 @@ fun Application.messageModule() {
if (!SessionManager.containSession(sessionKey)) throw IllegalSessionException if (!SessionManager.containSession(sessionKey)) throw IllegalSessionException
val session = try { val session = try {
SessionManager[sessionKey] as AuthedSession SessionManager[sessionKey] as AuthedSession
} catch (e: TypeCastException) { throw NotVerifiedSessionException } } catch (e: TypeCastException) {
throw NotVerifiedSessionException
}
val type = parts.value("type") val type = parts.value("type")
parts.file("img")?.apply { parts.file("img")?.apply {
val image = streamProvider().use { val image = streamProvider().use {
when(type) { when (type) {
"group" -> session.bot.groups.toList().random().uploadImage(it) "group" -> session.bot.groups.toList().random().uploadImage(it)
"friend" -> session.bot.qqs.toList().random().uploadImage(it) "friend" -> session.bot.qqs.toList().random().uploadImage(it)
else -> null else -> null
......
...@@ -13,6 +13,7 @@ import kotlinx.serialization.* ...@@ -13,6 +13,7 @@ import kotlinx.serialization.*
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
import net.mamoe.mirai.api.http.data.common.* import net.mamoe.mirai.api.http.data.common.*
import net.mamoe.mirai.message.data.MessageSource
// 解析失败时直接返回null,由路由判断响应400状态 // 解析失败时直接返回null,由路由判断响应400状态
@UseExperimental(ImplicitReflectionSerializer::class) @UseExperimental(ImplicitReflectionSerializer::class)
...@@ -50,7 +51,9 @@ object MiraiJson { ...@@ -50,7 +51,9 @@ object MiraiJson {
UnKnownMessagePacketDTO::class with UnKnownMessagePacketDTO.serializer() UnKnownMessagePacketDTO::class with UnKnownMessagePacketDTO.serializer()
} }
polymorphic(MessageDTO.serializer()) { polymorphic(MessageDTO.serializer()) {
MessageSourceDTO::class with MessageSourceDTO.serializer()
AtDTO::class with AtDTO.serializer() AtDTO::class with AtDTO.serializer()
AtAllDTO::class with AtAllDTO.serializer()
FaceDTO::class with FaceDTO.serializer() FaceDTO::class with FaceDTO.serializer()
PlainDTO::class with PlainDTO.serializer() PlainDTO::class with PlainDTO.serializer()
ImageDTO::class with ImageDTO.serializer() ImageDTO::class with ImageDTO.serializer()
......
...@@ -20,9 +20,13 @@ import kotlinx.io.core.buildPacket ...@@ -20,9 +20,13 @@ import kotlinx.io.core.buildPacket
import kotlinx.io.core.use import kotlinx.io.core.use
import net.mamoe.mirai.data.MultiPacket import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.* import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.CancellableEvent
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.WrongPasswordException
import net.mamoe.mirai.qqandroid.FriendInfoImpl import net.mamoe.mirai.qqandroid.FriendInfoImpl
import net.mamoe.mirai.qqandroid.GroupImpl import net.mamoe.mirai.qqandroid.GroupImpl
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
...@@ -37,7 +41,10 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat ...@@ -37,7 +41,10 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.PlatformSocket
import net.mamoe.mirai.utils.io.readPacket
import net.mamoe.mirai.utils.io.useBytes
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.jvm.Volatile import kotlin.jvm.Volatile
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
...@@ -55,13 +62,48 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -55,13 +62,48 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
private lateinit var channel: PlatformSocket private lateinit var channel: PlatformSocket
override suspend fun login() { private var _packetReceiverJob: Job? = null
private var heartbeatJob: Job? = null
private val packetReceiveLock: Mutex = Mutex()
private fun startPacketReceiverJobOrKill(cancelCause: CancellationException? = null): Job {
_packetReceiverJob?.cancel(cancelCause)
return this.launch(CoroutineName("Incoming Packet Receiver")) {
while (channel.isOpen) {
val rawInput = try {
channel.read()
} catch (e: CancellationException) {
return@launch
} catch (e: Throwable) {
BotOfflineEvent.Dropped(bot).broadcast()
return@launch
}
packetReceiveLock.withLock {
processPacket(rawInput)
}
}
}.also { _packetReceiverJob = it }
}
override suspend fun relogin() {
heartbeatJob?.cancel()
if (::channel.isInitialized) { if (::channel.isInitialized) {
if (channel.isOpen) {
kotlin.runCatching {
registerClientOnline()
}.exceptionOrNull() ?: return
logger.info("Cannot do fast relogin. Trying slow relogin")
}
channel.close() channel.close()
} }
channel = PlatformSocket() channel = PlatformSocket()
channel.connect("113.96.13.208", 8080) // TODO: 2020/2/14 连接多个服务器
this.launch(CoroutineName("Incoming Packet Receiver")) { processReceive() } withTimeoutOrNull(3000) {
channel.connect("113.96.13.208", 8080)
} ?: error("timeout connecting server")
startPacketReceiverJobOrKill(CancellationException("reconnect"))
// logger.info("Trying login") // logger.info("Trying login")
var response: WtLogin.Login.LoginPacketResponse = WtLogin.Login.SubCommand9(bot.client).sendAndExpect() var response: WtLogin.Login.LoginPacketResponse = WtLogin.Login.SubCommand9(bot.client).sendAndExpect()
...@@ -94,7 +136,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -94,7 +136,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
} }
} }
is WtLogin.Login.LoginPacketResponse.Error -> error(response.toString()) is WtLogin.Login.LoginPacketResponse.Error ->
throw WrongPasswordException(response.toString())
is WtLogin.Login.LoginPacketResponse.DeviceLockLogin -> { is WtLogin.Login.LoginPacketResponse.DeviceLockLogin -> {
response = WtLogin.Login.SubCommand20( response = WtLogin.Login.SubCommand20(
...@@ -112,18 +155,15 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -112,18 +155,15 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
} }
// println("d2key=${bot.client.wLoginSigInfo.d2Key.toUHexString()}") // println("d2key=${bot.client.wLoginSigInfo.d2Key.toUHexString()}")
StatSvc.Register(bot.client).sendAndExpect<StatSvc.Register.Response>(6000) // it's slow registerClientOnline()
}
private suspend fun registerClientOnline() {
StatSvc.Register(bot.client).sendAndExpect<StatSvc.Register.Response>()
} }
@UseExperimental(MiraiExperimentalAPI::class, ExperimentalTime::class) @UseExperimental(MiraiExperimentalAPI::class, ExperimentalTime::class)
override suspend fun init(): Unit = coroutineScope { override suspend fun init(): Unit = coroutineScope {
this@QQAndroidBotNetworkHandler.subscribeAlways<BotOfflineEvent> {
if (this@QQAndroidBotNetworkHandler.bot == this.bot) {
logger.error("被挤下线")
close()
}
}
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendWithoutExpect() MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendWithoutExpect()
bot.qqs.delegate.clear() bot.qqs.delegate.clear()
...@@ -172,6 +212,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -172,6 +212,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
launch { launch {
try { try {
bot.groups.delegate.addLast( bot.groups.delegate.addLast(
@Suppress("DuplicatedCode")
GroupImpl( GroupImpl(
bot = bot, bot = bot,
coroutineContext = bot.coroutineContext, coroutineContext = bot.coroutineContext,
...@@ -211,14 +252,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -211,14 +252,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
joinAll(friendListJob, groupJob) joinAll(friendListJob, groupJob)
this@QQAndroidBotNetworkHandler.launch(CoroutineName("Heartbeat")) { heartbeatJob = this@QQAndroidBotNetworkHandler.launch(CoroutineName("Heartbeat")) {
while (this.isActive) { while (this.isActive) {
delay(bot.configuration.heartbeatPeriodMillis) delay(bot.configuration.heartbeatPeriodMillis)
val failException = doHeartBeat() val failException = doHeartBeat()
if (failException != null) { if (failException != null) {
delay(bot.configuration.firstReconnectDelayMillis) delay(bot.configuration.firstReconnectDelayMillis)
close() close()
bot.tryReinitializeNetworkHandler(failException) BotOfflineEvent.Dropped(bot).broadcast()
} }
} }
} }
...@@ -408,33 +449,6 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -408,33 +449,6 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
} }
@UseExperimental(ExperimentalCoroutinesApi::class)
private suspend fun processReceive() {
while (channel.isOpen) {
val rawInput = try {
channel.read()
} catch (e: ClosedChannelException) {
bot.tryReinitializeNetworkHandler(e)
return
} catch (e: ReadPacketInternalException) {
logger.error("Socket channel read failed: ${e.message}")
bot.tryReinitializeNetworkHandler(e)
return
} catch (e: CancellationException) {
return
} catch (e: Throwable) {
logger.error("Caught unexpected exceptions", e)
bot.tryReinitializeNetworkHandler(e)
return
}
packetReceiveLock.withLock {
processPacket(rawInput)
}
}
}
private val packetReceiveLock: Mutex = Mutex()
/** /**
* 发送一个包, 但不期待任何返回. * 发送一个包, 但不期待任何返回.
*/ */
......
...@@ -13,7 +13,7 @@ import kotlinx.serialization.SerialId ...@@ -13,7 +13,7 @@ import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
class OnlinePushPack { internal class OnlinePushPack {
@Serializable @Serializable
internal class DelMsgInfo( internal class DelMsgInfo(
@SerialId(0) val fromUin: Long, @SerialId(0) val fromUin: Long,
......
...@@ -35,6 +35,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory ...@@ -35,6 +35,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.debug
import net.mamoe.mirai.utils.io.discardExact import net.mamoe.mirai.utils.io.discardExact
import net.mamoe.mirai.utils.io.read import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readString import net.mamoe.mirai.utils.io.readString
...@@ -157,6 +158,8 @@ internal class OnlinePush { ...@@ -157,6 +158,8 @@ internal class OnlinePush {
val reqPushMsg = decodeUniPacket(OnlinePushPack.SvcReqPushMsg.serializer(), "req") val reqPushMsg = decodeUniPacket(OnlinePushPack.SvcReqPushMsg.serializer(), "req")
reqPushMsg.vMsgInfos.forEach { msgInfo: MsgInfo -> reqPushMsg.vMsgInfos.forEach { msgInfo: MsgInfo ->
msgInfo.vMsg!!.read { msgInfo.vMsg!!.read {
// TODO: 2020/2/13 可能会同时收到多个事件. 使用 map 而不要直接 return
when { when {
msgInfo.shMsgType.toInt() == 732 -> { msgInfo.shMsgType.toInt() == 732 -> {
val group = bot.getGroup(this.readUInt().toLong()) val group = bot.getGroup(this.readUInt().toLong())
...@@ -164,7 +167,11 @@ internal class OnlinePush { ...@@ -164,7 +167,11 @@ internal class OnlinePush {
when (val internalType = this.readShort().toInt()) { when (val internalType = this.readShort().toInt()) {
3073 -> { // mute 3073 -> { // mute
val operator = group[this.readUInt().toLong()] val operatorUin = this.readUInt().toLong()
if (operatorUin == bot.uin) {
return NoPacket
}
val operator = group[operatorUin]
this.readUInt().toLong() // time this.readUInt().toLong() // time
this.discardExact(2) this.discardExact(2)
val target = this.readUInt().toLong() val target = this.readUInt().toLong()
...@@ -215,7 +222,7 @@ internal class OnlinePush { ...@@ -215,7 +222,7 @@ internal class OnlinePush {
4096 -> { 4096 -> {
val dataBytes = this.readBytes(26) val dataBytes = this.readBytes(26)
val message = this.readString(this.readByte().toInt()) val message = this.readString(this.readByte().toInt())
println(dataBytes.toUHexString()) // println(dataBytes.toUHexString())
if (dataBytes[0].toInt() != 59) { if (dataBytes[0].toInt() != 59) {
return GroupNameChangeEvent( return GroupNameChangeEvent(
...@@ -244,7 +251,7 @@ internal class OnlinePush { ...@@ -244,7 +251,7 @@ internal class OnlinePush {
) )
} }
else -> { else -> {
println("Unknown server messages $message") bot.network.logger.debug { "Unknown server messages $message" }
return NoPacket return NoPacket
} }
} }
...@@ -255,17 +262,17 @@ internal class OnlinePush { ...@@ -255,17 +262,17 @@ internal class OnlinePush {
// println(msgInfo.vMsg.toUHexString()) // println(msgInfo.vMsg.toUHexString())
// } // }
else -> { else -> {
println("unknown group internal type $internalType , data: " + this.readBytes().toUHexString() + " ") bot.network.logger.debug { "unknown group internal type $internalType , data: " + this.readBytes().toUHexString() + " " }
} }
} }
} }
msgInfo.shMsgType.toInt() == 528 -> { msgInfo.shMsgType.toInt() == 528 -> {
println("unknown shtype ${msgInfo.shMsgType.toInt()}") bot.network.logger.debug { "unknown shtype ${msgInfo.shMsgType.toInt()}" }
// val content = msgInfo.vMsg.loadAs(OnlinePushPack.MsgType0x210.serializer()) // val content = msgInfo.vMsg.loadAs(OnlinePushPack.MsgType0x210.serializer())
// println(content.contentToString()) // println(content.contentToString())
} }
else -> { else -> {
println("unknown shtype ${msgInfo.shMsgType.toInt()}") bot.network.logger.debug { "unknown shtype ${msgInfo.shMsgType.toInt()}" }
} }
} }
} }
......
...@@ -124,7 +124,7 @@ fun ByteReadPacket.decodeMultiClientToServerPackets() { ...@@ -124,7 +124,7 @@ fun ByteReadPacket.decodeMultiClientToServerPackets() {
} }
fun Map<Int, ByteArray>.printTLVMap(name: String = "", keyLength: Int = 2) = fun Map<Int, ByteArray>.printTLVMap(name: String = "", keyLength: Int = 2) =
debugPrintln("TLVMap $name= " + this.mapValues { (_, value) -> value.toUHexString() }.mapKeys { DebugLogger.debug("TLVMap $name= " + this.mapValues { (_, value) -> value.toUHexString() }.mapKeys {
when (keyLength) { when (keyLength) {
1 -> it.key.toUByte().contentToString() 1 -> it.key.toUByte().contentToString()
2 -> it.key.toUShort().contentToString() 2 -> it.key.toUShort().contentToString()
......
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/**
* 在各平台实现的默认的验证码处理器.
*/
actual var defaultLoginSolver: LoginSolver = object : LoginSolver() {
override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
error("should be implemented manually by you")
}
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? {
error("should be implemented manually by you")
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
error("should be implemented manually by you")
}
}
@Suppress("ClassName", "PropertyName")
actual open class BotConfiguration actual constructor() {
/**
* 日志记录器
*/
actual var botLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Bot(${it.uin})") }
/**
* 网络层日志构造器
*/
actual var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) = { DefaultLogger("Network(${it.bot.uin})") }
/**
* 设备信息覆盖. 默认使用随机的设备信息.
*/
actual var deviceInfo: ((Context) -> DeviceInfo)? = null
/**
* 父 [CoroutineContext]
*/
actual var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
/**
* 心跳周期. 过长会导致被服务器断开连接.
*/
actual var heartbeatPeriodMillis: Long = 60.secondsToMillis
/**
* 每次心跳时等待结果的时间.
* 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响.
*/
actual var heartbeatTimeoutMillis: Long = 2.secondsToMillis
/**
* 心跳失败后的第一次重连前的等待时间.
*/
actual var firstReconnectDelayMillis: Long = 5.secondsToMillis
/**
* 重连失败后, 继续尝试的每次等待时间
*/
actual var reconnectPeriodMillis: Long = 60.secondsToMillis
/**
* 最多尝试多少次重连
*/
actual var reconnectionRetryTimes: Int = 3
/**
* 验证码处理器
*/
actual var loginSolver: LoginSolver = defaultLoginSolver
actual companion object {
/**
* 默认的配置实例
*/
@JvmStatic
actual val Default = BotConfiguration()
}
actual operator fun _NoNetworkLog.unaryPlus() {
networkLoggerSupplier = supplier
}
/**
* 不记录网络层的 log.
* 网络层 log 包含包接收, 包发送, 和一些调试用的记录.
*/
@BotConfigurationDsl
actual val NoNetworkLog: _NoNetworkLog
get() = _NoNetworkLog
@BotConfigurationDsl
actual object _NoNetworkLog {
internal val supplier = { _: BotNetworkHandler -> SilentLogger }
}
}
/**
* 使用文件系统存储设备信息.
*/
@BotConfigurationDsl
inline class FileBasedDeviceInfo @BotConfigurationDsl constructor(val filepath: String) {
/**
* 使用 "device.json" 存储设备信息
*/
@BotConfigurationDsl
companion object ByDeviceDotJson
}
\ No newline at end of file
...@@ -16,40 +16,40 @@ import android.util.Log ...@@ -16,40 +16,40 @@ import android.util.Log
* 不应该直接构造这个类的实例. 需使用 [DefaultLogger] * 不应该直接构造这个类的实例. 需使用 [DefaultLogger]
*/ */
actual open class PlatformLogger actual constructor(override val identity: String?) : MiraiLoggerPlatformBase() { actual open class PlatformLogger actual constructor(override val identity: String?) : MiraiLoggerPlatformBase() {
override fun verbose0(any: Any?) { override fun verbose0(message: String?) {
Log.v(identity, any?.toString() ?: "") Log.v(identity, message ?: "")
} }
override fun verbose0(message: String?, e: Throwable?) { override fun verbose0(message: String?, e: Throwable?) {
Log.v(identity, message ?: "", e) Log.v(identity, message ?: "", e)
} }
override fun debug0(any: Any?) { override fun debug0(message: String?) {
Log.d(identity, any?.toString() ?: "") Log.d(identity, message ?: "")
} }
override fun debug0(message: String?, e: Throwable?) { override fun debug0(message: String?, e: Throwable?) {
Log.d(identity, message ?: "", e) Log.d(identity, message ?: "", e)
} }
override fun info0(any: Any?) { override fun info0(message: String?) {
Log.i(identity, any?.toString() ?: "") Log.i(identity, message ?: "")
} }
override fun info0(message: String?, e: Throwable?) { override fun info0(message: String?, e: Throwable?) {
Log.i(identity, message ?: "", e) Log.i(identity, message ?: "", e)
} }
override fun warning0(any: Any?) { override fun warning0(message: String?) {
Log.w(identity, any?.toString() ?: "") Log.w(identity, message ?: "")
} }
override fun warning0(message: String?, e: Throwable?) { override fun warning0(message: String?, e: Throwable?) {
Log.w(identity, message ?: "", e) Log.w(identity, message ?: "", e)
} }
override fun error0(any: Any?) { override fun error0(message: String?) {
Log.e(identity, any?.toString() ?: "") Log.e(identity, message ?: "")
} }
override fun error0(message: String?, e: Throwable?) { override fun error0(message: String?, e: Throwable?) {
......
...@@ -14,13 +14,40 @@ import android.net.wifi.WifiManager ...@@ -14,13 +14,40 @@ import android.net.wifi.WifiManager
import android.os.Build import android.os.Build
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json
import java.io.File import java.io.File
/**
* 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存.
*/
@UseExperimental(UnstableDefault::class)
fun File.loadAsDeviceInfo(context: Context): DeviceInfo {
if (!this.exists() || this.length() == 0L) {
return SystemDeviceInfo(context).also {
this.writeText(Json.plain.stringify(SystemDeviceInfo.serializer(), it))
}
}
return Json.nonstrict.parse(DeviceInfoData.serializer(), this.readText()).also {
it.context = context
}
}
/** /**
* 部分引用指向 [Build]. * 部分引用指向 [Build].
* 部分需要权限, 若无权限则会使用默认值. * 部分需要权限, 若无权限则会使用默认值.
*/ */
actual open class SystemDeviceInfo actual constructor(context: Context) : DeviceInfo(context) { @Serializable
actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
actual constructor(context: Context) : this() {
this.context = context
}
@Transient
final override lateinit var context: Context
override val display: ByteArray get() = Build.DISPLAY.toByteArray() override val display: ByteArray get() = Build.DISPLAY.toByteArray()
override val product: ByteArray get() = Build.PRODUCT.toByteArray() override val product: ByteArray get() = Build.PRODUCT.toByteArray()
override val device: ByteArray get() = Build.DEVICE.toByteArray() override val device: ByteArray get() = Build.DEVICE.toByteArray()
...@@ -88,7 +115,8 @@ actual open class SystemDeviceInfo actual constructor(context: Context) : Device ...@@ -88,7 +115,8 @@ actual open class SystemDeviceInfo actual constructor(context: Context) : Device
override val androidId: ByteArray get() = Build.ID.toByteArray() override val androidId: ByteArray get() = Build.ID.toByteArray()
override val apn: ByteArray get() = "wifi".toByteArray() override val apn: ByteArray get() = "wifi".toByteArray()
object Version : DeviceInfo.Version { @Serializable
actual object Version : DeviceInfo.Version {
override val incremental: ByteArray get() = Build.VERSION.INCREMENTAL.toByteArray() override val incremental: ByteArray get() = Build.VERSION.INCREMENTAL.toByteArray()
override val release: ByteArray get() = Build.VERSION.RELEASE.toByteArray() override val release: ByteArray get() = Build.VERSION.RELEASE.toByteArray()
override val codename: ByteArray get() = Build.VERSION.CODENAME.toByteArray() override val codename: ByteArray get() = Build.VERSION.CODENAME.toByteArray()
......
...@@ -32,7 +32,7 @@ actual class PlatformSocket : Closeable { ...@@ -32,7 +32,7 @@ actual class PlatformSocket : Closeable {
actual val isOpen: Boolean actual val isOpen: Boolean
get() = socket.isConnected get() = socket.isConnected
override fun close() = socket.close() actual override fun close() = socket.close()
@PublishedApi @PublishedApi
internal lateinit var writeChannel: BufferedOutputStream internal lateinit var writeChannel: BufferedOutputStream
......
...@@ -24,6 +24,7 @@ import net.mamoe.mirai.data.GroupInfo ...@@ -24,6 +24,7 @@ import net.mamoe.mirai.data.GroupInfo
import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.transferTo import net.mamoe.mirai.utils.io.transferTo
...@@ -175,9 +176,11 @@ abstract class Bot : CoroutineScope { ...@@ -175,9 +176,11 @@ abstract class Bot : CoroutineScope {
/** /**
* 登录, 或重新登录. * 登录, 或重新登录.
* 不建议调用这个函数. * 重新登录时不会再次拉取联系人列表.
* *
* 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.login] * 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.relogin]
*
* @throws LoginFailedException
*/ */
abstract suspend fun login() abstract suspend fun login()
// endregion // endregion
......
...@@ -12,10 +12,15 @@ ...@@ -12,10 +12,15 @@
package net.mamoe.mirai package net.mamoe.mirai
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.BotReloginEvent
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.ForceOfflineException
import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.network.closeAndJoin import net.mamoe.mirai.network.closeAndJoin
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.logStacktrace import net.mamoe.mirai.utils.io.logStacktrace
...@@ -33,7 +38,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -33,7 +38,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
private val botJob = SupervisorJob(configuration.parentCoroutineContext[Job]) private val botJob = SupervisorJob(configuration.parentCoroutineContext[Job])
override val coroutineContext: CoroutineContext = override val coroutineContext: CoroutineContext =
configuration.parentCoroutineContext + botJob + (configuration.parentCoroutineContext[CoroutineExceptionHandler] configuration.parentCoroutineContext + botJob + (configuration.parentCoroutineContext[CoroutineExceptionHandler]
?: CoroutineExceptionHandler { _, e -> e.logStacktrace("An exception was thrown under a coroutine of Bot") }) ?: CoroutineExceptionHandler { _, e -> logger.error("An exception was thrown under a coroutine of Bot", e) })
@Suppress("CanBePrimaryConstructorProperty") // for logger @Suppress("CanBePrimaryConstructorProperty") // for logger
final override val account: BotAccount = account final override val account: BotAccount = account
...@@ -78,60 +83,70 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -78,60 +83,70 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
@Suppress("PropertyName") @Suppress("PropertyName")
internal lateinit var _network: N internal lateinit var _network: N
final override suspend fun login() = reinitializeNetworkHandler(null) @Suppress("unused")
private val offlineListener: Listener<BotOfflineEvent> = this.subscribeAlways { event ->
// shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close when (event) {
fun tryReinitializeNetworkHandler( is BotOfflineEvent.Dropped -> {
cause: Throwable? bot.logger.info("Connection dropped or lost by server, retrying login")
): Job = launch {
var lastFailedException: Throwable? = null var lastFailedException: Throwable? = null
repeat(configuration.reconnectionRetryTimes) { repeat(configuration.reconnectionRetryTimes) {
try { try {
reinitializeNetworkHandler(cause) network.relogin()
logger.info("Reconnected successfully") logger.info("Reconnected successfully")
return@launch return@subscribeAlways
} catch (e: Throwable) { } catch (e: Throwable) {
lastFailedException = e lastFailedException = e
delay(configuration.reconnectPeriodMillis) delay(configuration.reconnectPeriodMillis)
}
}
if (lastFailedException != null) {
throw lastFailedException!!
}
}
is BotOfflineEvent.Active -> {
val msg = if (event.cause == null) {
""
} else {
" with exception: " + event.cause.message
}
bot.logger.info("Bot is closed manually$msg")
close(CancellationException(event.toString()))
}
is BotOfflineEvent.Force -> {
bot.logger.info("Connection occupied by another android device: ${event.message}")
close(ForceOfflineException(event.toString()))
} }
}
if (lastFailedException != null) {
throw lastFailedException!!
} }
} }
final override suspend fun login() = reinitializeNetworkHandler(null)
private suspend fun reinitializeNetworkHandler( private suspend fun reinitializeNetworkHandler(
cause: Throwable? cause: Throwable?
) { ) {
logger.info("BotAccount: $uin") suspend fun doRelogin() {
logger.info("Initializing BotNetworkHandler") while (true) {
try { _network = createNetworkHandler(this.coroutineContext)
if (::_network.isInitialized) { try {
BotOfflineEvent.Active(this, cause).broadcast() _network.relogin()
_network.closeAndJoin(cause) return
} } catch (e: LoginFailedException) {
} catch (e: Exception) { throw e
logger.error("Cannot close network handler", e) } catch (e: Exception) {
} network.logger.error(e)
_network.closeAndJoin(e)
loginLoop@ while (true) { }
_network = createNetworkHandler(this.coroutineContext) logger.warning("Login failed. Retrying in 3s...")
try { delay(3000)
_network.login()
break@loginLoop
} catch (e: Exception) {
e.logStacktrace()
_network.closeAndJoin(e)
} }
logger.warning("Login failed. Retrying in 3s...")
delay(3000)
} }
repeat(1) block@{ suspend fun doInit() {
repeat(2) { repeat(2) {
try { try {
_network.init() _network.init()
return@block return
} catch (e: Exception) { } catch (e: Exception) {
e.logStacktrace() e.logStacktrace()
} }
...@@ -141,6 +156,16 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -141,6 +156,16 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
logger.error("cannot init. some features may be affected") logger.error("cannot init. some features may be affected")
} }
logger.info("Initializing BotNetworkHandler")
if (::_network.isInitialized) {
BotReloginEvent(this, cause).broadcast()
doRelogin()
return
}
doRelogin()
doInit()
} }
protected abstract fun createNetworkHandler(coroutineContext: CoroutineContext): N protected abstract fun createNetworkHandler(coroutineContext: CoroutineContext): N
...@@ -153,9 +178,11 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -153,9 +178,11 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
if (cause == null) { if (cause == null) {
network.close() network.close()
this.botJob.complete() this.botJob.complete()
offlineListener.complete()
} else { } else {
network.close(cause) network.close(cause)
this.botJob.completeExceptionally(cause) this.botJob.completeExceptionally(cause)
offlineListener.completeExceptionally(cause)
} }
} }
groups.delegate.clear() groups.delegate.clear()
......
...@@ -98,4 +98,6 @@ suspend inline fun Member.mute(duration: Duration): Boolean { ...@@ -98,4 +98,6 @@ suspend inline fun Member.mute(duration: Duration): Boolean {
require(duration.inDays <= 30) { "duration must be at most 1 month" } require(duration.inDays <= 30) { "duration must be at most 1 month" }
require(duration.inSeconds > 0) { "duration must be greater than 0 second" } require(duration.inSeconds > 0) { "duration must be greater than 0 second" }
return this.mute(duration.inSeconds.toInt()) return this.mute(duration.inSeconds.toInt())
} }
\ No newline at end of file
suspend inline fun Member.mute(durationSeconds: Long) = this.mute(durationSeconds.toInt())
\ No newline at end of file
...@@ -631,4 +631,4 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>( ...@@ -631,4 +631,4 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.TYPE) @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.TYPE)
@DslMarker @DslMarker
internal annotation class MessageDsl annotation class MessageDsl
\ No newline at end of file \ No newline at end of file
...@@ -16,6 +16,7 @@ import kotlinx.coroutines.GlobalScope ...@@ -16,6 +16,7 @@ import kotlinx.coroutines.GlobalScope
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.internal.Handler import net.mamoe.mirai.event.internal.Handler
import net.mamoe.mirai.event.internal.subscribeInternal import net.mamoe.mirai.event.internal.subscribeInternal
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/* /*
...@@ -96,6 +97,7 @@ interface Listener<in E : Event> : CompletableJob { ...@@ -96,6 +97,7 @@ interface Listener<in E : Event> : CompletableJob {
* @see subscribeGroupMessages 监听群消息 DSL * @see subscribeGroupMessages 监听群消息 DSL
* @see subscribeFriendMessages 监听好友消息 DSL * @see subscribeFriendMessages 监听好友消息 DSL
*/ */
@UseExperimental(MiraiInternalAPI::class)
inline fun <reified E : Event> CoroutineScope.subscribe(crossinline handler: suspend E.(E) -> ListeningStatus): Listener<E> = inline fun <reified E : Event> CoroutineScope.subscribe(crossinline handler: suspend E.(E) -> ListeningStatus): Listener<E> =
E::class.subscribeInternal(Handler { it.handler(it); }) E::class.subscribeInternal(Handler { it.handler(it); })
...@@ -107,6 +109,7 @@ inline fun <reified E : Event> CoroutineScope.subscribe(crossinline handler: sus ...@@ -107,6 +109,7 @@ inline fun <reified E : Event> CoroutineScope.subscribe(crossinline handler: sus
* *
* @see subscribe 获取更多说明 * @see subscribe 获取更多说明
*/ */
@UseExperimental(MiraiInternalAPI::class)
inline fun <reified E : Event> CoroutineScope.subscribeAlways(crossinline listener: suspend E.(E) -> Unit): Listener<E> = inline fun <reified E : Event> CoroutineScope.subscribeAlways(crossinline listener: suspend E.(E) -> Unit): Listener<E> =
E::class.subscribeInternal(Handler { it.listener(it); ListeningStatus.LISTENING }) E::class.subscribeInternal(Handler { it.listener(it); ListeningStatus.LISTENING })
...@@ -118,6 +121,7 @@ inline fun <reified E : Event> CoroutineScope.subscribeAlways(crossinline listen ...@@ -118,6 +121,7 @@ inline fun <reified E : Event> CoroutineScope.subscribeAlways(crossinline listen
* *
* @see subscribe 获取更多说明 * @see subscribe 获取更多说明
*/ */
@UseExperimental(MiraiInternalAPI::class)
inline fun <reified E : Event> CoroutineScope.subscribeOnce(crossinline listener: suspend E.(E) -> Unit): Listener<E> = inline fun <reified E : Event> CoroutineScope.subscribeOnce(crossinline listener: suspend E.(E) -> Unit): Listener<E> =
E::class.subscribeInternal(Handler { it.listener(it); ListeningStatus.STOPPED }) E::class.subscribeInternal(Handler { it.listener(it); ListeningStatus.STOPPED })
...@@ -129,6 +133,7 @@ inline fun <reified E : Event> CoroutineScope.subscribeOnce(crossinline listener ...@@ -129,6 +133,7 @@ inline fun <reified E : Event> CoroutineScope.subscribeOnce(crossinline listener
* *
* @see subscribe 获取更多说明 * @see subscribe 获取更多说明
*/ */
@UseExperimental(MiraiInternalAPI::class)
inline fun <reified E : Event, T> CoroutineScope.subscribeUntil(valueIfStop: T, crossinline listener: suspend E.(E) -> T): Listener<E> = inline fun <reified E : Event, T> CoroutineScope.subscribeUntil(valueIfStop: T, crossinline listener: suspend E.(E) -> T): Listener<E> =
E::class.subscribeInternal(Handler { if (it.listener(it) == valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) E::class.subscribeInternal(Handler { if (it.listener(it) == valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
...@@ -141,6 +146,7 @@ inline fun <reified E : Event, T> CoroutineScope.subscribeUntil(valueIfStop: T, ...@@ -141,6 +146,7 @@ inline fun <reified E : Event, T> CoroutineScope.subscribeUntil(valueIfStop: T,
* *
* @see subscribe 获取更多说明 * @see subscribe 获取更多说明
*/ */
@UseExperimental(MiraiInternalAPI::class)
inline fun <reified E : Event, T> CoroutineScope.subscribeWhile(valueIfContinue: T, crossinline listener: suspend E.(E) -> T): Listener<E> = inline fun <reified E : Event, T> CoroutineScope.subscribeWhile(valueIfContinue: T, crossinline listener: suspend E.(E) -> T): Listener<E> =
E::class.subscribeInternal(Handler { if (it.listener(it) != valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) E::class.subscribeInternal(Handler { if (it.listener(it) != valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
......
...@@ -42,19 +42,32 @@ data class BotOnlineEvent(override val bot: Bot) : BotActiveEvent ...@@ -42,19 +42,32 @@ data class BotOnlineEvent(override val bot: Bot) : BotActiveEvent
/** /**
* [Bot] 离线. * [Bot] 离线.
*/ */
sealed class BotOfflineEvent : BotActiveEvent { sealed class BotOfflineEvent : BotEvent {
/** /**
* 主动离线 * 主动离线
*/ */
data class Active(override val bot: Bot, val cause: Throwable?) : BotOfflineEvent() data class Active(override val bot: Bot, val cause: Throwable?) : BotOfflineEvent(), BotActiveEvent
/** /**
* 被挤下线 * 被挤下线
*/ */
data class Force(override val bot: Bot, val title: String, val message: String) : BotOfflineEvent(), Packet data class Force(override val bot: Bot, val title: String, val message: String) : BotOfflineEvent(), Packet, BotPassiveEvent
/**
* 被服务器断开或因网络问题而掉线
*/
data class Dropped(override val bot: Bot) : BotOfflineEvent(), Packet, BotPassiveEvent
} }
/**
* [Bot] 主动重新登录.
*/
data class BotReloginEvent(
override val bot: Bot,
val cause: Throwable?
) : BotEvent, BotActiveEvent
// endregion // endregion
// region 消息 // region 消息
......
...@@ -23,8 +23,8 @@ import kotlin.reflect.KClass ...@@ -23,8 +23,8 @@ import kotlin.reflect.KClass
val EventLogger: MiraiLoggerWithSwitch = DefaultLogger("Event").withSwitch(false) val EventLogger: MiraiLoggerWithSwitch = DefaultLogger("Event").withSwitch(false)
@PublishedApi @MiraiInternalAPI
internal fun <L : Listener<E>, E : Event> KClass<out E>.subscribeInternal(listener: L): L { fun <L : Listener<E>, E : Event> KClass<out E>.subscribeInternal(listener: L): L {
this.listeners().addLast(listener) this.listeners().addLast(listener)
return listener return listener
} }
......
...@@ -14,7 +14,7 @@ package net.mamoe.mirai.message.data ...@@ -14,7 +14,7 @@ package net.mamoe.mirai.message.data
* *
* @see At at 单个群成员 * @see At at 单个群成员
*/ */
object AtAll : Message { object AtAll : Message, Message.Key<AtAll> {
override fun toString(): String = "@全体成员" override fun toString(): String = "@全体成员"
// 自动为消息补充 " " // 自动为消息补充 " "
......
...@@ -155,6 +155,7 @@ inline fun <reified M : Message> MessageChain.any(): Boolean = this.any { it is ...@@ -155,6 +155,7 @@ inline fun <reified M : Message> MessageChain.any(): Boolean = this.any { it is
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) { fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) {
At -> first<At>() At -> first<At>()
AtAll -> first<AtAll>()
PlainText -> first<PlainText>() PlainText -> first<PlainText>()
Image -> first<Image>() Image -> first<Image>()
Face -> first<Face>() Face -> first<Face>()
......
...@@ -14,6 +14,8 @@ package net.mamoe.mirai.message.data ...@@ -14,6 +14,8 @@ package net.mamoe.mirai.message.data
* 消息源只用于 [QuoteReply] * 消息源只用于 [QuoteReply]
* *
* `mirai-core-qqandroid`: `net.mamoe.mirai.qqandroid.message.MessageSourceFromMsg` * `mirai-core-qqandroid`: `net.mamoe.mirai.qqandroid.message.MessageSourceFromMsg`
*
* @see MessageSource.quote 引用这条消息, 创建 [MessageChain]
*/ */
interface MessageSource : Message { interface MessageSource : Message {
companion object : Message.Key<MessageSource> companion object : Message.Key<MessageSource>
......
...@@ -33,4 +33,13 @@ fun MessageChain.quote(sender: Member): MessageChain { ...@@ -33,4 +33,13 @@ fun MessageChain.quote(sender: Member): MessageChain {
return QuoteReply(it) + sender.at() + " " // required return QuoteReply(it) + sender.at() + " " // required
} }
error("cannot find MessageSource") error("cannot find MessageSource")
}
/**
* 引用这条消息.
* 返回 `[QuoteReply] + [At] + [PlainText]`(必要的结构)
*/
fun MessageSource.quote(sender: Member): MessageChain {
@UseExperimental(MiraiInternalAPI::class)
return QuoteReply(this) + sender.at() + " " // required
} }
\ No newline at end of file
...@@ -55,12 +55,20 @@ abstract class BotNetworkHandler : CoroutineScope { ...@@ -55,12 +55,20 @@ abstract class BotNetworkHandler : CoroutineScope {
/** /**
* 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回. * 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回.
* 本函数将挂起直到登录成功. *
* - 会断开连接并重新登录.
* - 不会停止网络层的 [Job].
* - 重新登录时不会再次拉取联系人列表.
* - 挂起直到登录成功.
* *
* 不要使用这个 API. 请使用 [Bot.login] * 不要使用这个 API. 请使用 [Bot.login]
*
* @throws LoginFailedException 登录失败时
* @throws WrongPasswordException 密码错误时
*/ */
@Suppress("SpellCheckingInspection")
@MiraiInternalAPI @MiraiInternalAPI
abstract suspend fun login() abstract suspend fun relogin()
/** /**
* 初始化获取好友列表等值. * 初始化获取好友列表等值.
...@@ -92,6 +100,7 @@ abstract class BotNetworkHandler : CoroutineScope { ...@@ -92,6 +100,7 @@ abstract class BotNetworkHandler : CoroutineScope {
} }
} }
@UseExperimental(MiraiInternalAPI::class)
suspend fun BotNetworkHandler.closeAndJoin(cause: Throwable? = null) { suspend fun BotNetworkHandler.closeAndJoin(cause: Throwable? = null) {
this.close(cause) this.close(cause)
this.supervisor.join() this.supervisor.join()
......
package net.mamoe.mirai.network
class ForceOfflineException(override val message: String?) : RuntimeException()
\ No newline at end of file
...@@ -7,24 +7,16 @@ ...@@ -7,24 +7,16 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.utils package net.mamoe.mirai.network
import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.Bot
/** /**
* 在各平台实现的默认的验证码处理器. * 正常登录失败时抛出
*/ */
actual var defaultLoginSolver: LoginSolver = object : LoginSolver() { sealed class LoginFailedException : RuntimeException {
override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? { constructor() : super()
error("should be implemented manually by you") constructor(message: String?) : super(message)
} constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? { }
error("should be implemented manually by you")
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? { class WrongPasswordException(message: String?) : LoginFailedException(message)
error("should be implemented manually by you") \ No newline at end of file
}
}
\ No newline at end of file
...@@ -30,7 +30,7 @@ annotation class MiraiInternalAPI( ...@@ -30,7 +30,7 @@ annotation class MiraiInternalAPI(
) )
/** /**
* 标记这个类, 类型, 函数, 属性, 字段, 或构造器为实验性的. * 标记这个类, 类型, 函数, 属性, 字段, 或构造器为实验性的 API.
* *
* 这些 API 不具有稳定性, 且可能会在任意时刻更改. * 这些 API 不具有稳定性, 且可能会在任意时刻更改.
* 不建议在发行版本中使用这些 API. * 不建议在发行版本中使用这些 API.
...@@ -56,7 +56,7 @@ annotation class MiraiDebugAPI( ...@@ -56,7 +56,7 @@ annotation class MiraiDebugAPI(
) )
/** /**
* 标记这个 API 是自 Mirai 某个版本起才受支持. * 标记一个自 Mirai 某个版本起才支持的 API.
*/ */
@Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS) @Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS)
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
......
...@@ -13,7 +13,6 @@ import kotlinx.io.core.IoBuffer ...@@ -13,7 +13,6 @@ import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmStatic import kotlin.jvm.JvmStatic
/** /**
...@@ -33,58 +32,74 @@ abstract class LoginSolver { ...@@ -33,58 +32,74 @@ abstract class LoginSolver {
expect var defaultLoginSolver: LoginSolver expect var defaultLoginSolver: LoginSolver
/** /**
* 网络和连接配置 * [Bot] 配置
*/ */
class BotConfiguration { @Suppress("PropertyName")
expect open class BotConfiguration() {
/** /**
* 日志记录器 * 日志记录器
*/ */
var botLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Bot(${it.uin})") } var botLoggerSupplier: ((Bot) -> MiraiLogger)
/** /**
* 网络层日志构造器 * 网络层日志构造器
*/ */
var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) = { DefaultLogger("Network(${it.bot.uin})") } var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger)
/** /**
* 设备信息覆盖 * 设备信息覆盖. 默认使用随机的设备信息.
*/ */
var deviceInfo: ((Context) -> DeviceInfo)? = null var deviceInfo: ((Context) -> DeviceInfo)?
/** /**
* 父 [CoroutineContext] * 父 [CoroutineContext]
*/ */
var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext var parentCoroutineContext: CoroutineContext
/** /**
* 心跳周期. 过长会导致被服务器断开连接. * 心跳周期. 过长会导致被服务器断开连接.
*/ */
var heartbeatPeriodMillis: Long = 30.secondsToMillis var heartbeatPeriodMillis: Long
/** /**
* 每次心跳时等待结果的时间. * 每次心跳时等待结果的时间.
* 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响. * 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响.
*/ */
var heartbeatTimeoutMillis: Long = 2.secondsToMillis var heartbeatTimeoutMillis: Long
/** /**
* 心跳失败后的第一次重连前的等待时间. * 心跳失败后的第一次重连前的等待时间.
*/ */
var firstReconnectDelayMillis: Long = 5.secondsToMillis var firstReconnectDelayMillis: Long
/** /**
* 重连失败后, 继续尝试的每次等待时间 * 重连失败后, 继续尝试的每次等待时间
*/ */
var reconnectPeriodMillis: Long = 60.secondsToMillis var reconnectPeriodMillis: Long
/** /**
* 最多尝试多少次重连 * 最多尝试多少次重连
*/ */
var reconnectionRetryTimes: Int = 3 var reconnectionRetryTimes: Int
/** /**
* 验证码处理器 * 验证码处理器
*/ */
var loginSolver: LoginSolver = defaultLoginSolver var loginSolver: LoginSolver
companion object { companion object {
/** /**
* 默认的配置实例 * 默认的配置实例
*/ */
@JvmStatic @JvmStatic
val Default = BotConfiguration() val Default: BotConfiguration
} }
}
\ No newline at end of file operator fun _NoNetworkLog.unaryPlus()
/**
* 不记录网络层的 log.
* 网络层 log 包含包接收, 包发送, 和一些调试用的记录.
*/
@BotConfigurationDsl
val NoNetworkLog: _NoNetworkLog
@Suppress("ClassName")
object _NoNetworkLog
}
@DslMarker
annotation class BotConfigurationDsl
\ No newline at end of file
...@@ -11,16 +11,15 @@ package net.mamoe.mirai.utils ...@@ -11,16 +11,15 @@ package net.mamoe.mirai.utils
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.utils.cryptor.contentToString
/** /**
* 设备信息. 可通过继承 [SystemDeviceInfo] 来在默认的基础上修改 * 设备信息. 可通过继承 [SystemDeviceInfo] 来在默认的基础上修改
*/ */
abstract class DeviceInfo internal constructor( abstract class DeviceInfo {
context: Context @Transient
) { abstract val context: Context
val context: Context by context.unsafeWeakRef()
abstract val display: ByteArray abstract val display: ByteArray
abstract val product: ByteArray abstract val product: ByteArray
...@@ -95,6 +94,45 @@ abstract class DeviceInfo internal constructor( ...@@ -95,6 +94,45 @@ abstract class DeviceInfo internal constructor(
} }
} }
@Serializable
class DeviceInfoData(
override val display: ByteArray,
override val product: ByteArray,
override val device: ByteArray,
override val board: ByteArray,
override val brand: ByteArray,
override val model: ByteArray,
override val bootloader: ByteArray,
override val fingerprint: ByteArray,
override val bootId: ByteArray,
override val procVersion: ByteArray,
override val baseBand: ByteArray,
override val version: VersionData,
override val simInfo: ByteArray,
override val osType: ByteArray,
override val macAddress: ByteArray,
override val wifiBSSID: ByteArray?,
override val wifiSSID: ByteArray?,
override val imsiMd5: ByteArray,
override val imei: String,
override val apn: ByteArray
) : DeviceInfo() {
@Transient
override lateinit var context: Context
@UseExperimental(ExperimentalUnsignedTypes::class)
override val ipAddress: ByteArray
get() = localIpAddress().split(".").map { it.toUByte().toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: byteArrayOf()
override val androidId: ByteArray get() = display
@Serializable
class VersionData(
override val incremental: ByteArray = SystemDeviceInfo.Version.incremental,
override val release: ByteArray = SystemDeviceInfo.Version.release,
override val codename: ByteArray = SystemDeviceInfo.Version.codename,
override val sdk: Int = SystemDeviceInfo.Version.sdk
) : Version
}
/** /**
* Defaults "%4;7t>;28<fc.5*6".toByteArray() * Defaults "%4;7t>;28<fc.5*6".toByteArray()
*/ */
......
...@@ -9,13 +9,15 @@ ...@@ -9,13 +9,15 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.DeviceInfo
/** /**
* 通过本机信息来获取设备信息. * 通过本机信息来获取设备信息.
* *
* Android: 获取手机信息, 与 QQ 官方相同. * Android: 获取手机信息, 与 QQ 官方相同.
* JVM: 部分为常量, 部分为随机 * JVM: 部分为常量, 部分为随机
*/ */
open expect class SystemDeviceInfo(context: Context) : DeviceInfo expect open class SystemDeviceInfo : DeviceInfo {
\ No newline at end of file constructor()
constructor(context: Context)
object Version : DeviceInfo.Version
}
\ No newline at end of file
...@@ -15,11 +15,13 @@ package net.mamoe.mirai.utils.io ...@@ -15,11 +15,13 @@ package net.mamoe.mirai.utils.io
import kotlinx.io.core.* import kotlinx.io.core.*
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiDebugAPI
import net.mamoe.mirai.utils.MiraiLoggerWithSwitch
import net.mamoe.mirai.utils.withSwitch
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
import kotlin.js.JsName
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
...@@ -30,9 +32,6 @@ val DebugLogger : MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwit ...@@ -30,9 +32,6 @@ val DebugLogger : MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwit
@MiraiDebugAPI("Unstable") @MiraiDebugAPI("Unstable")
inline fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this) inline fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this)
@MiraiDebugAPI("Low efficiency.")
inline fun debugPrintln(any: Any?) = DebugLogger.debug(any)
@MiraiDebugAPI("Low efficiency.") @MiraiDebugAPI("Low efficiency.")
inline fun String.debugPrintThis(name: String): String { inline fun String.debugPrintThis(name: String): String {
DebugLogger.debug("$name=$this") DebugLogger.debug("$name=$this")
......
...@@ -81,7 +81,7 @@ inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteAr ...@@ -81,7 +81,7 @@ inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteAr
return this[tag] ?: error(lazyMessage(tag)) return this[tag] ?: error(lazyMessage(tag))
} }
@MiraiDebugAPI @MiraiInternalAPI
inline fun Input.readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap = readTLVMap(true, tagSize, suppressDuplication) inline fun Input.readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap = readTLVMap(true, tagSize, suppressDuplication)
@MiraiDebugAPI @MiraiDebugAPI
......
...@@ -11,7 +11,6 @@ package net.mamoe.mirai.utils.io ...@@ -11,7 +11,6 @@ package net.mamoe.mirai.utils.io
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable import kotlinx.io.core.Closeable
import kotlinx.io.errors.IOException
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
/** /**
...@@ -37,4 +36,6 @@ expect class PlatformSocket() : Closeable { ...@@ -37,4 +36,6 @@ expect class PlatformSocket() : Closeable {
suspend fun read(): ByteReadPacket suspend fun read(): ByteReadPacket
val isOpen: Boolean val isOpen: Boolean
override fun close()
} }
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.event.internal
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.ListeningStatus
import net.mamoe.mirai.utils.MiraiInternalAPI
import java.util.function.Consumer
import java.util.function.Function
@MiraiInternalAPI
@Suppress("FunctionName")
fun <E : Event> Class<E>._subscribeEventForJaptOnly(scope: CoroutineScope, onEvent: Function<E, ListeningStatus>): Listener<E> {
return this.kotlin.subscribeInternal(scope.Handler { onEvent.apply(it) })
}
@MiraiInternalAPI
@Suppress("FunctionName")
fun <E : Event> Class<E>._subscribeEventForJaptOnly(scope: CoroutineScope, onEvent: Consumer<E>): Listener<E> {
return this.kotlin.subscribeInternal(scope.Handler { onEvent.accept(it); ListeningStatus.LISTENING; })
}
\ No newline at end of file
...@@ -22,12 +22,14 @@ import kotlinx.coroutines.withContext ...@@ -22,12 +22,14 @@ import kotlinx.coroutines.withContext
import kotlinx.io.core.IoBuffer import kotlinx.io.core.IoBuffer
import kotlinx.io.core.use import kotlinx.io.core.use
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler
import java.awt.Image import java.awt.Image
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.File import java.io.File
import java.io.RandomAccessFile import java.io.RandomAccessFile
import javax.imageio.ImageIO import javax.imageio.ImageIO
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/** /**
* 平台默认的验证码识别器. * 平台默认的验证码识别器.
...@@ -157,3 +159,98 @@ private fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Doub ...@@ -157,3 +159,98 @@ private fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Doub
} }
} }
@Suppress("ClassName", "PropertyName")
actual open class BotConfiguration actual constructor() {
/**
* 日志记录器
*/
actual var botLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Bot(${it.uin})") }
/**
* 网络层日志构造器
*/
actual var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) = { DefaultLogger("Network(${it.bot.uin})") }
/**
* 设备信息覆盖. 默认使用随机的设备信息.
*/
actual var deviceInfo: ((Context) -> DeviceInfo)? = null
/**
* 父 [CoroutineContext]
*/
actual var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
/**
* 心跳周期. 过长会导致被服务器断开连接.
*/
actual var heartbeatPeriodMillis: Long = 60.secondsToMillis
/**
* 每次心跳时等待结果的时间.
* 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响.
*/
actual var heartbeatTimeoutMillis: Long = 2.secondsToMillis
/**
* 心跳失败后的第一次重连前的等待时间.
*/
actual var firstReconnectDelayMillis: Long = 5.secondsToMillis
/**
* 重连失败后, 继续尝试的每次等待时间
*/
actual var reconnectPeriodMillis: Long = 60.secondsToMillis
/**
* 最多尝试多少次重连
*/
actual var reconnectionRetryTimes: Int = 3
/**
* 验证码处理器
*/
actual var loginSolver: LoginSolver = defaultLoginSolver
actual companion object {
/**
* 默认的配置实例
*/
@JvmStatic
actual val Default = BotConfiguration()
}
@Suppress("NOTHING_TO_INLINE")
@BotConfigurationDsl
inline operator fun FileBasedDeviceInfo.unaryPlus() {
deviceInfo = { File(filepath).loadAsDeviceInfo(it) }
}
@Suppress("NOTHING_TO_INLINE")
@BotConfigurationDsl
inline operator fun FileBasedDeviceInfo.ByDeviceDotJson.unaryPlus() {
deviceInfo = { File("device.json").loadAsDeviceInfo(it) }
}
actual operator fun _NoNetworkLog.unaryPlus() {
networkLoggerSupplier = supplier
}
/**
* 不记录网络层的 log.
* 网络层 log 包含包接收, 包发送, 和一些调试用的记录.
*/
@BotConfigurationDsl
actual val NoNetworkLog: _NoNetworkLog
get() = _NoNetworkLog
@BotConfigurationDsl
actual object _NoNetworkLog {
internal val supplier = { _: BotNetworkHandler -> SilentLogger }
}
}
/**
* 使用文件系统存储设备信息.
*/
@BotConfigurationDsl
inline class FileBasedDeviceInfo @BotConfigurationDsl constructor(val filepath: String) {
/**
* 使用 "device.json" 存储设备信息
*/
@BotConfigurationDsl
companion object ByDeviceDotJson
}
\ No newline at end of file
...@@ -18,37 +18,37 @@ import java.util.* ...@@ -18,37 +18,37 @@ import java.util.*
actual open class PlatformLogger @JvmOverloads internal actual constructor( actual open class PlatformLogger @JvmOverloads internal actual constructor(
override val identity: String? override val identity: String?
) : MiraiLoggerPlatformBase() { ) : MiraiLoggerPlatformBase() {
override fun verbose0(any: Any?) = println(any, LoggerTextFormat.RESET) override fun verbose0(message: String?) = println(message, LoggerTextFormat.RESET)
override fun verbose0(message: String?, e: Throwable?) { override fun verbose0(message: String?, e: Throwable?) {
if (message != null) verbose(message.toString()) if (message != null) verbose(message.toString())
e?.printStackTrace() e?.printStackTrace()
} }
override fun info0(any: Any?) = println(any, LoggerTextFormat.LIGHT_GREEN) override fun info0(message: String?) = println(message, LoggerTextFormat.LIGHT_GREEN)
override fun info0(message: String?, e: Throwable?) { override fun info0(message: String?, e: Throwable?) {
if (message != null) info(message.toString()) if (message != null) info(message.toString())
e?.printStackTrace() e?.printStackTrace()
} }
override fun warning0(any: Any?) = println(any, LoggerTextFormat.LIGHT_RED) override fun warning0(message: String?) = println(message, LoggerTextFormat.LIGHT_RED)
override fun warning0(message: String?, e: Throwable?) { override fun warning0(message: String?, e: Throwable?) {
if (message != null) warning(message.toString()) if (message != null) warning(message.toString())
e?.printStackTrace() e?.printStackTrace()
} }
override fun error0(any: Any?) = println(any, LoggerTextFormat.RED) override fun error0(message: String?) = println(message, LoggerTextFormat.RED)
override fun error0(message: String?, e: Throwable?) { override fun error0(message: String?, e: Throwable?) {
if (message != null) error(message.toString()) if (message != null) error(message.toString())
e?.printStackTrace() e?.printStackTrace()
} }
override fun debug0(any: Any?) = println(any, LoggerTextFormat.LIGHT_CYAN) override fun debug0(message: String?) = println(message, LoggerTextFormat.LIGHT_CYAN)
override fun debug0(message: String?, e: Throwable?) { override fun debug0(message: String?, e: Throwable?) {
if (message != null) debug(message.toString()) if (message != null) debug(message.toString())
e?.printStackTrace() e?.printStackTrace()
} }
private fun println(value: Any?, color: LoggerTextFormat) { private fun println(value: String?, color: LoggerTextFormat) {
val time = SimpleDateFormat("HH:mm:ss", Locale.SIMPLIFIED_CHINESE).format(Date()) val time = SimpleDateFormat("HH:mm:ss", Locale.SIMPLIFIED_CHINESE).format(Date())
if (identity == null) { if (identity == null) {
...@@ -62,6 +62,7 @@ actual open class PlatformLogger @JvmOverloads internal actual constructor( ...@@ -62,6 +62,7 @@ actual open class PlatformLogger @JvmOverloads internal actual constructor(
/** /**
* @author NaturalHG * @author NaturalHG
*/ */
@Suppress("unused")
internal enum class LoggerTextFormat(private val format: String) { internal enum class LoggerTextFormat(private val format: String) {
RESET("\u001b[0m"), RESET("\u001b[0m"),
......
...@@ -10,38 +10,68 @@ ...@@ -10,38 +10,68 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json
import net.mamoe.mirai.utils.io.getRandomByteArray import net.mamoe.mirai.utils.io.getRandomByteArray
import net.mamoe.mirai.utils.io.getRandomString import net.mamoe.mirai.utils.io.getRandomString
import java.io.File
/**
* 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存.
*/
@UseExperimental(UnstableDefault::class)
fun File.loadAsDeviceInfo(context: Context = ContextImpl()): DeviceInfo {
if (!this.exists() || this.length() == 0L) {
return SystemDeviceInfo(context).also {
this.writeText(Json.plain.stringify(SystemDeviceInfo.serializer(), it))
}
}
return Json.nonstrict.parse(DeviceInfoData.serializer(), this.readText()).also {
it.context = context
}
}
@Serializable
@UseExperimental(ExperimentalUnsignedTypes::class) @UseExperimental(ExperimentalUnsignedTypes::class)
actual open class SystemDeviceInfo actual constructor(context: Context) : DeviceInfo(context) { actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
override val display: ByteArray get() = "MIRAI.200122.001".toByteArray() actual constructor(context: Context) : this() {
override val product: ByteArray get() = "mirai".toByteArray() this.context = context
override val device: ByteArray get() = "mirai".toByteArray() }
override val board: ByteArray get() = "mirai".toByteArray()
override val brand: ByteArray get() = "mamoe".toByteArray() @Transient
override val model: ByteArray get() = "mirai".toByteArray() final override lateinit var context: Context
override val bootloader: ByteArray get() = "unknown".toByteArray()
override val fingerprint: ByteArray get() = "mamoe/mirai/mirai:10/MIRAI.200122.001/5891938:user/release-keys".toByteArray() override val display: ByteArray = "MIRAI.200122.001".toByteArray()
override val product: ByteArray = "mirai".toByteArray()
override val device: ByteArray = "mirai".toByteArray()
override val board: ByteArray = "mirai".toByteArray()
override val brand: ByteArray = "mamoe".toByteArray()
override val model: ByteArray = "mirai".toByteArray()
override val bootloader: ByteArray = "unknown".toByteArray()
override val fingerprint: ByteArray = "mamoe/mirai/mirai:10/MIRAI.200122.001/${getRandomString(7, '0'..'9')}:user/release-keys".toByteArray()
override val bootId: ByteArray = ExternalImage.generateUUID(md5(getRandomByteArray(16))).toByteArray() override val bootId: ByteArray = ExternalImage.generateUUID(md5(getRandomByteArray(16))).toByteArray()
override val procVersion: ByteArray get() = "Linux version 3.0.31-g6fb96c9 (android-build@xxx.xxx.xxx.xxx.com)".toByteArray() override val procVersion: ByteArray = "Linux version 3.0.31-${getRandomString(8)} (android-build@xxx.xxx.xxx.xxx.com)".toByteArray()
override val baseBand: ByteArray get() = byteArrayOf() override val baseBand: ByteArray = byteArrayOf()
override val version: DeviceInfo.Version get() = Version override val version: Version = Version
override val simInfo: ByteArray get() = "T-Mobile".toByteArray() override val simInfo: ByteArray = "T-Mobile".toByteArray()
override val osType: ByteArray get() = "android".toByteArray() override val osType: ByteArray = "android".toByteArray()
override val macAddress: ByteArray get() = "02:00:00:00:00:00".toByteArray() override val macAddress: ByteArray = "02:00:00:00:00:00".toByteArray()
override val wifiBSSID: ByteArray? get() = "02:00:00:00:00:00".toByteArray() override val wifiBSSID: ByteArray? = "02:00:00:00:00:00".toByteArray()
override val wifiSSID: ByteArray? get() = "<unknown ssid>".toByteArray() override val wifiSSID: ByteArray? = "<unknown ssid>".toByteArray()
override val imsiMd5: ByteArray get() = md5(getRandomByteArray(16)) override val imsiMd5: ByteArray = md5(getRandomByteArray(16))
override val imei: String get() = getRandomString(15, '0'..'9') override val imei: String = getRandomString(15, '0'..'9')
override val ipAddress: ByteArray get() = localIpAddress().split(".").map { it.toUByte().toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: byteArrayOf() override val ipAddress: ByteArray get() = localIpAddress().split(".").map { it.toUByte().toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: byteArrayOf()
override val androidId: ByteArray get() = display override val androidId: ByteArray get() = display
override val apn: ByteArray get() = "wifi".toByteArray() override val apn: ByteArray = "wifi".toByteArray()
object Version : DeviceInfo.Version { @Serializable
override val incremental: ByteArray get() = "5891938".toByteArray() actual object Version : DeviceInfo.Version {
override val release: ByteArray get() = "10".toByteArray() override val incremental: ByteArray = "5891938".toByteArray()
override val codename: ByteArray get() = "REL".toByteArray() override val release: ByteArray = "10".toByteArray()
override val sdk: Int get() = 29 override val codename: ByteArray = "REL".toByteArray()
override val sdk: Int = 29
} }
} }
\ No newline at end of file
...@@ -32,7 +32,7 @@ actual class PlatformSocket : Closeable { ...@@ -32,7 +32,7 @@ actual class PlatformSocket : Closeable {
actual val isOpen: Boolean actual val isOpen: Boolean
get() = socket.isConnected get() = socket.isConnected
override fun close() { actual override fun close() {
if (::socket.isInitialized) { if (::socket.isInitialized) {
socket.close() socket.close()
} }
......
...@@ -27,6 +27,7 @@ import net.mamoe.mirai.message.data.firstOrNull ...@@ -27,6 +27,7 @@ import net.mamoe.mirai.message.data.firstOrNull
import net.mamoe.mirai.message.sendAsImageTo import net.mamoe.mirai.message.sendAsImageTo
import net.mamoe.mirai.qqandroid.Bot import net.mamoe.mirai.qqandroid.Bot
import net.mamoe.mirai.qqandroid.QQAndroid import net.mamoe.mirai.qqandroid.QQAndroid
import net.mamoe.mirai.utils.FileBasedDeviceInfo
import java.io.File import java.io.File
private fun readTestAccount(): BotAccount? { private fun readTestAccount(): BotAccount? {
...@@ -51,7 +52,7 @@ suspend fun main() { ...@@ -51,7 +52,7 @@ suspend fun main() {
"123456" "123456"
) { ) {
// 覆盖默认的配置 // 覆盖默认的配置
+FileBasedDeviceInfo // 使用 "device.json" 保存设备信息
// networkLoggerSupplier = { SilentLogger } // 禁用网络层输出 // networkLoggerSupplier = { SilentLogger } // 禁用网络层输出
}.alsoLogin() }.alsoLogin()
......
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.japt;
import kotlinx.coroutines.GlobalScope;
import net.mamoe.mirai.event.Event;
import net.mamoe.mirai.event.Listener;
import net.mamoe.mirai.event.ListeningStatus;
import net.mamoe.mirai.event.internal.EventInternalJvmKt;
import net.mamoe.mirai.japt.internal.EventsImplKt;
import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
import java.util.function.Function;
public final class Events {
@NotNull
public static <E extends Event> Listener<E> subscribe(@NotNull Class<E> eventClass, @NotNull Function<E, ListeningStatus> onEvent) {
return EventInternalJvmKt._subscribeEventForJaptOnly(eventClass, GlobalScope.INSTANCE, onEvent);
}
@NotNull
public static <E extends Event> Listener<E> subscribeAlways(@NotNull Class<E> eventClass, @NotNull Consumer<E> onEvent) {
return EventInternalJvmKt._subscribeEventForJaptOnly(eventClass, GlobalScope.INSTANCE, onEvent);
}
@NotNull
public static <E extends Event> E broadcast(@NotNull E event) {
return EventsImplKt.broadcast(event);
}
}
\ No newline at end of file
...@@ -7,16 +7,10 @@ ...@@ -7,16 +7,10 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.event package net.mamoe.mirai.japt.internal
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.broadcast
// TODO 添加更多 internal fun <E : Event> broadcast(e: E): E = runBlocking { e.broadcast() }
/** \ No newline at end of file
* Jvm 调用实现(阻塞)
*/
object Events {
/*
@JvmStatic
fun <E : Event> subscribe(type: Class<E>, handler: suspend (E) -> ListeningStatus) =
runBlocking { type.kotlin.subscribe(handler) }*/
}
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