mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-01 03:50:18 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
3eab26a509
25
CHANGELOG.md
25
CHANGELOG.md
@ -2,11 +2,34 @@
|
||||
|
||||
开发版本. 频繁更新, 不保证高稳定性
|
||||
|
||||
## `0.14.0` 2020/2/13
|
||||
## `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
|
||||
|
||||
### mirai-core
|
||||
|
||||
- **支持 at 全体成员: `AtAll`**
|
||||
|
||||
### mirai-core-qqandroid
|
||||
|
||||
- **支持 `AtAll` 的发送和解析**
|
||||
- **修复某些情况下禁言处理异常**
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# style guide
|
||||
kotlin.code.style=official
|
||||
# config
|
||||
mirai_version=0.14.0
|
||||
mirai_version=0.15.0
|
||||
kotlin.incremental.multiplatform=true
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
# kotlin
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
#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
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -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)
|
||||
|
||||
```
|
||||
@ -308,6 +346,9 @@ Content-Type:multipart/form-data
|
||||
[{
|
||||
"type": "GroupMessage", // 消息类型:GroupMessage或FriendMessage
|
||||
"messageChain": [{ // 消息链,是一个消息对象构成的数组
|
||||
"type": "Source",
|
||||
"uid": 123456
|
||||
},{
|
||||
"type": "Plain",
|
||||
"text": "Miral牛逼"
|
||||
}],
|
||||
@ -343,12 +384,26 @@ Content-Type:multipart/form-data
|
||||
#### 消息是构成消息链的基本对象,目前支持的消息类型有
|
||||
|
||||
+ [x] At,@消息
|
||||
+ [x] AtAll,@全体成员
|
||||
+ [x] Face,表情消息
|
||||
+ [x] Plain,文字消息
|
||||
+ [ ] Image,图片消息
|
||||
+ [x] Image,图片消息
|
||||
+ [ ] Xml,Xml卡片消息
|
||||
+ [ ] 敬请期待
|
||||
|
||||
#### Source
|
||||
|
||||
```json5
|
||||
{
|
||||
"type": "Source",
|
||||
"uid": 123456
|
||||
}
|
||||
```
|
||||
|
||||
| 名字 | 类型 | 说明 |
|
||||
| ---- | ---- | ------------------------------------------------------------ |
|
||||
| uid | Long | 消息的识别号,用于引用回复(Source类型只在群消息中返回,且永远为chain的第一个元素) |
|
||||
|
||||
#### At
|
||||
|
||||
```json5
|
||||
@ -364,6 +419,18 @@ Content-Type:multipart/form-data
|
||||
| target | Long | 群员QQ号 |
|
||||
| display | String | @时显示的文本如:"@Mirai" |
|
||||
|
||||
#### AtAll
|
||||
|
||||
```json5
|
||||
{
|
||||
"type": "AtAll"
|
||||
}
|
||||
```
|
||||
|
||||
| 名字 | 类型 | 说明 |
|
||||
| ------- | ------ | ------------------------- |
|
||||
| - | - | - |
|
||||
|
||||
#### Face
|
||||
|
||||
```json5
|
||||
|
@ -36,9 +36,15 @@ data class UnKnownMessagePacketDTO(val msg: String) : MessagePacketDTO()
|
||||
|
||||
// Message
|
||||
@Serializable
|
||||
@SerialName("Source")
|
||||
data class MessageSourceDTO(val uid: Long) : MessageDTO()
|
||||
@Serializable
|
||||
@SerialName("At")
|
||||
data class AtDTO(val target: Long, val display: String) : MessageDTO()
|
||||
@Serializable
|
||||
@SerialName("AtAll")
|
||||
data class AtAllDTO(val target: Long = 0) : MessageDTO() // target为保留字段
|
||||
@Serializable
|
||||
@SerialName("Face")
|
||||
data class FaceDTO(val faceId: Int) : MessageDTO()
|
||||
@Serializable
|
||||
@ -82,7 +88,9 @@ fun MessageChainDTO.toMessageChain() =
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
fun Message.toDTO() = when (this) {
|
||||
is MessageSource -> MessageSourceDTO(messageUid)
|
||||
is At -> AtDTO(target, display)
|
||||
is AtAll -> AtAllDTO(0L)
|
||||
is Face -> FaceDTO(id.value.toInt())
|
||||
is PlainText -> PlainDTO(stringValue)
|
||||
is Image -> ImageDTO(imageId)
|
||||
@ -93,11 +101,12 @@ fun Message.toDTO() = when (this) {
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
||||
fun MessageDTO.toMessage() = when (this) {
|
||||
is AtDTO -> At(target, display)
|
||||
is AtAllDTO -> AtAll
|
||||
is FaceDTO -> Face(FaceId(faceId.toUByte()))
|
||||
is PlainDTO -> PlainText(text)
|
||||
is ImageDTO -> Image(imageId)
|
||||
is XmlDTO -> XMLMessage(xml)
|
||||
is UnknownMessageDTO -> PlainText("assert cannot reach")
|
||||
is MessageSourceDTO, is UnknownMessageDTO -> PlainText("assert cannot reach")
|
||||
}
|
||||
|
||||
|
||||
|
@ -9,17 +9,32 @@
|
||||
|
||||
package net.mamoe.mirai.api.http.queue
|
||||
|
||||
import net.mamoe.mirai.message.GroupMessage
|
||||
import net.mamoe.mirai.message.MessagePacket
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentLinkedDeque
|
||||
|
||||
class MessageQueue : ConcurrentLinkedDeque<MessagePacket<*, *>>() {
|
||||
|
||||
val quoteCache = ConcurrentHashMap<Long, GroupMessage>()
|
||||
|
||||
fun fetch(size: Int): List<MessagePacket<*, *>> {
|
||||
var count = size
|
||||
quoteCache.clear()
|
||||
val ret = ArrayList<MessagePacket<*, *>>(count)
|
||||
while (!this.isEmpty() && count-- > 0) {
|
||||
ret.add(this.pop())
|
||||
val packet = pop()
|
||||
ret.add(packet)
|
||||
|
||||
if (packet is GroupMessage) {
|
||||
addCache(packet)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
private fun addCache(msg: GroupMessage) {
|
||||
quoteCache[msg.message[MessageSource].messageUid] = msg
|
||||
}
|
||||
}
|
@ -52,6 +52,12 @@ fun Application.messageModule() {
|
||||
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") {
|
||||
val bot = it.session.bot
|
||||
val contact = when {
|
||||
@ -72,12 +78,14 @@ fun Application.messageModule() {
|
||||
if (!SessionManager.containSession(sessionKey)) throw IllegalSessionException
|
||||
val session = try {
|
||||
SessionManager[sessionKey] as AuthedSession
|
||||
} catch (e: TypeCastException) { throw NotVerifiedSessionException }
|
||||
} catch (e: TypeCastException) {
|
||||
throw NotVerifiedSessionException
|
||||
}
|
||||
|
||||
val type = parts.value("type")
|
||||
parts.file("img")?.apply {
|
||||
val image = streamProvider().use {
|
||||
when(type) {
|
||||
when (type) {
|
||||
"group" -> session.bot.groups.toList().random().uploadImage(it)
|
||||
"friend" -> session.bot.qqs.toList().random().uploadImage(it)
|
||||
else -> null
|
||||
|
@ -13,6 +13,7 @@ import kotlinx.serialization.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import net.mamoe.mirai.api.http.data.common.*
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
|
||||
// 解析失败时直接返回null,由路由判断响应400状态
|
||||
@UseExperimental(ImplicitReflectionSerializer::class)
|
||||
@ -50,7 +51,9 @@ object MiraiJson {
|
||||
UnKnownMessagePacketDTO::class with UnKnownMessagePacketDTO.serializer()
|
||||
}
|
||||
polymorphic(MessageDTO.serializer()) {
|
||||
MessageSourceDTO::class with MessageSourceDTO.serializer()
|
||||
AtDTO::class with AtDTO.serializer()
|
||||
AtAllDTO::class with AtAllDTO.serializer()
|
||||
FaceDTO::class with FaceDTO.serializer()
|
||||
PlainDTO::class with PlainDTO.serializer()
|
||||
ImageDTO::class with ImageDTO.serializer()
|
||||
|
@ -20,9 +20,13 @@ import kotlinx.io.core.buildPacket
|
||||
import kotlinx.io.core.use
|
||||
import net.mamoe.mirai.data.MultiPacket
|
||||
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.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.network.WrongPasswordException
|
||||
import net.mamoe.mirai.qqandroid.FriendInfoImpl
|
||||
import net.mamoe.mirai.qqandroid.GroupImpl
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
@ -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.WtLogin
|
||||
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.jvm.Volatile
|
||||
import kotlin.time.ExperimentalTime
|
||||
@ -55,13 +62,48 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
|
||||
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.isOpen) {
|
||||
kotlin.runCatching {
|
||||
registerClientOnline()
|
||||
}.exceptionOrNull() ?: return
|
||||
logger.info("Cannot do fast relogin. Trying slow relogin")
|
||||
}
|
||||
channel.close()
|
||||
}
|
||||
channel = PlatformSocket()
|
||||
channel.connect("113.96.13.208", 8080)
|
||||
this.launch(CoroutineName("Incoming Packet Receiver")) { processReceive() }
|
||||
// TODO: 2020/2/14 连接多个服务器
|
||||
withTimeoutOrNull(3000) {
|
||||
channel.connect("113.96.13.208", 8080)
|
||||
} ?: error("timeout connecting server")
|
||||
startPacketReceiverJobOrKill(CancellationException("reconnect"))
|
||||
|
||||
// logger.info("Trying login")
|
||||
var response: WtLogin.Login.LoginPacketResponse = WtLogin.Login.SubCommand9(bot.client).sendAndExpect()
|
||||
@ -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 -> {
|
||||
response = WtLogin.Login.SubCommand20(
|
||||
@ -112,18 +155,15 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
}
|
||||
|
||||
// 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)
|
||||
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()
|
||||
|
||||
bot.qqs.delegate.clear()
|
||||
@ -172,6 +212,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
launch {
|
||||
try {
|
||||
bot.groups.delegate.addLast(
|
||||
@Suppress("DuplicatedCode")
|
||||
GroupImpl(
|
||||
bot = bot,
|
||||
coroutineContext = bot.coroutineContext,
|
||||
@ -211,14 +252,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
|
||||
joinAll(friendListJob, groupJob)
|
||||
|
||||
this@QQAndroidBotNetworkHandler.launch(CoroutineName("Heartbeat")) {
|
||||
heartbeatJob = this@QQAndroidBotNetworkHandler.launch(CoroutineName("Heartbeat")) {
|
||||
while (this.isActive) {
|
||||
delay(bot.configuration.heartbeatPeriodMillis)
|
||||
val failException = doHeartBeat()
|
||||
if (failException != null) {
|
||||
delay(bot.configuration.firstReconnectDelayMillis)
|
||||
close()
|
||||
bot.tryReinitializeNetworkHandler(failException)
|
||||
BotOfflineEvent.Dropped(bot).broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.qqandroid.io.JceStruct
|
||||
|
||||
class OnlinePushPack {
|
||||
internal class OnlinePushPack {
|
||||
@Serializable
|
||||
internal class DelMsgInfo(
|
||||
@SerialId(0) val fromUin: Long,
|
||||
|
@ -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.buildResponseUniPacket
|
||||
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.read
|
||||
import net.mamoe.mirai.utils.io.readString
|
||||
@ -157,6 +158,8 @@ internal class OnlinePush {
|
||||
val reqPushMsg = decodeUniPacket(OnlinePushPack.SvcReqPushMsg.serializer(), "req")
|
||||
reqPushMsg.vMsgInfos.forEach { msgInfo: MsgInfo ->
|
||||
msgInfo.vMsg!!.read {
|
||||
|
||||
// TODO: 2020/2/13 可能会同时收到多个事件. 使用 map 而不要直接 return
|
||||
when {
|
||||
msgInfo.shMsgType.toInt() == 732 -> {
|
||||
val group = bot.getGroup(this.readUInt().toLong())
|
||||
@ -164,7 +167,11 @@ internal class OnlinePush {
|
||||
|
||||
when (val internalType = this.readShort().toInt()) {
|
||||
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.discardExact(2)
|
||||
val target = this.readUInt().toLong()
|
||||
@ -215,7 +222,7 @@ internal class OnlinePush {
|
||||
4096 -> {
|
||||
val dataBytes = this.readBytes(26)
|
||||
val message = this.readString(this.readByte().toInt())
|
||||
println(dataBytes.toUHexString())
|
||||
// println(dataBytes.toUHexString())
|
||||
|
||||
if (dataBytes[0].toInt() != 59) {
|
||||
return GroupNameChangeEvent(
|
||||
@ -244,7 +251,7 @@ internal class OnlinePush {
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
println("Unknown server messages $message")
|
||||
bot.network.logger.debug { "Unknown server messages $message" }
|
||||
return NoPacket
|
||||
}
|
||||
}
|
||||
@ -255,17 +262,17 @@ internal class OnlinePush {
|
||||
// println(msgInfo.vMsg.toUHexString())
|
||||
// }
|
||||
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 -> {
|
||||
println("unknown shtype ${msgInfo.shMsgType.toInt()}")
|
||||
bot.network.logger.debug { "unknown shtype ${msgInfo.shMsgType.toInt()}" }
|
||||
// val content = msgInfo.vMsg.loadAs(OnlinePushPack.MsgType0x210.serializer())
|
||||
// println(content.contentToString())
|
||||
}
|
||||
else -> {
|
||||
println("unknown shtype ${msgInfo.shMsgType.toInt()}")
|
||||
bot.network.logger.debug { "unknown shtype ${msgInfo.shMsgType.toInt()}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -124,7 +124,7 @@ fun ByteReadPacket.decodeMultiClientToServerPackets() {
|
||||
}
|
||||
|
||||
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) {
|
||||
1 -> it.key.toUByte().contentToString()
|
||||
2 -> it.key.toUShort().contentToString()
|
||||
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
@ -16,40 +16,40 @@ import android.util.Log
|
||||
* 不应该直接构造这个类的实例. 需使用 [DefaultLogger]
|
||||
*/
|
||||
actual open class PlatformLogger actual constructor(override val identity: String?) : MiraiLoggerPlatformBase() {
|
||||
override fun verbose0(any: Any?) {
|
||||
Log.v(identity, any?.toString() ?: "")
|
||||
override fun verbose0(message: String?) {
|
||||
Log.v(identity, message ?: "")
|
||||
}
|
||||
|
||||
override fun verbose0(message: String?, e: Throwable?) {
|
||||
Log.v(identity, message ?: "", e)
|
||||
}
|
||||
|
||||
override fun debug0(any: Any?) {
|
||||
Log.d(identity, any?.toString() ?: "")
|
||||
override fun debug0(message: String?) {
|
||||
Log.d(identity, message ?: "")
|
||||
}
|
||||
|
||||
override fun debug0(message: String?, e: Throwable?) {
|
||||
Log.d(identity, message ?: "", e)
|
||||
}
|
||||
|
||||
override fun info0(any: Any?) {
|
||||
Log.i(identity, any?.toString() ?: "")
|
||||
override fun info0(message: String?) {
|
||||
Log.i(identity, message ?: "")
|
||||
}
|
||||
|
||||
override fun info0(message: String?, e: Throwable?) {
|
||||
Log.i(identity, message ?: "", e)
|
||||
}
|
||||
|
||||
override fun warning0(any: Any?) {
|
||||
Log.w(identity, any?.toString() ?: "")
|
||||
override fun warning0(message: String?) {
|
||||
Log.w(identity, message ?: "")
|
||||
}
|
||||
|
||||
override fun warning0(message: String?, e: Throwable?) {
|
||||
Log.w(identity, message ?: "", e)
|
||||
}
|
||||
|
||||
override fun error0(any: Any?) {
|
||||
Log.e(identity, any?.toString() ?: "")
|
||||
override fun error0(message: String?) {
|
||||
Log.e(identity, message ?: "")
|
||||
}
|
||||
|
||||
override fun error0(message: String?, e: Throwable?) {
|
||||
|
@ -14,13 +14,40 @@ import android.net.wifi.WifiManager
|
||||
import android.os.Build
|
||||
import android.telephony.TelephonyManager
|
||||
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
|
||||
|
||||
/**
|
||||
* 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存.
|
||||
*/
|
||||
@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].
|
||||
* 部分需要权限, 若无权限则会使用默认值.
|
||||
*/
|
||||
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 product: ByteArray get() = Build.PRODUCT.toByteArray()
|
||||
override val device: ByteArray get() = Build.DEVICE.toByteArray()
|
||||
@ -88,7 +115,8 @@ actual open class SystemDeviceInfo actual constructor(context: Context) : Device
|
||||
override val androidId: ByteArray get() = Build.ID.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 release: ByteArray get() = Build.VERSION.RELEASE.toByteArray()
|
||||
override val codename: ByteArray get() = Build.VERSION.CODENAME.toByteArray()
|
||||
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.io.core.IoBuffer
|
||||
import net.mamoe.mirai.Bot
|
||||
|
||||
/**
|
||||
* 在各平台实现的默认的验证码处理器.
|
||||
*/
|
||||
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")
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@ actual class PlatformSocket : Closeable {
|
||||
actual val isOpen: Boolean
|
||||
get() = socket.isConnected
|
||||
|
||||
override fun close() = socket.close()
|
||||
actual override fun close() = socket.close()
|
||||
|
||||
@PublishedApi
|
||||
internal lateinit var writeChannel: BufferedOutputStream
|
||||
|
@ -24,6 +24,7 @@ import net.mamoe.mirai.data.GroupInfo
|
||||
import net.mamoe.mirai.data.MemberInfo
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.io.transferTo
|
||||
|
||||
@ -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()
|
||||
// endregion
|
||||
|
@ -12,10 +12,15 @@
|
||||
package net.mamoe.mirai
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.event.Listener
|
||||
import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.BotEvent
|
||||
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.ForceOfflineException
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.network.closeAndJoin
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.io.logStacktrace
|
||||
@ -33,7 +38,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
private val botJob = SupervisorJob(configuration.parentCoroutineContext[Job])
|
||||
override val coroutineContext: CoroutineContext =
|
||||
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
|
||||
final override val account: BotAccount = account
|
||||
@ -78,60 +83,70 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
@Suppress("PropertyName")
|
||||
internal lateinit var _network: N
|
||||
|
||||
final override suspend fun login() = reinitializeNetworkHandler(null)
|
||||
@Suppress("unused")
|
||||
private val offlineListener: Listener<BotOfflineEvent> = this.subscribeAlways { event ->
|
||||
when (event) {
|
||||
is BotOfflineEvent.Dropped -> {
|
||||
bot.logger.info("Connection dropped or lost by server, retrying login")
|
||||
|
||||
// shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close
|
||||
fun tryReinitializeNetworkHandler(
|
||||
cause: Throwable?
|
||||
): Job = launch {
|
||||
var lastFailedException: Throwable? = null
|
||||
repeat(configuration.reconnectionRetryTimes) {
|
||||
try {
|
||||
reinitializeNetworkHandler(cause)
|
||||
logger.info("Reconnected successfully")
|
||||
return@launch
|
||||
} catch (e: Throwable) {
|
||||
lastFailedException = e
|
||||
delay(configuration.reconnectPeriodMillis)
|
||||
var lastFailedException: Throwable? = null
|
||||
repeat(configuration.reconnectionRetryTimes) {
|
||||
try {
|
||||
network.relogin()
|
||||
logger.info("Reconnected successfully")
|
||||
return@subscribeAlways
|
||||
} catch (e: Throwable) {
|
||||
lastFailedException = e
|
||||
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(
|
||||
cause: Throwable?
|
||||
) {
|
||||
logger.info("BotAccount: $uin")
|
||||
logger.info("Initializing BotNetworkHandler")
|
||||
try {
|
||||
if (::_network.isInitialized) {
|
||||
BotOfflineEvent.Active(this, cause).broadcast()
|
||||
_network.closeAndJoin(cause)
|
||||
suspend fun doRelogin() {
|
||||
while (true) {
|
||||
_network = createNetworkHandler(this.coroutineContext)
|
||||
try {
|
||||
_network.relogin()
|
||||
return
|
||||
} catch (e: LoginFailedException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
network.logger.error(e)
|
||||
_network.closeAndJoin(e)
|
||||
}
|
||||
logger.warning("Login failed. Retrying in 3s...")
|
||||
delay(3000)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error("Cannot close network handler", e)
|
||||
}
|
||||
|
||||
loginLoop@ while (true) {
|
||||
_network = createNetworkHandler(this.coroutineContext)
|
||||
try {
|
||||
_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) {
|
||||
try {
|
||||
_network.init()
|
||||
return@block
|
||||
return
|
||||
} catch (e: Exception) {
|
||||
e.logStacktrace()
|
||||
}
|
||||
@ -141,6 +156,16 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
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
|
||||
@ -153,9 +178,11 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
if (cause == null) {
|
||||
network.close()
|
||||
this.botJob.complete()
|
||||
offlineListener.complete()
|
||||
} else {
|
||||
network.close(cause)
|
||||
this.botJob.completeExceptionally(cause)
|
||||
offlineListener.completeExceptionally(cause)
|
||||
}
|
||||
}
|
||||
groups.delegate.clear()
|
||||
|
@ -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.inSeconds > 0) { "duration must be greater than 0 second" }
|
||||
return this.mute(duration.inSeconds.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun Member.mute(durationSeconds: Long) = this.mute(durationSeconds.toInt())
|
@ -631,4 +631,4 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.TYPE)
|
||||
@DslMarker
|
||||
internal annotation class MessageDsl
|
||||
annotation class MessageDsl
|
@ -16,6 +16,7 @@ import kotlinx.coroutines.GlobalScope
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.event.internal.Handler
|
||||
import net.mamoe.mirai.event.internal.subscribeInternal
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/*
|
||||
@ -96,6 +97,7 @@ interface Listener<in E : Event> : CompletableJob {
|
||||
* @see subscribeGroupMessages 监听群消息 DSL
|
||||
* @see subscribeFriendMessages 监听好友消息 DSL
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
inline fun <reified E : Event> CoroutineScope.subscribe(crossinline handler: suspend E.(E) -> ListeningStatus): Listener<E> =
|
||||
E::class.subscribeInternal(Handler { it.handler(it); })
|
||||
|
||||
@ -107,6 +109,7 @@ inline fun <reified E : Event> CoroutineScope.subscribe(crossinline handler: sus
|
||||
*
|
||||
* @see subscribe 获取更多说明
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
inline fun <reified E : Event> CoroutineScope.subscribeAlways(crossinline listener: suspend E.(E) -> Unit): Listener<E> =
|
||||
E::class.subscribeInternal(Handler { it.listener(it); ListeningStatus.LISTENING })
|
||||
|
||||
@ -118,6 +121,7 @@ inline fun <reified E : Event> CoroutineScope.subscribeAlways(crossinline listen
|
||||
*
|
||||
* @see subscribe 获取更多说明
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
inline fun <reified E : Event> CoroutineScope.subscribeOnce(crossinline listener: suspend E.(E) -> Unit): Listener<E> =
|
||||
E::class.subscribeInternal(Handler { it.listener(it); ListeningStatus.STOPPED })
|
||||
|
||||
@ -129,6 +133,7 @@ inline fun <reified E : Event> CoroutineScope.subscribeOnce(crossinline listener
|
||||
*
|
||||
* @see subscribe 获取更多说明
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
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 })
|
||||
|
||||
@ -141,6 +146,7 @@ inline fun <reified E : Event, T> CoroutineScope.subscribeUntil(valueIfStop: T,
|
||||
*
|
||||
* @see subscribe 获取更多说明
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
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 })
|
||||
|
||||
|
@ -42,19 +42,32 @@ data class BotOnlineEvent(override val bot: Bot) : BotActiveEvent
|
||||
/**
|
||||
* [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
|
||||
|
||||
// region 消息
|
||||
|
@ -23,8 +23,8 @@ import kotlin.reflect.KClass
|
||||
|
||||
val EventLogger: MiraiLoggerWithSwitch = DefaultLogger("Event").withSwitch(false)
|
||||
|
||||
@PublishedApi
|
||||
internal fun <L : Listener<E>, E : Event> KClass<out E>.subscribeInternal(listener: L): L {
|
||||
@MiraiInternalAPI
|
||||
fun <L : Listener<E>, E : Event> KClass<out E>.subscribeInternal(listener: L): L {
|
||||
this.listeners().addLast(listener)
|
||||
return listener
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ package net.mamoe.mirai.message.data
|
||||
*
|
||||
* @see At at 单个群成员
|
||||
*/
|
||||
object AtAll : Message {
|
||||
object AtAll : Message, Message.Key<AtAll> {
|
||||
override fun toString(): String = "@全体成员"
|
||||
|
||||
// 自动为消息补充 " "
|
||||
|
@ -155,6 +155,7 @@ inline fun <reified M : Message> MessageChain.any(): Boolean = this.any { it is
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) {
|
||||
At -> first<At>()
|
||||
AtAll -> first<AtAll>()
|
||||
PlainText -> first<PlainText>()
|
||||
Image -> first<Image>()
|
||||
Face -> first<Face>()
|
||||
|
@ -14,6 +14,8 @@ package net.mamoe.mirai.message.data
|
||||
* 消息源只用于 [QuoteReply]
|
||||
*
|
||||
* `mirai-core-qqandroid`: `net.mamoe.mirai.qqandroid.message.MessageSourceFromMsg`
|
||||
*
|
||||
* @see MessageSource.quote 引用这条消息, 创建 [MessageChain]
|
||||
*/
|
||||
interface MessageSource : Message {
|
||||
companion object : Message.Key<MessageSource>
|
||||
|
@ -33,4 +33,13 @@ fun MessageChain.quote(sender: Member): MessageChain {
|
||||
return QuoteReply(it) + sender.at() + " " // required
|
||||
}
|
||||
error("cannot find MessageSource")
|
||||
}
|
||||
|
||||
/**
|
||||
* 引用这条消息.
|
||||
* 返回 `[QuoteReply] + [At] + [PlainText]`(必要的结构)
|
||||
*/
|
||||
fun MessageSource.quote(sender: Member): MessageChain {
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
return QuoteReply(this) + sender.at() + " " // required
|
||||
}
|
@ -55,12 +55,20 @@ abstract class BotNetworkHandler : CoroutineScope {
|
||||
|
||||
/**
|
||||
* 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回.
|
||||
* 本函数将挂起直到登录成功.
|
||||
*
|
||||
* - 会断开连接并重新登录.
|
||||
* - 不会停止网络层的 [Job].
|
||||
* - 重新登录时不会再次拉取联系人列表.
|
||||
* - 挂起直到登录成功.
|
||||
*
|
||||
* 不要使用这个 API. 请使用 [Bot.login]
|
||||
*
|
||||
* @throws LoginFailedException 登录失败时
|
||||
* @throws WrongPasswordException 密码错误时
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@MiraiInternalAPI
|
||||
abstract suspend fun login()
|
||||
abstract suspend fun relogin()
|
||||
|
||||
/**
|
||||
* 初始化获取好友列表等值.
|
||||
@ -92,6 +100,7 @@ abstract class BotNetworkHandler : CoroutineScope {
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
suspend fun BotNetworkHandler.closeAndJoin(cause: Throwable? = null) {
|
||||
this.close(cause)
|
||||
this.supervisor.join()
|
||||
|
@ -0,0 +1,3 @@
|
||||
package net.mamoe.mirai.network
|
||||
|
||||
class ForceOfflineException(override val message: String?) : RuntimeException()
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.network
|
||||
|
||||
/**
|
||||
* 正常登录失败时抛出
|
||||
*/
|
||||
sealed class LoginFailedException : RuntimeException {
|
||||
constructor() : super()
|
||||
constructor(message: String?) : super(message)
|
||||
constructor(message: String?, cause: Throwable?) : super(message, cause)
|
||||
constructor(cause: Throwable?) : super(cause)
|
||||
}
|
||||
|
||||
class WrongPasswordException(message: String?) : LoginFailedException(message)
|
@ -30,7 +30,7 @@ annotation class MiraiInternalAPI(
|
||||
)
|
||||
|
||||
/**
|
||||
* 标记这个类, 类型, 函数, 属性, 字段, 或构造器为实验性的.
|
||||
* 标记这个类, 类型, 函数, 属性, 字段, 或构造器为实验性的 API.
|
||||
*
|
||||
* 这些 API 不具有稳定性, 且可能会在任意时刻更改.
|
||||
* 不建议在发行版本中使用这些 API.
|
||||
@ -56,7 +56,7 @@ annotation class MiraiDebugAPI(
|
||||
)
|
||||
|
||||
/**
|
||||
* 标记这个 API 是自 Mirai 某个版本起才受支持.
|
||||
* 标记一个自 Mirai 某个版本起才支持的 API.
|
||||
*/
|
||||
@Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
|
@ -13,7 +13,6 @@ import kotlinx.io.core.IoBuffer
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.jvm.JvmStatic
|
||||
|
||||
/**
|
||||
@ -33,58 +32,74 @@ abstract class 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]
|
||||
*/
|
||||
var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||
var parentCoroutineContext: CoroutineContext
|
||||
|
||||
/**
|
||||
* 心跳周期. 过长会导致被服务器断开连接.
|
||||
*/
|
||||
var heartbeatPeriodMillis: Long = 30.secondsToMillis
|
||||
var heartbeatPeriodMillis: Long
|
||||
/**
|
||||
* 每次心跳时等待结果的时间.
|
||||
* 一旦心跳超时, 整个网络服务将会重启 (将消耗约 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 {
|
||||
/**
|
||||
* 默认的配置实例
|
||||
*/
|
||||
@JvmStatic
|
||||
val Default = BotConfiguration()
|
||||
val Default: BotConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
operator fun _NoNetworkLog.unaryPlus()
|
||||
|
||||
/**
|
||||
* 不记录网络层的 log.
|
||||
* 网络层 log 包含包接收, 包发送, 和一些调试用的记录.
|
||||
*/
|
||||
@BotConfigurationDsl
|
||||
val NoNetworkLog: _NoNetworkLog
|
||||
|
||||
@Suppress("ClassName")
|
||||
object _NoNetworkLog
|
||||
}
|
||||
|
||||
@DslMarker
|
||||
annotation class BotConfigurationDsl
|
@ -11,16 +11,15 @@ package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.serialization.SerialId
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.utils.cryptor.contentToString
|
||||
|
||||
/**
|
||||
* 设备信息. 可通过继承 [SystemDeviceInfo] 来在默认的基础上修改
|
||||
*/
|
||||
abstract class DeviceInfo internal constructor(
|
||||
context: Context
|
||||
) {
|
||||
val context: Context by context.unsafeWeakRef()
|
||||
abstract class DeviceInfo {
|
||||
@Transient
|
||||
abstract val context: Context
|
||||
|
||||
abstract val display: ByteArray
|
||||
abstract val product: ByteArray
|
||||
@ -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()
|
||||
*/
|
||||
|
@ -86,7 +86,7 @@ interface MiraiLogger {
|
||||
* 记录一个 `verbose` 级别的日志.
|
||||
* 无关紧要的, 经常大量输出的日志应使用它.
|
||||
*/
|
||||
fun verbose(any: Any?)
|
||||
fun verbose(message: String?)
|
||||
|
||||
fun verbose(e: Throwable?) = verbose(null, e)
|
||||
fun verbose(message: String?, e: Throwable?)
|
||||
@ -94,7 +94,7 @@ interface MiraiLogger {
|
||||
/**
|
||||
* 记录一个 _调试_ 级别的日志.
|
||||
*/
|
||||
fun debug(any: Any?)
|
||||
fun debug(message: String?)
|
||||
|
||||
fun debug(e: Throwable?) = debug(null, e)
|
||||
fun debug(message: String?, e: Throwable?)
|
||||
@ -103,7 +103,7 @@ interface MiraiLogger {
|
||||
/**
|
||||
* 记录一个 _信息_ 级别的日志.
|
||||
*/
|
||||
fun info(any: Any?)
|
||||
fun info(message: String?)
|
||||
|
||||
fun info(e: Throwable?) = info(null, e)
|
||||
fun info(message: String?, e: Throwable?)
|
||||
@ -112,7 +112,7 @@ interface MiraiLogger {
|
||||
/**
|
||||
* 记录一个 _警告_ 级别的日志.
|
||||
*/
|
||||
fun warning(any: Any?)
|
||||
fun warning(message: String?)
|
||||
|
||||
fun warning(e: Throwable?) = warning(null, e)
|
||||
fun warning(message: String?, e: Throwable?)
|
||||
@ -121,7 +121,7 @@ interface MiraiLogger {
|
||||
/**
|
||||
* 记录一个 _错误_ 级别的日志.
|
||||
*/
|
||||
fun error(e: Any?)
|
||||
fun error(message: String?)
|
||||
|
||||
fun error(e: Throwable?) = error(null, e)
|
||||
fun error(message: String?, e: Throwable?)
|
||||
@ -149,6 +149,47 @@ interface MiraiLogger {
|
||||
operator fun plusAssign(follower: MiraiLogger)
|
||||
}
|
||||
|
||||
|
||||
inline fun MiraiLogger.verbose(lazyMessage: () -> String) {
|
||||
if (this is MiraiLoggerWithSwitch && switch) verbose(lazyMessage())
|
||||
}
|
||||
|
||||
inline fun MiraiLogger.verbose(lazyMessage: () -> String, e: Throwable?) {
|
||||
if (this is MiraiLoggerWithSwitch && switch) verbose(lazyMessage(), e)
|
||||
}
|
||||
|
||||
inline fun MiraiLogger.debug(lazyMessage: () -> String?) {
|
||||
if (this is MiraiLoggerWithSwitch && switch) debug(lazyMessage())
|
||||
}
|
||||
|
||||
inline fun MiraiLogger.debug(lazyMessage: () -> String?, e: Throwable?) {
|
||||
if (this is MiraiLoggerWithSwitch && switch) debug(lazyMessage(), e)
|
||||
}
|
||||
|
||||
inline fun MiraiLogger.info(lazyMessage: () -> String?) {
|
||||
if (this is MiraiLoggerWithSwitch && switch) info(lazyMessage())
|
||||
}
|
||||
|
||||
inline fun MiraiLogger.info(lazyMessage: () -> String?, e: Throwable?) {
|
||||
if (this is MiraiLoggerWithSwitch && switch) info(lazyMessage(), e)
|
||||
}
|
||||
|
||||
inline fun MiraiLogger.warning(lazyMessage: () -> String?) {
|
||||
if (this is MiraiLoggerWithSwitch && switch) warning(lazyMessage())
|
||||
}
|
||||
|
||||
inline fun MiraiLogger.warning(lazyMessage: () -> String?, e: Throwable?) {
|
||||
if (this is MiraiLoggerWithSwitch && switch) warning(lazyMessage(), e)
|
||||
}
|
||||
|
||||
inline fun MiraiLogger.error(lazyMessage: () -> String?) {
|
||||
if (this is MiraiLoggerWithSwitch && switch) error(lazyMessage())
|
||||
}
|
||||
|
||||
inline fun MiraiLogger.error(lazyMessage: () -> String?, e: Throwable?) {
|
||||
if (this is MiraiLoggerWithSwitch && switch) error(lazyMessage(), e)
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前平台的默认的日志记录器.
|
||||
* 在 _JVM 控制台_ 端的实现为 [println]
|
||||
@ -165,11 +206,11 @@ expect open class PlatformLogger @JvmOverloads internal constructor(identity: St
|
||||
object SilentLogger : PlatformLogger() {
|
||||
override val identity: String? = null
|
||||
|
||||
override fun error0(any: Any?) = Unit
|
||||
override fun debug0(any: Any?) = Unit
|
||||
override fun warning0(any: Any?) = Unit
|
||||
override fun verbose0(any: Any?) = Unit
|
||||
override fun info0(any: Any?) = Unit
|
||||
override fun error0(message: String?) = Unit
|
||||
override fun debug0(message: String?) = Unit
|
||||
override fun warning0(message: String?) = Unit
|
||||
override fun verbose0(message: String?) = Unit
|
||||
override fun info0(message: String?) = Unit
|
||||
}
|
||||
|
||||
/**
|
||||
@ -180,15 +221,15 @@ class SimpleLogger(override val identity: String?, private val logger: (String?,
|
||||
operator fun invoke(logger: (String?, Throwable?) -> Unit): SimpleLogger = SimpleLogger(null, logger)
|
||||
}
|
||||
|
||||
override fun verbose0(any: Any?) = logger(any?.toString(), null)
|
||||
override fun verbose0(message: String?) = logger(message, null)
|
||||
override fun verbose0(message: String?, e: Throwable?) = logger(message, e)
|
||||
override fun debug0(any: Any?) = logger(any?.toString(), null)
|
||||
override fun debug0(message: String?) = logger(message, null)
|
||||
override fun debug0(message: String?, e: Throwable?) = logger(message, e)
|
||||
override fun info0(any: Any?) = logger(any?.toString(), null)
|
||||
override fun info0(message: String?) = logger(message, null)
|
||||
override fun info0(message: String?, e: Throwable?) = logger(message, e)
|
||||
override fun warning0(any: Any?) = logger(any?.toString(), null)
|
||||
override fun warning0(message: String?) = logger(message, null)
|
||||
override fun warning0(message: String?, e: Throwable?) = logger(message, e)
|
||||
override fun error0(any: Any?) = logger(any?.toString(), null)
|
||||
override fun error0(message: String?) = logger(message, null)
|
||||
override fun error0(message: String?, e: Throwable?) = logger(message, e)
|
||||
}
|
||||
|
||||
@ -218,56 +259,16 @@ class MiraiLoggerWithSwitch internal constructor(private val delegate: MiraiLogg
|
||||
switch = false
|
||||
}
|
||||
|
||||
override fun verbose0(any: Any?) = if (switch) delegate.verbose(any) else Unit
|
||||
override fun verbose0(message: String?) = if (switch) delegate.verbose(message) else Unit
|
||||
override fun verbose0(message: String?, e: Throwable?) = if (switch) delegate.verbose(message, e) else Unit
|
||||
override fun debug0(any: Any?) = if (switch) delegate.debug(any) else Unit
|
||||
override fun debug0(message: String?) = if (switch) delegate.debug(message) else Unit
|
||||
override fun debug0(message: String?, e: Throwable?) = if (switch) delegate.debug(message, e) else Unit
|
||||
override fun info0(any: Any?) = if (switch) delegate.info(any) else Unit
|
||||
override fun info0(message: String?) = if (switch) delegate.info(message) else Unit
|
||||
override fun info0(message: String?, e: Throwable?) = if (switch) delegate.info(message, e) else Unit
|
||||
override fun warning0(any: Any?) = if (switch) delegate.warning(any) else Unit
|
||||
override fun warning0(message: String?) = if (switch) delegate.warning(message) else Unit
|
||||
override fun warning0(message: String?, e: Throwable?) = if (switch) delegate.warning(message, e) else Unit
|
||||
override fun error0(any: Any?) = if (switch) delegate.error(any) else Unit
|
||||
override fun error0(message: String?) = if (switch) delegate.error(message) else Unit
|
||||
override fun error0(message: String?, e: Throwable?) = if (switch) delegate.error(message, e) else Unit
|
||||
|
||||
inline fun verbose(lazyMessage: () -> String) {
|
||||
if (switch) verbose(lazyMessage())
|
||||
}
|
||||
|
||||
inline fun verbose(lazyMessage: () -> String, e: Throwable?) {
|
||||
if (switch) verbose(lazyMessage(), e)
|
||||
}
|
||||
|
||||
inline fun debug(lazyMessage: () -> Any?) {
|
||||
if (switch) debug(lazyMessage())
|
||||
}
|
||||
|
||||
inline fun debug(lazyMessage: () -> String?, e: Throwable?) {
|
||||
if (switch) debug(lazyMessage(), e)
|
||||
}
|
||||
|
||||
inline fun info(lazyMessage: () -> Any?) {
|
||||
if (switch) info(lazyMessage())
|
||||
}
|
||||
|
||||
inline fun info(lazyMessage: () -> String?, e: Throwable?) {
|
||||
if (switch) info(lazyMessage(), e)
|
||||
}
|
||||
|
||||
inline fun warning(lazyMessage: () -> Any?) {
|
||||
if (switch) warning(lazyMessage())
|
||||
}
|
||||
|
||||
inline fun warning(lazyMessage: () -> String?, e: Throwable?) {
|
||||
if (switch) warning(lazyMessage(), e)
|
||||
}
|
||||
|
||||
inline fun error(lazyMessage: () -> Any?) {
|
||||
if (switch) error(lazyMessage())
|
||||
}
|
||||
|
||||
inline fun error(lazyMessage: () -> String?, e: Throwable?) {
|
||||
if (switch) error(lazyMessage(), e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -280,9 +281,9 @@ class MiraiLoggerWithSwitch internal constructor(private val delegate: MiraiLogg
|
||||
abstract class MiraiLoggerPlatformBase : MiraiLogger {
|
||||
final override var follower: MiraiLogger? = null
|
||||
|
||||
final override fun verbose(any: Any?) {
|
||||
follower?.verbose(any)
|
||||
verbose0(any)
|
||||
final override fun verbose(message: String?) {
|
||||
follower?.verbose(message)
|
||||
verbose0(message)
|
||||
}
|
||||
|
||||
final override fun verbose(message: String?, e: Throwable?) {
|
||||
@ -290,9 +291,9 @@ abstract class MiraiLoggerPlatformBase : MiraiLogger {
|
||||
verbose0(message, e)
|
||||
}
|
||||
|
||||
final override fun debug(any: Any?) {
|
||||
follower?.debug(any)
|
||||
debug0(any)
|
||||
final override fun debug(message: String?) {
|
||||
follower?.debug(message)
|
||||
debug0(message)
|
||||
}
|
||||
|
||||
final override fun debug(message: String?, e: Throwable?) {
|
||||
@ -300,9 +301,9 @@ abstract class MiraiLoggerPlatformBase : MiraiLogger {
|
||||
debug0(message, e)
|
||||
}
|
||||
|
||||
final override fun info(any: Any?) {
|
||||
follower?.info(any)
|
||||
info0(any)
|
||||
final override fun info(message: String?) {
|
||||
follower?.info(message)
|
||||
info0(message)
|
||||
}
|
||||
|
||||
final override fun info(message: String?, e: Throwable?) {
|
||||
@ -310,9 +311,9 @@ abstract class MiraiLoggerPlatformBase : MiraiLogger {
|
||||
info0(message, e)
|
||||
}
|
||||
|
||||
final override fun warning(any: Any?) {
|
||||
follower?.warning(any)
|
||||
warning0(any)
|
||||
final override fun warning(message: String?) {
|
||||
follower?.warning(message)
|
||||
warning0(message)
|
||||
}
|
||||
|
||||
final override fun warning(message: String?, e: Throwable?) {
|
||||
@ -320,9 +321,9 @@ abstract class MiraiLoggerPlatformBase : MiraiLogger {
|
||||
warning0(message, e)
|
||||
}
|
||||
|
||||
final override fun error(e: Any?) {
|
||||
follower?.error(e)
|
||||
error0(e)
|
||||
final override fun error(message: String?) {
|
||||
follower?.error(message)
|
||||
error0(message)
|
||||
}
|
||||
|
||||
final override fun error(message: String?, e: Throwable?) {
|
||||
@ -330,15 +331,15 @@ abstract class MiraiLoggerPlatformBase : MiraiLogger {
|
||||
error0(message, e)
|
||||
}
|
||||
|
||||
protected abstract fun verbose0(any: Any?)
|
||||
protected abstract fun verbose0(message: String?)
|
||||
protected abstract fun verbose0(message: String?, e: Throwable?)
|
||||
protected abstract fun debug0(any: Any?)
|
||||
protected abstract fun debug0(message: String?)
|
||||
protected abstract fun debug0(message: String?, e: Throwable?)
|
||||
protected abstract fun info0(any: Any?)
|
||||
protected abstract fun info0(message: String?)
|
||||
protected abstract fun info0(message: String?, e: Throwable?)
|
||||
protected abstract fun warning0(any: Any?)
|
||||
protected abstract fun warning0(message: String?)
|
||||
protected abstract fun warning0(message: String?, e: Throwable?)
|
||||
protected abstract fun error0(any: Any?)
|
||||
protected abstract fun error0(message: String?)
|
||||
protected abstract fun error0(message: String?, e: Throwable?)
|
||||
|
||||
override operator fun <T : MiraiLogger> plus(follower: T): T {
|
||||
|
@ -9,13 +9,15 @@
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import net.mamoe.mirai.utils.Context
|
||||
import net.mamoe.mirai.utils.DeviceInfo
|
||||
|
||||
/**
|
||||
* 通过本机信息来获取设备信息.
|
||||
*
|
||||
* Android: 获取手机信息, 与 QQ 官方相同.
|
||||
* JVM: 部分为常量, 部分为随机
|
||||
*/
|
||||
open expect class SystemDeviceInfo(context: Context) : DeviceInfo
|
||||
expect open class SystemDeviceInfo : DeviceInfo {
|
||||
constructor()
|
||||
constructor(context: Context)
|
||||
|
||||
object Version : DeviceInfo.Version
|
||||
}
|
@ -15,11 +15,13 @@ package net.mamoe.mirai.utils.io
|
||||
|
||||
import kotlinx.io.core.*
|
||||
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.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.js.JsName
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
@ -30,9 +32,6 @@ val DebugLogger : MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwit
|
||||
@MiraiDebugAPI("Unstable")
|
||||
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.")
|
||||
inline fun String.debugPrintThis(name: String): String {
|
||||
DebugLogger.debug("$name=$this")
|
||||
|
@ -81,7 +81,7 @@ inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteAr
|
||||
return this[tag] ?: error(lazyMessage(tag))
|
||||
}
|
||||
|
||||
@MiraiDebugAPI
|
||||
@MiraiInternalAPI
|
||||
inline fun Input.readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap = readTLVMap(true, tagSize, suppressDuplication)
|
||||
|
||||
@MiraiDebugAPI
|
||||
|
@ -11,7 +11,6 @@ package net.mamoe.mirai.utils.io
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.Closeable
|
||||
import kotlinx.io.errors.IOException
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
|
||||
/**
|
||||
@ -37,4 +36,6 @@ expect class PlatformSocket() : Closeable {
|
||||
suspend fun read(): ByteReadPacket
|
||||
|
||||
val isOpen: Boolean
|
||||
|
||||
override fun close()
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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; })
|
||||
}
|
@ -22,12 +22,14 @@ import kotlinx.coroutines.withContext
|
||||
import kotlinx.io.core.IoBuffer
|
||||
import kotlinx.io.core.use
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
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
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
/**
|
||||
* 平台默认的验证码识别器.
|
||||
@ -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
|
||||
}
|
@ -18,37 +18,37 @@ import java.util.*
|
||||
actual open class PlatformLogger @JvmOverloads internal actual constructor(
|
||||
override val identity: String?
|
||||
) : 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?) {
|
||||
if (message != null) verbose(message.toString())
|
||||
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?) {
|
||||
if (message != null) info(message.toString())
|
||||
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?) {
|
||||
if (message != null) warning(message.toString())
|
||||
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?) {
|
||||
if (message != null) error(message.toString())
|
||||
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?) {
|
||||
if (message != null) debug(message.toString())
|
||||
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())
|
||||
|
||||
if (identity == null) {
|
||||
@ -62,6 +62,7 @@ actual open class PlatformLogger @JvmOverloads internal actual constructor(
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
@Suppress("unused")
|
||||
internal enum class LoggerTextFormat(private val format: String) {
|
||||
RESET("\u001b[0m"),
|
||||
|
||||
|
@ -10,38 +10,68 @@
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
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.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)
|
||||
actual open class SystemDeviceInfo actual constructor(context: Context) : DeviceInfo(context) {
|
||||
override val display: ByteArray get() = "MIRAI.200122.001".toByteArray()
|
||||
override val product: ByteArray get() = "mirai".toByteArray()
|
||||
override val device: ByteArray get() = "mirai".toByteArray()
|
||||
override val board: ByteArray get() = "mirai".toByteArray()
|
||||
override val brand: ByteArray get() = "mamoe".toByteArray()
|
||||
override val model: ByteArray get() = "mirai".toByteArray()
|
||||
override val bootloader: ByteArray get() = "unknown".toByteArray()
|
||||
override val fingerprint: ByteArray get() = "mamoe/mirai/mirai:10/MIRAI.200122.001/5891938:user/release-keys".toByteArray()
|
||||
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 = "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 procVersion: ByteArray get() = "Linux version 3.0.31-g6fb96c9 (android-build@xxx.xxx.xxx.xxx.com)".toByteArray()
|
||||
override val baseBand: ByteArray get() = byteArrayOf()
|
||||
override val version: DeviceInfo.Version get() = Version
|
||||
override val simInfo: ByteArray get() = "T-Mobile".toByteArray()
|
||||
override val osType: ByteArray get() = "android".toByteArray()
|
||||
override val macAddress: ByteArray get() = "02:00:00:00:00:00".toByteArray()
|
||||
override val wifiBSSID: ByteArray? get() = "02:00:00:00:00:00".toByteArray()
|
||||
override val wifiSSID: ByteArray? get() = "<unknown ssid>".toByteArray()
|
||||
override val imsiMd5: ByteArray get() = md5(getRandomByteArray(16))
|
||||
override val imei: String get() = getRandomString(15, '0'..'9')
|
||||
override val procVersion: ByteArray = "Linux version 3.0.31-${getRandomString(8)} (android-build@xxx.xxx.xxx.xxx.com)".toByteArray()
|
||||
override val baseBand: ByteArray = byteArrayOf()
|
||||
override val version: Version = Version
|
||||
override val simInfo: ByteArray = "T-Mobile".toByteArray()
|
||||
override val osType: ByteArray = "android".toByteArray()
|
||||
override val macAddress: ByteArray = "02:00:00:00:00:00".toByteArray()
|
||||
override val wifiBSSID: ByteArray? = "02:00:00:00:00:00".toByteArray()
|
||||
override val wifiSSID: ByteArray? = "<unknown ssid>".toByteArray()
|
||||
override val imsiMd5: ByteArray = md5(getRandomByteArray(16))
|
||||
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 androidId: ByteArray get() = display
|
||||
override val apn: ByteArray get() = "wifi".toByteArray()
|
||||
override val apn: ByteArray = "wifi".toByteArray()
|
||||
|
||||
object Version : DeviceInfo.Version {
|
||||
override val incremental: ByteArray get() = "5891938".toByteArray()
|
||||
override val release: ByteArray get() = "10".toByteArray()
|
||||
override val codename: ByteArray get() = "REL".toByteArray()
|
||||
override val sdk: Int get() = 29
|
||||
@Serializable
|
||||
actual object Version : DeviceInfo.Version {
|
||||
override val incremental: ByteArray = "5891938".toByteArray()
|
||||
override val release: ByteArray = "10".toByteArray()
|
||||
override val codename: ByteArray = "REL".toByteArray()
|
||||
override val sdk: Int = 29
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@ actual class PlatformSocket : Closeable {
|
||||
actual val isOpen: Boolean
|
||||
get() = socket.isConnected
|
||||
|
||||
override fun close() {
|
||||
actual override fun close() {
|
||||
if (::socket.isInitialized) {
|
||||
socket.close()
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import net.mamoe.mirai.message.data.firstOrNull
|
||||
import net.mamoe.mirai.message.sendAsImageTo
|
||||
import net.mamoe.mirai.qqandroid.Bot
|
||||
import net.mamoe.mirai.qqandroid.QQAndroid
|
||||
import net.mamoe.mirai.utils.FileBasedDeviceInfo
|
||||
import java.io.File
|
||||
|
||||
private fun readTestAccount(): BotAccount? {
|
||||
@ -51,7 +52,7 @@ suspend fun main() {
|
||||
"123456"
|
||||
) {
|
||||
// 覆盖默认的配置
|
||||
|
||||
+FileBasedDeviceInfo // 使用 "device.json" 保存设备信息
|
||||
// networkLoggerSupplier = { SilentLogger } // 禁用网络层输出
|
||||
}.alsoLogin()
|
||||
|
||||
|
39
mirai-japt/src/main/java/net/mamoe/mirai/japt/Events.java
Normal file
39
mirai-japt/src/main/java/net/mamoe/mirai/japt/Events.java
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
@ -7,16 +7,10 @@
|
||||
* 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 添加更多
|
||||
/**
|
||||
* Jvm 调用实现(阻塞)
|
||||
*/
|
||||
object Events {
|
||||
/*
|
||||
@JvmStatic
|
||||
fun <E : Event> subscribe(type: Class<E>, handler: suspend (E) -> ListeningStatus) =
|
||||
runBlocking { type.kotlin.subscribe(handler) }*/
|
||||
}
|
||||
internal fun <E : Event> broadcast(e: E): E = runBlocking { e.broadcast() }
|
Loading…
Reference in New Issue
Block a user