mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-13 20:02:57 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
e9c794296d
33
CHANGELOG.md
33
CHANGELOG.md
@ -2,6 +2,37 @@
|
||||
|
||||
开发版本. 频繁更新, 不保证高稳定性
|
||||
|
||||
## `0.19.1` 2020/2/21
|
||||
|
||||
### mirai-core
|
||||
- 支持机器人撤回群消息 (含自己发送的消息): `Group.recall`, `MessageReceipt.recall`
|
||||
- 支持一定时间后自动撤回: `Group.recallIn`, `MessageReceipt.recallIn`
|
||||
- `sendMessage` 返回 `MessageReceipt` 以实现撤回功能
|
||||
- 添加 `MessageChain.addOrRemove`
|
||||
- 添加 `ContactList.firstOrNull`, `ContactList.first`
|
||||
- 新的异步事件监听方式: `subscribingGetAsync` 启动一个协程并从一个事件从获取返回值到 `Deferred`.
|
||||
- 新的线性事件监听方式: `subscribingGet` 挂起当前协程并从一个事件从获取返回值.
|
||||
|
||||
##### 新的线性消息连续处理: `nextMessage` 挂起当前协程并等待下一条消息:
|
||||
使用该示例, 发送两条消息, 一条为 "禁言", 另一条包含一个 At
|
||||
```kotlin
|
||||
case("禁言") {
|
||||
val value: At = nextMessage { message.any(At) }[At]
|
||||
value.member().mute(10)
|
||||
}
|
||||
```
|
||||
示例 2:
|
||||
```kotlin
|
||||
case("复读下一条") {
|
||||
reply(nextMessage().message)
|
||||
}
|
||||
```
|
||||
|
||||
### mirai-core-qqandroid
|
||||
- 修复一些情况下 `At` 无法发送的问题
|
||||
- 统一 ImageId: 群消息收到的 ImageId 均为 `{xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx}.jpg` 形式(固定长度 37)
|
||||
- 支持成员主动离开事件的解析 (#51)
|
||||
|
||||
## `0.18.0` 2020/2/20
|
||||
|
||||
### mirai-core
|
||||
@ -212,7 +243,7 @@ TIMPC
|
||||
## `0.10.0` *2019/12/23*
|
||||
**事件优化**
|
||||
更快的监听过程
|
||||
现在监听不再是 `suspend`, 而必须显式指定 `CoroutineScope`. 详见 [`Subscribers.kt`](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt#L69)
|
||||
现在监听不再是 `suspend`, 而必须显式指定 `CoroutineScope`. 详见 `Subscribers.kt`
|
||||
删除原本的 bot.subscribe 等监听模式.
|
||||
|
||||
**其他**
|
||||
|
@ -1,6 +1,7 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven { url "https://mirrors.huaweicloud.com/repository/maven" }
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
@ -9,7 +10,6 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Do try to waste your time.
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
classpath("com.github.jengelman.gradle.plugins:shadow:5.2.0")
|
||||
@ -32,14 +32,10 @@ allprojects {
|
||||
group = "net.mamoe"
|
||||
version = getProperty("mirai_version")
|
||||
|
||||
// tasks.withType(KotlinCompile).all { task ->
|
||||
// task.kotlinOptions{
|
||||
// jvmTarget = '1.6'
|
||||
// }
|
||||
// }
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven { url "https://mirrors.huaweicloud.com/repository/maven" }
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
|
186
docs/guide_build_for_mirai.md
Normal file
186
docs/guide_build_for_mirai.md
Normal file
@ -0,0 +1,186 @@
|
||||
# Mirai Guide - Build For Mirai
|
||||
|
||||
由于Mirai项目在快速推进中,因此内容时有变动,本文档的最后更新日期为```2020-02-22```,对应版本```0.19.1```
|
||||
|
||||
本页面采用Kotlin作为开发语言,**若你希望使用 Java 开发**, 请参阅: [mirai-japt](mirai-japt/README.md)
|
||||
|
||||
本页面是[Mirai Guide - Subscribe Events](/docs/guide_subscribe_events.md)的后续Guide
|
||||
|
||||
## build.gradle
|
||||
|
||||
我们首先来看一下完整的```build.gradle```文件
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
|
||||
}
|
||||
|
||||
group 'test'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'net.mamoe:mirai-core-qqandroid-jvm:0.19.1'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
```
|
||||
|
||||
使用gradle直接打包,不会将依赖也打包进去
|
||||
|
||||
因此,我们引入一些插件进行打包
|
||||
|
||||
### ShadowJar
|
||||
|
||||
shadowJar支持将依赖也打包到Jar内,下面介绍用法。
|
||||
|
||||
#### 1.buildscript
|
||||
|
||||
首先声明buildScript
|
||||
|
||||
```groovy
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在plugin前加入以上语句
|
||||
|
||||
|
||||
|
||||
#### 2.在plugins中进行插件的使用
|
||||
|
||||
将原本的plugins
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
|
||||
}
|
||||
```
|
||||
|
||||
覆盖为
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
|
||||
id 'com.github.johnrengelman.shadow' version '5.2.0'//使用shadow对依赖进行打包
|
||||
}
|
||||
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'java'
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 3.添加shadowJar
|
||||
|
||||
在文件底部添加
|
||||
|
||||
```groovy
|
||||
shadowJar {
|
||||
// 生成包的命名规则: baseName-version-classifier.jar
|
||||
manifest {
|
||||
attributes(
|
||||
'Main-Class': 'net.mamoe.mirai.simpleloader.MyLoaderKt'//入口点
|
||||
)
|
||||
}
|
||||
|
||||
// 将 build.gradle 打入到 jar 中, 方便查看依赖包版本
|
||||
from("./"){
|
||||
include 'build.gradle'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 4.运行build
|
||||
|
||||
在IDEA中点击```ShadowJar```左侧的run按钮(绿色小三角),build完成后在```build\libs```中找到jar
|
||||
|
||||
|
||||
|
||||
至此,build.gradle内的内容是
|
||||
|
||||
```groovy
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
|
||||
id 'com.github.johnrengelman.shadow' version '5.2.0'//使用shadow对依赖进行打包
|
||||
}
|
||||
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'java'
|
||||
|
||||
group 'test'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'net.mamoe:mirai-core-qqandroid-jvm:0.19.1'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
}
|
||||
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
// 生成包的命名规则: baseName-version-classifier.jar
|
||||
manifest {
|
||||
attributes(
|
||||
'Main-Class': 'net.mamoe.mirai.simpleloader.MyLoaderKt'
|
||||
)
|
||||
}
|
||||
|
||||
// 将 build.gradle 打入到 jar 中, 方便查看依赖包版本
|
||||
from("./"){
|
||||
include 'build.gradle'
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
@ -26,6 +26,8 @@ JDK要求6以上
|
||||
|
||||
### 2 新建Gradle项目
|
||||
|
||||
*使用gradle项目可能需要代理,在IDEA的```settings```->```proxy settings```中可以设置
|
||||
|
||||
- 在```File->new project```中选择```Gradle```
|
||||
- 在面板中的```Additional Libraries and Frameworks```中勾选```Java```以及```Kotlin/JVM```
|
||||
- 点击```next```,填入```GroupId```与```ArtifactId```(对于测试项目来说,可随意填写)
|
||||
@ -69,7 +71,7 @@ JDK要求6以上
|
||||
### 4 Try Bot
|
||||
|
||||
- 在src/main文件夹下新建文件夹,命名为```kotlin```
|
||||
- 在```kotlin```下新建包(在```kotlin```文件夹上右键-```New```-```Packages```) 包名为```net.mamoe.mirai.simpleloader```
|
||||
- 在```kotlin```下新建包(在```kotlin```文件夹上右键-```New```-```Package```) 包名为```net.mamoe.mirai.simpleloader```
|
||||
|
||||
- 在包下新建kotlin文件```MyLoader.kt```
|
||||
|
||||
@ -103,9 +105,9 @@ suspend fun main() {
|
||||
- 本例的功能中,在任意群内任意成员发送包含“舔”字或“刘老板”字样的消息,MiraiBot会回复“刘老板太强了”
|
||||
|
||||
|
||||
|
||||
至此,简单的入门已经结束,下面可根据不同的需求参阅wiki进行功能的添加。
|
||||
|
||||
下面,可以尝试对不同事件进行监听[Mirai Guide - Subscribe Events](/docs/guide_subscribe_events.md)
|
||||
|
||||
### 此外,还可以使用Maven作为包管理工具
|
||||
本项目推荐使用gradle,因此不提供详细入门指导
|
||||
|
116
docs/guide_subscribe_events.md
Normal file
116
docs/guide_subscribe_events.md
Normal file
@ -0,0 +1,116 @@
|
||||
# Mirai Guide - Subscribe Events
|
||||
|
||||
由于Mirai项目在快速推进中,因此内容时有变动,本文档的最后更新日期为```2020-02-21```,对应版本```0.17.0```
|
||||
|
||||
本页面采用Kotlin作为开发语言,**若你希望使用 Java 开发**, 请参阅: [mirai-japt](mirai-japt/README.md)
|
||||
|
||||
本页面是[Mirai Guide - Getting Started](/docs/guide_getting_started.md)的后续Guide
|
||||
|
||||
## 消息事件-Message Event
|
||||
|
||||
首先我们来回顾上一个Guide的源码
|
||||
|
||||
```kotlin
|
||||
suspend fun main() {
|
||||
val qqId = 10000L//Bot的QQ号,需为Long类型,在结尾处添加大写L
|
||||
val password = "your_password"//Bot的密码
|
||||
val miraiBot = Bot(qqId, password).alsoLogin()//新建Bot并登录
|
||||
miraiBot.subscribeMessages {
|
||||
"你好" reply "你好!"
|
||||
case("at me") {
|
||||
reply(sender.at() + " 给爷爬 ")
|
||||
}
|
||||
|
||||
(contains("舔") or contains("刘老板")) {
|
||||
"刘老板太强了".reply()
|
||||
}
|
||||
}
|
||||
miraiBot.join() // 等待 Bot 离线, 避免主线程退出
|
||||
}
|
||||
```
|
||||
|
||||
在本例中,```miraiBot```是一个Bot对象,让其登录,然后对```Message Event```进行了监听。
|
||||
|
||||
对于``````Message Event``````,```Mirai```提供了较其他Event更强大的[MessageSubscribersBuilder](https://github.com/mamoe/mirai/wiki/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt#L140),本例也采用了[MessageSubscribersBuilder](https://github.com/mamoe/mirai/wiki/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt#L140)。其他具体使用方法可以参考[Wiki:Message Event](https://github.com/mamoe/mirai/wiki/Development-Guide---Kotlin#Message-Event)部分。
|
||||
|
||||
|
||||
|
||||
## 事件-Event
|
||||
|
||||
上一节中提到的```Message Event```仅仅是众多```Event```的这一种,其他```Event```有群员加入群,离开群,私聊等等...
|
||||
|
||||
具体doc暂不提供,现可翻阅源码[**BotEvents.kt**](https://github.com/mamoe/mirai/blob/master/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt),查看注释。当前事件仍在扩充中,可能有一定不足。
|
||||
|
||||
下面我们开始示例对一些事件进行监听。
|
||||
|
||||
|
||||
|
||||
## 尝试监听事件-Try Subscribing Events
|
||||
|
||||
### 监听加群事件
|
||||
|
||||
在代码中的```miraiBot.join()```前添加
|
||||
|
||||
```kotlin
|
||||
miraiBot.subscribeAlways<MemberJoinEvent> {
|
||||
it.group.sendMessage("欢迎 ${it.member.nameCardOrNick} 加入本群!")
|
||||
}
|
||||
```
|
||||
|
||||
本段语句监听了加入群的事件。
|
||||
|
||||
### 监听禁言事件
|
||||
|
||||
在代码中添加
|
||||
|
||||
```kotlin
|
||||
miraiBot.subscribeAlways<MemberMuteEvent> (){
|
||||
it.group.sendMessage("恭喜老哥 ${it.member.nameCardOrNick} 喜提禁言套餐一份")
|
||||
}
|
||||
```
|
||||
|
||||
在被禁言后,Bot将发送恭喜语句。
|
||||
|
||||
### 添加后的可执行代码
|
||||
|
||||
至此,当前的代码为
|
||||
|
||||
```kotlin
|
||||
package net.mamoe.mirai.simpleloader
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.alsoLogin
|
||||
import net.mamoe.mirai.event.subscribeMessages
|
||||
import net.mamoe.mirai.contact.nameCardOrNick
|
||||
import net.mamoe.mirai.contact.sendMessage
|
||||
import net.mamoe.mirai.event.events.MemberJoinEvent
|
||||
import net.mamoe.mirai.event.events.MemberMuteEvent
|
||||
import net.mamoe.mirai.event.subscribeAlways
|
||||
|
||||
suspend fun main() {
|
||||
val qqId = 10000L//Bot的QQ号,需为Long类型,在结尾处添加大写L
|
||||
val password = "your_password"//Bot的密码
|
||||
val miraiBot = Bot(qqId, password).alsoLogin()//新建Bot并登录
|
||||
miraiBot.subscribeMessages {
|
||||
"你好" reply "你好!"
|
||||
case("at me") {
|
||||
reply(sender.at() + " 给爷爬 ")
|
||||
}
|
||||
|
||||
(contains("舔") or contains("刘老板")) {
|
||||
"刘老板太强了".reply()
|
||||
}
|
||||
}
|
||||
miraiBot.subscribeAlways<MemberJoinEvent> {
|
||||
it.group.sendMessage("欢迎 ${it.member.nameCardOrNick} 加入本群!")
|
||||
}
|
||||
miraiBot.subscribeAlways<MemberMuteEvent> (){
|
||||
it.group.sendMessage("恭喜老哥 ${it.member.nameCardOrNick} 喜提禁言套餐一份")
|
||||
}
|
||||
miraiBot.join() // 等待 Bot 离线, 避免主线程退出
|
||||
}
|
||||
```
|
||||
|
||||
下面可以参阅[Mirai Guide - Build For Mirai](/docs/guide_build_for_mirai.md),对你的Mirai应用进行打包
|
||||
|
@ -1,7 +1,7 @@
|
||||
# style guide
|
||||
kotlin.code.style=official
|
||||
# config
|
||||
mirai_version=0.18.0
|
||||
mirai_version=0.19.1
|
||||
mirai_japt_version=1.0.1
|
||||
kotlin.incremental.multiplatform=true
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
|
@ -408,7 +408,7 @@ Content-Type:multipart/form-data
|
||||
|
||||
|
||||
### 事件类型一览
|
||||
[事件类型一览](./EventType_CN.md)
|
||||
[事件类型一览](EventType_CH.md)
|
||||
|
||||
> 事件为Bot被动接收的信息,无法主动构建
|
||||
|
||||
@ -430,13 +430,13 @@ Content-Type:multipart/form-data
|
||||
```json5
|
||||
{
|
||||
"type": "Source",
|
||||
"uid": 123456
|
||||
"id": 123456
|
||||
}
|
||||
```
|
||||
|
||||
| 名字 | 类型 | 说明 |
|
||||
| ---- | ---- | ------------------------------------------------------------ |
|
||||
| uid | Long | 消息的识别号,用于引用回复(Source类型只在群消息中返回,且永远为chain的第一个元素) |
|
||||
| id | Long | 消息的识别号,用于引用回复(Source类型只在群消息中返回,且永远为chain的第一个元素) |
|
||||
|
||||
#### At
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
package net.mamoe.mirai.api.http.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.api.http.data.common.DTO
|
||||
|
||||
@Serializable
|
||||
open class StateCode(val code: Int, var msg: String) {
|
||||
open class StateCode(val code: Int, var msg: String) : DTO {
|
||||
object Success : StateCode(0, "success") // 成功
|
||||
object NoBot : StateCode(2, "指定Bot不存在")
|
||||
object IllegalSession : StateCode(3, "Session失效或不存在")
|
||||
|
@ -36,7 +36,7 @@ data class GroupMessagePacketDTO(val sender: MemberDTO) : MessagePacketDTO()
|
||||
// Message
|
||||
@Serializable
|
||||
@SerialName("Source")
|
||||
data class MessageSourceDTO(val uid: Long) : MessageDTO()
|
||||
data class MessageSourceDTO(val id: Long) : MessageDTO()
|
||||
|
||||
@Serializable
|
||||
@SerialName("At")
|
||||
@ -64,7 +64,7 @@ data class XmlDTO(val xml: String) : MessageDTO()
|
||||
|
||||
@Serializable
|
||||
@SerialName("Unknown")
|
||||
data class UnknownMessageDTO(val text: String) : MessageDTO()
|
||||
object UnknownMessageDTO : MessageDTO()
|
||||
|
||||
/*
|
||||
* Abstract Class
|
||||
@ -88,24 +88,29 @@ fun MessagePacket<*, *>.toDTO() = when (this) {
|
||||
is GroupMessage -> GroupMessagePacketDTO(MemberDTO(sender))
|
||||
else -> IgnoreEventDTO
|
||||
}.apply {
|
||||
if (this is MessagePacketDTO) {
|
||||
messageChain = mutableListOf<MessageDTO>().also { ls -> message.foreachContent { ls.add(it.toDTO()) } }
|
||||
}
|
||||
if (this is MessagePacketDTO) { messageChain = message.toDTOChain() }
|
||||
// else: `this` is bot event
|
||||
}
|
||||
|
||||
fun MessageChain.toDTOChain() = mutableListOf(this[MessageSource].toDTO()).apply {
|
||||
foreachContent { content -> content.toDTO().takeUnless { it == UnknownMessageDTO }?.let(::add) }
|
||||
}
|
||||
|
||||
fun MessageChainDTO.toMessageChain(contact: Contact) =
|
||||
MessageChain().apply { this@toMessageChain.forEach { add(it.toMessage(contact)) } }
|
||||
|
||||
internal fun MessageSource.calMessageId() = (messageUid.toLong() shl 32) or (sequenceId.toLong() and 0xFFFFFFFF)
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
fun Message.toDTO() = when (this) {
|
||||
is MessageSource -> MessageSourceDTO(messageUid)
|
||||
is MessageSource -> MessageSourceDTO(calMessageId())
|
||||
is At -> AtDTO(target, display)
|
||||
is AtAll -> AtAllDTO(0L)
|
||||
is Face -> FaceDTO(id)
|
||||
is PlainText -> PlainDTO(stringValue)
|
||||
is Image -> ImageDTO(imageId)
|
||||
is XMLMessage -> XmlDTO(stringValue)
|
||||
else -> UnknownMessageDTO("未知消息类型")
|
||||
else -> UnknownMessageDTO
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
||||
|
@ -11,20 +11,22 @@ package net.mamoe.mirai.api.http.queue
|
||||
|
||||
import net.mamoe.mirai.api.http.data.common.EventDTO
|
||||
import net.mamoe.mirai.api.http.data.common.IgnoreEventDTO
|
||||
import net.mamoe.mirai.api.http.data.common.calMessageId
|
||||
import net.mamoe.mirai.api.http.data.common.toDTO
|
||||
import net.mamoe.mirai.event.events.BotEvent
|
||||
import net.mamoe.mirai.message.GroupMessage
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import net.mamoe.mirai.utils.firstKey
|
||||
import java.util.concurrent.ConcurrentLinkedDeque
|
||||
|
||||
class MessageQueue : ConcurrentLinkedDeque<BotEvent>() {
|
||||
|
||||
val quoteCache = ConcurrentHashMap<Long, GroupMessage>()
|
||||
val quoteCacheSize = 4096
|
||||
val quoteCache = LinkedHashMap<Long, GroupMessage>()
|
||||
|
||||
fun fetch(size: Int): List<EventDTO> {
|
||||
var count = size
|
||||
quoteCache.clear()
|
||||
|
||||
val ret = ArrayList<EventDTO>(count)
|
||||
while (!this.isEmpty() && count > 0) {
|
||||
val event = pop()
|
||||
@ -36,14 +38,18 @@ class MessageQueue : ConcurrentLinkedDeque<BotEvent>() {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 等FriendMessage支持quote
|
||||
if (event is GroupMessage) {
|
||||
addCache(event)
|
||||
addQuoteCache(event)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
private fun addCache(msg: GroupMessage) {
|
||||
quoteCache[msg.message[MessageSource].messageUid] = msg
|
||||
private fun addQuoteCache(msg: GroupMessage) {
|
||||
quoteCache[msg.message[MessageSource].calMessageId()] = msg
|
||||
if (quoteCache.size > quoteCacheSize) {
|
||||
quoteCache.remove(quoteCache.firstKey())
|
||||
}
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.content.PartData
|
||||
import io.ktor.request.receive
|
||||
import io.ktor.response.defaultTextContentType
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.route
|
||||
@ -141,6 +142,9 @@ internal inline fun Route.intercept(crossinline blk: suspend PipelineContext<Uni
|
||||
call.respondStateCode(StateCode.PermissionDenied)
|
||||
} catch (e: IllegalAccessException) {
|
||||
call.respondStateCode(StateCode(400, e.message), HttpStatusCode.BadRequest)
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
call.respond(HttpStatusCode.InternalServerError, e.message!!)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,19 +11,21 @@ package net.mamoe.mirai.api.http.route
|
||||
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.content.readAllParts
|
||||
import io.ktor.http.content.streamProvider
|
||||
import io.ktor.request.receiveMultipart
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.post
|
||||
import io.ktor.routing.routing
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import net.mamoe.mirai.api.http.AuthedSession
|
||||
import net.mamoe.mirai.api.http.SessionManager
|
||||
import net.mamoe.mirai.api.http.data.*
|
||||
import net.mamoe.mirai.api.http.data.common.MessageChainDTO
|
||||
import net.mamoe.mirai.api.http.data.common.VerifyDTO
|
||||
import net.mamoe.mirai.api.http.data.common.toDTO
|
||||
import net.mamoe.mirai.api.http.data.common.toMessageChain
|
||||
import net.mamoe.mirai.api.http.util.toJson
|
||||
import net.mamoe.mirai.contact.toList
|
||||
@ -45,14 +47,12 @@ fun Application.messageModule() {
|
||||
it.session.bot.getFriend(it.target).apply {
|
||||
sendMessage(it.messageChain.toMessageChain(this)) // this aka QQ
|
||||
}
|
||||
call.respondStateCode(StateCode.Success)
|
||||
}
|
||||
|
||||
miraiVerify<SendDTO>("/sendGroupMessage") {
|
||||
it.session.bot.getGroup(it.target).apply {
|
||||
sendMessage(it.messageChain.toMessageChain(this)) // this aka Group
|
||||
}
|
||||
call.respondStateCode(StateCode.Success)
|
||||
}
|
||||
|
||||
miraiVerify<SendDTO>("/quoteMessage") {
|
||||
@ -100,6 +100,11 @@ fun Application.messageModule() {
|
||||
} ?: throw IllegalAccessException("图片上传错误")
|
||||
} ?: throw IllegalAccessException("未知错误")
|
||||
}
|
||||
|
||||
miraiVerify<RecallDTO>("recall") {
|
||||
// TODO
|
||||
call.respond(HttpStatusCode.NotFound, "未完成")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,3 +124,15 @@ private data class SendImageDTO(
|
||||
val urls: List<String>
|
||||
) : VerifyDTO()
|
||||
|
||||
@Serializable
|
||||
private class SendRetDTO(
|
||||
val messageId: Long,
|
||||
@Transient val stateCode: StateCode = Success
|
||||
) : StateCode(stateCode.code, stateCode.msg)
|
||||
|
||||
@Serializable
|
||||
private data class RecallDTO(
|
||||
override val sessionKey: String,
|
||||
val target: Long,
|
||||
val sender: Long
|
||||
) : VerifyDTO()
|
@ -1,6 +1,7 @@
|
||||
package net.mamoe.mirai.console.graphical.controller
|
||||
|
||||
import javafx.application.Platform
|
||||
import javafx.collections.ObservableList
|
||||
import javafx.stage.Modality
|
||||
import kotlinx.io.core.IoBuffer
|
||||
import net.mamoe.mirai.Bot
|
||||
@ -8,6 +9,7 @@ import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.MiraiConsoleUI
|
||||
import net.mamoe.mirai.console.graphical.model.BotModel
|
||||
import net.mamoe.mirai.console.graphical.model.ConsoleInfo
|
||||
import net.mamoe.mirai.console.graphical.model.PluginModel
|
||||
import net.mamoe.mirai.console.graphical.model.VerificationCodeModel
|
||||
import net.mamoe.mirai.console.graphical.view.VerificationCodeFragment
|
||||
import net.mamoe.mirai.utils.LoginSolver
|
||||
@ -21,13 +23,21 @@ class MiraiGraphicalUIController : Controller(), MiraiConsoleUI {
|
||||
private val loginSolver = GraphicalLoginSolver()
|
||||
private val cache = mutableMapOf<Long, BotModel>()
|
||||
val mainLog = observableListOf<String>()
|
||||
|
||||
|
||||
val botList = observableListOf<BotModel>()
|
||||
val pluginList: ObservableList<PluginModel> by lazy(::getPluginsFromConsole)
|
||||
|
||||
val consoleConfig : Map<String, Any> by lazy(::getConfigFromConsole)
|
||||
|
||||
val consoleInfo = ConsoleInfo()
|
||||
|
||||
suspend fun login(qq: String, psd: String) {
|
||||
MiraiConsole.CommandListener.commandChannel.send("/login $qq $psd")
|
||||
}
|
||||
|
||||
suspend fun sendCommand(command: String) = MiraiConsole.CommandListener.commandChannel.send(command)
|
||||
|
||||
override fun pushLog(identity: Long, message: String) = Platform.runLater {
|
||||
when (identity) {
|
||||
0L -> mainLog.add(message)
|
||||
@ -68,6 +78,13 @@ class MiraiGraphicalUIController : Controller(), MiraiConsoleUI {
|
||||
}
|
||||
|
||||
override fun createLoginSolver(): LoginSolver = loginSolver
|
||||
|
||||
private fun getPluginsFromConsole(): ObservableList<PluginModel> {
|
||||
// TODO
|
||||
return observableListOf<PluginModel>()
|
||||
}
|
||||
|
||||
private fun getConfigFromConsole() = MiraiConsole.MiraiProperties.config.asMap()
|
||||
}
|
||||
|
||||
class GraphicalLoginSolver : LoginSolver() {
|
||||
|
@ -0,0 +1,19 @@
|
||||
package net.mamoe.mirai.console.graphical.model
|
||||
|
||||
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject
|
||||
import javafx.beans.property.SimpleBooleanProperty
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import tornadofx.getValue
|
||||
import tornadofx.setValue
|
||||
|
||||
class PluginModel : RecursiveTreeObject<PluginModel>() {
|
||||
|
||||
val nameProperty = SimpleStringProperty(this, "nameProperty")
|
||||
val name by nameProperty
|
||||
|
||||
val descriptionProperty = SimpleStringProperty(this, "descriptionProperty")
|
||||
val description by descriptionProperty
|
||||
|
||||
val enabledProperty = SimpleBooleanProperty(this, "enabledProperty")
|
||||
var enabled by enabledProperty
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package net.mamoe.mirai.console.graphical.util
|
||||
|
||||
import com.jfoenix.controls.*
|
||||
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject
|
||||
import javafx.beans.value.ObservableValue
|
||||
import javafx.collections.ObservableList
|
||||
import javafx.event.EventTarget
|
||||
@ -42,3 +43,6 @@ internal fun <T> EventTarget.jfxListView(values: ObservableList<T>? = null, op:
|
||||
else it.items = values
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : RecursiveTreeObject<T>?> EventTarget.jfxTreeTableView(items: ObservableList<T>? = null, op: JFXTreeTableView<T>.() -> Unit = {})
|
||||
= JFXTreeTableView<T>(RecursiveTreeItem(items, RecursiveTreeObject<T>::getChildren)).attachTo(this, op)
|
@ -0,0 +1,22 @@
|
||||
package net.mamoe.mirai.console.graphical.view
|
||||
|
||||
import com.jfoenix.controls.JFXTreeTableColumn
|
||||
import net.mamoe.mirai.console.graphical.controller.MiraiGraphicalUIController
|
||||
import net.mamoe.mirai.console.graphical.model.PluginModel
|
||||
import net.mamoe.mirai.console.graphical.util.jfxTreeTableView
|
||||
import tornadofx.View
|
||||
|
||||
class PluginsView : View() {
|
||||
|
||||
private val controller = find<MiraiGraphicalUIController>()
|
||||
val plugins = controller.pluginList
|
||||
|
||||
override val root = jfxTreeTableView(plugins) {
|
||||
columns.addAll(
|
||||
JFXTreeTableColumn<PluginModel, String>("插件名").apply { },
|
||||
JFXTreeTableColumn<PluginModel, String>("版本").apply { },
|
||||
JFXTreeTableColumn<PluginModel, String>("作者").apply { },
|
||||
JFXTreeTableColumn<PluginModel, String>("介绍").apply { }
|
||||
)
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ import javafx.collections.ObservableList
|
||||
import javafx.scene.control.Tab
|
||||
import javafx.scene.control.TabPane
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.input.KeyCode
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.mamoe.mirai.console.graphical.controller.MiraiGraphicalUIController
|
||||
import net.mamoe.mirai.console.graphical.model.BotModel
|
||||
import net.mamoe.mirai.console.graphical.util.jfxListView
|
||||
@ -52,17 +54,26 @@ class PrimaryView : View() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// command input
|
||||
textfield {
|
||||
setOnKeyPressed {
|
||||
if (it.code == KeyCode.ENTER) {
|
||||
runAsync {
|
||||
runBlocking { controller.sendCommand(text) }
|
||||
}.ui { text = "" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
center = jfxTabPane {
|
||||
|
||||
tab("Login") {
|
||||
this += find<LoginView>().root
|
||||
}
|
||||
tab("Login").content = find<LoginView>().root
|
||||
|
||||
tab("Plugin")
|
||||
tab("Plugins").content = find<PluginsView>().root
|
||||
|
||||
tab("Settings")
|
||||
tab("Settings").content = find<SettingsView>().root
|
||||
|
||||
logTab("Main", controller.mainLog)
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
package net.mamoe.mirai.console.graphical.view
|
||||
|
||||
import net.mamoe.mirai.console.graphical.controller.MiraiGraphicalUIController
|
||||
import net.mamoe.mirai.console.graphical.util.jfxTextfield
|
||||
import tornadofx.View
|
||||
import tornadofx.field
|
||||
import tornadofx.fieldset
|
||||
import tornadofx.form
|
||||
|
||||
class SettingsView : View() {
|
||||
|
||||
private val controller = find<MiraiGraphicalUIController>()
|
||||
|
||||
override val root = form {
|
||||
controller.consoleConfig.forEach {
|
||||
fieldset {
|
||||
field(it.key) {
|
||||
jfxTextfield(it.value.toString()) { isEditable = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,14 +16,13 @@ import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.data.CustomFaceFromFile
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.NotOnlineImageFromFile
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
|
||||
import net.mamoe.mirai.qqandroid.network.highway.postImage
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopMemberInfo
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
|
||||
@ -66,20 +65,22 @@ internal class QQImpl(
|
||||
override val nick: String
|
||||
get() = friendInfo.nick
|
||||
|
||||
override suspend fun sendMessage(message: MessageChain) {
|
||||
override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ> {
|
||||
val event = FriendMessageSendEvent(this, message).broadcast()
|
||||
if (event.isCancelled) {
|
||||
throw EventCancelledException("cancelled by FriendMessageSendEvent")
|
||||
}
|
||||
lateinit var source: MessageSource
|
||||
bot.network.run {
|
||||
check(
|
||||
MessageSvc.PbSendMsg.ToFriend(
|
||||
bot.client,
|
||||
id,
|
||||
event.message
|
||||
).sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS
|
||||
) { source = it }.sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS
|
||||
) { "send message failed" }
|
||||
}
|
||||
return MessageReceipt(message, source, this)
|
||||
}
|
||||
|
||||
override suspend fun uploadImage(image: ExternalImage): Image = try {
|
||||
@ -166,7 +167,7 @@ internal class QQImpl(
|
||||
}
|
||||
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate", "DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
internal class MemberImpl(
|
||||
qq: QQImpl,
|
||||
group: GroupImpl,
|
||||
@ -286,26 +287,39 @@ internal class MemberImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = bot.hashCode()
|
||||
result = 31 * result + id.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
override fun equals(other: Any?): Boolean { // 不要删除. trust me
|
||||
if (this === other) return true
|
||||
if (other !is Contact) return false
|
||||
if (this::class != other::class) return false
|
||||
return this.id == other.id && this.bot == other.bot
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Member($id)"
|
||||
}
|
||||
}
|
||||
|
||||
internal class MemberInfoImpl(
|
||||
private val jceInfo: StTroopMemberInfo,
|
||||
private val groupOwnerId: Long
|
||||
jceInfo: StTroopMemberInfo,
|
||||
groupOwnerId: Long
|
||||
) : MemberInfo {
|
||||
override val uin: Long get() = jceInfo.memberUin
|
||||
override val nameCard: String get() = jceInfo.sName ?: ""
|
||||
override val nick: String get() = jceInfo.nick
|
||||
override val permission: MemberPermission
|
||||
get() = when {
|
||||
jceInfo.memberUin == groupOwnerId -> MemberPermission.OWNER
|
||||
jceInfo.dwFlag == 1L -> MemberPermission.ADMINISTRATOR
|
||||
else -> MemberPermission.MEMBER
|
||||
}
|
||||
override val specialTitle: String get() = jceInfo.sSpecialTitle ?: ""
|
||||
override val muteTimestamp: Int get() = jceInfo.dwShutupTimestap?.toInt() ?: 0
|
||||
override val uin: Long = jceInfo.memberUin
|
||||
override val nameCard: String = jceInfo.sName ?: ""
|
||||
override val nick: String = jceInfo.nick
|
||||
override val permission: MemberPermission = when {
|
||||
jceInfo.memberUin == groupOwnerId -> MemberPermission.OWNER
|
||||
jceInfo.dwFlag == 1L -> MemberPermission.ADMINISTRATOR
|
||||
else -> MemberPermission.MEMBER
|
||||
}
|
||||
override val specialTitle: String = jceInfo.sSpecialTitle ?: ""
|
||||
override val muteTimestamp: Int = jceInfo.dwShutupTimestap?.toInt() ?: 0
|
||||
}
|
||||
|
||||
/**
|
||||
@ -325,6 +339,24 @@ internal class GroupImpl(
|
||||
|
||||
override lateinit var owner: Member
|
||||
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
override val botAsMember: Member by lazy {
|
||||
Member(object : MemberInfo {
|
||||
override val nameCard: String
|
||||
get() = bot.nick // TODO: 2020/2/21 机器人在群内的昵称获取
|
||||
override val permission: MemberPermission
|
||||
get() = botPermission
|
||||
override val specialTitle: String
|
||||
get() = "" // TODO: 2020/2/21 获取机器人在群里的头衔
|
||||
override val muteTimestamp: Int
|
||||
get() = botMuteRemaining
|
||||
override val uin: Long
|
||||
get() = bot.uin
|
||||
override val nick: String
|
||||
get() = bot.nick
|
||||
})
|
||||
}
|
||||
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
override lateinit var botPermission: MemberPermission
|
||||
|
||||
@ -340,6 +372,9 @@ internal class GroupImpl(
|
||||
override val members: ContactList<Member> = ContactList(members.mapNotNull {
|
||||
if (it.uin == bot.uin) {
|
||||
botPermission = it.permission
|
||||
if (it.permission == MemberPermission.OWNER) {
|
||||
owner = botAsMember
|
||||
}
|
||||
null
|
||||
} else Member(it).also { member ->
|
||||
if (member.permission == MemberPermission.OWNER) {
|
||||
@ -475,6 +510,20 @@ internal class GroupImpl(
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
override suspend fun recall(source: MessageSource) {
|
||||
if (source.senderId != bot.uin) {
|
||||
checkBotPermissionOperator()
|
||||
}
|
||||
|
||||
source.ensureSequenceIdAvailable()
|
||||
|
||||
bot.network.run {
|
||||
val response = PbMessageSvc.PbMsgWithDraw.Group(bot.client, this@GroupImpl.id, source.sequenceId, source.messageUid.toInt())
|
||||
.sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
|
||||
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.sequenceId}: $response" }
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
override fun Member(memberInfo: MemberInfo): Member {
|
||||
return MemberImpl(
|
||||
@ -498,22 +547,27 @@ internal class GroupImpl(
|
||||
return members.delegate.filteringGetOrNull { it.id == id }
|
||||
}
|
||||
|
||||
override suspend fun sendMessage(message: MessageChain) {
|
||||
override suspend fun sendMessage(message: MessageChain): MessageReceipt<Group> {
|
||||
check(!isBotMuted) { "bot is muted. Remaining seconds=$botMuteRemaining" }
|
||||
val event = GroupMessageSendEvent(this, message).broadcast()
|
||||
if (event.isCancelled) {
|
||||
throw EventCancelledException("cancelled by FriendMessageSendEvent")
|
||||
}
|
||||
lateinit var source: MessageSvc.PbSendMsg.MessageSourceFromSend
|
||||
bot.network.run {
|
||||
val response = MessageSvc.PbSendMsg.ToGroup(
|
||||
val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.ToGroup(
|
||||
bot.client,
|
||||
id,
|
||||
event.message
|
||||
).sendAndExpect<MessageSvc.PbSendMsg.Response>()
|
||||
) { source = it }.sendAndExpect()
|
||||
check(
|
||||
response is MessageSvc.PbSendMsg.Response.SUCCESS
|
||||
) { "send message failed: $response" }
|
||||
}
|
||||
|
||||
source.startWaitingSequenceId(this)
|
||||
|
||||
return MessageReceipt(message, source, this)
|
||||
}
|
||||
|
||||
override suspend fun uploadImage(image: ExternalImage): Image = try {
|
||||
|
@ -10,7 +10,6 @@
|
||||
package net.mamoe.mirai.qqandroid.message
|
||||
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
|
||||
@ -22,8 +21,14 @@ internal inline class MessageSourceFromServer(
|
||||
val delegate: ImMsgBody.SourceMsg
|
||||
) : MessageSource {
|
||||
override val time: Long get() = delegate.time.toLong() and 0xFFFFFFFF
|
||||
override val messageUid: Long get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!
|
||||
override val sourceMessage: MessageChain get() = delegate.toMessageChain()
|
||||
override val sequenceId: Int get() = delegate.origSeqs?.firstOrNull() ?: error("cannot find sequenceId from ImMsgBody.SourceMsg")
|
||||
|
||||
override suspend fun ensureSequenceIdAvailable() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
override val messageUid: Int get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!.toInt()
|
||||
// override val sourceMessage: MessageChain get() = delegate.toMessageChain()
|
||||
override val senderId: Long get() = delegate.senderUin
|
||||
override val groupId: Long get() = Group.calculateGroupCodeByGroupUin(delegate.toUin)
|
||||
|
||||
@ -34,8 +39,13 @@ internal inline class MessageSourceFromMsg(
|
||||
val delegate: MsgComm.Msg
|
||||
) : MessageSource {
|
||||
override val time: Long get() = delegate.msgHead.msgTime.toLong() and 0xFFFFFFFF
|
||||
override val messageUid: Long get() = delegate.msgBody.richText.attr!!.random.toLong()
|
||||
override val sourceMessage: MessageChain get() = delegate.toMessageChain()
|
||||
override val sequenceId: Int get() = delegate.msgHead.msgSeq
|
||||
override suspend fun ensureSequenceIdAvailable() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
override val messageUid: Int get() = delegate.msgBody.richText.attr!!.random
|
||||
// override val sourceMessage: MessageChain get() = delegate.toMessageChain()
|
||||
override val senderId: Long get() = delegate.msgHead.fromUin
|
||||
override val groupId: Long get() = delegate.msgHead.groupInfo!!.groupCode
|
||||
|
||||
@ -52,7 +62,7 @@ internal inline class MessageSourceFromMsg(
|
||||
type = 0,
|
||||
time = delegate.msgHead.msgTime,
|
||||
pbReserve = SourceMsg.ResvAttr(
|
||||
origUids = messageUid
|
||||
origUids = messageUid.toLong() and 0xffFFffFF
|
||||
).toByteArray(SourceMsg.ResvAttr.serializer()),
|
||||
srcMsg = MsgComm.Msg(
|
||||
msgHead = MsgComm.MsgHead(
|
||||
@ -62,7 +72,8 @@ internal inline class MessageSourceFromMsg(
|
||||
c2cCmd = delegate.msgHead.c2cCmd,
|
||||
msgSeq = delegate.msgHead.msgSeq,
|
||||
msgTime = delegate.msgHead.msgTime,
|
||||
msgUid = messageUid, // ok
|
||||
msgUid = messageUid.toLong() and 0xffFFffFF
|
||||
, // ok
|
||||
groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode),
|
||||
isSrcMsg = true
|
||||
),
|
||||
|
@ -15,6 +15,7 @@ import kotlinx.io.core.readUInt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiDebugAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.io.discardExact
|
||||
@ -27,12 +28,13 @@ internal fun At.toJceData(): ImMsgBody.Text {
|
||||
return ImMsgBody.Text(
|
||||
str = text,
|
||||
attr6Buf = buildPacket {
|
||||
writeShort(1)
|
||||
writeShort(0)
|
||||
writeShort(text.length.toShort())
|
||||
writeByte(1)
|
||||
writeInt(target.toInt())
|
||||
writeShort(0)
|
||||
// MessageForText$AtTroopMemberInfo
|
||||
writeShort(1) // const
|
||||
writeShort(0) // startPos
|
||||
writeShort(text.length.toShort()) // textLen
|
||||
writeByte(0) // flag, may=1
|
||||
writeInt(target.toInt()) // uin
|
||||
writeShort(0) // const
|
||||
}.readBytes()
|
||||
)
|
||||
}
|
||||
@ -206,7 +208,15 @@ notOnlineImage=NotOnlineImage#2050019814 {
|
||||
private val atAllData = ImMsgBody.Elem(
|
||||
text = ImMsgBody.Text(
|
||||
str = "@全体成员",
|
||||
attr6Buf = "00 01 00 00 00 05 01 00 00 00 00 00 00".hexToBytes()
|
||||
attr6Buf = buildPacket {
|
||||
// MessageForText$AtTroopMemberInfo
|
||||
writeShort(1) // const
|
||||
writeShort(0) // startPos
|
||||
writeShort("@全体成员".length.toShort()) // textLen
|
||||
writeByte(1) // flag, may=1
|
||||
writeInt(0) // uin
|
||||
writeShort(0) // const
|
||||
}.readBytes()
|
||||
)
|
||||
)
|
||||
|
||||
@ -224,7 +234,7 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
|
||||
this.forEach {
|
||||
when (it) {
|
||||
is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
|
||||
is At -> elements.add(ImMsgBody.Elem(text = it.toJceData()))
|
||||
is At -> elements.add(ImMsgBody.Elem(text = it.toJceData())).also { elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " "))) }
|
||||
is CustomFaceFromFile -> elements.add(ImMsgBody.Elem(customFace = it.toJceData()))
|
||||
is CustomFaceFromServer -> elements.add(ImMsgBody.Elem(customFace = it.delegate))
|
||||
is NotOnlineImageFromServer -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate))
|
||||
@ -249,7 +259,7 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
|
||||
internal class CustomFaceFromServer(
|
||||
internal val delegate: ImMsgBody.CustomFace
|
||||
) : CustomFace() {
|
||||
override val filepath: String get() = delegate.filePath
|
||||
override val filepath: String = delegate.filePath
|
||||
override val fileId: Int get() = delegate.fileId
|
||||
override val serverIp: Int get() = delegate.serverIp
|
||||
override val serverPort: Int get() = delegate.serverPort
|
||||
@ -265,14 +275,14 @@ internal class CustomFaceFromServer(
|
||||
override val size: Int get() = delegate.size
|
||||
override val original: Int get() = delegate.origin
|
||||
override val pbReserve: ByteArray get() = delegate.pbReserve
|
||||
override val imageId: String get() = delegate.filePath
|
||||
override val imageId: String = ExternalImage.generateImageId(delegate.md5, imageType)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is CustomFaceFromServer && other.filepath == this.filepath && other.md5.contentEquals(this.md5)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filepath.hashCode() + 31 * md5.hashCode()
|
||||
return imageId.hashCode() + 31 * md5.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,7 +306,7 @@ internal class NotOnlineImageFromServer(
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return resourceId.hashCode() + 31 * md5.hashCode()
|
||||
return imageId.hashCode() + 31 * md5.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,12 +374,12 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
// with generic type, less mistakes
|
||||
private suspend fun <P : Packet?> generifiedParsePacket(input: Input) {
|
||||
KnownPacketFactories.parseIncomingPacket(bot, input) { packetFactory: PacketFactory<P>, packet: P, commandName: String, sequenceId: Int ->
|
||||
handlePacket(packetFactory, packet, commandName, sequenceId)
|
||||
if (packet is MultiPacket<*>) {
|
||||
packet.forEach {
|
||||
handlePacket(null, it, commandName, sequenceId)
|
||||
}
|
||||
}
|
||||
handlePacket(packetFactory, packet, commandName, sequenceId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,29 +388,6 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
*/
|
||||
suspend fun <P : Packet?> handlePacket(packetFactory: PacketFactory<P>?, packet: P, commandName: String, sequenceId: Int) {
|
||||
// highest priority: pass to listeners (attached by sendAndExpect).
|
||||
packetListeners.forEach { listener ->
|
||||
if (listener.filter(commandName, sequenceId) && packetListeners.remove(listener)) {
|
||||
listener.complete(packet)
|
||||
}
|
||||
}
|
||||
|
||||
// check top-level cancelling
|
||||
if (packet != null && PacketReceivedEvent(packet).broadcast().isCancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// broadcast
|
||||
if (packet is Event) {
|
||||
if (packet is BroadcastControllable) {
|
||||
if (packet.shouldBroadcast) packet.broadcast()
|
||||
} else {
|
||||
packet.broadcast()
|
||||
}
|
||||
|
||||
if (packet is CancellableEvent && packet.isCancelled) return
|
||||
}
|
||||
|
||||
if (packet != null && (bot.logger.isEnabled || logger.isEnabled)) {
|
||||
val logMessage = "Received: ${packet.toString().replace("\n", """\n""").replace("\r", "")}"
|
||||
|
||||
@ -419,12 +396,32 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
} else logger.verbose(logMessage)
|
||||
}
|
||||
|
||||
packetListeners.forEach { listener ->
|
||||
if (listener.filter(commandName, sequenceId) && packetListeners.remove(listener)) {
|
||||
listener.complete(packet)
|
||||
}
|
||||
}
|
||||
|
||||
packetFactory?.run {
|
||||
when (this) {
|
||||
is OutgoingPacketFactory<P> -> bot.handle(packet)
|
||||
is IncomingPacketFactory<P> -> bot.handle(packet, sequenceId)?.sendWithoutExpect()
|
||||
}
|
||||
}
|
||||
|
||||
if (packet != null && PacketReceivedEvent(packet).broadcast().isCancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (packet is Event) {
|
||||
if (packet is BroadcastControllable) {
|
||||
if (packet.shouldBroadcast) packet.broadcast()
|
||||
} else {
|
||||
packet.broadcast()
|
||||
}
|
||||
|
||||
if (packet is CancellableEvent && packet.isCancelled) return
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.qqandroid.network.protocol.data.proto
|
||||
|
||||
import kotlinx.serialization.SerialId
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.qqandroid.io.ProtoBuf
|
||||
|
||||
class MsgRevokeUserDef : ProtoBuf {
|
||||
@Serializable
|
||||
class MsgInfoUserDef(
|
||||
@SerialId(1) val longMessageFlag: Int = 0,
|
||||
@SerialId(2) val longMsgInfo: List<MsgInfoDef>? = null,
|
||||
@SerialId(3) val fileUuid: List<String> = listOf()
|
||||
) : ProtoBuf {
|
||||
@Serializable
|
||||
class MsgInfoDef(
|
||||
@SerialId(1) val msgSeq: Int = 0,
|
||||
@SerialId(2) val longMsgId: Int = 0,
|
||||
@SerialId(3) val longMsgNum: Int = 0,
|
||||
@SerialId(4) val longMsgIndex: Int = 0
|
||||
) : ProtoBuf
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class UinTypeUserDef(
|
||||
@SerialId(1) val fromUinType: Int = 0,
|
||||
@SerialId(2) val fromGroupCode: Long = 0L,
|
||||
@SerialId(3) val fileUuid: List<String> = listOf()
|
||||
) : ProtoBuf
|
||||
}
|
@ -26,7 +26,7 @@ internal class MsgSvc : ProtoBuf {
|
||||
internal class PbGetMsgResp(
|
||||
@SerialId(1) val result: Int = 0,
|
||||
@SerialId(2) val errmsg: String = "",
|
||||
@SerialId(3) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@SerialId(3) val syncCookie: ByteArray? = EMPTY_BYTE_ARRAY,
|
||||
@SerialId(4) val syncFlag: SyncFlag,
|
||||
@SerialId(5) val uinPairMsgs: List<MsgComm.UinPairMsg>? = null,
|
||||
@SerialId(6) val bindUin: Long = 0L,
|
||||
|
@ -14,6 +14,7 @@ import kotlinx.io.pool.useInstance
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
|
||||
@ -138,7 +139,8 @@ internal object KnownPacketFactories {
|
||||
TroopManagement.GetGroupInfo,
|
||||
TroopManagement.EditGroupNametag,
|
||||
TroopManagement.Kick,
|
||||
Heartbeat.Alive
|
||||
Heartbeat.Alive,
|
||||
PbMessageSvc.PbMsgWithDraw
|
||||
)
|
||||
|
||||
object IncomingFactories : List<IncomingPacketFactory<*>> by mutableListOf(
|
||||
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.qqandroid.network.protocol.packet.chat
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgRevokeUserDef
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
|
||||
|
||||
internal class PbMessageSvc {
|
||||
object PbMsgWithDraw : OutgoingPacketFactory<PbMsgWithDraw.Response>(
|
||||
"PbMessageSvc.PbMsgWithDraw"
|
||||
) {
|
||||
sealed class Response : Packet {
|
||||
object Success : Response() {
|
||||
override fun toString(): String {
|
||||
return "PbMessageSvc.PbMsgWithDraw.Response.Success"
|
||||
}
|
||||
}
|
||||
|
||||
data class Failed(
|
||||
val result: Int,
|
||||
val errorMessage: String
|
||||
) : Response()
|
||||
}
|
||||
|
||||
// 12 1A 08 01 10 00 18 E7 C1 AD B8 02 22 0A 08 BF BA 03 10 BF 81 CB B7 03 2A 02 08 00
|
||||
fun Group(
|
||||
client: QQAndroidClient,
|
||||
groupCode: Long,
|
||||
messageSequenceId: Int, // 56639
|
||||
messageRandom: Int, // 921878719
|
||||
messageType: Int = 0
|
||||
): OutgoingPacket = buildOutgoingUniPacket(client) {
|
||||
writeProtoBuf(
|
||||
MsgSvc.PbMsgWithDrawReq.serializer(),
|
||||
MsgSvc.PbMsgWithDrawReq(
|
||||
groupWithDraw = listOf(
|
||||
MsgSvc.PbGroupMsgWithDrawReq(
|
||||
subCmd = 1,
|
||||
groupType = 0, // 普通群
|
||||
groupCode = groupCode,
|
||||
msgList = listOf(
|
||||
MsgSvc.PbGroupMsgWithDrawReq.MessageInfo(
|
||||
msgSeq = messageSequenceId,
|
||||
msgRandom = messageRandom,
|
||||
msgType = messageType
|
||||
)
|
||||
),
|
||||
userdef = MsgRevokeUserDef.MsgInfoUserDef(
|
||||
longMessageFlag = 0
|
||||
).toByteArray(MsgRevokeUserDef.MsgInfoUserDef.serializer())
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
|
||||
val resp = readProtoBuf(MsgSvc.PbMsgWithDrawResp.serializer())
|
||||
resp.groupWithDraw?.firstOrNull()?.let {
|
||||
if (it.result != 0) {
|
||||
return Response.Failed(it.result, it.errmsg)
|
||||
}
|
||||
return Response.Success
|
||||
}
|
||||
resp.c2cWithDraw?.firstOrNull()?.let {
|
||||
if (it.result != 0) {
|
||||
return Response.Failed(it.result, it.errmsg)
|
||||
}
|
||||
return Response.Success
|
||||
}
|
||||
return Response.Failed(-1, "No response")
|
||||
}
|
||||
}
|
||||
}
|
@ -9,8 +9,11 @@
|
||||
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
|
||||
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.discardExact
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.data.MemberInfo
|
||||
@ -19,8 +22,11 @@ import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.event.events.BotJoinGroupEvent
|
||||
import net.mamoe.mirai.event.events.BotOfflineEvent
|
||||
import net.mamoe.mirai.event.events.MemberJoinEvent
|
||||
import net.mamoe.mirai.event.subscribingGetAsync
|
||||
import net.mamoe.mirai.message.FriendMessage
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.message.data.addOrRemove
|
||||
import net.mamoe.mirai.qqandroid.GroupImpl
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
|
||||
@ -256,22 +262,73 @@ internal class MessageSvc {
|
||||
override fun toString(): String = "MessageSvc.PbSendMsg.Response.SUCCESS"
|
||||
}
|
||||
|
||||
/**
|
||||
* 121: 被限制? 个别号才不能发
|
||||
*/
|
||||
data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() {
|
||||
override fun toString(): String =
|
||||
"MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)"
|
||||
}
|
||||
}
|
||||
|
||||
internal class MessageSourceFromSend(
|
||||
override val messageUid: Int,
|
||||
override val time: Long,
|
||||
override val senderId: Long,
|
||||
override val groupId: Long// ,
|
||||
// override val sourceMessage: MessageChain
|
||||
) : MessageSource {
|
||||
private lateinit var sequenceIdDeferred: Deferred<Int>
|
||||
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
fun startWaitingSequenceId(contact: Contact) {
|
||||
sequenceIdDeferred = contact.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int> {
|
||||
if (it.messageRandom == messageUid) {
|
||||
it.sequenceId
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
override val sequenceId: Int
|
||||
get() = sequenceIdDeferred.getCompleted()
|
||||
|
||||
override suspend fun ensureSequenceIdAvailable() {
|
||||
sequenceIdDeferred.join()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
inline fun ToFriend(
|
||||
client: QQAndroidClient,
|
||||
toUin: Long,
|
||||
message: MessageChain,
|
||||
crossinline sourceCallback: (MessageSource) -> Unit
|
||||
): OutgoingPacket {
|
||||
val source = MessageSourceFromSend(
|
||||
messageUid = Random.nextInt().absoluteValue,
|
||||
senderId = client.uin,
|
||||
time = currentTimeSeconds + client.timeDifference,
|
||||
groupId = 0//
|
||||
// sourceMessage = message
|
||||
)
|
||||
sourceCallback(source)
|
||||
return ToFriend(client, toUin, message, source)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送好友消息
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
fun ToFriend(
|
||||
private fun ToFriend(
|
||||
client: QQAndroidClient,
|
||||
toUin: Long,
|
||||
message: MessageChain
|
||||
message: MessageChain,
|
||||
source: MessageSource
|
||||
): OutgoingPacket = buildOutgoingUniPacket(client) {
|
||||
|
||||
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
|
||||
|
||||
///return@buildOutgoingUniPacket
|
||||
@ -285,13 +342,32 @@ internal class MessageSvc {
|
||||
)
|
||||
),
|
||||
msgSeq = client.atomicNextMessageSequenceId(),
|
||||
msgRand = Random.nextInt().absoluteValue,
|
||||
syncCookie = SyncCookie(time = currentTimeSeconds).toByteArray(SyncCookie.serializer())
|
||||
msgRand = source.messageUid,
|
||||
syncCookie = SyncCookie(time = source.time).toByteArray(SyncCookie.serializer())
|
||||
// msgVia = 1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
inline fun ToGroup(
|
||||
client: QQAndroidClient,
|
||||
groupCode: Long,
|
||||
message: MessageChain,
|
||||
sourceCallback: (MessageSourceFromSend) -> Unit
|
||||
): OutgoingPacket {
|
||||
|
||||
val source = MessageSourceFromSend(
|
||||
messageUid = Random.nextInt().absoluteValue,
|
||||
senderId = client.uin,
|
||||
time = currentTimeSeconds + client.timeDifference,
|
||||
groupId = groupCode//,
|
||||
// sourceMessage = message
|
||||
)
|
||||
sourceCallback(source)
|
||||
return ToGroup(client, groupCode, message, source)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送群消息
|
||||
*/
|
||||
@ -299,14 +375,13 @@ internal class MessageSvc {
|
||||
fun ToGroup(
|
||||
client: QQAndroidClient,
|
||||
groupCode: Long,
|
||||
message: MessageChain
|
||||
message: MessageChain,
|
||||
source: MessageSource
|
||||
): OutgoingPacket = buildOutgoingUniPacket(client) {
|
||||
|
||||
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
|
||||
|
||||
// DebugLogger.debug("sending group message: " + message.toRichTextElems().contentToString())
|
||||
|
||||
val seq = client.atomicNextMessageSequenceId()
|
||||
///return@buildOutgoingUniPacket
|
||||
writeProtoBuf(
|
||||
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
|
||||
@ -317,12 +392,14 @@ internal class MessageSvc {
|
||||
elems = message.toRichTextElems()
|
||||
)
|
||||
),
|
||||
msgSeq = seq,
|
||||
msgRand = Random.nextInt().absoluteValue,
|
||||
msgSeq = client.atomicNextMessageSequenceId(),
|
||||
msgRand = source.messageUid,
|
||||
syncCookie = EMPTY_BYTE_ARRAY,
|
||||
msgVia = 1
|
||||
)
|
||||
)
|
||||
|
||||
message.addOrRemove(source)
|
||||
}
|
||||
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
|
||||
|
@ -19,6 +19,7 @@ import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.data.MultiPacket
|
||||
import net.mamoe.mirai.data.NoPacket
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.message.GroupMessage
|
||||
import net.mamoe.mirai.qqandroid.GroupImpl
|
||||
@ -46,9 +47,18 @@ internal class OnlinePush {
|
||||
/**
|
||||
* 接受群消息
|
||||
*/
|
||||
internal object PbPushGroupMsg : IncomingPacketFactory<GroupMessage?>("OnlinePush.PbPushGroupMsg") {
|
||||
internal object PbPushGroupMsg : IncomingPacketFactory<Packet?>("OnlinePush.PbPushGroupMsg") {
|
||||
internal class SendGroupMessageReceipt(
|
||||
val messageRandom: Int,
|
||||
val sequenceId: Int
|
||||
) : Packet, Event {
|
||||
override fun toString(): String {
|
||||
return "OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt(messageRandom=$messageRandom, sequenceId=$sequenceId)"
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalStdlibApi::class)
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): GroupMessage? {
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
|
||||
// 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00
|
||||
if (!bot.firstLoginSucceed) return null
|
||||
val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer())
|
||||
@ -56,7 +66,7 @@ internal class OnlinePush {
|
||||
val extraInfo: ImMsgBody.ExtraInfo? = pbPushMsg.msg.msgBody.richText.elems.firstOrNull { it.extraInfo != null }?.extraInfo
|
||||
|
||||
if (pbPushMsg.msg.msgHead.fromUin == bot.uin) {
|
||||
return null
|
||||
return SendGroupMessageReceipt(pbPushMsg.msg.msgBody.richText.attr!!.random, pbPushMsg.msg.msgHead.msgSeq)
|
||||
}
|
||||
|
||||
val group = bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode)
|
||||
@ -64,8 +74,6 @@ internal class OnlinePush {
|
||||
// println(pbPushMsg.msg.msgBody.richText.contentToString())
|
||||
val flags = extraInfo?.flags ?: 0
|
||||
return GroupMessage(
|
||||
bot = bot,
|
||||
group = group,
|
||||
senderName = pbPushMsg.msg.msgHead.groupInfo.groupCard,
|
||||
sender = group[pbPushMsg.msg.msgHead.fromUin],
|
||||
message = pbPushMsg.msg.toMessageChain(),
|
||||
|
@ -107,6 +107,8 @@ fun main() {
|
||||
* 顶层方法. TCP 切掉头后直接来这里
|
||||
*/
|
||||
fun ByteReadPacket.decodeMultiClientToServerPackets() {
|
||||
DebugLogger.enable()
|
||||
PacketLogger.enable()
|
||||
println("=======================处理客户端到服务器=======================")
|
||||
var count = 0
|
||||
while (remaining != 0L) {
|
||||
|
@ -25,7 +25,7 @@ fun main() {
|
||||
println(
|
||||
File(
|
||||
"""
|
||||
E:\Projects\QQAndroidFF\app\src\main\java\tencent\im\statsvc\getonline
|
||||
E:\Projects\QQAndroidFF\app\src\main\java\tencent\im\msgrevoke
|
||||
""".trimIndent()
|
||||
)
|
||||
.generateUnarrangedClasses().toMutableList().arrangeClasses().joinToString("\n\n")
|
||||
|
@ -9,7 +9,6 @@
|
||||
|
||||
package net.mamoe.mirai.message
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
@ -18,7 +17,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
* 平台相关扩展
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor(bot: Bot) : MessagePacketBase<TSender, TSubject>(bot) {
|
||||
actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor() : MessagePacketBase<TSender, TSubject>() {
|
||||
// suspend inline fun uploadImage(image: Bitmap): Image = subject.uploadImage(image)
|
||||
//suspend inline fun uploadImage(image: URL): Image = subject.uploadImage(image)
|
||||
//suspend inline fun uploadImage(image: Input): Image = subject.uploadImage(image)
|
||||
|
@ -84,7 +84,7 @@ abstract class Bot : CoroutineScope {
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
val nick: String
|
||||
get() = TODO("bot 昵称获取")
|
||||
get() = ""// TODO("bot 昵称获取")
|
||||
|
||||
/**
|
||||
* 日志记录器
|
||||
@ -175,8 +175,6 @@ abstract class Bot : CoroutineScope {
|
||||
*/
|
||||
abstract suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence<MemberInfo>
|
||||
|
||||
// TODO 目前还不能构造群对象. 这将在以后支持
|
||||
|
||||
// endregion
|
||||
|
||||
// region network
|
||||
|
@ -18,6 +18,7 @@ import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.event.events.ImageUploadEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.WeakRefProperty
|
||||
@ -40,6 +41,9 @@ interface Contact : CoroutineScope {
|
||||
*
|
||||
* 对于 [QQ], `uin` 与 `id` 是相同的意思.
|
||||
* 对于 [Group], `groupCode` 与 `id` 是相同的意思.
|
||||
*
|
||||
* @see QQ.id
|
||||
* @see Group.id
|
||||
*/
|
||||
val id: Long
|
||||
|
||||
@ -51,8 +55,10 @@ interface Contact : CoroutineScope {
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息.
|
||||
*/
|
||||
suspend fun sendMessage(message: MessageChain)
|
||||
suspend fun sendMessage(message: MessageChain): MessageReceipt<out Contact>
|
||||
|
||||
/**
|
||||
* 上传一个图片以备发送.
|
||||
@ -88,4 +94,4 @@ interface Contact : CoroutineScope {
|
||||
|
||||
suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.toChain())
|
||||
|
||||
suspend inline fun Contact.sendMessage(plain: String) = sendMessage(plain.singleChain())
|
||||
suspend inline fun Contact.sendMessage(plain: String) = sendMessage(plain.toMessage())
|
@ -40,6 +40,15 @@ class ContactList<C : Contact>(@MiraiInternalAPI val delegate: LockFreeLinkedLis
|
||||
fun containsAll(elements: Collection<C>): Boolean = elements.all { contains(it) }
|
||||
fun isEmpty(): Boolean = delegate.isEmpty()
|
||||
inline fun forEach(block: (C) -> Unit) = delegate.forEach(block)
|
||||
fun first(): C {
|
||||
forEach { return it }
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
|
||||
fun firstOrNull(): C? {
|
||||
forEach { return it }
|
||||
return null
|
||||
}
|
||||
|
||||
override fun toString(): String = delegate.joinToString(separator = ", ", prefix = "ContactList(", postfix = ")")
|
||||
}
|
||||
|
@ -11,11 +11,21 @@
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.data.MemberInfo
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
/**
|
||||
@ -83,10 +93,18 @@ interface Group : Contact, CoroutineScope {
|
||||
override val id: Long
|
||||
|
||||
/**
|
||||
* 群主
|
||||
* 群主.
|
||||
*
|
||||
* @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员
|
||||
*/
|
||||
val owner: Member
|
||||
|
||||
/**
|
||||
* [Bot] 在群内的 [Member] 实例
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
val botAsMember: Member
|
||||
|
||||
/**
|
||||
* 机器人被禁言还剩余多少秒
|
||||
*
|
||||
@ -133,6 +151,17 @@ interface Group : Contact, CoroutineScope {
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
suspend fun quit(): Boolean
|
||||
|
||||
/**
|
||||
* 撤回这条消息.
|
||||
*
|
||||
* [Bot] 撤回自己的消息不需要权限.
|
||||
* [Bot] 撤回群员的消息需要管理员权限.
|
||||
*
|
||||
* @throws PermissionDeniedException 当 [Bot] 无权限操作时
|
||||
* @see Group.recall (扩展函数) 接受参数 [MessageChain]
|
||||
*/
|
||||
suspend fun recall(source: MessageSource)
|
||||
|
||||
/**
|
||||
* 构造一个 [Member].
|
||||
* 非特殊情况请不要使用这个函数. 优先使用 [get].
|
||||
@ -142,6 +171,19 @@ interface Group : Contact, CoroutineScope {
|
||||
@JvmName("newMember")
|
||||
fun Member(memberInfo: MemberInfo): Member
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
|
||||
*/
|
||||
override suspend fun sendMessage(message: MessageChain): MessageReceipt<Group>
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
@ -184,7 +226,52 @@ interface Group : Contact, CoroutineScope {
|
||||
fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})"
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回这条消息.
|
||||
*
|
||||
* [Bot] 撤回自己的消息不需要权限.
|
||||
* [Bot] 撤回群员的消息需要管理员权限.
|
||||
*
|
||||
* @throws PermissionDeniedException 当 [Bot] 无权限操作时
|
||||
* @see Group.recall
|
||||
*/
|
||||
suspend inline fun Group.recall(message: MessageChain) = this.recall(message[MessageSource])
|
||||
|
||||
/**
|
||||
* 在一段时间后撤回这条消息.
|
||||
*
|
||||
* @param millis 延迟的时间, 单位为毫秒
|
||||
* @param coroutineContext 额外的 [CoroutineContext]
|
||||
* @see recall
|
||||
*/
|
||||
fun Group.recallIn(
|
||||
message: MessageSource,
|
||||
millis: Long,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||
): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
|
||||
kotlinx.coroutines.delay(millis)
|
||||
recall(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* 在一段时间后撤回这条消息.
|
||||
*
|
||||
* @param millis 延迟的时间, 单位为毫秒
|
||||
* @param coroutineContext 额外的 [CoroutineContext]
|
||||
* @see recall
|
||||
*/
|
||||
fun Group.recallIn(
|
||||
message: MessageChain,
|
||||
millis: Long,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||
): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
|
||||
kotlinx.coroutines.delay(millis)
|
||||
recall(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回机器人是否正在被禁言
|
||||
*
|
||||
* @see Group.botMuteRemaining 剩余禁言时间
|
||||
*/
|
||||
val Group.isBotMuted: Boolean get() = this.botMuteRemaining != 0
|
||||
|
@ -72,6 +72,9 @@ interface Member : QQ, Contact {
|
||||
/**
|
||||
* 禁言.
|
||||
*
|
||||
* QQ 中最小操作和显示的时间都是一分钟.
|
||||
* 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间.
|
||||
*
|
||||
* 管理员可禁言成员, 群主可禁言管理员和群员.
|
||||
*
|
||||
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
|
||||
|
@ -16,6 +16,11 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.data.FriendNameRemark
|
||||
import net.mamoe.mirai.data.PreviousNameList
|
||||
import net.mamoe.mirai.data.Profile
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
|
||||
/**
|
||||
@ -68,4 +73,17 @@ interface QQ : Contact, CoroutineScope {
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
suspend fun queryRemark(): FriendNameRemark
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
|
||||
*/
|
||||
override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ>
|
||||
}
|
@ -62,4 +62,15 @@ interface GroupInfo {
|
||||
* 机器人被禁言还剩时间, 秒.
|
||||
*/
|
||||
val botMuteRemaining: Int
|
||||
|
||||
/*
|
||||
/**
|
||||
* 机器人的头衔
|
||||
*/
|
||||
val botSpecialTitle: String
|
||||
|
||||
/**
|
||||
* 机器人的昵称
|
||||
*/
|
||||
val botNameCard: String*/
|
||||
}
|
112
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt
Normal file
112
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
/**
|
||||
* 挂起当前协程, 监听这个事件, 并尝试从这个事件中获取一个值.
|
||||
*
|
||||
* 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
|
||||
*
|
||||
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
|
||||
* @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值
|
||||
*
|
||||
* @see subscribingGetAsync 本函数的异步版本
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
suspend inline fun <reified E : Event, R : Any> subscribingGet(
|
||||
timeoutMillis: Long = -1,
|
||||
noinline filter: E.(E) -> R? // 不要 crossinline: crossinline 后 stacktrace 会不正常
|
||||
): R {
|
||||
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" }
|
||||
return subscribingGetOrNull(timeoutMillis, filter) ?: error("timeout subscribingGet")
|
||||
}
|
||||
|
||||
/**
|
||||
* 挂起当前协程, 监听这个事件, 并尝试从这个事件中获取一个值.
|
||||
*
|
||||
* 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
|
||||
*
|
||||
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
|
||||
* @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值
|
||||
*
|
||||
* @see subscribingGetAsync 本函数的异步版本
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
suspend inline fun <reified E : Event, R : Any> subscribingGetOrNull(
|
||||
timeoutMillis: Long = -1,
|
||||
noinline filter: E.(E) -> R? // 不要 crossinline: crossinline 后 stacktrace 会不正常
|
||||
): R? {
|
||||
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" }
|
||||
var result: R? = null
|
||||
var resultThrowable: Throwable? = null
|
||||
|
||||
if (timeoutMillis == -1L) {
|
||||
@Suppress("DuplicatedCode") // for better performance
|
||||
coroutineScope {
|
||||
var listener: Listener<E>? = null
|
||||
listener = this.subscribe {
|
||||
val value = try {
|
||||
filter.invoke(this, it)
|
||||
} catch (e: Exception) {
|
||||
resultThrowable = e
|
||||
return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() }
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
result = value
|
||||
return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() }
|
||||
} else return@subscribe ListeningStatus.LISTENING
|
||||
}
|
||||
}
|
||||
} else {
|
||||
withTimeoutOrNull(timeoutMillis) {
|
||||
var listener: Listener<E>? = null
|
||||
@Suppress("DuplicatedCode") // for better performance
|
||||
listener = this.subscribe {
|
||||
val value = try {
|
||||
filter.invoke(this, it)
|
||||
} catch (e: Exception) {
|
||||
resultThrowable = e
|
||||
return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() }
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
result = value
|
||||
return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() }
|
||||
} else return@subscribe ListeningStatus.LISTENING
|
||||
}
|
||||
}
|
||||
}
|
||||
resultThrowable?.let { throw it }
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步监听这个事件, 并尝试从这个事件中获取一个值.
|
||||
*
|
||||
* 若 [filter] 抛出了一个异常, [Deferred.await] 会抛出这个异常或.
|
||||
*
|
||||
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
|
||||
* @param coroutineContext 额外的 [CoroutineContext]
|
||||
* @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
inline fun <reified E : Event, R : Any> CoroutineScope.subscribingGetAsync(
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
timeoutMillis: Long = -1,
|
||||
noinline filter: E.(E) -> R? // 不要 crossinline: crossinline 后 stacktrace 会不正常
|
||||
): Deferred<R> = this.async(coroutineContext) {
|
||||
subscribingGet(timeoutMillis, filter)
|
||||
}
|
@ -101,6 +101,9 @@ interface Listener<in E : Event> : CompletableJob {
|
||||
*
|
||||
* @param coroutineContext 给事件监听协程的额外的 [CoroutineContext]
|
||||
*
|
||||
* @see subscribingGet 监听一个事件, 并尝试从这个事件中获取一个值.
|
||||
* @see subscribingGetAsync 异步监听一个事件, 并尝试从这个事件中获取一个值.
|
||||
*
|
||||
* @see subscribeAlways 一直监听
|
||||
* @see subscribeOnce 只监听一次
|
||||
*
|
@ -13,12 +13,16 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.event.BroadcastControllable
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.utils.getValue
|
||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
|
||||
class FriendMessage(
|
||||
bot: Bot,
|
||||
override val sender: QQ,
|
||||
sender: QQ,
|
||||
override val message: MessageChain
|
||||
) : MessagePacket<QQ, QQ>(bot), BroadcastControllable {
|
||||
) : MessagePacket<QQ, QQ>(), BroadcastControllable {
|
||||
override val sender: QQ by sender.unsafeWeakRef()
|
||||
override val bot: Bot get() = sender.bot
|
||||
override val subject: QQ get() = sender
|
||||
|
||||
override fun toString(): String = "FriendMessage(sender=${sender.id}, message=$message)"
|
||||
|
@ -9,21 +9,19 @@
|
||||
|
||||
package net.mamoe.mirai.message
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.utils.getValue
|
||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
@Suppress("unused", "NOTHING_TO_INLINE")
|
||||
class GroupMessage(
|
||||
bot: Bot,
|
||||
group: Group,
|
||||
val senderName: String,
|
||||
/**
|
||||
* 发送方权限.
|
||||
@ -31,9 +29,10 @@ class GroupMessage(
|
||||
val permission: MemberPermission,
|
||||
sender: Member,
|
||||
override val message: MessageChain
|
||||
) : MessagePacket<Member, Group>(bot), Event {
|
||||
val group: Group by group.unsafeWeakRef()
|
||||
) : MessagePacket<Member, Group>(), Event {
|
||||
override val sender: Member by sender.unsafeWeakRef()
|
||||
val group: Group get() = sender.group
|
||||
override val bot: Bot get() = sender.bot
|
||||
|
||||
override val subject: Group get() = group
|
||||
|
||||
@ -45,20 +44,25 @@ class GroupMessage(
|
||||
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
|
||||
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
|
||||
*/
|
||||
suspend inline fun quoteReply(message: MessageChain) = reply(this.message.quote() + message)
|
||||
suspend inline fun quoteReply(message: MessageChain): MessageReceipt<Group> = reply(this.message.quote() + message)
|
||||
|
||||
suspend inline fun quoteReply(message: Message) = reply(this.message.quote() + message)
|
||||
suspend inline fun quoteReply(plain: String) = reply(this.message.quote() + plain)
|
||||
suspend inline fun quoteReply(message: Message): MessageReceipt<Group> = reply(this.message.quote() + message)
|
||||
suspend inline fun quoteReply(plain: String): MessageReceipt<Group> = reply(this.message.quote() + plain)
|
||||
|
||||
|
||||
@JvmName("reply2")
|
||||
suspend inline fun String.quoteReply() = quoteReply(this)
|
||||
suspend inline fun String.quoteReply(): MessageReceipt<Group> = quoteReply(this)
|
||||
|
||||
@JvmName("reply2")
|
||||
suspend inline fun Message.quoteReply() = quoteReply(this)
|
||||
suspend inline fun Message.quoteReply(): MessageReceipt<Group> = quoteReply(this)
|
||||
|
||||
@JvmName("reply2")
|
||||
suspend inline fun MessageChain.quoteReply() = quoteReply(this)
|
||||
suspend inline fun MessageChain.quoteReply(): MessageReceipt<Group> = quoteReply(this)
|
||||
|
||||
suspend inline fun MessageChain.recall() = group.recall(this)
|
||||
suspend inline fun MessageSource.recall() = group.recall(this)
|
||||
inline fun MessageSource.recallIn(delay: Long): Job = group.recallIn(this, delay)
|
||||
inline fun MessageChain.recallIn(delay: Long): Job = group.recallIn(this, delay)
|
||||
|
||||
override fun toString(): String =
|
||||
"GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)"
|
||||
|
@ -21,6 +21,8 @@ import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.event.events.BotEvent
|
||||
import net.mamoe.mirai.event.subscribingGet
|
||||
import net.mamoe.mirai.event.subscribingGetAsync
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.jvm.JvmName
|
||||
@ -30,18 +32,19 @@ import kotlin.jvm.JvmName
|
||||
* 请查看各平台的 `actual` 实现的说明.
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
expect abstract class MessagePacket<TSender : QQ, TSubject : Contact>(bot: Bot) : MessagePacketBase<TSender, TSubject>
|
||||
expect abstract class MessagePacket<TSender : QQ, TSubject : Contact>() : MessagePacketBase<TSender, TSubject>
|
||||
|
||||
/**
|
||||
* 仅内部使用, 请使用 [MessagePacket]
|
||||
*/ // Tips: 在 IntelliJ 中 (左侧边栏) 打开 `Structure`, 可查看类结构
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST")
|
||||
@MiraiInternalAPI
|
||||
abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) : Packet, BotEvent {
|
||||
abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : Packet, BotEvent {
|
||||
/**
|
||||
* 接受到这条消息的
|
||||
*/
|
||||
override val bot: Bot by _bot.unsafeWeakRef()
|
||||
@WeakRefProperty
|
||||
abstract override val bot: Bot
|
||||
|
||||
/**
|
||||
* 消息事件主体.
|
||||
@ -51,6 +54,7 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
|
||||
*
|
||||
* 在回复消息时, 可通过 [subject] 作为回复对象
|
||||
*/
|
||||
@WeakRefProperty
|
||||
abstract val subject: TSubject
|
||||
|
||||
/**
|
||||
@ -58,6 +62,7 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
|
||||
*
|
||||
* 在好友消息时为 [QQ] 的实例, 在群消息时为 [Member] 的实例
|
||||
*/
|
||||
@WeakRefProperty
|
||||
abstract val sender: TSender
|
||||
|
||||
/**
|
||||
@ -73,20 +78,19 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
|
||||
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
|
||||
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
|
||||
*/
|
||||
suspend inline fun reply(message: MessageChain) = subject.sendMessage(message)
|
||||
suspend inline fun reply(message: MessageChain): MessageReceipt<TSubject> = subject.sendMessage(message) as MessageReceipt<TSubject>
|
||||
|
||||
suspend inline fun reply(message: Message) = subject.sendMessage(message.toChain())
|
||||
suspend inline fun reply(plain: String) = subject.sendMessage(plain.singleChain())
|
||||
suspend inline fun reply(message: Message): MessageReceipt<TSubject> = subject.sendMessage(message.toChain()) as MessageReceipt<TSubject>
|
||||
suspend inline fun reply(plain: String): MessageReceipt<TSubject> = subject.sendMessage(plain.toMessage().toChain()) as MessageReceipt<TSubject>
|
||||
|
||||
@JvmName("reply1")
|
||||
suspend inline fun String.reply() = reply(this)
|
||||
suspend inline fun String.reply(): MessageReceipt<TSubject> = reply(this)
|
||||
|
||||
@JvmName("reply1")
|
||||
suspend inline fun Message.reply() = reply(this)
|
||||
suspend inline fun Message.reply(): MessageReceipt<TSubject> = reply(this)
|
||||
|
||||
@JvmName("reply1")
|
||||
suspend inline fun MessageChain.reply() = reply(this)
|
||||
|
||||
suspend inline fun MessageChain.reply(): MessageReceipt<TSubject> = reply(this)
|
||||
// endregion
|
||||
|
||||
// region
|
||||
@ -110,12 +114,16 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
|
||||
suspend inline fun String.send() = this.toMessage().sendTo(subject)
|
||||
// endregion
|
||||
|
||||
operator fun <M : Message> get(at: Message.Key<M>): M {
|
||||
return this.message[at]
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 @ 这个账号的消息. 当且仅当消息为群消息时可用. 否则将会抛出 [IllegalArgumentException]
|
||||
*/
|
||||
inline fun QQ.at(): At = At(this as? Member ?: error("`QQ.at` can only be used in GroupMessage"))
|
||||
fun QQ.at(): At = At(this as? Member ?: error("`QQ.at` can only be used in GroupMessage"))
|
||||
|
||||
inline fun At.member(): Member = (this@MessagePacketBase as? GroupMessage)?.group?.get(this.target) ?: error("`At.member` can only be used in GroupMessage")
|
||||
fun At.member(): Member = (this@MessagePacketBase as? GroupMessage)?.group?.get(this.target) ?: error("`At.member` can only be used in GroupMessage")
|
||||
|
||||
// endregion
|
||||
|
||||
@ -135,16 +143,47 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
|
||||
*/
|
||||
suspend inline fun Image.download(): ByteReadPacket = bot.run { download() }
|
||||
// endregion
|
||||
}
|
||||
|
||||
@Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getFriend(this.target)"))
|
||||
fun At.qq(): QQ = bot.getFriend(this.target)
|
||||
/**
|
||||
* 判断两个 [MessagePacket] 的 [MessagePacket.sender] 和 [MessagePacket.subject] 是否相同
|
||||
*/
|
||||
fun MessagePacket<*, *>.isContextIdenticalWith(another: MessagePacket<*, *>): Boolean {
|
||||
return this.sender == another.sender && this.subject == another.subject
|
||||
}
|
||||
|
||||
@Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getFriend(this.toLong())"))
|
||||
fun Int.qq(): QQ = bot.getFriend(this.coerceAtLeastOrFail(0).toLong())
|
||||
/**
|
||||
* 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [P] 相同且通过 [筛选][filter] 的 [MessagePacket]
|
||||
*
|
||||
* 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
|
||||
*
|
||||
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
|
||||
* @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值
|
||||
*
|
||||
* @see subscribingGetAsync 本函数的异步版本
|
||||
*/
|
||||
suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage(
|
||||
timeoutMillis: Long = -1,
|
||||
crossinline filter: P.(P) -> Boolean
|
||||
): MessageChain {
|
||||
return subscribingGet<P, P>(timeoutMillis) {
|
||||
takeIf { this.isContextIdenticalWith(this@nextMessage) }?.takeIf { filter(it, it) }
|
||||
}.message
|
||||
}
|
||||
|
||||
@Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getFriend(this)"))
|
||||
fun Long.qq(): QQ = bot.getFriend(this.coerceAtLeastOrFail(0))
|
||||
|
||||
@Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getGroup(this)"))
|
||||
fun Long.group(): Group = bot.getGroup(this)
|
||||
/**
|
||||
* 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [P] 相同的 [MessagePacket]
|
||||
*
|
||||
* 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
|
||||
*
|
||||
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
|
||||
*
|
||||
* @see subscribingGetAsync 本函数的异步版本
|
||||
*/
|
||||
suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage(
|
||||
timeoutMillis: Long = -1
|
||||
): MessageChain {
|
||||
return subscribingGet<P, P>(timeoutMillis) {
|
||||
takeIf { this.isContextIdenticalWith(this@nextMessage) }
|
||||
}.message
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.message
|
||||
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.getValue
|
||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
|
||||
/**
|
||||
* 发送消息后得到的回执. 可用于撤回.
|
||||
*
|
||||
* 此对象持有 [Contact] 的弱引用, [Bot] 离线后将会释放引用, 届时 [target] 将无法访问.
|
||||
*
|
||||
* @see Group.sendMessage 发送群消息, 返回回执(此对象)
|
||||
* @see QQ.sendMessage 发送群消息, 返回回执(此对象)
|
||||
*/
|
||||
open class MessageReceipt<C : Contact>(
|
||||
val originalMessage: MessageChain,
|
||||
private val source: MessageSource,
|
||||
target: C
|
||||
) {
|
||||
init {
|
||||
require(target is Group || target is QQ) { "target must be either Group or QQ" }
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送目标, 为 [Group] 或 [QQ]
|
||||
*/
|
||||
val target: C by target.unsafeWeakRef()
|
||||
|
||||
private val _isRecalled = atomic(false)
|
||||
|
||||
/**
|
||||
* 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
|
||||
*
|
||||
* @see Group.recall
|
||||
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
|
||||
*/
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
suspend fun recall() {
|
||||
@Suppress("BooleanLiteralArgument")
|
||||
if (_isRecalled.compareAndSet(false, true)) {
|
||||
when (val contact = target) {
|
||||
is Group -> {
|
||||
contact.recall(source)
|
||||
}
|
||||
is QQ -> {
|
||||
TODO()
|
||||
}
|
||||
else -> error("Unknown contact type")
|
||||
}
|
||||
} else error("message is already or planned to be recalled")
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
|
||||
*
|
||||
* @param millis 延迟时间, 单位为毫秒
|
||||
*
|
||||
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
|
||||
*/
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
fun recallIn(millis: Long): Job {
|
||||
@Suppress("BooleanLiteralArgument")
|
||||
if (_isRecalled.compareAndSet(false, true)) {
|
||||
when (val contact = target) {
|
||||
is Group -> {
|
||||
return contact.recallIn(source, millis)
|
||||
}
|
||||
is QQ -> {
|
||||
TODO()
|
||||
}
|
||||
else -> error("Unknown contact type")
|
||||
}
|
||||
} else error("message is already or planned to be recalled")
|
||||
}
|
||||
|
||||
/**
|
||||
* 引用这条消息. 仅群消息能被引用
|
||||
*
|
||||
* @see MessageChain.quote 引用一条消息
|
||||
*
|
||||
* @throws IllegalStateException 当此消息不是群消息时
|
||||
*/
|
||||
@MiraiExperimentalAPI("unstable")
|
||||
open fun quote(): MessageChain {
|
||||
val target = target
|
||||
check(target is Group) { "quote is only available for GroupMessage" }
|
||||
return this.source.quote(target.botAsMember)
|
||||
}
|
||||
|
||||
/**
|
||||
* 引用这条消息并回复. 仅群消息能被引用
|
||||
*
|
||||
* @see MessageChain.quote 引用一条消息
|
||||
*
|
||||
* @throws IllegalStateException 当此消息不是群消息时
|
||||
*/
|
||||
@MiraiExperimentalAPI("unstable")
|
||||
suspend fun quoteReply(message: MessageChain) {
|
||||
target.sendMessage(this.quote() + message)
|
||||
}
|
||||
}
|
||||
|
||||
@MiraiExperimentalAPI("unstable")
|
||||
suspend inline fun MessageReceipt<out Contact>.quoteReply(message: Message) {
|
||||
return this.quoteReply(message.toChain())
|
||||
}
|
||||
|
||||
@MiraiExperimentalAPI("unstable")
|
||||
suspend inline fun MessageReceipt<out Contact>.quoteReply(message: String) {
|
||||
return this.quoteReply(message.toMessage().toChain())
|
||||
}
|
||||
|
@ -34,10 +34,6 @@ class At @MiraiInternalAPI constructor(val target: Long, val display: String) :
|
||||
|
||||
companion object Key : Message.Key<At>
|
||||
|
||||
override fun eq(other: Message): Boolean {
|
||||
return other is At && other.target == this.target
|
||||
}
|
||||
|
||||
// 自动为消息补充 " "
|
||||
|
||||
override fun followedBy(tail: Message): MessageChain {
|
||||
|
@ -16,7 +16,9 @@ import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
/**
|
||||
* "@全体成员"
|
||||
* "@全体成员".
|
||||
*
|
||||
* 非会员每天只能发送 10 次 [AtAll]. 超出部分会被以普通文字看待.
|
||||
*
|
||||
* @see At at 单个群成员
|
||||
*/
|
||||
@ -26,7 +28,7 @@ object AtAll : Message, Message.Key<AtAll> {
|
||||
// 自动为消息补充 " "
|
||||
|
||||
override fun followedBy(tail: Message): MessageChain {
|
||||
if(tail is PlainText && tail.stringValue.startsWith(' ')){
|
||||
if (tail is PlainText && tail.stringValue.startsWith(' ')) {
|
||||
return super.followedBy(tail)
|
||||
}
|
||||
return super.followedBy(PlainText(" ")) + tail
|
||||
|
@ -167,8 +167,4 @@ class Face(val id: Int) : Message {
|
||||
const val shouqiang: Int = 169
|
||||
const val qingwa: Int = 170
|
||||
}
|
||||
|
||||
override fun eq(other: Message): Boolean {
|
||||
return other is Face && other.id == this.id
|
||||
}
|
||||
}
|
@ -49,11 +49,6 @@ sealed class Image : Message {
|
||||
final override fun toString(): String {
|
||||
return "[mirai:$imageId]"
|
||||
}
|
||||
|
||||
final override fun eq(other: Message): Boolean {
|
||||
return if (other is Image) return other.imageId == this.imageId
|
||||
else this.toString() == other.toString()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CustomFace : Image() {
|
||||
|
@ -13,9 +13,6 @@
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.message.data.NullMessageChain.toString
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.js.JsName
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
@ -60,12 +57,15 @@ interface MessageChain : Message, MutableList<Message> {
|
||||
* @param key 由各个类型消息的伴生对象持有. 如 [PlainText.Key]
|
||||
*/
|
||||
operator fun <M : Message> get(key: Message.Key<M>): M = first(key)
|
||||
}
|
||||
|
||||
override fun eq(other: Message): Boolean {
|
||||
if (other is MessageChain && other.size != this.size)
|
||||
return false
|
||||
return this.toString() == other.toString()
|
||||
}
|
||||
/**
|
||||
* 先删除同类型的消息, 再添加 [message]
|
||||
*/
|
||||
fun <T : Message> MessageChain.addOrRemove(message: T) {
|
||||
val clazz = message::class
|
||||
this.removeAll { clazz.isInstance(it) }
|
||||
this.add(message)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,6 +132,16 @@ fun MessageChain(vararg messages: Message): MessageChain =
|
||||
if (messages.isEmpty()) EmptyMessageChain()
|
||||
else MessageChainImpl(messages.toMutableList())
|
||||
|
||||
/**
|
||||
* 构造 [MessageChain] 的快速途径 (无 [Array] 创建)
|
||||
* 若仅提供一个参数, 请考虑使用 [Message.toChain] 以优化性能
|
||||
*/
|
||||
@JvmName("newChain")
|
||||
@JsName("newChain")
|
||||
@Suppress("FunctionName")
|
||||
fun MessageChain(message: Message): MessageChain =
|
||||
MessageChainImpl(mutableListOf(message))
|
||||
|
||||
/**
|
||||
* 构造 [MessageChain]
|
||||
*/
|
||||
@ -141,30 +151,6 @@ fun MessageChain(vararg messages: Message): MessageChain =
|
||||
fun MessageChain(messages: Iterable<Message>): MessageChain =
|
||||
MessageChainImpl(messages.toMutableList())
|
||||
|
||||
/**
|
||||
* 构造单元素的不可修改的 [MessageChain]. 内部类实现为 [SingleMessageChain]
|
||||
*
|
||||
* 参数 [delegate] 不能为 [MessageChain] 的实例, 否则将会抛出异常.
|
||||
* 使用 [Message.toChain] 将帮助提前处理这个问题.
|
||||
*
|
||||
* @param delegate 所构造的单元素 [MessageChain] 代表的 [Message]
|
||||
* @throws IllegalArgumentException 当 [delegate] 为 [MessageChain] 的实例时
|
||||
*
|
||||
* @see Message.toChain receiver 模式
|
||||
*/
|
||||
@JvmName("newSingleMessageChain")
|
||||
@JsName("newChain")
|
||||
@MiraiExperimentalAPI
|
||||
@UseExperimental(ExperimentalContracts::class)
|
||||
@Suppress("FunctionName")
|
||||
fun SingleMessageChain(delegate: Message): MessageChain {
|
||||
contract {
|
||||
returns() implies (delegate !is MessageChain)
|
||||
}
|
||||
require(delegate !is MessageChain) { "delegate for SingleMessageChain should not be any instance of MessageChain" }
|
||||
return SingleMessageChainImpl(delegate)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 得到包含 [this] 的 [MessageChain].
|
||||
@ -217,7 +203,7 @@ fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key)
|
||||
* @throws [NoSuchElementException] 如果找不到该类型的实例
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <M : Message> MessageChain.first(key: Message.Key<M>): M = firstOrNull(key) ?: error("unknown key: $key")
|
||||
fun <M : Message> MessageChain.first(key: Message.Key<M>): M = firstOrNull(key) ?: throw NoSuchElementException("no such element: $key")
|
||||
|
||||
/**
|
||||
* 获取第一个 [M] 类型的 [Message] 实例
|
||||
@ -387,97 +373,3 @@ internal inline class MessageChainImpl constructor(
|
||||
// endregion
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个成员的不可修改的 [MessageChain].
|
||||
*
|
||||
* 在连接时将会把它当做一个普通 [Message] 看待, 但它不能被 [plusAssign]
|
||||
*/
|
||||
@PublishedApi
|
||||
internal inline class SingleMessageChainImpl(
|
||||
private val delegate: Message
|
||||
) : Message, MutableList<Message>,
|
||||
MessageChain {
|
||||
|
||||
// region Message override
|
||||
override operator fun contains(sub: String): Boolean = delegate.contains(sub)
|
||||
|
||||
override fun followedBy(tail: Message): MessageChain {
|
||||
require(tail !is SingleOnly) { "SingleOnly Message cannot follow another message" }
|
||||
return if (tail is MessageChain) tail.apply { followedBy(delegate) }
|
||||
else MessageChain(delegate, tail)
|
||||
}
|
||||
|
||||
override fun plusAssign(message: Message) =
|
||||
throw UnsupportedOperationException("SingleMessageChainImpl cannot be plusAssigned")
|
||||
|
||||
override fun toString(): String = delegate.toString()
|
||||
// endregion
|
||||
|
||||
// region MutableList override
|
||||
override fun containsAll(elements: Collection<Message>): Boolean = elements.all { it == delegate }
|
||||
|
||||
override operator fun get(index: Int): Message = if (index == 0) delegate else throw NoSuchElementException()
|
||||
override fun indexOf(element: Message): Int = if (delegate == element) 0 else -1
|
||||
override fun isEmpty(): Boolean = false
|
||||
override fun lastIndexOf(element: Message): Int = if (delegate == element) 0 else -1
|
||||
override fun add(element: Message): Boolean = throw UnsupportedOperationException()
|
||||
override fun add(index: Int, element: Message) = throw UnsupportedOperationException()
|
||||
override fun addAll(index: Int, elements: Collection<Message>): Boolean = throw UnsupportedOperationException()
|
||||
override fun addAll(elements: Collection<Message>): Boolean = throw UnsupportedOperationException()
|
||||
override fun clear() = throw UnsupportedOperationException()
|
||||
override fun listIterator(): MutableListIterator<Message> = object : MutableListIterator<Message> {
|
||||
private var hasNext = true
|
||||
override fun hasPrevious(): Boolean = !hasNext
|
||||
override fun nextIndex(): Int = if (hasNext) 0 else -1
|
||||
override fun previous(): Message =
|
||||
if (hasPrevious()) {
|
||||
hasNext = true
|
||||
delegate
|
||||
} else throw NoSuchElementException()
|
||||
|
||||
override fun previousIndex(): Int = if (!hasNext) 0 else -1
|
||||
override fun add(element: Message) = throw UnsupportedOperationException()
|
||||
override fun hasNext(): Boolean = hasNext
|
||||
override fun next(): Message =
|
||||
if (hasNext) {
|
||||
hasNext = false
|
||||
delegate
|
||||
} else throw NoSuchElementException()
|
||||
|
||||
override fun remove() = throw UnsupportedOperationException()
|
||||
override fun set(element: Message) = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun listIterator(index: Int): MutableListIterator<Message> =
|
||||
if (index == 0) listIterator() else throw UnsupportedOperationException()
|
||||
|
||||
override fun remove(element: Message): Boolean = throw UnsupportedOperationException()
|
||||
override fun removeAll(elements: Collection<Message>): Boolean = throw UnsupportedOperationException()
|
||||
override fun removeAt(index: Int): Message = throw UnsupportedOperationException()
|
||||
override fun retainAll(elements: Collection<Message>): Boolean = throw UnsupportedOperationException()
|
||||
override fun set(index: Int, element: Message): Message = throw UnsupportedOperationException()
|
||||
override fun subList(fromIndex: Int, toIndex: Int): MutableList<Message> {
|
||||
return if (fromIndex == 0) when (toIndex) {
|
||||
1 -> mutableListOf<Message>(this)
|
||||
0 -> mutableListOf()
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
else throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun iterator(): MutableIterator<Message> = object : MutableIterator<Message> {
|
||||
private var hasNext = true
|
||||
override fun hasNext(): Boolean = hasNext
|
||||
override fun next(): Message =
|
||||
if (hasNext) {
|
||||
hasNext = false
|
||||
delegate
|
||||
} else throw NoSuchElementException()
|
||||
|
||||
override fun remove() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override operator fun contains(element: Message): Boolean = element == delegate
|
||||
override val size: Int get() = 1
|
||||
// endregion
|
||||
}
|
@ -26,10 +26,22 @@ import kotlin.jvm.JvmName
|
||||
interface MessageSource : Message {
|
||||
companion object Key : Message.Key<MessageSource>
|
||||
|
||||
/**
|
||||
* 序列号. 若是机器人发出去的消息, 请先 [确保 sequenceId 可用][ensureSequenceIdAvailable]
|
||||
*/
|
||||
val sequenceId: Int
|
||||
|
||||
/**
|
||||
* 等待 [sequenceId] 获取, 确保其可用.
|
||||
*
|
||||
* 若原消息发送失败, 这个方法会等待最多 3 秒随后抛出 [IllegalStateException]
|
||||
*/
|
||||
suspend fun ensureSequenceIdAvailable()
|
||||
|
||||
/**
|
||||
* 实际上是个随机数, 但服务器确实是用它当做 uid
|
||||
*/
|
||||
val messageUid: Long
|
||||
val messageUid: Int
|
||||
|
||||
/**
|
||||
* 发送时间, 单位为秒
|
||||
@ -42,17 +54,22 @@ interface MessageSource : Message {
|
||||
val senderId: Long
|
||||
|
||||
/**
|
||||
* 群号码
|
||||
* 群号码, 为 0 时则来自好友消息
|
||||
*/
|
||||
val groupId: Long
|
||||
|
||||
/**
|
||||
* 原消息内容
|
||||
*/
|
||||
val sourceMessage: MessageChain
|
||||
|
||||
/**
|
||||
* 固定返回空字符串 ("")
|
||||
*/
|
||||
override fun toString(): String
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息唯一标识符. 实际上是个随机数, 但服务器确实是用它当做 uid
|
||||
*/
|
||||
val MessageChain.messageUid get() = this[MessageSource].messageUid
|
||||
|
||||
/**
|
||||
* 消息序列号, 可能来自服务器也可以发送时赋值, 不唯一.
|
||||
*/
|
||||
val MessageChain.sequenceId get() = this[MessageSource].sequenceId
|
@ -25,28 +25,10 @@ inline class PlainText(val stringValue: String) : Message {
|
||||
override fun toString(): String = stringValue
|
||||
|
||||
companion object Key : Message.Key<PlainText>
|
||||
|
||||
override fun eq(other: Message): Boolean {
|
||||
if (other is MessageChain) {
|
||||
return other eq this.toString()
|
||||
}
|
||||
return other is PlainText && other.stringValue == this.stringValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 [PlainText]
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun String.toMessage(): PlainText = PlainText(this)
|
||||
|
||||
/**
|
||||
* 得到包含作为 [PlainText] 的 [this] 的 [MessageChain].
|
||||
*
|
||||
* @return 唯一成员且不可修改的 [SingleMessageChainImpl]
|
||||
*
|
||||
* @see SingleMessageChain
|
||||
* @see SingleMessageChainImpl
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun String.singleChain(): MessageChain = SingleMessageChainImpl(this.toMessage())
|
||||
inline fun String.toMessage(): PlainText = PlainText(this)
|
@ -27,10 +27,6 @@ inline class XMLMessage(val stringValue: String) : Message,
|
||||
SingleOnly {
|
||||
override fun followedBy(tail: Message): Nothing = error("XMLMessage Message cannot be followed")
|
||||
override fun toString(): String = stringValue
|
||||
|
||||
override fun eq(other: Message): Boolean {
|
||||
return other is XMLMessage && other.stringValue == this.stringValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,9 +51,39 @@ class ExternalImage(
|
||||
filename: String
|
||||
): ExternalImage = ExternalImage(width, height, md5, format, data, data.remaining, filename)
|
||||
|
||||
fun generateUUID(md5: ByteArray): String{
|
||||
fun generateUUID(md5: ByteArray): String {
|
||||
return "${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}"
|
||||
}
|
||||
|
||||
fun generateImageId(md5: ByteArray, imageType: Int): String {
|
||||
return """{${generateUUID(md5)}}.${determineFormat(imageType)}"""
|
||||
}
|
||||
|
||||
fun determineImageType(format: String): Int {
|
||||
return when (format) {
|
||||
"jpg" -> 1000
|
||||
"png" -> 1001
|
||||
"webp" -> 1002
|
||||
"bmp" -> 1005
|
||||
"gig" -> 2000
|
||||
"apng" -> 2001
|
||||
"sharpp" -> 1004
|
||||
else -> 1000 // unsupported, just make it jpg
|
||||
}
|
||||
}
|
||||
|
||||
fun determineFormat(imageType: Int): String {
|
||||
return when (imageType) {
|
||||
1000 -> "jpg"
|
||||
1001 -> "png"
|
||||
1002 -> "webp"
|
||||
1005 -> "bmp"
|
||||
2000 -> "gig"
|
||||
2001 -> "apng"
|
||||
1004 -> "sharpp"
|
||||
else -> "jpg" // unsupported, just make it jpg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val format: String =
|
||||
@ -73,16 +103,7 @@ class ExternalImage(
|
||||
* SHARPP: 1004
|
||||
*/
|
||||
val imageType: Int
|
||||
get() = when (format) {
|
||||
"jpg" -> 1000
|
||||
"png" -> 1001
|
||||
"webp" -> 1002
|
||||
"bmp" -> 1005
|
||||
"gig" -> 2000
|
||||
"apng" -> 2001
|
||||
"sharpp" -> 1004
|
||||
else -> 1000 // unsupported, just make it jpg
|
||||
}
|
||||
get() = determineImageType(format)
|
||||
|
||||
override fun toString(): String = "[ExternalImage(${width}x$height $format)]"
|
||||
|
||||
|
@ -68,6 +68,10 @@ fun <E> LockFreeLinkedList<E>.asSequence(): Sequence<E> {
|
||||
}
|
||||
}
|
||||
|
||||
operator fun <E> LockFreeLinkedList<E>.iterator(): Iterator<E> {
|
||||
return asSequence().iterator()
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建链表结构然后转为 [LockFreeLinkedList]
|
||||
*/
|
||||
|
@ -13,7 +13,7 @@ import kotlinx.io.pool.DefaultPool
|
||||
import kotlinx.io.pool.ObjectPool
|
||||
|
||||
internal const val DEFAULT_BYTE_ARRAY_POOL_SIZE = 256
|
||||
internal const val DEFAULT_BYTE_ARRAY_SIZE = 81920
|
||||
internal const val DEFAULT_BYTE_ARRAY_SIZE = 81920 / 2
|
||||
|
||||
val ByteArrayPool: ObjectPool<ByteArray> = ByteArrayPoolImpl
|
||||
|
||||
|
@ -16,7 +16,6 @@ import kotlinx.coroutines.withContext
|
||||
import kotlinx.io.core.Input
|
||||
import kotlinx.io.core.use
|
||||
import kotlinx.io.streams.inputStream
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
@ -35,7 +34,7 @@ import javax.imageio.ImageIO
|
||||
* JVM 平台相关扩展
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor(bot: Bot) : MessagePacketBase<TSender, TSubject>(bot) {
|
||||
actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor() : MessagePacketBase<TSender, TSubject>() {
|
||||
// region 上传图片
|
||||
suspend inline fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image)
|
||||
|
||||
|
@ -16,17 +16,14 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.BotAccount
|
||||
import net.mamoe.mirai.alsoLogin
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.contact.isOperator
|
||||
import net.mamoe.mirai.contact.sendMessage
|
||||
import net.mamoe.mirai.event.*
|
||||
import net.mamoe.mirai.message.FriendMessage
|
||||
import net.mamoe.mirai.message.GroupMessage
|
||||
import net.mamoe.mirai.message.data.AtAll
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import net.mamoe.mirai.message.data.firstOrNull
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.message.nextMessage
|
||||
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 net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import java.io.File
|
||||
@ -49,7 +46,7 @@ private fun readTestAccount(): BotAccount? {
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
suspend fun main() {
|
||||
val bot = QQAndroid.Bot( // JVM 下也可以不写 `QQAndroid.` 引用顶层函数
|
||||
val bot = Bot( // JVM 下也可以不写 `QQAndroid.` 引用顶层函数
|
||||
123456789,
|
||||
"123456"
|
||||
) {
|
||||
@ -207,6 +204,25 @@ fun Bot.messageDSL() {
|
||||
// sender: QQ
|
||||
// it: String (来自 MessageChain.toString)
|
||||
// group: Group
|
||||
|
||||
case("recall") {
|
||||
reply("😎").recallIn(3000) // 3 秒后自动撤回这条消息
|
||||
}
|
||||
|
||||
case("禁言") {
|
||||
// 挂起当前协程, 等待下一条满足条件的消息.
|
||||
// 发送 "禁言" 后需要再发送一条消息 at 一个人.
|
||||
val value: At = nextMessage { message.any(At) }[At]
|
||||
value.member().mute(10)
|
||||
}
|
||||
|
||||
startsWith("群名=") {
|
||||
if (!sender.isOperator()) {
|
||||
sender.mute(5)
|
||||
return@startsWith
|
||||
}
|
||||
group.name = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import net.mamoe.mirai.event.events.EventCancelledException;
|
||||
import net.mamoe.mirai.event.events.ImageUploadEvent;
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent;
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent;
|
||||
import net.mamoe.mirai.message.MessageReceipt;
|
||||
import net.mamoe.mirai.message.data.Image;
|
||||
import net.mamoe.mirai.message.data.Message;
|
||||
import net.mamoe.mirai.message.data.MessageChain;
|
||||
@ -42,8 +43,7 @@ public interface BlockingContact {
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
// kotlin bug
|
||||
void sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException;
|
||||
MessageReceipt<? extends Contact> sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
@ -53,7 +53,7 @@ public interface BlockingContact {
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
void sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException;
|
||||
MessageReceipt<? extends Contact> sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
@ -63,7 +63,7 @@ public interface BlockingContact {
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
void sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException;
|
||||
MessageReceipt<? extends Contact> sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 上传一个图片以备发送.
|
||||
|
@ -3,6 +3,9 @@ package net.mamoe.mirai.japt;
|
||||
import net.mamoe.mirai.contact.*;
|
||||
import net.mamoe.mirai.data.MemberInfo;
|
||||
import net.mamoe.mirai.event.events.*;
|
||||
import net.mamoe.mirai.message.MessageReceipt;
|
||||
import net.mamoe.mirai.message.data.Message;
|
||||
import net.mamoe.mirai.message.data.MessageChain;
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -152,6 +155,36 @@ public interface BlockingGroup extends BlockingContact {
|
||||
@Nullable
|
||||
BlockingMember getMemberOrNull(long id);
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
* @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
MessageReceipt<Group> sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
* @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
MessageReceipt<Group> sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
* @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
MessageReceipt<Group> sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 检查此 id 的群成员是否存在
|
||||
*/
|
||||
|
@ -1,8 +1,14 @@
|
||||
package net.mamoe.mirai.japt;
|
||||
|
||||
import net.mamoe.mirai.contact.QQ;
|
||||
import net.mamoe.mirai.data.FriendNameRemark;
|
||||
import net.mamoe.mirai.data.PreviousNameList;
|
||||
import net.mamoe.mirai.data.Profile;
|
||||
import net.mamoe.mirai.event.events.EventCancelledException;
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent;
|
||||
import net.mamoe.mirai.message.MessageReceipt;
|
||||
import net.mamoe.mirai.message.data.Message;
|
||||
import net.mamoe.mirai.message.data.MessageChain;
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -47,4 +53,35 @@ public interface BlockingQQ extends BlockingContact {
|
||||
@MiraiExperimentalAPI(message = "还未支持")
|
||||
@NotNull
|
||||
FriendNameRemark queryRemark();
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
* @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
MessageReceipt<QQ> sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
* @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
MessageReceipt<QQ> sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
* @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*/
|
||||
MessageReceipt<QQ> sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException;
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import net.mamoe.mirai.japt.BlockingBot
|
||||
import net.mamoe.mirai.japt.BlockingGroup
|
||||
import net.mamoe.mirai.japt.BlockingMember
|
||||
import net.mamoe.mirai.japt.BlockingQQ
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
@ -35,9 +36,9 @@ internal class BlockingQQImpl(private val delegate: QQ) : BlockingQQ {
|
||||
override fun getId(): Long = delegate.id
|
||||
override fun getNick(): String = delegate.nick
|
||||
|
||||
override fun sendMessage(messages: MessageChain) = runBlocking { delegate.sendMessage(messages) }
|
||||
override fun sendMessage(message: String) = runBlocking { delegate.sendMessage(message.toMessage().toChain()) }
|
||||
override fun sendMessage(message: Message) = runBlocking { delegate.sendMessage(message.toChain()) }
|
||||
override fun sendMessage(messages: MessageChain): MessageReceipt<QQ> = runBlocking { delegate.sendMessage(messages) }
|
||||
override fun sendMessage(message: String): MessageReceipt<QQ> = runBlocking { delegate.sendMessage(message.toMessage().toChain()) }
|
||||
override fun sendMessage(message: Message): MessageReceipt<QQ> = runBlocking { delegate.sendMessage(message.toChain()) }
|
||||
override fun uploadImage(image: ExternalImage): Image = runBlocking { delegate.uploadImage(image) }
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@ -51,9 +52,9 @@ internal class BlockingQQImpl(private val delegate: QQ) : BlockingQQ {
|
||||
}
|
||||
|
||||
internal class BlockingGroupImpl(private val delegate: Group) : BlockingGroup {
|
||||
override fun sendMessage(messages: MessageChain) = runBlocking { delegate.sendMessage(messages) }
|
||||
override fun sendMessage(message: String) = runBlocking { delegate.sendMessage(message.toMessage().toChain()) }
|
||||
override fun sendMessage(message: Message) = runBlocking { delegate.sendMessage(message.toChain()) }
|
||||
override fun sendMessage(messages: MessageChain): MessageReceipt<Group> = runBlocking { delegate.sendMessage(messages) }
|
||||
override fun sendMessage(message: String): MessageReceipt<Group> = runBlocking { delegate.sendMessage(message.toMessage().toChain()) }
|
||||
override fun sendMessage(message: Message): MessageReceipt<Group> = runBlocking { delegate.sendMessage(message.toChain()) }
|
||||
override fun getOwner(): BlockingMember = delegate.owner.blocking()
|
||||
@MiraiExperimentalAPI
|
||||
override fun newMember(memberInfo: MemberInfo): Member = delegate.Member(memberInfo)
|
||||
|
Loading…
Reference in New Issue
Block a user