Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
M
Mirai
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
MyCard
Mirai
Commits
3eab26a5
Commit
3eab26a5
authored
Feb 15, 2020
by
jiahua.liu
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/master'
parents
b7fd77b6
35e9138c
Changes
47
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
47 changed files
with
948 additions
and
301 deletions
+948
-301
CHANGELOG.md
CHANGELOG.md
+23
-0
gradle.properties
gradle.properties
+1
-1
gradle/wrapper/gradle-wrapper.properties
gradle/wrapper/gradle-wrapper.properties
+1
-1
mirai-api-http/README_CH.md
mirai-api-http/README_CH.md
+68
-1
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt
...kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt
+10
-1
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
...ain/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
+16
-1
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt
.../net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt
+10
-2
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt
...ttp/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt
+3
-0
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
...moe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
+57
-43
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt
...rai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt
+1
-1
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt
...ndroid/network/protocol/packet/chat/receive/OnlinePush.kt
+13
-6
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt
.../mirai/qqandroid/network/protocol/packet/login/WtLogin.kt
+28
-18
mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt
...d/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt
+1
-1
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/BotConfigurationAndroid.kt
...n/kotlin/net/mamoe/mirai/utils/BotConfigurationAndroid.kt
+118
-0
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformLoggerAndroid.kt
...ain/kotlin/net/mamoe/mirai/utils/PlatformLoggerAndroid.kt
+10
-10
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt
...roidMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt
+30
-2
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt
...oidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt
+1
-1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
+5
-2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt
+69
-42
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt
...e/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt
+3
-1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt
...onMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt
+1
-1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt
...rc/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt
+6
-0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
...mmonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
+16
-3
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt
.../net.mamoe.mirai/event/internal/InternalEventListeners.kt
+2
-2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt
...c/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt
+1
-1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt
...nMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt
+1
-0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt
...Main/kotlin/net.mamoe.mirai/message/data/MessageSource.kt
+2
-0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt
...monMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt
+9
-0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt
...nMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt
+11
-2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/ForceOfflineException.kt
...n/kotlin/net.mamoe.mirai/network/ForceOfflineException.kt
+3
-0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt
...in/kotlin/net.mamoe.mirai/network/LoginFailedException.kt
+22
-0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Annotations.kt
...rc/commonMain/kotlin/net.mamoe.mirai/utils/Annotations.kt
+2
-2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt
...mmonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt
+31
-16
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/DeviceInfo.kt
...src/commonMain/kotlin/net.mamoe.mirai/utils/DeviceInfo.kt
+43
-5
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt
...rc/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt
+81
-80
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SystemDeviceInfo.kt
...mmonMain/kotlin/net.mamoe.mirai/utils/SystemDeviceInfo.kt
+6
-4
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt
...c/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt
+4
-5
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt
.../commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt
+1
-1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt
...monMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt
+2
-1
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/internal/EventInternalJvm.kt
...kotlin/net/mamoe/mirai/event/internal/EventInternalJvm.kt
+30
-0
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt
...mMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt
+97
-0
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt
...rc/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt
+7
-6
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt
.../jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt
+55
-25
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt
...jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt
+1
-1
mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt
...i-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt
+2
-1
mirai-japt/src/main/java/net/mamoe/mirai/japt/Events.java
mirai-japt/src/main/java/net/mamoe/mirai/japt/Events.java
+39
-0
mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/EventsImpl.kt
...c/main/kotlin/net/mamoe/mirai/japt/internal/EventsImpl.kt
+5
-11
No files found.
CHANGELOG.md
View file @
3eab26a5
...
...
@@ -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` 的发送和解析**
-
**修复某些情况下禁言处理异常**
...
...
gradle.properties
View file @
3eab26a5
# style guide
kotlin.code.style
=
official
# config
mirai_version
=
0.1
4
.0
mirai_version
=
0.1
5
.0
kotlin.incremental.multiplatform
=
true
kotlin.parallel.tasks.in.project
=
true
# kotlin
...
...
gradle/wrapper/gradle-wrapper.properties
View file @
3eab26a5
#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
...
...
mirai-api-http/README_CH.md
View file @
3eab26a5
...
...
@@ -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
...
...
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt
View file @
3eab26a5
...
...
@@ -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"
)
}
...
...
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
View file @
3eab26a5
...
...
@@ -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
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt
View file @
3eab26a5
...
...
@@ -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
...
...
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt
View file @
3eab26a5
...
...
@@ -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
()
...
...
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
View file @
3eab26a5
...
...
@@ -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
()
/**
* 发送一个包, 但不期待任何返回.
*/
...
...
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt
View file @
3eab26a5
...
...
@@ -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
,
...
...
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt
View file @
3eab26a5
...
...
@@ -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()}"
}
}
}
}
...
...
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt
View file @
3eab26a5
This diff is collapsed.
Click to expand it.
mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt
View file @
3eab26a5
...
...
@@ -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
()
...
...
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/BotConfigurationAndroid.kt
0 → 100644
View file @
3eab26a5
/*
* 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
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformLoggerAndroid.kt
View file @
3eab26a5
...
...
@@ -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
?)
{
...
...
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt
View file @
3eab26a5
...
...
@@ -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
()
...
...
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt
View file @
3eab26a5
...
...
@@ -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
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
View file @
3eab26a5
...
...
@@ -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
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt
View file @
3eab26a5
...
...
@@ -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
)
// 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
)
@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"
)
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
)
}
}
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
)
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
)
}
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
()
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt
View file @
3eab26a5
...
...
@@ -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
())
}
\ No newline at end of file
}
suspend
inline
fun
Member
.
mute
(
durationSeconds
:
Long
)
=
this
.
mute
(
durationSeconds
.
toInt
())
\ No newline at end of file
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt
View file @
3eab26a5
...
...
@@ -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
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt
View file @
3eab26a5
...
...
@@ -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
})
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
View file @
3eab26a5
...
...
@@ -42,19 +42,32 @@ data class BotOnlineEvent(override val bot: Bot) : BotActiveEvent
/**
* [Bot] 离线.
*/
sealed
class
BotOfflineEvent
:
Bot
Active
Event
{
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 消息
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt
View file @
3eab26a5
...
...
@@ -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
}
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt
View file @
3eab26a5
...
...
@@ -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
=
"@全体成员"
// 自动为消息补充 " "
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt
View file @
3eab26a5
...
...
@@ -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
>()
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt
View file @
3eab26a5
...
...
@@ -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
>
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt
View file @
3eab26a5
...
...
@@ -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
}
\ No newline at end of file
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt
View file @
3eab26a5
...
...
@@ -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
re
login
()
/**
* 初始化获取好友列表等值.
...
...
@@ -92,6 +100,7 @@ abstract class BotNetworkHandler : CoroutineScope {
}
}
@UseExperimental
(
MiraiInternalAPI
::
class
)
suspend
fun
BotNetworkHandler
.
closeAndJoin
(
cause
:
Throwable
?
=
null
)
{
this
.
close
(
cause
)
this
.
supervisor
.
join
()
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/ForceOfflineException.kt
0 → 100644
View file @
3eab26a5
package
net.mamoe.mirai.network
class
ForceOfflineException
(
override
val
message
:
String
?)
:
RuntimeException
()
\ No newline at end of file
mirai-core/src/
androidMain/kotlin/net/mamoe/mirai/utils/defaultLoginSolver
.kt
→
mirai-core/src/
commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException
.kt
View file @
3eab26a5
...
...
@@ -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"
)
}
sealed
class
LoginFailedException
:
RuntimeException
{
constructor
()
:
super
()
constructor
(
message
:
String
?)
:
super
(
message
)
constructor
(
message
:
String
?,
cause
:
Throwable
?)
:
super
(
message
,
cause
)
constructor
(
cause
:
Throwable
?)
:
super
(
cause
)
}
override
suspend
fun
onSolveUnsafeDeviceLoginVerify
(
bot
:
Bot
,
url
:
String
):
String
?
{
error
(
"should be implemented manually by you"
)
}
}
\ No newline at end of file
class
WrongPasswordException
(
message
:
String
?)
:
LoginFailedException
(
message
)
\ No newline at end of file
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Annotations.kt
View file @
3eab26a5
...
...
@@ -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
)
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt
View file @
3eab26a5
...
...
@@ -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
}
}
\ No newline at end of file
operator
fun
_NoNetworkLog
.
unaryPlus
()
/**
* 不记录网络层的 log.
* 网络层 log 包含包接收, 包发送, 和一些调试用的记录.
*/
@BotConfigurationDsl
val
NoNetworkLog
:
_NoNetworkLog
@Suppress
(
"ClassName"
)
object
_
NoNetworkLog
}
@DslMarker
annotation
class
BotConfigurationDsl
\ No newline at end of file
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/DeviceInfo.kt
View file @
3eab26a5
...
...
@@ -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()
*/
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt
View file @
3eab26a5
This diff is collapsed.
Click to expand it.
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SystemDeviceInfo.kt
View file @
3eab26a5
...
...
@@ -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
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt
View file @
3eab26a5
...
...
@@ -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"
)
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt
View file @
3eab26a5
...
...
@@ -81,7 +81,7 @@ inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteAr
return
this
[
tag
]
?:
error
(
lazyMessage
(
tag
))
}
@Mirai
Debug
API
@Mirai
Internal
API
inline
fun
Input
.
readTLVMap
(
tagSize
:
Int
=
2
,
suppressDuplication
:
Boolean
=
true
):
TlvMap
=
readTLVMap
(
true
,
tagSize
,
suppressDuplication
)
@MiraiDebugAPI
...
...
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt
View file @
3eab26a5
...
...
@@ -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
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/internal/EventInternalJvm.kt
0 → 100644
View file @
3eab26a5
/*
* 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
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/
DefaultCaptchaSolver
Jvm.kt
→
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/
BotConfiguration
Jvm.kt
View file @
3eab26a5
...
...
@@ -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
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt
View file @
3eab26a5
...
...
@@ -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"
),
...
...
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt
View file @
3eab26a5
...
...
@@ -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
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt
View file @
3eab26a5
...
...
@@ -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
()
}
...
...
mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt
View file @
3eab26a5
...
...
@@ -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
()
...
...
mirai-japt/src/main/java/net/mamoe/mirai/japt/Events.java
0 → 100644
View file @
3eab26a5
/*
* 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
mirai-
core/src/jvmMain/kotlin/net/mamoe/mirai/event/SubscribersJvm
.kt
→
mirai-
japt/src/main/kotlin/net/mamoe/mirai/japt/internal/EventsImpl
.kt
View file @
3eab26a5
...
...
@@ -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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment