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

Merge remote-tracking branch 'origin/master'

parents b7fd77b6 35e9138c
......@@ -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
### mirai-core
- **支持 at 全体成员: `AtAll`**
### mirai-core-qqandroid
- **支持 `AtAll` 的发送和解析**
- **修复某些情况下禁言处理异常**
......
# 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
......
#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
}
}
\ No newline at end of file
......@@ -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()
// TODO: 2020/2/14 连接多个服务器
withTimeoutOrNull(3000) {
channel.connect("113.96.13.208", 8080)
this.launch(CoroutineName("Incoming Packet Receiver")) { processReceive() }
} ?: 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()
}
@UseExperimental(MiraiExperimentalAPI::class, ExperimentalTime::class)
override suspend fun init(): Unit = coroutineScope {
this@QQAndroidBotNetworkHandler.subscribeAlways<BotOfflineEvent> {
if (this@QQAndroidBotNetworkHandler.bot == this.bot) {
logger.error("被挤下线")
close()
}
private suspend fun registerClientOnline() {
StatSvc.Register(bot.client).sendAndExpect<StatSvc.Register.Response>()
}
@UseExperimental(MiraiExperimentalAPI::class, ExperimentalTime::class)
override suspend fun init(): Unit = coroutineScope {
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()}" }
}
}
}
......
......@@ -29,7 +29,7 @@ import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.io.discardExact
import net.mamoe.mirai.utils.md5
internal class WtLogin{
internal class WtLogin {
/**
* OicqRequest
*/
......@@ -359,15 +359,25 @@ internal class WtLogin{
@InternalAPI
@UseExperimental(MiraiDebugAPI::class)
private fun onSolveLoginCaptcha(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Captcha {
/*
java.lang.IllegalStateException: UNKNOWN CAPTCHA QUESTION:
00 00 00 01 0A 70 69 63 5F 72 65 61 73 6F 6E 00 00 00 7F 51 51 E5 AE 89 E5 85 A8 E4 B8 AD E5 BF 83 E6 B8 A9 E9 A6 A8 E6 8F 90 E7 A4 BA EF BC 9A E5 BD 93 E5 89 8D E7 BD 91 E7 BB 9C E7 8E AF E5 A2 83 E6 9C 89 E5 8D B1 E5 8F 8A 51 51 E5 AE 89 E5 85 A8 E7 9A 84 E8 A1 8C E4 B8 BA EF BC 8C E4 B8 BA E4 BA 86 E4 BD A0 E7 9A 84 E5 B8 90 E5 8F B7 E5 AE 89 E5 85 A8 EF BC 8C E8 AF B7 E4 BD A0 E5 A1 AB E5 86 99 E9 AA 8C E8 AF 81 E7 A0 81 E3 80 82,
tlvMap={
0x00000104(260)=41 74 78 43 52 56 6A 46 61 79 33 76 56 5A 66 56 47 50 4B 63 4F 59 54 6D 76 32 4A 67 35 35 76 6A 51 67 3D 3D,
0x00000105(261)=00 04 41 7A 55 47 08 88 FF D8 FF E0 00 10 4A 46 49 46 00 01 01 00 00 01 00 01 00 00 FF FE 00 22 39 35 30 35 34 31 36 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 88 5C DF 07 FF 7F 00 00 FF DB 00 43 00 0A 07 07 08 07 06 0A 08 08 08 0B 0A 0A 0B 0E 18 10 0E 0D 0D 0E 1D 15 16 11 18 23 1F 25 24 22 1F 22 21 26 2B 37 2F 26 29 34 29 21 22 30 41 31 34 39 3B 3E 3E 3E 25 2E 44 49 43 3C 48 37 3D 3E 3B FF DB 00 43 01 0A 0B 0B 0E 0D 0E 1C 10 10 1C 3B 28 22 28 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B FF C0 00 11 08 00 35 00 82 03 01 22 00 02 11 01 03 11 01 FF C4 00 1F 00 00 01 05 01 01 01 01 01 01 00 00 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B FF C4 00 B5 10 00 02 01 03 03 02 04 03 05 05 04 04 00 00 01 7D 01 02 03 00 04 11 05 12 21 31 41 06 13 51 61 07 22 71 14 32 81 91 A1 08 23 42 B1 C1 15 52 D1 F0 24 33 62 72 82 09 0A 16 17 18 19 1A 25 26 27 28 29 2A 34 35 36 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 77 78 79 7A 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2 D3 D4 D5 D6 D7 D8 D9 DA E1 E2 E3 E4 E5 E6 E7 E8 E9 EA F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FF C4 00 1F 01 00 03 01 01 01 01 01 01 01 01 01 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B FF C4 00 B5 11 00 02 01 02 04 04 03 04 07 05 04 04 00 01 02 77 00 01 02 03 11 04 05 21 31 06 12 41 51 07 61 71 13 22 32 81 08 14 42 91 A1 B1 C1 09 23 33 52 F0 15 62 72 D1 0A 16 24 34 E1 25 F1 17 18 19 1A 26 27 28 29 2A 35 36 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 77 78 79 7A 82 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2 D3 D4 D5 D6 D7 D8 D9 DA E2 E3 E4 E5 E6 E7 E8 E9 EA F2 F3 F4 F5 F6 F7 F8 F9 FA FF DA 00 0C 03 01 00 02 11 03 11 00 3F 00 F5 EA 42 40 EA 40 AC CF 10 E9 D7 BA A6 96 6D AC 6F 05 AC 85 C3 12 77 00 EA 3F 87 2A 41 19 E3 91 5C BE 9D A7 E8 D2 5C B6 99 AB E9 CF 6B AA 85 3B 16 7B A9 5E 29 FD D1 8B 72 3D BA D7 4D 2A 31 9C 1C 9B F9 25 7F 9E E8 CA 53 69 DA C7 72 F2 C7 1A EE 92 45 45 F5 66 00 54 0B A9 D8 3C CB 0A DF 5B 34 8C 70 A8 25 5D C7 F0 CD 72 36 3A 22 18 2D 3F B5 EC 64 B4 8B 4A DC B2 24 8C B3 41 3E EE EA 0E 4F 24 8F A7 4A DE B6 F0 FD 9B 5D 45 76 F6 16 D6 C2 16 DF 0C 11 42 80 A9 EC 58 81 C9 F6 1C 0F 7A 73 A3 4A 1B CA FF 00 D7 A8 94 A4 FA 0F 6D 7C 16 B9 36 FA 75 D5 CC 56 AE D1 CB 32 49 0A AA B2 FD E0 77 C8 A4 63 DC 0E 39 1C 10 6A 7B 9D 5E 3B 5D 32 1B E9 2D AE 07 9D B0 2C 25 42 B8 66 E8 AD B8 85 53 DB 92 06 78 EA 40 34 0D F5 AE 16 39 60 86 6B 7D 55 F3 70 F1 96 C0 49 41 48 49 04 74 65 40 87 91 83 8F 5A 9B 4F 7B B8 ED 9A C6 ED 66 91 AC 64 09 E7 1D DF E9 0A 30 63 3B 8E 32 C7 20 37 6C AB 67 82 28 74 E2 B5 E5 FE BF E1 FD 37 40 A4 FB 96 ED 35 19 6E 2E 8D BC BA 65 DD A1 D8 5F 74 CD 11 5E A0 63 E4 76 20 F3 DF D0 D3 2F 7C 41 A4 69 C4 AD D6 A1 02 3A F5 8C 36 E7 FF 00 BE 46 4F E9 58 D7 A8 FA D4 36 33 C5 21 B3 1A D5 98 86 42 06 FD AE BF BD 55 23 8E 02 F9 EA 7A 75 FA 54 96 96 EB 1F 85 2F E2 B4 B4 B7 86 F6 DC 4B 14 AB 6B 17 97 BD D7 38 C6 39 F9 97 07 FE 05 55 EC 61 A3 97 A5 97 DD 7B BB F5 0E 77 B2 34 35 8D 7A 0D 2F 4D 82 F5 7C B9 56 E5 D5 62 67 93 CB 8F 90 58 16 62 0E 06 07 A1 ED 55 2E 24 F1 16 EB 77 7B DB 0B 61 2C EA B1 C3 0C 46 52 E3 A9 05 D8 8F E1 0C 78 03 A5 3B FE 12 7D 1A EE D0 43 69 1C D7 EB 24 7B 7C 8B 7B 76 7F 94 8E 87 8C 0F C4 D1 73 6D 35 A7 83 E0 DE 18 CD A7 C5 14 D8 E0 B7 EE C8 62 BC 77 21 4A FE 34 E3 1E 4B 27 1B 36 FA FF 00 97 90 9B BE CC DF A2 A8 DF 6B 5A 76 98 B1 35 E5 D2 45 E7 0C C6 30 58 B8 F6 03 93 D4 55 55 F1 56 8E CB 21 FB 44 8A 23 88 CA 7C CB 79 13 2A 08 1C 6E 51 9E 58 0C 0F 51 5C CA 8D 49 2B A8 BF B8 D1 CA 2B 76 6C 51 58 47 C5 0A 0A 96 D1 75 65 8C BA AF 98 D6 E1 46 58 80 38 2D 9E A4 76 A8 A4 F1 45 CB FD AD EC 34 77 B9 82 D2 46 8E 49 9E E5 23 1B 97 A8 DB CB 7D 38 E7 8A A5 87 A8 FA 7E 28 5E D2 27 45 45 47 0B BC 90 46 F2 46 62 76 50 59 09 CE D3 8E 46 7D AA 4A C4 B0 A2 8A 29 01 15 C5 C4 36 90 34 F7 12 AC 51 20 CB 3B 9C 01 59 1A 91 B3 D4 AD 3E D9 7B 6D 8B 1B 43 E7 87 96 32 1D 8A F3 95 1D 40 FE 7F 4A D2 BF D3 AD 75 3B 71 6F 77 19 92 30 C1 C0 0E CB C8 E8 72 08 35 53 FE 11 8D 14 FD EB 14 93 FE BA 33 3F F3 35 D1 4A 54 E3 AB 6E FF 00 D7 99 12 52 7A 74 32 8D FF 00 F6 CD C1 B1 D4 52 C6 EB 49 BD 83 CE 8E 58 A4 2A 62 00 F0 1B 3D F3 D0 8C 74 3E 95 9D 71 AC DC 78 6A 17 B3 6D 45 75 3D 3D C1 54 9A 2B 84 FB 5D B8 3C 63 04 FC E7 D0 FF 00 FA AB AD 7D 13 4A 94 C6 64 D3 AD 5C C6 81 13 74 4A 76 A8 E8 07 1D 2A 58 B4 FB 18 08 30 D9 C1 1E 3F B9 12 8F E9 5B AC 45 25 A7 2D D7 6F D7 D4 87 09 3E BA 99 D6 B6 F3 EB 16 BA 94 3A 95 9C 90 59 DD 95 F2 12 57 53 22 A1 8D 41 E0 64 2E 18 12 39 CE 49 E0 63 9B 3A 58 BF 96 18 BF B4 E2 D9 35 B6 50 B6 50 89 98 71 E6 80 07 CA 08 E8 38 FB CC 08 E0 13 A3 45 72 CA AD D5 AD FD 6D F8 F5 34 51 B1 CB B4 7A 89 D2 3E CF 16 8D 7B 1D CA DD 35 DC 6E CD 03 2A 39 94 C9 B7 89 41 23 E6 2A 4F 19 04 FA E2 B4 2C 1A 58 35 B9 A3 9D 04 6F 7B 6D 1D C3 22 9D C0 48 BF 24 98 3D F8 F2 C5 6C 56 36 A5 71 24 3A E5 A3 26 9D 77 72 63 85 F6 B4 0A 30 4B 10 36 96 24 28 1F 2E 79 3E 95 B2 A8 EA 5E 36 5A DF FC FA BE E8 97 1E 5D 48 61 D3 20 D6 A6 BB 7B F9 EE 9F CA B8 78 8D A8 9D 92 34 00 E5 78 5C 67 2A 55 B9 CF 5A 97 4A B0 87 4D D4 6F B4 DB 78 F6 D9 B4 71 CC 91 F5 08 5B 72 B0 FA 1D 80 FE 26 A3 B3 D3 EF E5 D7 46 AA F1 36 9F 1C 8B 99 AD C5 CF 99 E7 1D BB 46 E5 03 68 20 63 90 C7 A0 15 6E 6D 0E 2B 8B D9 AE 24 BC BC 0B 36 37 43 1C C6 34 E0 01 D5 70 DD BA 67 1D 69 CE 69 5E 2E 5A 5B EE 7F 90 92 EB 63 9F 8E DA 57 41 69 6D 1D CD BE B7 A4 44 56 DE 46 C8 8E EA 14 6C 2A 93 9C 30 61 B7 39 E4 13 53 DD DF 0B A7 B5 D4 D2 DD 6E EC 35 5B 6F B2 98 64 5D DE 54 B9 25 41 F4 05 B2 AD E8 42 D7 43 65 A5 D8 69 BB BE C7 69 14 25 F1 BD 95 7E 67 C7 4C 9E A7 F1 AC C4 B7 D4 74 7D 4E EB EC 36 02 EE CA ED 84 C1 44 CA 9E 4C A7 87 EB D8 F0 78 EF 9A D1 56 8C DB B7 E3 A7 AF A6 BA AD 7B F7 27 91 A4 43 7A E2 6B 69 B4 8D 33 CC 5B DD 21 22 9A DC 13 81 36 D1 C2 8E 79 07 05 4F A1 35 7E C4 34 EF 1C AD 0B 42 F7 04 DD 4C 8D C1 1C 05 45 61 EB 80 3F 14 A8 6F 34 8B C5 6B 2B DB 03 08 BF B6 67 DE 25 76 58 E5 59 39 75 24 02 7E F6 08 E3 B5 68 D8 C5 72 A8 D2 DE F9 5F 68 93 1B 84 44 95 50 3A 00 48 04 F7 3C FA 9A CA 73 8F 27 BB FF 00 07 FE 1B A9 49 3B EA 5A A2 8A 2B 90 D4 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 FF D9,
0x00000165(357)=00 00 00 01 0A 70 69 63 5F 72 65 61 73 6F 6E 00 00 00 7F 51 51 E5 AE 89 E5 85 A8 E4 B8 AD E5 BF 83 E6 B8 A9 E9 A6 A8 E6 8F 90 E7 A4 BA EF BC 9A E5 BD 93 E5 89 8D E7 BD 91 E7 BB 9C E7 8E AF E5 A2 83 E6 9C 89 E5 8D B1 E5 8F 8A 51 51 E5 AE 89 E5 85 A8 E7 9A 84 E8 A1 8C E4 B8 BA EF BC 8C E4 B8 BA E4 BA 86 E4 BD A0 E7 9A 84 E5 B8 90 E5 8F B7 E5 AE 89 E5 85 A8 EF BC 8C E8 AF B7 E4 BD A0 E5 A1 AB E5 86 99 E9 AA 8C E8 AF 81 E7 A0 81 E3 80 82
}
*/
// val ret = tlvMap[0x104]?.let { println(it.toUHexString()) }
bot.client.t104 = tlvMap.getOrFail(0x104)
tlvMap[0x192]?.let {
return LoginPacketResponse.Captcha.Slider(it.encodeToString())
}
tlvMap[0x165]?.let { question ->
if (question[18].toInt() == 0x36) {
//图片验证
DebugLogger.debug("是一个图片验证码")
tlvMap[0x165]?.let {
// if (question[18].toInt() == 0x36) {
// 图片验证
// DebugLogger.debug("是一个图片验证码")
val imageData = tlvMap.getOrFail(0x105).toReadPacket()
val signInfoLength = imageData.readShort()
imageData.discardExact(2)//image Length
......@@ -375,12 +385,12 @@ internal class WtLogin{
val buffer = IoBuffer.Pool.borrow()
imageData.readFully(buffer)
imageData.readAvailable(buffer)
return LoginPacketResponse.Captcha.Picture(
data = buffer,
sign = sign
)
} else error("UNKNOWN CAPTCHA QUESTION: ${question.toUHexString()}, tlvMap=" + tlvMap.contentToString())
// } else error("UNKNOWN CAPTCHA QUESTION: ${question.toUHexString()}, tlvMap=" + tlvMap.contentToString())
}
error("UNKNOWN CAPTCHA, tlvMap=" + tlvMap.contentToString())
......
......@@ -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()
......
/*
* 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
* 不应该直接构造这个类的实例. 需使用 [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()
......
......@@ -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,18 +83,18 @@ 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)
network.relogin()
logger.info("Reconnected successfully")
return@launch
return@subscribeAlways
} catch (e: Throwable) {
lastFailedException = e
delay(configuration.reconnectPeriodMillis)
......@@ -99,39 +104,49 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
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()))
}
}
}
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)
}
} catch (e: Exception) {
logger.error("Cannot close network handler", e)
}
loginLoop@ while (true) {
suspend fun doRelogin() {
while (true) {
_network = createNetworkHandler(this.coroutineContext)
try {
_network.login()
break@loginLoop
_network.relogin()
return
} catch (e: LoginFailedException) {
throw e
} catch (e: Exception) {
e.logStacktrace()
network.logger.error(e)
_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()
......
......@@ -99,3 +99,5 @@ suspend inline fun Member.mute(duration: Duration): Boolean {
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())
\ No newline at end of file
......@@ -631,4 +631,4 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.TYPE)
@DslMarker
internal annotation class MessageDsl
\ No newline at end of file
annotation class MessageDsl
\ No newline at end of file
......@@ -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>
......
......@@ -34,3 +34,12 @@ fun MessageChain.quote(sender: Member): MessageChain {
}
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 {
/**
* 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回.
* 本函数将挂起直到登录成功.
*
* - 会断开连接并重新登录.
* - 不会停止网络层的 [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()
......
package net.mamoe.mirai.network
class ForceOfflineException(override val message: String?) : RuntimeException()
\ No newline at end of file
......@@ -7,24 +7,16 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.Bot
package net.mamoe.mirai.network
/**
* 在各平台实现的默认的验证码处理器.
* 正常登录失败时抛出
*/
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")
}
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)
\ No newline at end of file
......@@ -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
\ No newline at end of file
......@@ -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()
*/
......
......@@ -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
\ No newline at end of file
expect open class SystemDeviceInfo : DeviceInfo {
constructor()
constructor(context: Context)
object Version : DeviceInfo.Version
}
\ No newline at end of file
......@@ -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()
}
\ 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
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
}
\ No newline at end of file
......@@ -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
}
}
\ No newline at end of file
......@@ -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()
......
/*
* 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 @@
* 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() }
\ No newline at end of file
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