1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-25 04:50:26 +08:00

Platform specified Contact

This commit is contained in:
Him188 2019-10-14 14:19:45 +08:00
parent ad43a92be4
commit 93607e4999
15 changed files with 275 additions and 180 deletions
README.md
mirai-core/src
commonMain/kotlin/net.mamoe.mirai
jvmMain/kotlin/net/mamoe/mirai/contact
mirai-demos/mirai-demo-1/src/main/java/demo1

View File

@ -1,12 +1,11 @@
# Mirai
[![HitCount](http://hits.dwyl.io/him188/mamoe/mirai.svg)](http://hits.dwyl.io/him188/mamoe/mirai)
一个以 **TIM QQ协议(非web)** 驱动的QQ机器人服务端核心
采用服务端-插件模式运行,同时提供独立的核心库
一个以 **TIM PC协议(非web)** 驱动的跨平台QQ机器人服务端核心, 虽然目前仅支持 JVM
采用服务端-插件模式运行,同时提供独立的跨平台核心库.
Mirai 的所有模块均开源
项目处于开发阶段,学生无法每日大量更新。
项目还有很多未完善的地方, 欢迎任何的代码贡献, 或是 issue.
项目处于开发阶段, 还有很多未完善的地方. 欢迎任何的代码贡献, 或是 issue.
部分协议来自网络上开源项目
**一切开发旨在学习,请勿用于非法用途**
@ -22,7 +21,8 @@ Mirai 的所有模块均开源
3. Run demo main [Demo 1 Main](mirai-demos/mirai-demo-1/src/main/java/demo1/Main.kt#L22)
### 事件
#### Kotlin
#### 使用 Kotlin
这里只演示进行不终止地监听。
##### Top-level reified
多数情况下这是最好的方式。
@ -35,7 +35,7 @@ subscribeAlways<FriendMessageEvent>{
```
##### DSL
查看更多: [ListenerBuilder](mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/Subscribers.kt#L69)
查看更多: [ListenerBuilder](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt#L87)
```kotlin
inline fun <reified E: Event> subscribeAll(builder: ListenerBuilder.() -> Unit)
@ -66,38 +66,25 @@ FriendMessageEvent::class.subscribeAlways{
![AYWVE86P](.github/A%7DYWVE860U%28%25YQD%24R1GB1%5BP.png)
### 图片测试
**现在可以接收图片消息**(并解析为消息链):
现在可以接收图片消息(并解析为消息链):
![JsssF](.github/J%5DCE%29IK4BU08%28EO~UVLJ%7B%5BF.png)
![](.github/68f8fec9.png)
发送图片已经完成,但我们还在开发上传图片至服务器。
现在你可以通过发送一张图片给机器人账号,再让机器人账号发送这张图片。你可以查看 [Image.kt](mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/Message.kt#L81)
机器人可以转发图片消息.详情查看 [Image.kt](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt#L81)
# TODO
- [x] 事件(Event)模块
- [ ] 插件(Plugin)模块
- [x] Network - Touch
- [X] Network - Login
- [X] Network - Session
- [X] Network - Verification Code
- [X] Network - Message Receiving
- [X] Network - Message Sending
- [ ] Network - Events
- [ ] Bot - Friend/group list
- [ ] Bot - Actions(joining group, adding friend, etc.)
- [x] Message Section
- [ ] Image uploading
- [ ] Contact
- [ ] UI
- [ ] Console
# 现已支持
- 发送好友/群消息(2019/10/14)
- 接收好友/群消息, 消息链解析(2019/10/14)
- 好友在线状态改变(2019/10/14)
<br>
# 使用方法
## 要求
- Kotlin 1.3+
### JVM
- Java 11
### 用于 JVM 平台
- Java 8
## 插件开发
``` text
to be continued

View File

@ -5,6 +5,7 @@ import kotlinx.coroutines.sync.Mutex
import net.mamoe.mirai.Bot.ContactSystem
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.groupIdToNumber
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult

View File

@ -7,15 +7,11 @@ import net.mamoe.mirai.message.PlainText
import net.mamoe.mirai.message.toChain
/**
* 联系人.
* 联系人平台基础. 包含所有平台通用的函数等.
*
* A contact is a [QQ] or a [Group] for one particular [Bot] instance only.
*
* @param bot the Owner [Bot]
* @param number the id number of this contact
* @author Him188moe
*/
abstract class Contact internal constructor(val bot: Bot, val number: Long) {
abstract class PlatformContactBase internal constructor(val bot: Bot, val number: Long) {
abstract suspend fun sendMessage(message: MessageChain)
@ -26,13 +22,15 @@ abstract class Contact internal constructor(val bot: Bot, val number: Long) {
return sendMessage(message.toChain())
}
suspend fun sendMessage(plain: String) {
this.sendMessage(PlainText(plain))
}
suspend fun sendMessage(plain: String) = this.sendMessage(PlainText(plain))
suspend fun sendMessage(message: List<Message>) {
this.sendMessage(MessageChain(message))
}
abstract suspend fun sendXMLMessage(message: String)
}
/**
* 所有的 [QQ], [Group] 都继承自这个类.
* 在不同平台可能有不同的实现.
* 如在 JVM, suspend 调用不便, [Contact] 中有简化调用的 `blocking`() `async`
*/
expect sealed class Contact(bot: Bot, number: Long) : PlatformContactBase

View File

@ -1,11 +1,8 @@
package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group.Companion.groupNumberToId
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.network.protocol.tim.handler.EventPacketHandler
import net.mamoe.mirai.utils.ContactList
import kotlin.jvm.JvmStatic
/**
* .
@ -14,125 +11,110 @@ import kotlin.jvm.JvmStatic
* - Group Number([Group.number]) 是通常使用的群号码.( QQ 客户端中可见)
* - Group ID([Group.groupId]) 是与服务器通讯时使用的 id.( QQ 客户端中不可见)
*
* Java 获取 groupNumber: `group.getNumber()`
* Java 获取所属 bot: `group.getBot()`
* Java 获取群成员列表: `group.getMembers()`
* Java 获取 groupId: `group.getGroupId()`
*
* Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)`
* @author Him188moe
*/
class Group(bot: Bot, number: Long) : Contact(bot, number) {
val groupId = groupNumberToId(number)
expect class Group(bot: Bot, number: Long) : Contact {
val groupId: Long
val members: ContactList<QQ>
//todo members
get() = throw UnsupportedOperationException("Not yet supported")
override suspend fun sendMessage(message: MessageChain) {
bot.network[EventPacketHandler].sendGroupMessage(this, message)
override suspend fun sendMessage(message: MessageChain)
override suspend fun sendXMLMessage(message: String)
companion object
}
fun Group.Companion.groupNumberToId(number: Long): Long {//求你别出错
val left: Long = number.toString().let {
if (it.length < 6) {
return@groupNumberToId number
}
it.substring(0, it.length - 6).toLong()
}
val right: Long = number.toString().let {
it.substring(it.length - 6).toLong()
}
override suspend fun sendXMLMessage(message: String) {
}
companion object {
@JvmStatic
fun groupNumberToId(number: Long): Long {//求你别出错
val left: Long = number.toString().let {
if (it.length < 6) {
return@groupNumberToId number
}
it.substring(0, it.length - 6).toLong()
}
val right: Long = number.toString().let {
it.substring(it.length - 6).toLong()
}
return when (left) {
in 1..10 -> {
((left + 202).toString() + right.toString()).toLong()
}
in 11..19 -> {
((left + 469).toString() + right.toString()).toLong()
}
in 20..66 -> {
((left + 208).toString() + right.toString()).toLong()
}
in 67..156 -> {
((left + 1943).toString() + right.toString()).toLong()
}
in 157..209 -> {
((left + 199).toString() + right.toString()).toLong()
}
in 210..309 -> {
((left + 389).toString() + right.toString()).toLong()
}
in 310..499 -> {
((left + 349).toString() + right.toString()).toLong()
}
else -> number
}
return when (left) {
in 1..10 -> {
((left + 202).toString() + right.toString()).toLong()
}
@JvmStatic
fun groupIdToNumber(id: Long): Long {//求你别出错
var left: Long = id.toString().let {
if (it.length < 6) {
return@groupIdToNumber id
}
it.substring(0 until it.length - 6).toLong()
}
return when (left) {
in 203..212 -> {
val right: Long = id.toString().let {
it.substring(it.length - 6).toLong()
}
((left - 202).toString() + right.toString()).toLong()
}
in 480..488 -> {
val right: Long = id.toString().let {
it.substring(it.length - 6).toLong()
}
((left - 469).toString() + right.toString()).toLong()
}
in 2100..2146 -> {
val right: Long = id.toString().let {
it.substring(it.length - 7).toLong()
}
left = left.toString().substring(0 until 3).toLong()
((left - 208).toString() + right.toString()).toLong()
}
in 2010..2099 -> {
val right: Long = id.toString().let {
it.substring(it.length - 6).toLong()
}
((left - 1943).toString() + right.toString()).toLong()
}
in 2147..2199 -> {
val right: Long = id.toString().let {
it.substring(it.length - 7).toLong()
}
left = left.toString().substring(0 until 3).toLong()
((left - 199).toString() + right.toString()).toLong()
}
in 4100..4199 -> {
val right: Long = id.toString().let {
it.substring(it.length - 7).toLong()
}
left = left.toString().substring(0 until 3).toLong()
((left - 389).toString() + right.toString()).toLong()
}
in 3800..3989 -> {
val right: Long = id.toString().let {
it.substring(it.length - 7).toLong()
}
left = left.toString().substring(0 until 3).toLong()
((left - 349).toString() + right.toString()).toLong()
}
else -> id
}
in 11..19 -> {
((left + 469).toString() + right.toString()).toLong()
}
in 20..66 -> {
((left + 208).toString() + right.toString()).toLong()
}
in 67..156 -> {
((left + 1943).toString() + right.toString()).toLong()
}
in 157..209 -> {
((left + 199).toString() + right.toString()).toLong()
}
in 210..309 -> {
((left + 389).toString() + right.toString()).toLong()
}
in 310..499 -> {
((left + 349).toString() + right.toString()).toLong()
}
else -> number
}
}
fun Group.Companion.groupIdToNumber(id: Long): Long {//求你别出错
var left: Long = id.toString().let {
if (it.length < 6) {
return@groupIdToNumber id
}
it.substring(0 until it.length - 6).toLong()
}
return when (left) {
in 203..212 -> {
val right: Long = id.toString().let {
it.substring(it.length - 6).toLong()
}
((left - 202).toString() + right.toString()).toLong()
}
in 480..488 -> {
val right: Long = id.toString().let {
it.substring(it.length - 6).toLong()
}
((left - 469).toString() + right.toString()).toLong()
}
in 2100..2146 -> {
val right: Long = id.toString().let {
it.substring(it.length - 7).toLong()
}
left = left.toString().substring(0 until 3).toLong()
((left - 208).toString() + right.toString()).toLong()
}
in 2010..2099 -> {
val right: Long = id.toString().let {
it.substring(it.length - 6).toLong()
}
((left - 1943).toString() + right.toString()).toLong()
}
in 2147..2199 -> {
val right: Long = id.toString().let {
it.substring(it.length - 7).toLong()
}
left = left.toString().substring(0 until 3).toLong()
((left - 199).toString() + right.toString()).toLong()
}
in 4100..4199 -> {
val right: Long = id.toString().let {
it.substring(it.length - 7).toLong()
}
left = left.toString().substring(0 until 3).toLong()
((left - 389).toString() + right.toString()).toLong()
}
in 3800..3989 -> {
val right: Long = id.toString().let {
it.substring(it.length - 7).toLong()
}
left = left.toString().substring(0 until 3).toLong()
((left - 349).toString() + right.toString()).toLong()
}
else -> id
}
}

View File

@ -4,28 +4,20 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.At
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.network.protocol.tim.handler.EventPacketHandler
/**
* QQ 账号.
* 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Bot].
*
* Java 获取 qq : `qq.getNumber()`
* Java 获取所属 bot: `qq.getBot()`
*
* A QQ instance helps you to receive event from or sendPacket event to.
* Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same.
*
* @author Him188moe
*/
class QQ(bot: Bot, number: Long) : Contact(bot, number) {
override suspend fun sendMessage(message: MessageChain) {
bot.network[EventPacketHandler].sendFriendMessage(this, message)
}
expect class QQ(bot: Bot, number: Long) : Contact {
override suspend fun sendMessage(message: MessageChain)
override suspend fun sendXMLMessage(message: String) {
}
override suspend fun sendXMLMessage(message: String)
}
/**

View File

@ -18,7 +18,5 @@ class FriendMessageEvent(bot: Bot, sender: QQ, val message: MessageChain) : Frie
suspend inline fun reply(message: String) = sender.sendMessage(message)
suspend inline fun reply(message: List<Message>) = sender.sendMessage(message)
suspend inline fun reply(message: MessageChain) = sender.sendMessage(message)//shortcut
}

View File

@ -15,7 +15,5 @@ class GroupMessageEvent(bot: Bot, group: Group, val sender: QQ, val message: Mes
suspend inline fun reply(message: String) = group.sendMessage(message)
suspend inline fun reply(message: List<Message>) = group.sendMessage(message)
suspend inline fun reply(message: MessageChain) = group.sendMessage(message)
}

View File

@ -70,7 +70,10 @@ internal fun ByteReadPacket.readMessage(): Message? {
0x19 -> {//未知, 可能是长文本?
//bot手机自己跟自己发消息会出这个
//似乎手机发消息就会有这个?
//sectionData: 01 00 1C AA 02 19 08 00 88 01 00 9A 01 11 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00
// 01 00 1C AA 02 19 08 00 88 01 00 9A 01 11 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00
return null
sectionData.readBytes().debugPrint("sectionData")
return PlainText("[UNKNOWN(${this.readBytes().toUHexString()})]")
println()

View File

@ -71,4 +71,9 @@ interface BotNetworkHandler<Socket : DataPacketSocket> : Closeable {
override fun close() {
NetworkScope.cancel("handler closed", HandlerClosedException())
}
}
}
/**
* [BotNetworkHandler] closed
*/
class HandlerClosedException : Exception()

View File

@ -1,3 +1 @@
package net.mamoe.mirai.network
class HandlerClosedException : Exception()

View File

@ -70,6 +70,9 @@ class EventPacketHandler(session: LoginSession) : PacketHandler(session) {
//TODO
}
is IgnoredServerEventPacket -> {
}
else -> {
//ignored
}

View File

@ -8,7 +8,7 @@ import net.mamoe.mirai.utils.*
@PacketId("00 58")
class ClientHeartbeatPacket(
private val qq: Long,
private val bot: Long,
private val sessionKey: ByteArray
) : ClientPacket() {
override val idHex: String by lazy {
@ -16,7 +16,7 @@ class ClientHeartbeatPacket(
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer)
this.encryptAndWrite(sessionKey) {
writeHex("00 01 00 01")

View File

@ -9,6 +9,9 @@ import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.*
import kotlin.properties.Delegates
/**
* 事件的识别 ID. [事件确认包][ServerEventPacket.ResponsePacket] 中被使用.
*/
data class EventPacketIdentity(
val from: UInt,//对于好友消息, 这个是发送人
val to: UInt,//对于好友消息, 这个是bot
@ -37,8 +40,9 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
to = readUInt(),
uniqueId = readIoBuffer(8)
)
readBytes(2).takeIf { it[0].toUInt() != 0x1Fu && it[1].toUInt() != 0x40u }?.debugPrint("type前面2个byte")
discardExact(2)
val type = readBytes(2)
//DebugLogger.logPurple("unknown2Byte+byte = ${unknown2Byte.toUHexString()} ${type.toUHexString()}")
return when (type.toUHexString()) {
"00 C4" -> {
discardExact(13)
@ -56,11 +60,17 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
//00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 16 00 00 00 37 08 02 1A 12 08 95 02 10 90 04 40 98 E1 8C ED 05 48 AF 96 C3 A4 03 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 1A 29 08 00 10 05 18 98 E1 8C ED 05 20 01 28 FF FF FF FF 0F 32 15 E5 AF B9 E6 96 B9 E6 AD A3 E5 9C A8 E8 BE 93 E5 85 A5 2E 2E 2E
//00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 07 00 00 00
"02 10" -> {
discardExact(19)
println(readUByte().toUInt())
//todo 错了. 可能是 00 79 才是.
return@with ServerFriendTypingCanceledPacket(input, eventIdentity)
if (readUByte().toUInt() == 0x37u) ServerFriendTypingStartedPacket(input, eventIdentity)
else /*0x22*/ ServerFriendTypingCanceledPacket(input, eventIdentity)
}
"00 79" -> IgnoredServerEventPacket(type, input, eventIdentity)
//"02 10", "00 12" -> ServerUnknownEventPacket(input, eventIdentity)
@ -95,6 +105,12 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
}
}
/**
* 忽略的事件.
* 00 79: 总是与 01 12 一起发生, 00 79 却没多大意义
*/
class IgnoredServerEventPacket(val eventId: ByteArray/*2*/, input: ByteReadPacket, eventIdentity: EventPacketIdentity) : ServerEventPacket(input, eventIdentity)
/**
* Unknown event
*/
@ -121,7 +137,6 @@ class ServerFriendTypingStartedPacket(input: ByteReadPacket, eventIdentity: Even
class ServerFriendTypingCanceledPacket(input: ByteReadPacket, eventIdentity: EventPacketIdentity) : ServerFriendTypingPacket(input, eventIdentity)
/**
* Android 客户端上线
*/

View File

@ -0,0 +1,118 @@
package net.mamoe.mirai.contact
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.network.protocol.tim.handler.EventPacketHandler
import net.mamoe.mirai.utils.ContactList
/**
* 联系人.
*
* A contact is a [QQ] or a [Group] for one particular [Bot] instance only.
*
* @param bot the Owner [Bot]
* @param number the id number of this contact
* @author Him188moe
*/
@Suppress("unused")
actual sealed class Contact actual constructor(bot: Bot, number: Long) : PlatformContactBase(bot, number) {
abstract override suspend fun sendMessage(message: MessageChain)
abstract override suspend fun sendXMLMessage(message: String)
/**
* 阻塞发送一个消息. 仅应在 Java 使用
*/
fun blockingSendMessage(chain: MessageChain) = runBlocking { sendMessage(chain) }
/**
* 阻塞发送一个消息. 仅应在 Java 使用
*/
fun blockingSendMessage(message: Message) = runBlocking { sendMessage(message) }
/**
* 阻塞发送一个消息. 仅应在 Java 使用
*/
fun blockingSendMessage(plain: String) = runBlocking { sendMessage(plain) }
/**
* 异步发送一个消息. 仅应在 Java 使用
*/
fun asyncSendMessage(chain: MessageChain) {
bot.network.NetworkScope.launch { sendMessage(chain) }
}
/**
* 异步发送一个消息. 仅应在 Java 使用
*/
fun asyncSendMessage(message: Message) {
bot.network.NetworkScope.launch { sendMessage(message) }
}
/**
* 异步发送一个消息. 仅应在 Java 使用
*/
fun asyncSendMessage(plain: String) {
bot.network.NetworkScope.launch { sendMessage(plain) }
}
}
/**
* .
*
* Group ID Group Number 并不是同一个值.
* - Group Number([Group.number]) 是通常使用的群号码.( QQ 客户端中可见)
* - Group ID([Group.groupId]) 是与服务器通讯时使用的 id.( QQ 客户端中不可见)
*
* Java 获取 groupNumber: `group.getNumber()`
* Java 获取所属 bot: `group.getBot()`
* Java 获取群成员列表: `group.getMembers()`
* Java 获取 groupId: `group.getGroupId()`
*
* Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)`
* @author Him188moe
*/
actual class Group actual constructor(bot: Bot, number: Long) : Contact(bot, number) {
actual val groupId = groupNumberToId(number)
actual val members: ContactList<QQ>
//todo members
get() = throw UnsupportedOperationException("Not yet supported")
actual override suspend fun sendMessage(message: MessageChain) {
bot.network[EventPacketHandler].sendGroupMessage(this, message)
}
actual override suspend fun sendXMLMessage(message: String) {
}
actual companion object
}
/**
* QQ 账号.
* 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Bot].
*
* Java 获取 qq : `qq.getNumber()`
* Java 获取所属 bot: `qq.getBot()`
*
* A QQ instance helps you to receive event from or sendPacket event to.
* Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same.
*
* @author Him188moe
*/
actual class QQ actual constructor(bot: Bot, number: Long) : Contact(bot, number) {
actual override suspend fun sendMessage(message: MessageChain) {
bot.network[EventPacketHandler].sendFriendMessage(this, message)
}
actual override suspend fun sendXMLMessage(message: String) {
TODO()
}
}

View File

@ -49,10 +49,7 @@ suspend fun main() {
"复读" in it.message -> it.sender.sendMessage(it.message)
"发群" in it.message -> {
it.message.list.toMutableList().let { messages ->
messages.removeAt(0)
Group(bot, 580266363).sendMessage(messages)
}
}
/*it.event eq "发图片群" -> sendGroupMessage(Group(session.bot, 580266363), PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->