Remove old files

This commit is contained in:
Him188 2020-02-28 22:16:01 +08:00
parent 69abf7a5a0
commit 205432db10
27 changed files with 0 additions and 9586 deletions

View File

@ -1,727 +0,0 @@
#### Bot登录成功
```json5
{
"type": "BotOnlineEvent",
"qq": 123456
}
```
| 名字 | 类型 | 说明 |
| ---- | ---- | ------------------- |
| qq | Long | 登录成功的Bot的QQ号 |
#### Bot主动离线
```json5
{
"type": "BotOfflineEventActive",
"qq": 123456
}
```
| 名字 | 类型 | 说明 |
| ---- | ---- | ------------------- |
| qq | Long | 主动离线的Bot的QQ号 |
#### Bot被挤下线
```json5
{
"type": "BotOfflineEventForce",
"qq": 123456
}
```
| 名字 | 类型 | 说明 |
| ---- | ---- | ------------------- |
| qq | Long | 被挤下线的Bot的QQ号 |
#### Bot被服务器断开或因网络问题而掉线
```json5
{
"type": "BotOfflineEventDropped",
"qq": 123456
}
```
| 名字 | 类型 | 说明 |
| ---- | ---- | ----------------------------------------- |
| qq | Long | 被服务器断开或因网络问题而掉线的Bot的QQ号 |
#### Bot主动重新登录.
```json5
{
"type": "BotReloginEvent",
"qq": 123456
}
```
| 名字 | 类型 | 说明 |
| ---- | ---- | ----------------------- |
| qq | Long | 主动重新登录的Bot的QQ号 |
#### Bot在群里的权限被改变. 操作人一定是群主
```json5
{
"type": "BotGroupPermissionChangeEvent",
"origin": "MEMBER",
"new": "ADMINISTRATOR",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "ADMINISTRATOR"
}
}
```
| 名字 | 类型 | 说明 |
| ---------------- | ------ | --------------------------------------------- |
| origin | String | Bot的原权限OWNER、ADMINISTRATOR或MEMBER |
| new | String | Bot的新权限OWNER、ADMINISTRATOR或MEMBER |
| group | Object | 权限改变所在的群信息 |
| group.id | Long | 群号 |
| group.name | String | 群名 |
| group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
#### Bot被禁言
```json5
{
"type": "BotMuteEvent",
"durationSeconds": 600,
"operator": {
"id": 123456789,
"memberName": "我是管理员",
"permission": "ADMINISTRATOR",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ------------------------- | ------ | ------------------------------------------------ |
| durationSeconds | Int | 禁言时长,单位为秒 |
| operator | Object | 操作的管理员或群主信息 |
| operator.id | Long | 操作者的QQ号 |
| operator.memberName | String | 操作者的群名片 |
| operator.permission | String | 操作者在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| operator.group | Object | Bot被禁言所在群的信息 |
| operator.group.id | Long | 群号 |
| operator.group.name | String | 群名 |
| operator.group.permission | String | Bot在群中的权限OWNER或ADMINISTRATOR |
#### Bot被取消禁言
```json5
{
"type": "BotUnmuteEvent",
"operator": {
"id": 123456789,
"memberName": "我是管理员",
"permission": "ADMINISTRATOR",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ------------------------- | ------ | ------------------------------------------------ |
| operator | Object | 操作的管理员或群主信息 |
| operator.id | Long | 操作者的QQ号 |
| operator.memberName | String | 操作者的群名片 |
| operator.permission | String | 操作者在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| operator.group | Object | Bot被取消禁言所在群的信息 |
| operator.group.id | Long | 群号 |
| operator.group.name | String | 群名 |
| operator.group.permission | String | Bot在群中的权限OWNER或ADMINISTRATOR |
#### Bot加入了一个新群
```json5
{
"type": "BotJoinGroupEvent",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
```
| 名字 | 类型 | 说明 |
| ---------------- | ------ | ------------------------------------------------------------ |
| group | Object | Bot新加入群的信息 |
| group.id | Long | 群号 |
| group.name | String | 群名 |
| group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER新加入群通常是Member |
#### 某个群名改变
```json5
{
"type": "GroupNameChangeEvent",
"origin": "miral technology",
"new": "MIRAI TECHNOLOGY",
"group": {
"id": 123456789,
"name": "MIRAI TECHNOLOGY",
"permission": "MEMBER"
},
"isByBot": false
}
```
| 名字 | 类型 | 说明 |
| ---------------- | ------- | --------------------------------------------- |
| origin | String | 原群名 |
| new | String | 新群名 |
| group | Object | 群名改名的群信息 |
| group.id | Long | 群号 |
| group.name | String | 群名 |
| group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| isByBot | Boolean | 是否Bot进行该操作 |
#### 某群入群公告改变
```json5
{
"type": "GroupEntranceAnnouncementChangeEvent",
"origin": "abc",
"new": "cba",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
},
"operator": {
"id": 123456789,
"memberName": "我是管理员",
"permission": "ADMINISTRATOR",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ------------------- | ------- | --------------------------------------------- |
| origin | String | 原公告 |
| new | String | 新公告 |
| group | Object | 公告改变的群信息 |
| group.id | Long | 群号 |
| group.name | String | 群名 |
| group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| operator | Object? | 操作的管理员或群主信息当null时为Bot操作 |
| operator.id | Long | 操作者的QQ号 |
| operator.memberName | String | 操作者的群名片 |
| operator.permission | String | 操作者在群中的权限OWNER或ADMINISTRATOR |
| operator.group | Object | 同group |
#### 全员禁言
```json5
{
"type": "GroupMuteAllEvent",
"origin": false,
"new": true,
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
},
"operator": {
"id": 123456789,
"memberName": "我是管理员",
"permission": "ADMINISTRATOR",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ------------------- | ------- | --------------------------------------------- |
| origin | Boolean | 原本是否处于全员禁言 |
| new | Boolean | 现在是否处于全员禁言 |
| group | Object | 全员禁言的群信息 |
| group.id | Long | 群号 |
| group.name | String | 群名 |
| group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| operator | Object? | 操作的管理员或群主信息当null时为Bot操作 |
| operator.id | Long | 操作者的QQ号 |
| operator.memberName | String | 操作者的群名片 |
| operator.permission | String | 操作者在群中的权限OWNER或ADMINISTRATOR |
| operator.group | Object | 同group |
#### 匿名聊天
```json5
{
"type": "GroupAllowAnonymousChatEvent",
"origin": false,
"new": true,
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
},
"operator": {
"id": 123456789,
"memberName": "我是管理员",
"permission": "ADMINISTRATOR",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ------------------- | ------- | --------------------------------------------- |
| origin | Boolean | 原本匿名聊天是否开启 |
| new | Boolean | 现在匿名聊天是否开启 |
| group | Object | 匿名聊天状态改变的群信息 |
| group.id | Long | 群号 |
| group.name | String | 群名 |
| group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| operator | Object? | 操作的管理员或群主信息当null时为Bot操作 |
| operator.id | Long | 操作者的QQ号 |
| operator.memberName | String | 操作者的群名片 |
| operator.permission | String | 操作者在群中的权限OWNER或ADMINISTRATOR |
| operator.group | Object | 同group |
#### 坦白说
```json5
{
"type": "GroupAllowConfessTalkEvent",
"origin": false,
"new": true,
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
},
"isByBot": false
}
```
| 名字 | 类型 | 说明 |
| ---------------- | ------- | --------------------------------------------- |
| origin | Boolean | 原本坦白说是否开启 |
| new | Boolean | 现在坦白说是否开启 |
| group | Object | 坦白说状态改变的群信息 |
| group.id | Long | 群号 |
| group.name | String | 群名 |
| group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| isByBot | Boolean | 是否Bot进行该操作 |
#### 允许群员邀请好友加群
```json5
{
"type": "GroupAllowMemberInviteEvent",
"origin": false,
"new": true,
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
},
"operator": {
"id": 123456789,
"memberName": "我是管理员",
"permission": "ADMINISTRATOR",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ------------------- | ------- | --------------------------------------------- |
| origin | Boolean | 原本是否允许群员邀请好友加群 |
| new | Boolean | 现在是否允许群员邀请好友加群 |
| group | Object | 允许群员邀请好友加群状态改变的群信息 |
| group.id | Long | 群号 |
| group.name | String | 群名 |
| group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| operator | Object? | 操作的管理员或群主信息当null时为Bot操作 |
| operator.id | Long | 操作者的QQ号 |
| operator.memberName | String | 操作者的群名片 |
| operator.permission | String | 操作者在群中的权限OWNER或ADMINISTRATOR |
| operator.group | Object | 同group |
#### 新人入群的事件
```json5
{
"type": "MemberJoinEvent",
"member": {
"id": 123456789,
"memberName": "我是新人",
"permission": "MEMBER",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ----------------------- | ------ | ------------------------------------------------------------ |
| member | Object | 新人信息 |
| member.id | Long | 新人的QQ号 |
| member.memberName | String | 新人的群名片 |
| member.permission | String | 新人在群中的权限OWNER、ADMINISTRATOR或MEMBER新入群通常是MEMBER |
| member.group | Object | 新人入群的群信息 |
| member.group.id | Long | 群号 |
| member.group.name | String | 群名 |
| member.group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
#### 成员被踢出群该成员不是Bot
```json5
{
"type": "MemberLeaveEventKick",
"member": {
"id": 123456789,
"memberName": "我是被踢的",
"permission": "MEMBER",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
},
"operator": {
"id": 123456789,
"memberName": "我是管理员",
"permission": "ADMINISTRATOR",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ----------------------- | ------- | --------------------------------------------- |
| member | Object | 被踢者的信息 |
| member.id | Long | 被踢者的QQ号 |
| member.memberName | String | 被踢者的群名片 |
| member.permission | String | 被踢者在群中的权限ADMINISTRATOR或MEMBER |
| member.group | Object | 被踢者所在的群 |
| member.group.id | Long | 群号 |
| member.group.name | String | 群名 |
| member.group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| operator | Object? | 操作的管理员或群主信息当null时为Bot操作 |
| operator.id | Long | 操作者的QQ号 |
| operator.memberName | String | 操作者的群名片 |
| operator.permission | String | 操作者在群中的权限OWNER或ADMINISTRATOR |
| operator.group | Object | 同member.group |
#### 成员主动离群该成员不是Bot
```json5
{
"type": "MemberLeaveEventQuit",
"member": {
"id": 123456789,
"memberName": "我是被踢的",
"permission": "MEMBER",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ----------------------- | ------ | --------------------------------------------- |
| member | Object | 退群群员的信息 |
| member.id | Long | 退群群员的QQ号 |
| member.memberName | String | 退群群员的群名片 |
| member.permission | String | 退群群员在群中的权限ADMINISTRATOR或MEMBER |
| member.group | Object | 退群群员所在的群信息 |
| member.group.id | Long | 群号 |
| member.group.name | String | 群名 |
| member.group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
#### 群名片改动
```json5
{
"type": "MemberCardChangeEvent",
"origin": "origin name",
"new": "我是被改名的",
"member": {
"id": 123456789,
"memberName": "我是被改名的",
"permission": "MEMBER",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
},
"operator": {
"id": 123456789,
"memberName": "我是管理员,也可能是我自己",
"permission": "ADMINISTRATOR",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ----------------------- | ------- | -------------------------------------------------------- |
| member | Object | 名片改动的群员的信息 |
| member.id | Long | 名片改动的群员的QQ号 |
| member.memberName | String | 名片改动的群员的群名片 |
| member.permission | String | 名片改动的群员在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| member.group | Object | 名片改动的群员所在群的信息 |
| member.group.id | Long | 群号 |
| member.group.name | String | 群名 |
| member.group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| operator | Object? | 操作者的信息可能为该群员自己当null时为Bot操作 |
| operator.id | Long | 操作者的QQ号 |
| operator.memberName | String | 操作者的群名片 |
| operator.permission | String | 操作者在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| operator.group | Object | 同member.group |
#### 群头衔改动(只有群主有操作限权)
```json5
{
"type": "MemberSpecialTitleChangeEvent",
"origin": "origin title",
"new": "new title",
"member": {
"id": 123456789,
"memberName": "我是被改头衔的",
"permission": "MEMBER",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ----------------------- | ------ | -------------------------------------------------------- |
| origin | String | 原头衔 |
| new | String | 现头衔 |
| member | Object | 头衔改动的群员的信息 |
| member.id | Long | 头衔改动的群员的QQ号 |
| member.memberName | String | 头衔改动的群员的群名片 |
| member.permission | String | 头衔改动的群员在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| member.group | Object | 头衔改动的群员所在群的信息 |
| member.group.id | Long | 群号 |
| member.group.name | String | 群名 |
| member.group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
#### 成员权限改变的事件该成员不可能是Bot见BotGroupPermissionChangeEvent
```json5
{
"type": "MemberPermissionChangeEvent",
"origin": "MEMBER",
"new": "ADMINISTRATOR",
"member": {
"id": 123456789,
"memberName": "我是被改权限的",
"permission": "ADMINISTRATOR",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ----------------------- | ------ | ------------------------------------------------- |
| origin | String | 原权限 |
| new | String | 现权限 |
| member | Object | 权限改动的群员的信息 |
| member.id | Long | 权限改动的群员的QQ号 |
| member.memberName | String | 权限改动的群员的群名片 |
| member.permission | String | 权限改动的群员在群中的权限ADMINISTRATOR或MEMBER |
| member.group | Object | 权限改动的群员所在群的信息 |
| member.group.id | Long | 群号 |
| member.group.name | String | 群名 |
| member.group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
#### 群成员被禁言事件该成员不可能是Bot见BotMuteEvent
```json5
{
"type": "MemberMuteEvent",
"durationSeconds": 600,
"member": {
"id": 123456789,
"memberName": "我是被禁言的",
"permission": "MEMBER",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
},
"operator": {
"id": 123456789,
"memberName": "我是管理员",
"permission": "ADMINISTRATOR",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ----------------------- | ------- | ----------------------------------------------- |
| durationSeconds | Long | 禁言时长,单位为秒 |
| member | Object | 被禁言的群员的信息 |
| member.id | Long | 被禁言的群员的QQ号 |
| member.memberName | String | 被禁言的群员的群名片 |
| member.permission | String | 被禁言的群员在群中的权限ADMINISTRATOR或MEMBER |
| member.group | Object | 被禁言的群员所在群的信息 |
| member.group.id | Long | 群号 |
| member.group.name | String | 群名 |
| member.group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| operator | Object? | 操作者的信息当null时为Bot操作 |
| operator.id | Long | 操作者的QQ号 |
| operator.memberName | String | 操作者的群名片 |
| operator.permission | String | 操作者在群中的权限OWNER、ADMINISTRATOR |
| operator.group | Object | 同member.group |
#### 群成员被取消禁言事件该成员不可能是Bot见BotUnmuteEvent
```json5
{
"type": "MemberUnmuteEvent",
"member": {
"id": 123456789,
"memberName": "我是被取消禁言的",
"permission": "MEMBER",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
},
"operator": {
"id": 123456789,
"memberName": "我是管理员",
"permission": "ADMINISTRATOR",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}
```
| 名字 | 类型 | 说明 |
| ----------------------- | ------- | --------------------------------------------------- |
| member | Object | 被取消禁言的群员的信息 |
| member.id | Long | 被取消禁言的群员的QQ号 |
| member.memberName | String | 被取消禁言的群员的群名片 |
| member.permission | String | 被取消禁言的群员在群中的权限ADMINISTRATOR或MEMBER |
| member.group | Object | 被取消禁言的群员所在群的信息 |
| member.group.id | Long | 群号 |
| member.group.name | String | 群名 |
| member.group.permission | String | Bot在群中的权限OWNER、ADMINISTRATOR或MEMBER |
| operator | Object? | 操作者的信息当null时为Bot操作 |
| operator.id | Long | 操作者的QQ号 |
| operator.memberName | String | 操作者的群名片 |
| operator.permission | String | 操作者在群中的权限OWNER、ADMINISTRATOR |
| operator.group | Object | 同member.group |

View File

@ -1,58 +0,0 @@
# mirai-api-http
<b>
Mirai-API-http provides adapter for ALL langugae to access mirai via HTTP protocol.<br>
</b>
**[中文](README_CH.md)**
### Start Session-Authorize
```php
Path: /auth
Method: POST
```
this verify your session to one bot and you could have full access to that bot<br>
NOTE that only 1 bot could be control under 1 session, you could have multiple session to control all bots.
#### Request:<br>
| name | type | optional|example|note|
| --- | --- | --- | --- | --- |
| key | String |false|U9HSaDXl39ksd918273hU|MIRAI API HTTP key, this could be found after initialize|
| qq | String |false|1040400290|bot QQ number you want to access|
#### Response if success:<br>
| name | type | example|note|
| --- | --- | --- | --- |
| success |Boolean |true|if this session is authorized|
| session |String |UANSHDKSLAOISN|your session key|
#### Response if failed:<br>
| name | type | example|note|
| --- | --- | --- | --- |
| success |Boolean |false|if this session is authorized|
| session |String |null|your session key|
| error |int |0|error code|
#### Error:<br>
| code | reason|
| --- | --- |
| 0 | wrong MIRAI API HTTP key |
| 1 | unknown bot number |
without session key, you are not able to access any method below.</br>
session key should be attached to your <b>cookies</b> like this:
| name | value |
| --- | --- |
| session |your session key here |
if you were getting HTTP error code 403, you should ask for a new session key.

View File

@ -1,927 +0,0 @@
# mirai-api-http
<b>Mirai-API-http 提供HTTP API供所有语言使用mirai</b>
## 快速开始
```kotlin
fun main() {
val bot = Bot(123456789, "password")
bot.login()
MiraiHttpAPIServer.start()
bot.join()
}
```
## 认证相关
### 开始会话-认证(Authorize)
```
[POST] /auth
```
使用此方法验证你的身份,并返回一个会话
#### 请求:
```json5
{
"authKey": "U9HSaDXl39ksd918273hU"
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------- | ------ | ----- | ----------------------- | ---------------------------------------------------------- |
| authKey | String | false | "U9HSaDXl39ksd918273hU" | 创建Mirai-Http-Server时生成的key可在启动时指定或随机生成 |
#### 响应: 返回(成功):
```json5
{
"code": 0,
"session": "UnVerifiedSession"
}
```
| 名字 | 类型 | 举例 | 说明 |
| ------- | ------ | ------------------- | --------------- |
| code | Int | 0 | 返回状态码 |
| session | String | "UnVerifiedSession" | 你的session key |
#### 状态码:
| 代码 | 原因 |
| ---- | ----------------------------- |
| 0 | 正常 |
| 1 | 错误的MIRAI API HTTP auth key |
session key 是使用以下方法必须携带的
session key 使用前必须进行校验和绑定指定的Bot**每个Session只能绑定一个Bot但一个Bot可有多个Session**
session Key 在未进行校验的情况下,一定时间后将会被自动释放
### 校验Session
```
[POST] /verify
```
使用此方法校验并激活你的Session同时将Session与一个**已登录**的Bot绑定
#### 请求:
```json5
{
"sessionKey": "UnVerifiedSession",
"qq": 123456789
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ---------- | ------ | ----- | ------------------- | -------------------------- |
| sessionKey | String | false | "UnVerifiedSession" | 你的session key |
| qq | Long | false | 123456789 | Session将要绑定的Bot的qq号 |
#### 响应: 返回统一状态码(后续不再赘述)
```json5
{
"code": 0,
"msg": "success"
}
```
| 状态码 | 原因 |
| ------ | ---------------------------------------- |
| 0 | 正常 |
| 1 | 错误的auth key |
| 2 | 指定的Bot不存在(常发生在Session认证时) |
| 3 | Session失效或不存在 |
| 4 | Session未认证(未激活) |
| 5 | 发送消息目标不存在(指定对象不存在) |
| 10 | 无操作权限指Bot没有对应操作的限权 |
| 20 | Bot被禁言指Bot当前无法向指定群发送消息 |
| 400 | 错误的访问,如参数错误等 |
### 释放Session
```
[POST] /release
```
使用此方式释放session及其相关资源Bot不会被释放
**不使用的Session应当被释放否则Session持续保存Bot收到的消息**
**长时间30分钟未被使用的Session会被系统自动释放以避免内存泄露**
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"qq": 123456789
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ---------- | ------ | ----- | -----------------| -------------------------- |
| sessionKey | String | false | "YourSessionKey" | 你的session key |
| qq | Long | false | 123456789 | 与该Session绑定Bot的QQ号码 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
> SessionKey与Bot 对应错误时将会返回状态码5指定对象不存在
## 消息相关
### 发送好友消息
```
[POST] /sendFriendMessage
```
使用此方法向指定好友发送消息
#### 请求
```json5
{
"sessionKey": "YourSession",
"target": 987654321,
"messageChain": [
{ "type": "Plain", "text":"hello\n" },
{ "type": "Plain", "text":"world" }
]
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | -------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | false | 987654321 | 发送消息目标好友的QQ号 |
| quote | Long | true | 135798642 | 引用一条消息的messageId进行回复 |
| messageChain | Array | false | [] | 消息链,是一个消息对象构成的数组 |
#### 响应: 返回统一状态码并携带messageId
```json5
{
"code": 0,
"msg": "success",
"messageId": 1234567890 // 一个Long类型属性标识本条消息用于撤回和引用回复
}
```
### 发送群消息
```
[POST] /sendGroupMessage
```
使用此方法向指定群发送消息
#### 请求
```json5
{
"sessionKey": "YourSession",
"target": 987654321,
"messageChain": [
{ "type": "Plain", "text":"hello\n" },
{ "type": "Plain", "text":"world" }
]
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | -------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | false | 987654321 | 发送消息目标群的群号 |
| quote | Long | true | 135798642 | 引用一条消息的messageId进行回复 |
| messageChain | Array | false | [] | 消息链,是一个消息对象构成的数组 |
#### 响应: 返回统一状态码并携带messageId
```json5
{
"code": 0,
"msg": "success",
"messageId": 1234567890 // 一个Long类型属性标识本条消息用于撤回和引用回复
}
```
### 发送图片消息通过URL
```
[POST] /sendImageMessage
```
使用此方法向指定对象(群或好友)发送图片消息
#### 请求
```json5
{
"sessionKey": "YourSession",
"target": 987654321,
"qq": 1234567890,
"group": 987654321,
"urls": [
"https://xxx.yyy.zzz/",
"https://aaa.bbb.ccc/"
]
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | ---------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | true | 987654321 | 发送对象的QQ号或群号可能存在歧义 |
| qq | Long | true | 123456789 | 发送对象的QQ号 |
| group | Long | true | 987654321 | 发送对象的群号 |
| urls | Array | false | [] | 是一个url字符串构成的数组 |
#### 响应: 图片的imageId数组
```json5
[
"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}.jpg",
"{YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY}.jpg"
]
```
### 图片文件上传
```
[POST] /uploadImage
```
使用此方法上传图片文件至服务器并返回ImageId
#### 请求
Content-Typemultipart/form-data
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | ---------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| type | String | false | "friend " | "friend" 或 "group" |
| img | File | false | - | 图片文件 |
#### 响应: 图片的imageId好友图片与群聊图片Id不同
```
{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}.jpg
```
### 撤回消息
```
[POST] /recall
```
使用此方法撤回指定消息。对于bot发送的消息又2分钟时间限制。对于撤回群聊中群员的消息需要有相应权限
#### 请求
```json5
{
"sessionKey": "YourSession",
"target": 987654321
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | -------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | false | 987654321 | 需要撤回的消息的messageId |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 获取Bot收到的消息和事件
```
[GET] /fetchMessage?sessionKey=YourSessionKey&count=10
```
使用此方法获取bot接收到的消息和各类事件
#### 请求:
| 名字 | 可选 | 举例 | 说明 |
| ---------- | ----- | -------------- | -------------------- |
| sessionKey | false | YourSessionKey | 你的session key |
| count | false | 10 | 获取消息和事件的数量 |
#### 响应: 返回JSON对象
```json5
[{
"type": "GroupMessage", // 消息类型GroupMessage或FriendMessage或各类Event
"messageChain": [{ // 消息链,是一个消息对象构成的数组
"type": "Source",
"uid": 123456
},{
"type": "Plain",
"text": "Miral牛逼"
}],
"sender": { // 发送者信息
"id": 123456789, // 发送者的QQ号码
"memberName": "化腾", // 发送者的群名片
"permission": "MEMBER", // 发送者的群限权OWNER、ADMINISTRATOR或MEMBER
"group": { // 消息发送群的信息
"id": 1234567890, // 发送群的群号
"name": "Miral Technology", // 发送群的群名称
"permission": "MEMBER" // 发送群中Bot的群限权
}
}
},{
"type": "FriendMessage", // 消息类型GroupMessage或FriendMessage或各类Event
"messageChain": [{ // 消息链,是一个消息对象构成的数组
"type": "Source",
"uid": 123456
},{
"type": "Plain",
"text": "Miral牛逼"
}],
"sender": { // 发送者信息
"id": 1234567890, // 发送者的QQ号码
"nickName": "", // 发送者的昵称
"remark": "" // 发送者的备注
}
},{
"type": "MemberMuteEvent", // 消息类型GroupMessage或FriendMessage或各类Event
"durationSeconds": 600,
"member":{
"id": 123456789,
"memberName": "禁言对象",
"permission": "MEMBER",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
},
"operator":{
"id": 987654321,
"memberName": "群主大人",
"permission": "OWNER",
"group": {
"id": 123456789,
"name": "Miral Technology",
"permission": "MEMBER"
}
}
}]
```
### 事件类型一览
[事件类型一览](EventType_CH.md)
> 事件为Bot被动接收的信息无法主动构建
### 消息类型一览
#### 消息是构成消息链的基本对象,目前支持的消息类型有
+ [x] At@消息
+ [x] AtAll@全体成员
+ [x] Face表情消息
+ [x] Plain文字消息
+ [x] Image图片消息
+ [ ] XmlXml卡片消息
+ [ ] 敬请期待
#### Source
```json5
{
"type": "Source",
"id": 123456
}
```
| 名字 | 类型 | 说明 |
| ---- | ---- | ------------------------------------------------------------ |
| id | Long | 消息的识别号用于引用回复Source类型只在群消息中返回且永远为chain的第一个元素 |
#### At
```json5
{
"type": "At",
"target": 123456,
"display": "@Mirai"
}
```
| 名字 | 类型 | 说明 |
| ------- | ------ | ---------------------------------------------- |
| target | Long | 群员QQ号 |
| dispaly | String | At时显示的文字发送消息时无效自动使用群名片 |
#### AtAll
```json5
{
"type": "AtAll"
}
```
| 名字 | 类型 | 说明 |
| ------- | ------ | ------------------------- |
| - | - | - |
#### Face
```json5
{
"type": "Face",
"faceId": 123
}
```
| 名字 | 类型 | 说明 |
| ------ | ---- | ---------- |
| faceId | Int | QQ表情编号 |
#### Plain
```json5
{
"type": "Plain",
"text": "Mirai牛逼"
}
```
| 名字 | 类型 | 说明 |
| ---- | ------ | -------- |
| text | String | 文字消息 |
#### Image
```json5
{
"type": "Image",
"imageId": "{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png" //群图片格式
//"imageId": "/f8f1ab55-bf8e-4236-b55e-955848d7069f" //好友图片格式
}
```
| 名字 | 类型 | 说明 |
| ------- | ------ | --------------------------------------- |
| imageId | String | 图片的imageId群图片与好友图片格式不同 |
#### Xml
```json5
{
"type": "Xml",
"xml": "XML"
}
```
| 名字 | 类型 | 说明 |
| ---- | ------ | ------- |
| xml | String | XML文本 |
## 管理相关
### 获取好友列表
使用此方法获取bot的好友列表
```
[GET] /friendList?sessionKey=YourSessionKey
```
#### 请求:
| 名字 | 可选 | 举例 | 说明 |
| ---------- | ----- | -------------- | --------------- |
| sessionKey | false | YourSessionKey | 你的session key |
#### 响应: 返回JSON对象
```json5
[{
"id":123456789,
"nickName":"",
"remark":""
},{
"id":987654321,
"nickName":"",
"remark":""
}]
```
### 获取群列表
使用此方法获取bot的群列表
```
[GET] /groupList?sessionKey=YourSessionKey
```
#### 请求:
| 名字 | 可选 | 举例 | 说明 |
| ---------- | ----- | -------------- | --------------- |
| sessionKey | false | YourSessionKey | 你的session key |
#### 响应: 返回JSON对象
```json5
[{
"id":123456789,
"name":"群名1",
"permission": "MEMBER"
},{
"id":987654321,
"name":"群名2",
"permission": "MEMBER"
}]
```
### 获取群成员列表
使用此方法获取bot指定群种的成员列表
```
[GET] /memberList?sessionKey=YourSessionKey
```
#### 请求:
| 名字 | 可选 | 举例 | 说明 |
| ---------- | ----- | -------------- | --------------- |
| sessionKey | false | YourSessionKey | 你的session key |
| target | false | 123456789 | 指定群的群号 |
#### 响应: 返回JSON对象
```json5
[{
"id":1234567890,
"memberName":"",
"permission":"MEMBER",
"group":{
"id":12345,
"name":"群名1",
"permission": "MEMBER"
}
},{
"id":9876543210,
"memberName":"",
"permission":"OWNER",
"group":{
"id":54321,
"name":"群名2",
"permission": "MEMBER"
}
}]
```
### 群全体禁言
使用此方法令指定群进行全体禁言(需要有相关限权)
```
[POST] /muteAll
```
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"target": 123456789,
}
```
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ---------- | ----- | ------ | ---------------- | --------------- |
| sessionKey | false | String | "YourSessionKey" | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 群解除全体禁言
使用此方法令指定群解除全体禁言(需要有相关限权)
```
[POST] /unmuteAll
```
#### 请求:
同全体禁言
#### 响应
同全体禁言
### 群禁言群成员
使用此方法指定群禁言指定群员(需要有相关限权)
```
[POST] /mute
```
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"target": 123456789,
"memberId": 987654321,
"time": 1800
}
```
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ---------- | ----- | ------ | ---------------- | ------------------------------------- |
| sessionKey | false | String | "YourSessionKey" | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
| memberId | false | Long | 987654321 | 指定群员QQ号 |
| time | true | Int | 1800 | 禁言时长单位为秒最多30天默认为0 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 群解除群成员禁言
使用此方法令指定群解除全体禁言(需要有相关限权)
```
[POST] /unmute
```
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"target": 123456789,
"memberId": 987654321
}
```
#### 响应
同群禁言群成员
### 移除群成员
使用此方法移除指定群成员(需要有相关限权)
```
[POST] /kick
```
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"target": 123456789,
"memberId": 987654321,
"msg": "您已被移出群聊"
}
```
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ---------- | ----- | ------ | ---------------- | --------------- |
| sessionKey | false | String | "YourSessionKey" | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
| memberId | false | Long | 987654321 | 指定群员QQ号 |
| msg | true | String | "" | 信息 |
#### 响应
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 群设置
使用此方法修改群设置(需要有相关限权)
```
[POST] /groupConfig
```
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"target": 123456789,
"config": {
"name": "群名称",
"announcement": "群公告",
"confessTalk": true,
"allowMemberInvite": true,
"autoApprove": true,
"anonymousChat": true
}
}
```
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ----------------- | ----- | ------- | ---------------- | -------------------- |
| sessionKey | false | String | "YourSessionKey" | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
| config | false | Object | {} | 群设置 |
| name | true | String | "Name" | 群名 |
| announcement | true | Boolean | true | 群公告 |
| confessTalk | true | Boolean | true | 是否开启坦白说 |
| allowMemberInvite | true | Boolean | true | 是否运行群员邀请 |
| autoApprove | true | Boolean | true | 是否开启自动审批入群 |
| anonymousChat | true | Boolean | true | 是否允许匿名聊天 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 获取群设置
使用此方法获取群设置
```
[Get] /groupConfig?sessionKey=YourSessionKey&target=123456789
```
#### 请求:
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ----------------- | ----- | ------- | ---------------- | -------------------- |
| sessionKey | false | String | YourSessionKey | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
#### 响应
```json5
{
"name": "群名称",
"announcement": "群公告",
"confessTalk": true,
"allowMemberInvite": true,
"autoApprove": true,
"anonymousChat": true
}
```
### 修改群员资料
使用此方法修改群员资料(需要有相关限权)
```
[POST] /memberInfo
```
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"target": 123456789,
"memberId": 987654321,
"info": {
"name": "群名片",
"specialTitle": "群头衔"
}
}
```
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ----------------- | ----- | ------- | ---------------- | -------------------- |
| sessionKey | false | String | "YourSessionKey" | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
| memberId | false | Long | 987654321 | 群员QQ号 |
| info | false | Object | {} | 群员资料 |
| name | true | String | "Name" | 群名片,即群昵称 |
| specialTitle | true | String | "Title" | 群头衔 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 获取群员资料
使用此方法获取群员资料
```
[Get] /memberInfo?sessionKey=YourSessionKey&target=123456789
```
#### 请求:
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ----------------- | ----- | ------- | ---------------- | -------------------- |
| sessionKey | false | String | YourSessionKey | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
| memberId | false | Long | 987654321 | 群员QQ号 |
#### 响应
```json5
{
"name": "群名片",
"announcement": "群头衔"
}
```

View File

@ -1,73 +0,0 @@
@file:Suppress("UNUSED_VARIABLE")
plugins {
id("kotlinx-atomicfu")
kotlin("jvm")
id("kotlinx-serialization")
}
group = "net.mamoe.mirai"
version = rootProject.ext["mirai_version"].toString()
description = "Mirai Http Api"
val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext
val klockVersion: String by rootProject.ext
val ktorVersion: String by rootProject.ext
val serializationVersion: String by rootProject.ext
fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
fun ktor(id: String, version: String = this@Build_gradle.ktorVersion) = "io.ktor:ktor-$id:$version"
kotlin {
sourceSets["main"].apply {
dependencies {
implementation(project(":mirai-core-qqandroid"))
implementation(kotlin("stdlib-jdk8", kotlinVersion))
implementation(kotlin("stdlib-jdk7", kotlinVersion))
implementation(kotlin("reflect", kotlinVersion))
implementation(ktor("server-cio"))
implementation(kotlinx("io-jvm", kotlinXIoVersion))
implementation(ktor("http-jvm"))
implementation("org.slf4j:slf4j-simple:1.7.30")
}
}
sourceSets["test"].apply {
dependencies {
}
kotlin.outputDir = file("build/classes/kotlin/jvm/test")
kotlin.setSrcDirs(listOf("src/$name/kotlin"))
}
sourceSets.all {
languageSettings.enableLanguageFeature("InlineClasses")
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
dependencies {
implementation(kotlin("stdlib", kotlinVersion))
implementation(kotlin("serialization", kotlinVersion))
implementation("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
implementation(kotlinx("io", kotlinXIoVersion))
implementation(kotlinx("coroutines-io", coroutinesIoVersion))
implementation(kotlinx("coroutines-core", coroutinesVersion))
implementation(kotlinx("serialization-runtime", serializationVersion))
implementation(ktor("server-core"))
implementation(ktor("http"))
}
}
}

View File

@ -1,67 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http
import io.ktor.application.Application
import io.ktor.server.cio.CIO
import io.ktor.server.engine.applicationEngineEnvironment
import io.ktor.server.engine.connector
import io.ktor.server.engine.embeddedServer
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import net.mamoe.mirai.api.http.route.mirai
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiLogger
import org.slf4j.helpers.NOPLoggerFactory
import kotlin.coroutines.CoroutineContext
object MiraiHttpAPIServer : CoroutineScope {
var logger: MiraiLogger = DefaultLogger("Mirai HTTP API")
override val coroutineContext: CoroutineContext =
CoroutineExceptionHandler { _, throwable -> logger.error(throwable) }
init {
SessionManager.authKey = generateSessionKey()//用于验证的key, 使用和SessionKey相同的方法生成, 但意义不同
}
fun setAuthKey(key: String) {
SessionManager.authKey = key
}
@UseExperimental(KtorExperimentalAPI::class)
fun start(
port: Int = 8080,
authKey: String,
callback: (() -> Unit)? = null
) {
require(authKey.length in 8..128) { "Expected authKey length is between 8 to 128" }
SessionManager.authKey = authKey
// TODO: start是无阻塞的理应获取启动状态后再执行后续代码
launch {
embeddedServer(CIO, environment = applicationEngineEnvironment {
this.parentCoroutineContext = coroutineContext
this.log = NOPLoggerFactory().getLogger("NMYSL")
this.module(Application::mirai)
connector {
this.port = port
}
}).start(wait = true)
}
logger.info("Http api server is running with authKey: ${SessionManager.authKey}")
callback?.invoke()
}
}

View File

@ -1,138 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http
import kotlinx.coroutines.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.api.http.queue.MessageQueue
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.utils.currentTimeSeconds
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
tailrec fun generateSessionKey(): String {
fun generateRandomSessionKey(): String {
val all = "QWERTYUIOPASDFGHJKLZXCVBNM1234567890qwertyuiopasdfghjklzxcvbnm"
return buildString(capacity = 8) {
repeat(8) {
append(all.random())
}
}
}
val key = generateRandomSessionKey()
if (!SessionManager.allSession.containsKey(key)) {
return key
}
return generateSessionKey()
}
internal object SessionManager {
val allSession: MutableMap<String, Session> = mutableMapOf()
lateinit var authKey: String
fun createTempSession(): TempSession = TempSession(EmptyCoroutineContext).also { newTempSession ->
allSession[newTempSession.key] = newTempSession
//设置180000ms后检测并回收
newTempSession.launch {
delay(180000)
allSession[newTempSession.key]?.run {
if (this is TempSession)
closeSession(newTempSession.key)
}
}
}
fun createAuthedSession(bot: Bot, originKey: String): AuthedSession = AuthedSession(bot, originKey, EmptyCoroutineContext).also { session ->
closeSession(originKey)
allSession[originKey] = session
}
operator fun get(sessionKey: String) = allSession[sessionKey]?.also {
if (it is AuthedSession) it.latestUsed = currentTimeSeconds }
fun containSession(sessionKey: String): Boolean = allSession.containsKey(sessionKey)
fun closeSession(sessionKey: String) = allSession.remove(sessionKey)?.also { it.close() }
fun closeSession(session: Session) = closeSession(session.key)
}
/**
* 管理不同 Client Mirai HTTP 的会话
*
* [Session] 均为内部操作用类
* @see [SessionManager]
* @author NaturalHG
*/
internal abstract class Session internal constructor(
coroutineContext: CoroutineContext,
val key: String = generateSessionKey()
) : CoroutineScope {
private val supervisorJob = SupervisorJob(coroutineContext[Job])
final override val coroutineContext: CoroutineContext = supervisorJob + coroutineContext
internal open fun close() {
supervisorJob.complete()
}
}
/**
* 任何新链接建立后分配一个[TempSession]
*
* TempSession在建立180s内没有转变为[AuthedSession]应被清除
*/
internal class TempSession internal constructor(coroutineContext: CoroutineContext) : Session(coroutineContext)
/**
* 任何[TempSession]认证后转化为一个[AuthedSession]
* 在这一步[AuthedSession]应该已经有assigned的bot
*/
internal class AuthedSession internal constructor(val bot: Bot, originKey: String, coroutineContext: CoroutineContext) : Session(coroutineContext, originKey) {
companion object {
const val CHECK_TIME = 1800L // 1800s aka 30min
}
val messageQueue = MessageQueue()
private val _listener: Listener<BotEvent>
private val releaseJob: Job //手动释放将会在下一次检查时回收Session
internal var latestUsed = currentTimeSeconds
init {
_listener = bot.subscribeAlways{ this.run(messageQueue::add) }
releaseJob = launch {
while (true) {
delay(CHECK_TIME * 1000)
if (currentTimeSeconds - latestUsed >= CHECK_TIME) {
SessionManager.closeSession(this@AuthedSession)
break
}
}
}
}
override fun close() {
messageQueue.clear()
_listener.complete()
super.close()
}
}

View File

@ -1,32 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.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) : DTO {
object Success : StateCode(0, "success") // 成功
object NoBot : StateCode(2, "指定Bot不存在")
object IllegalSession : StateCode(3, "Session失效或不存在")
object NotVerifySession : StateCode(4, "Session未认证")
object NoElement : StateCode(5, "指定对象不存在")
object PermissionDenied : StateCode(10, "无操作权限")
object BotMuted : StateCode(20, "Bot被禁言")
// KS bug: 主构造器中不能有非字段参数 https://github.com/Kotlin/kotlinx.serialization/issues/575
@Serializable
class IllegalAccess() : StateCode(400, "") { // 非法访问
constructor(msg: String) : this() {
this.msg = msg
}
}
}

View File

@ -1,126 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.data.common
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.message.MessagePacket
import net.mamoe.mirai.utils.MiraiExperimentalAPI
@Serializable
sealed class BotEventDTO : EventDTO()
@UseExperimental(MiraiExperimentalAPI::class)
suspend fun BotEvent.toDTO() = when(this) {
is MessagePacket<*, *> -> toDTO()
else -> when(this) {
is BotOnlineEvent -> BotOnlineEventDTO(bot.uin)
is BotOfflineEvent.Active -> BotOfflineEventActiveDTO(bot.uin)
is BotOfflineEvent.Force -> BotOfflineEventForceDTO(bot.uin, title, message)
is BotOfflineEvent.Dropped -> BotOfflineEventDroppedDTO(bot.uin)
is BotReloginEvent -> BotReloginEventDTO(bot.uin)
// is MessageSendEvent.GroupMessageSendEvent -> {}
// is MessageSendEvent.FriendMessageSendEvent -> {}
// is BeforeImageUploadEvent -> {}
// is ImageUploadEvent.Succeed -> {}
is BotGroupPermissionChangeEvent -> BotGroupPermissionChangeEventDTO(origin, new, GroupDTO(group))
is BotMuteEvent -> BotMuteEventDTO(durationSeconds, MemberDTO(operator))
is BotUnmuteEvent -> BotUnmuteEventDTO(MemberDTO(operator))
is BotJoinGroupEvent -> BotJoinGroupEventDTO(GroupDTO(group))
// is GroupSettingChangeEvent<*> -> {} // 不知道会改什么
is GroupNameChangeEvent -> GroupNameChangeEventDTO(origin, new, GroupDTO(group), isByBot)
is GroupEntranceAnnouncementChangeEvent -> GroupEntranceAnnouncementChangeEventDTO(origin, new, GroupDTO(group), operator?.let(::MemberDTO))
is GroupMuteAllEvent -> GroupMuteAllEventDTO(origin, new, GroupDTO(group), operator?.let(::MemberDTO))
is GroupAllowAnonymousChatEvent -> GroupAllowAnonymousChatEventDTO(origin, new, GroupDTO(group), operator?.let(::MemberDTO))
is GroupAllowConfessTalkEvent -> GroupAllowConfessTalkEventDTO(origin, new, GroupDTO(group), isByBot)
is GroupAllowMemberInviteEvent -> GroupAllowMemberInviteEventDTO(origin, new, GroupDTO(group), operator?.let(::MemberDTO))
is MemberJoinEvent -> MemberJoinEventDTO(MemberDTO(member))
is MemberLeaveEvent.Kick -> MemberLeaveEventKickDTO(MemberDTO(member), operator?.let(::MemberDTO))
is MemberLeaveEvent.Quit -> MemberLeaveEventQuitDTO(MemberDTO(member))
is MemberCardChangeEvent -> MemberCardChangeEventDTO(origin, new, MemberDTO(member), operator?.let(::MemberDTO))
is MemberSpecialTitleChangeEvent -> MemberSpecialTitleChangeEventDTO(origin, new, MemberDTO(member))
is MemberPermissionChangeEvent -> MemberPermissionChangeEventDTO(origin, new, MemberDTO(member))
is MemberMuteEvent -> MemberMuteEventDTO(durationSeconds, MemberDTO(member), operator?.let(::MemberDTO))
is MemberUnmuteEvent -> MemberUnmuteEventDTO(MemberDTO(member), operator?.let(::MemberDTO))
else -> IgnoreEventDTO
}
}
@Serializable
@SerialName("BotOnlineEvent")
data class BotOnlineEventDTO(val qq: Long) : BotEventDTO()
@Serializable
@SerialName("BotOfflineEventActive")
data class BotOfflineEventActiveDTO(val qq: Long) : BotEventDTO()
@Serializable
@SerialName("BotOfflineEventForce")
data class BotOfflineEventForceDTO(val qq: Long, val title: String, val message: String) : BotEventDTO()
@Serializable
@SerialName("BotOfflineEventDropped")
data class BotOfflineEventDroppedDTO(val qq: Long) : BotEventDTO()
@Serializable
@SerialName("BotReloginEvent")
data class BotReloginEventDTO(val qq: Long) : BotEventDTO()
@Serializable
@SerialName("BotGroupPermissionChangeEvent")
data class BotGroupPermissionChangeEventDTO(val origin: MemberPermission, val new: MemberPermission, val group: GroupDTO) : BotEventDTO()
@Serializable
@SerialName("BotMuteEvent")
data class BotMuteEventDTO(val durationSeconds: Int, val operator: MemberDTO) : BotEventDTO()
@Serializable
@SerialName("BotUnmuteEvent")
data class BotUnmuteEventDTO(val operator: MemberDTO) : BotEventDTO()
@Serializable
@SerialName("BotJoinGroupEvent")
data class BotJoinGroupEventDTO(val group: GroupDTO) : BotEventDTO()
@Serializable
@SerialName("GroupNameChangeEvent")
data class GroupNameChangeEventDTO(val origin: String, val new: String, val group: GroupDTO, val isByBot: Boolean) : BotEventDTO()
@Serializable
@SerialName("GroupEntranceAnnouncementChangeEvent")
data class GroupEntranceAnnouncementChangeEventDTO(val origin: String, val new: String, val group: GroupDTO, val operator: MemberDTO?) : BotEventDTO()
@Serializable
@SerialName("GroupMuteAllEvent")
data class GroupMuteAllEventDTO(val origin: Boolean, val new: Boolean, val group: GroupDTO, val operator: MemberDTO?) : BotEventDTO()
@Serializable
@SerialName("GroupAllowAnonymousChatEvent")
data class GroupAllowAnonymousChatEventDTO(val origin: Boolean, val new: Boolean, val group: GroupDTO, val operator: MemberDTO?) : BotEventDTO()
@Serializable
@SerialName("GroupAllowConfessTalkEvent")
data class GroupAllowConfessTalkEventDTO(val origin: Boolean, val new: Boolean, val group: GroupDTO, val isByBot: Boolean) : BotEventDTO()
@Serializable
@SerialName("GroupAllowMemberInviteEvent")
data class GroupAllowMemberInviteEventDTO(val origin: Boolean, val new: Boolean, val group: GroupDTO, val operator: MemberDTO?) : BotEventDTO()
@Serializable
@SerialName("MemberJoinEvent")
data class MemberJoinEventDTO(val member: MemberDTO) : BotEventDTO()
@Serializable
@SerialName("MemberLeaveEventKick")
data class MemberLeaveEventKickDTO(val member: MemberDTO, val operator: MemberDTO?) : BotEventDTO()
@Serializable
@SerialName("MemberLeaveEventQuit")
data class MemberLeaveEventQuitDTO(val member: MemberDTO) : BotEventDTO()
@Serializable
@SerialName("MemberCardChangeEvent")
data class MemberCardChangeEventDTO(val origin: String, val new: String, val member: MemberDTO, val operator: MemberDTO?) : BotEventDTO()
@Serializable
@SerialName("MemberSpecialTitleChangeEvent")
data class MemberSpecialTitleChangeEventDTO(val origin: String, val new: String, val member: MemberDTO) : BotEventDTO()
@Serializable
@SerialName("MemberPermissionChangeEvent")
data class MemberPermissionChangeEventDTO(val origin: MemberPermission, val new: MemberPermission, val member: MemberDTO) : BotEventDTO()
@Serializable
@SerialName("MemberMuteEvent")
data class MemberMuteEventDTO(val durationSeconds: Int, val member: MemberDTO, val operator: MemberDTO?) : BotEventDTO()
@Serializable
@SerialName("MemberUnmuteEvent")
data class MemberUnmuteEventDTO(val member: MemberDTO, val operator: MemberDTO?) : BotEventDTO()

View File

@ -1,53 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.data.common
import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.utils.MiraiExperimentalAPI
@Serializable
abstract class ContactDTO : DTO {
abstract val id: Long
}
@Serializable
data class QQDTO(
override val id: Long,
val nickName: String,
val remark: String
) : ContactDTO() {
// TODO: queryProfile.nickname & queryRemark.value not support now
constructor(qq: QQ) : this(qq.id, "", "")
}
@Serializable
data class MemberDTO(
override val id: Long,
val memberName: String,
val permission: MemberPermission,
val group: GroupDTO
) : ContactDTO() {
constructor(member: Member) : this(
member.id, member.nameCardOrNick, member.permission,
GroupDTO(member.group)
)
}
@Serializable
data class GroupDTO(
override val id: Long,
val name: String,
val permission: MemberPermission
) : ContactDTO() {
@UseExperimental(MiraiExperimentalAPI::class)
constructor(group: Group) : this(group.id, group.name, group.botPermission)
}

View File

@ -1,31 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.data.common
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.api.http.AuthedSession
interface DTO
@Serializable
data class AuthDTO(val authKey: String) : DTO
@Serializable
abstract class VerifyDTO : DTO {
abstract val sessionKey: String
@Transient
internal lateinit var session: AuthedSession // 反序列化验证成功后传入
}
@Serializable
abstract class EventDTO : DTO
object IgnoreEventDTO : EventDTO()

View File

@ -1,133 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.data.common
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.MessagePacket
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.uploadImage
import net.mamoe.mirai.utils.MiraiInternalAPI
import java.net.URL
/*
* DTO data class
* */
// MessagePacket
@Serializable
@SerialName("FriendMessage")
data class FriendMessagePacketDTO(val sender: QQDTO) : MessagePacketDTO()
@Serializable
@SerialName("GroupMessage")
data class GroupMessagePacketDTO(val sender: MemberDTO) : MessagePacketDTO()
// Message
@Serializable
@SerialName("Source")
data class MessageSourceDTO(val id: Long) : MessageDTO()
@Serializable
@SerialName("At")
data class AtDTO(val target: Long, val display: String = "") : MessageDTO()
@Serializable
@SerialName("AtAll")
data class AtAllDTO(val target: Long = 0) : MessageDTO() // target为保留字段
@Serializable
@SerialName("Face")
data class FaceDTO(val faceId: Int) : MessageDTO()
@Serializable
@SerialName("Plain")
data class PlainDTO(val text: String) : MessageDTO()
@Serializable
@SerialName("Image")
data class ImageDTO(val imageId: String? = null, val url: String? = null) : MessageDTO()
@Serializable
@SerialName("Xml")
data class XmlDTO(val xml: String) : MessageDTO()
@Serializable
@SerialName("Unknown")
object UnknownMessageDTO : MessageDTO()
/*
* Abstract Class
* */
@Serializable
sealed class MessagePacketDTO : EventDTO() {
lateinit var messageChain: MessageChainDTO
}
typealias MessageChainDTO = List<MessageDTO>
@Serializable
sealed class MessageDTO : DTO
/*
Extend function
*/
suspend fun MessagePacket<*, *>.toDTO() = when (this) {
is FriendMessage -> FriendMessagePacketDTO(QQDTO(sender))
is GroupMessage -> GroupMessagePacketDTO(MemberDTO(sender))
else -> IgnoreEventDTO
}.apply {
if (this is MessagePacketDTO) {
// 将MessagePacket中的所有Message转为DTO对象并添加到messageChain
// foreachContent会忽略MessageSource一次主动获取
messageChain = mutableListOf(messageDTO(message[MessageSource])).apply {
message.foreachContent { content -> messageDTO(content).takeUnless { it == UnknownMessageDTO }?.let(::add) }
}
// else: `this` is bot event
}
}
suspend fun MessageChainDTO.toMessageChain(contact: Contact) =
buildMessageChain { this@toMessageChain.forEach { it.toMessage(contact)?.let(::add) } }
@UseExperimental(ExperimentalUnsignedTypes::class)
suspend fun MessagePacket<*, *>.messageDTO(message: Message) = when (message) {
is MessageSource -> MessageSourceDTO(message.id)
is At -> AtDTO(message.target, message.display)
is AtAll -> AtAllDTO(0L)
is Face -> FaceDTO(message.id)
is PlainText -> PlainDTO(message.stringValue)
is Image -> ImageDTO(message.imageId, message.url())
is XMLMessage -> XmlDTO(message.stringValue)
else -> UnknownMessageDTO
}
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
suspend fun MessageDTO.toMessage(contact: Contact) = when (this) {
is AtDTO -> At((contact as Group)[target])
is AtAllDTO -> AtAll
is FaceDTO -> Face(faceId)
is PlainDTO -> PlainText(text)
is ImageDTO -> when {
!imageId.isNullOrBlank() -> Image(imageId)
!url.isNullOrBlank() -> contact.uploadImage(URL(url))
else -> null
}
is XmlDTO -> XMLMessage(xml)
is MessageSourceDTO, is UnknownMessageDTO -> null
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.data
/**
* 错误请求. 抛出这个异常后将会返回错误给一个请求
*/
@Suppress("unused")
open class IllegalAccessException : Exception {
override val message: String get() = super.message!!
constructor(message: String) : super(message, null)
constructor(cause: Throwable) : super(cause.toString(), cause)
constructor(message: String, cause: Throwable?) : super(message, cause)
}
/**
* Session失效或不存在
*/
object IllegalSessionException : IllegalAccessException("Session失效或不存在")
/**
* Session未激活
*/
object NotVerifiedSessionException : IllegalAccessException("Session未激活")
/**
* 指定Bot不存在
*/
object NoSuchBotException: IllegalAccessException("指定Bot不存在")
/**
* 错误参数
*/
class IllegalParamException(message: String) : IllegalAccessException(message)

View File

@ -1,56 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.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.toDTO
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.message.MessagePacket
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.utils.firstKey
import java.util.concurrent.ConcurrentLinkedDeque
class MessageQueue : ConcurrentLinkedDeque<BotEvent>() {
val cacheSize = 4096
val cache = LinkedHashMap<Long, MessagePacket<*, *>>()
suspend fun fetch(size: Int): List<EventDTO> {
var count = size
val ret = ArrayList<EventDTO>(count)
while (!this.isEmpty() && count > 0) {
val event = pop()
event.toDTO().also {
if (it != IgnoreEventDTO) {
ret.add(it)
count--
}
}
if (event is MessagePacket<*, *>) {
addQuoteCache(event)
}
}
return ret
}
fun cache(messageId: Long) =
cache[messageId] ?: throw NoSuchElementException()
fun addQuoteCache(msg: MessagePacket<*, *>) {
cache[msg.message[MessageSource].id] = msg
if (cache.size > cacheSize) {
cache.remove(cache.firstKey())
}
}
}

View File

@ -1,66 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.route
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.routing.routing
import kotlinx.serialization.Serializable
import net.mamoe.mirai.Bot
import net.mamoe.mirai.api.http.AuthedSession
import net.mamoe.mirai.api.http.SessionManager
import net.mamoe.mirai.api.http.data.NoSuchBotException
import net.mamoe.mirai.api.http.data.StateCode
import net.mamoe.mirai.api.http.data.common.DTO
import net.mamoe.mirai.api.http.data.common.VerifyDTO
import kotlin.coroutines.EmptyCoroutineContext
fun Application.authModule() {
routing {
miraiAuth("/auth") {
if (it.authKey != SessionManager.authKey) {
call.respondStateCode(StateCode(1, "Auth Key错误"))
} else {
call.respondDTO(AuthRetDTO(0, SessionManager.createTempSession().key))
}
}
miraiVerify<BindDTO>("/verify", verifiedSessionKey = false) {
val bot = getBotOrThrow(it.qq)
SessionManager.createAuthedSession(bot, it.sessionKey)
call.respondStateCode(StateCode.Success)
}
miraiVerify<BindDTO>("/release") {
val bot = getBotOrThrow(it.qq)
val session = SessionManager[it.sessionKey] as AuthedSession
if (bot.uin == session.bot.uin) {
SessionManager.closeSession(it.sessionKey)
call.respondStateCode(StateCode.Success)
} else {
throw NoSuchElementException()
}
}
}
}
@Serializable
private data class AuthRetDTO(val code: Int, val session: String) : DTO
@Serializable
private data class BindDTO(override val sessionKey: String, val qq: Long) : VerifyDTO()
private fun getBotOrThrow(qq: Long) = try {
Bot.getInstance(qq)
} catch (e: NoSuchElementException) {
throw NoSuchBotException
}

View File

@ -1,214 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.route
import io.ktor.application.Application
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.features.CallLogging
import io.ktor.features.DefaultHeaders
import io.ktor.http.ContentType
import io.ktor.http.HttpMethod
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
import io.ktor.util.pipeline.ContextDsl
import io.ktor.util.pipeline.PipelineContext
import net.mamoe.mirai.api.http.AuthedSession
import net.mamoe.mirai.api.http.SessionManager
import net.mamoe.mirai.api.http.TempSession
import net.mamoe.mirai.api.http.data.*
import net.mamoe.mirai.api.http.data.common.AuthDTO
import net.mamoe.mirai.api.http.data.common.DTO
import net.mamoe.mirai.api.http.data.common.VerifyDTO
import net.mamoe.mirai.api.http.util.jsonParseOrNull
import net.mamoe.mirai.api.http.util.toJson
import net.mamoe.mirai.contact.PermissionDeniedException
import org.slf4j.helpers.NOPLoggerFactory
fun Application.mirai() {
install(DefaultHeaders)
install(CallLogging) {
logger = NOPLoggerFactory().getLogger("NMSL")
}
authModule()
messageModule()
infoModule()
groupManageModule()
}
/**
* Auth处理http server的验证
* 为闭包传入一个AuthDTO对象
*/
@ContextDsl
internal fun Route.miraiAuth(
path: String,
body: suspend PipelineContext<Unit, ApplicationCall>.(AuthDTO) -> Unit
): Route {
return route(path, HttpMethod.Post) {
intercept {
val dto = context.receiveDTO<AuthDTO>() ?: throw IllegalParamException("参数格式错误")
this.body(dto)
}
}
}
/**
* Get用于获取bot的属性
* 验证请求参数中sessionKey参数的有效性
*/
@ContextDsl
internal fun Route.miraiGet(
path: String,
body: suspend PipelineContext<Unit, ApplicationCall>.(AuthedSession) -> Unit
): Route {
return route(path, HttpMethod.Get) {
intercept {
val sessionKey = call.parameters["sessionKey"] ?: throw IllegalParamException("参数格式错误")
if (!SessionManager.containSession(sessionKey)) throw IllegalSessionException
when (val session = SessionManager[sessionKey]) {
is TempSession -> throw NotVerifiedSessionException
is AuthedSession -> this.body(session)
}
}
}
}
/**
* Verify用于处理bot的行为请求
* 验证数据传输对象(DTO)中是否包含sessionKey字段
* 且验证sessionKey的有效性
*
* @param verifiedSessionKey 是否验证sessionKey是否被激活
*
* it 为json解析出的DTO对象
*/
@ContextDsl
internal inline fun <reified T : VerifyDTO> Route.miraiVerify(
path: String,
verifiedSessionKey: Boolean = true,
crossinline body: suspend PipelineContext<Unit, ApplicationCall>.(T) -> Unit
): Route {
return route(path, HttpMethod.Post) {
intercept {
val dto = context.receiveDTO<T>() ?: throw IllegalParamException("参数格式错误")
SessionManager[dto.sessionKey]?.let {
when {
it is TempSession && verifiedSessionKey -> throw NotVerifiedSessionException
it is AuthedSession -> dto.session = it
}
} ?: throw IllegalSessionException
this.body(dto)
}
}
}
/**
* 统一捕获并处理异常
*/
internal inline fun Route.intercept(crossinline blk: suspend PipelineContext<Unit, ApplicationCall>.() -> Unit) = handle {
try {
blk(this)
} catch (e: NoSuchBotException) { // Bot不存在
call.respondStateCode(StateCode.NoBot)
} catch (e: IllegalSessionException) { // Session过期
call.respondStateCode(StateCode.IllegalSession)
} catch (e: NotVerifiedSessionException) { // Session未认证
call.respondStateCode(StateCode.NotVerifySession)
} catch (e: NoSuchElementException) { // 指定对象不存在
call.respondStateCode(StateCode.NoElement)
} catch (e: PermissionDeniedException) { // 缺少权限
call.respondStateCode(StateCode.PermissionDenied)
} catch (e: IllegalStateException) { // Bot被禁言
call.respondStateCode(StateCode.BotMuted)
} catch (e: IllegalAccessException) { // 错误访问
call.respondStateCode(StateCode(400, e.message), HttpStatusCode.BadRequest)
} catch (e: Throwable) {
e.printStackTrace()
call.respond(HttpStatusCode.InternalServerError, e.message!!)
}
}
/*
extend function
*/
internal suspend inline fun <reified T : StateCode> ApplicationCall.respondStateCode(code: T, status: HttpStatusCode = HttpStatusCode.OK) =
respondJson(code.toJson(StateCode.serializer()), status)
internal suspend inline fun <reified T : DTO> ApplicationCall.respondDTO(dto: T, status: HttpStatusCode = HttpStatusCode.OK) =
respondJson(dto.toJson(), status)
internal suspend fun ApplicationCall.respondJson(json: String, status: HttpStatusCode = HttpStatusCode.OK) =
respondText(json, defaultTextContentType(ContentType("application", "json")), status)
internal suspend inline fun <reified T : DTO> ApplicationCall.receiveDTO(): T? = receive<String>().jsonParseOrNull()
fun PipelineContext<Unit, ApplicationCall>.illegalParam(
expectingType: String?,
paramName: String,
actualValue: String? = call.parameters[paramName]
): Nothing = throw IllegalParamException("Illegal param. A $expectingType is required for `$paramName` while `$actualValue` is given")
@Suppress("IMPLICIT_CAST_TO_ANY")
@UseExperimental(ExperimentalUnsignedTypes::class)
internal inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNull(name: String): R =
when (R::class) {
Byte::class -> call.parameters[name]?.toByte()
Int::class -> call.parameters[name]?.toInt()
Short::class -> call.parameters[name]?.toShort()
Float::class -> call.parameters[name]?.toFloat()
Long::class -> call.parameters[name]?.toLong()
Double::class -> call.parameters[name]?.toDouble()
Boolean::class -> when (call.parameters[name]) {
"true" -> true
"false" -> false
"0" -> false
"1" -> true
null -> null
else -> illegalParam("boolean", name)
}
String::class -> call.parameters[name]
UByte::class -> call.parameters[name]?.toUByte()
UInt::class -> call.parameters[name]?.toUInt()
UShort::class -> call.parameters[name]?.toUShort()
else -> error(name::class.simpleName + " is not supported")
} as R ?: illegalParam(R::class.simpleName, name)
/**
* multi part
*/
internal fun List<PartData>.value(name: String) =
try {
(filter { it.name == name }[0] as PartData.FormItem).value
} catch (e: Exception) {
throw IllegalParamException("参数格式错误")
}
internal fun List<PartData>.file(name: String) =
try {
filter { it.name == name }[0] as? PartData.FileItem
} catch (e: Exception) {
throw IllegalParamException("参数格式错误")
}

View File

@ -1,11 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.route

View File

@ -1,152 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.route
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.routing.routing
import kotlinx.serialization.Serializable
import net.mamoe.mirai.api.http.data.StateCode
import net.mamoe.mirai.api.http.data.common.DTO
import net.mamoe.mirai.api.http.data.common.VerifyDTO
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
fun Application.groupManageModule() {
routing {
/**
* 禁言需要相关权限
*/
miraiVerify<MuteDTO>("/muteAll") {
it.session.bot.getGroup(it.target).isMuteAll = true
call.respondStateCode(StateCode.Success)
}
miraiVerify<MuteDTO>("/unmuteAll") {
it.session.bot.getGroup(it.target).isMuteAll = false
call.respondStateCode(StateCode.Success)
}
miraiVerify<MuteDTO>("/mute") {
it.session.bot.getGroup(it.target)[it.memberId].mute(it.time)
call.respondStateCode(StateCode.Success)
}
miraiVerify<MuteDTO>("/unmute") {
it.session.bot.getGroup(it.target).members[it.memberId].unmute()
call.respondStateCode(StateCode.Success)
}
/**
* 移出群聊需要相关权限
*/
miraiVerify<KickDTO>("/kick") {
it.session.bot.getGroup(it.target)[it.memberId].kick(it.msg)
call.respondStateCode(StateCode.Success)
}
/**
* 群设置需要相关权限
*/
miraiGet("/groupConfig") {
val group = it.bot.getGroup(paramOrNull("target"))
call.respondDTO(GroupDetailDTO(group))
}
miraiVerify<GroupConfigDTO>("/groupConfig") { dto ->
val group = dto.session.bot.getGroup(dto.target)
with(dto.config) {
name?.let { group.name = it }
announcement?.let { group.entranceAnnouncement = it }
confessTalk?.let { group.isConfessTalkEnabled = it }
allowMemberInvite?.let { group.isAllowMemberInvite = it }
// TODO: 待core接口实现设置可改
// autoApprove?.let { group.autoApprove = it }
// anonymousChat?.let { group.anonymousChat = it }
}
call.respondStateCode(StateCode.Success)
}
/**
* 群员信息管理需要相关权限
*/
miraiGet("/memberInfo") {
val member = it.bot.getGroup(paramOrNull("target"))[paramOrNull("memberId")]
call.respondDTO(MemberDetailDTO(member))
}
miraiVerify<MemberInfoDTO>("/memberInfo") { dto ->
val member = dto.session.bot.getGroup(dto.target)[dto.memberId]
with(dto.info) {
name?.let { member.nameCard = it }
specialTitle?.let { member.specialTitle = it }
}
call.respondStateCode(StateCode.Success)
}
}
}
@Serializable
private data class MuteDTO(
override val sessionKey: String,
val target: Long,
val memberId: Long = 0,
val time: Int = 0
) : VerifyDTO()
@Serializable
private data class KickDTO(
override val sessionKey: String,
val target: Long,
val memberId: Long,
val msg: String = ""
) : VerifyDTO()
@Serializable
private data class GroupConfigDTO(
override val sessionKey: String,
val target: Long,
val config: GroupDetailDTO
) : VerifyDTO()
@Serializable
private data class GroupDetailDTO(
val name: String? = null,
val announcement: String? = null,
val confessTalk: Boolean? = null,
val allowMemberInvite: Boolean? = null,
val autoApprove: Boolean? = null,
val anonymousChat: Boolean? = null
) : DTO {
constructor(group: Group) : this(
group.name, group.entranceAnnouncement, group.isConfessTalkEnabled, group.isAllowMemberInvite,
group.isAutoApproveEnabled, group.isAnonymousChatEnabled
)
}
@Serializable
private data class MemberInfoDTO(
override val sessionKey: String,
val target: Long,
val memberId: Long,
val info: MemberDetailDTO
) : VerifyDTO()
@Serializable
private data class MemberDetailDTO(
val name: String? = null,
val specialTitle: String? = null
) : DTO {
constructor(member: Member) : this(member.nameCard, member.specialTitle)
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.route
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.routing.routing
import net.mamoe.mirai.api.http.data.common.GroupDTO
import net.mamoe.mirai.api.http.data.common.MemberDTO
import net.mamoe.mirai.api.http.data.common.QQDTO
import net.mamoe.mirai.api.http.util.toJson
import net.mamoe.mirai.contact.toMutableList
fun Application.infoModule() {
routing {
miraiGet("/friendList") {
val ls = it.bot.qqs.toMutableList().map { qq -> QQDTO(qq) }
call.respondJson(ls.toJson())
}
miraiGet("/groupList") {
val ls = it.bot.groups.toMutableList().map { group -> GroupDTO(group) }
call.respondJson(ls.toJson())
}
miraiGet("/memberList") {
val ls = it.bot.getGroup(paramOrNull("target")).members.toMutableList().map { member -> MemberDTO(member) }
call.respondJson(ls.toJson())
}
}
}

View File

@ -1,168 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.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 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.DTO
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.toMessageChain
import net.mamoe.mirai.api.http.util.toJson
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.uploadImage
import java.net.URL
fun Application.messageModule() {
routing {
miraiGet("/fetchMessage") {
val count: Int = paramOrNull("count")
val fetch = it.messageQueue.fetch(count)
call.respondJson(fetch.toJson())
}
suspend fun <C : Contact> sendMessage(
quote: QuoteReplyToSend?,
messageChain: MessageChain,
target: C
): MessageReceipt<out Contact> {
val send = if (quote == null) {
messageChain
} else {
(quote + messageChain).toChain()
}
return target.sendMessage(send)
}
miraiVerify<SendDTO>("/sendFriendMessage") {
val quote = it.quote?.let { q ->
it.session.messageQueue.cache(q).run {
this[MessageSource].quote(sender)
}}
it.session.bot.getFriend(it.target).apply {
val receipt = sendMessage(quote, it.messageChain.toMessageChain(this), this)
receipt.source.ensureSequenceIdAvailable()
it.session.messageQueue.addQuoteCache(FriendMessage(bot.selfQQ, receipt.source.toChain()))
call.respondDTO(SendRetDTO(messageId = receipt.source.id))
}
}
miraiVerify<SendDTO>("/sendGroupMessage") {
val quote = it.quote?.let { q ->
it.session.messageQueue.cache(q).run {
this[MessageSource].quote(sender)
}}
it.session.bot.getGroup(it.target).apply {
val receipt = sendMessage(quote, it.messageChain.toMessageChain(this), this)
receipt.source.ensureSequenceIdAvailable()
it.session.messageQueue.addQuoteCache(GroupMessage("", botPermission, botAsMember, receipt.source.toChain()))
call.respondDTO(SendRetDTO(messageId = receipt.source.id))
}
}
miraiVerify<SendImageDTO>("sendImageMessage") {
val bot = it.session.bot
val contact = when {
it.target != null -> bot[it.target]
it.qq != null -> bot.getFriend(it.qq)
it.group != null -> bot.getGroup(it.group)
else -> throw IllegalParamException("target、qq、group不可全为null")
}
val ls = it.urls.map { url -> contact.uploadImage(URL(url)) }
contact.sendMessage(MessageChain(ls))
call.respondJson(ls.map { image -> image.imageId }.toJson())
}
// TODO: 重构
post("uploadImage") {
val parts = call.receiveMultipart().readAllParts()
val sessionKey = parts.value("sessionKey")
if (!SessionManager.containSession(sessionKey)) throw IllegalSessionException
val session = try {
SessionManager[sessionKey] as AuthedSession
} catch (e: TypeCastException) {
throw NotVerifiedSessionException
}
val type = parts.value("type")
parts.file("img")?.apply {
val image = streamProvider().use {
when (type) {
"group" -> session.bot.groups.firstOrNull()?.uploadImage(it)
"friend" -> session.bot.qqs.firstOrNull()?.uploadImage(it)
else -> null
}
}
image?.apply {
call.respondText(imageId)
} ?: throw IllegalAccessException("图片上传错误")
} ?: throw IllegalAccessException("未知错误")
}
miraiVerify<RecallDTO>("recall") {
it.session.messageQueue.cache(it.target).apply {
it.session.bot.recall(get(MessageSource))
}
call.respondStateCode(StateCode.Success)
}
}
}
@Serializable
private data class SendDTO(
override val sessionKey: String,
val quote: Long? = null,
val target: Long,
val messageChain: MessageChainDTO
) : VerifyDTO()
@Serializable
private data class SendImageDTO(
override val sessionKey: String,
val target: Long? = null,
val qq: Long? = null,
val group: Long? = null,
val urls: List<String>
) : VerifyDTO()
@Serializable
private class SendRetDTO(
val code: Int = 0,
val msg: String = "success",
val messageId: Long
) : DTO
@Serializable
private data class RecallDTO(
override val sessionKey: String,
val target: Long
) : VerifyDTO()

View File

@ -1,89 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.util
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import net.mamoe.mirai.api.http.data.common.*
// 解析失败时直接返回null由路由判断响应400状态
@UseExperimental(ImplicitReflectionSerializer::class)
inline fun <reified T : Any> String.jsonParseOrNull(
serializer: DeserializationStrategy<T>? = null
): T? = try {
if(serializer == null) MiraiJson.json.parse(this) else Json.parse(this)
} catch (e: Exception) { null }
@UseExperimental(ImplicitReflectionSerializer::class, UnstableDefault::class)
inline fun <reified T : Any> T.toJson(
serializer: SerializationStrategy<T>? = null
): String = if (serializer == null) MiraiJson.json.stringify(this)
else MiraiJson.json.stringify(serializer, this)
// 序列化列表时stringify需要使用的泛型是T而非List<T>
// 因为使用的stringify的stringify(objs: List<T>)重载
@UseExperimental(ImplicitReflectionSerializer::class, UnstableDefault::class)
inline fun <reified T : Any> List<T>.toJson(
serializer: SerializationStrategy<List<T>>? = null
): String = if (serializer == null) MiraiJson.json.stringify(this)
else MiraiJson.json.stringify(serializer, this)
/**
* Json解析规则需要注册支持的多态的类
*/
object MiraiJson {
val json = Json(context = SerializersModule {
polymorphic(EventDTO.serializer()) {
GroupMessagePacketDTO::class with GroupMessagePacketDTO.serializer()
FriendMessagePacketDTO::class with FriendMessagePacketDTO.serializer()
BotOnlineEventDTO::class with BotOnlineEventDTO.serializer()
BotOfflineEventActiveDTO::class with BotOfflineEventActiveDTO.serializer()
BotOfflineEventForceDTO::class with BotOfflineEventForceDTO.serializer()
BotOfflineEventDroppedDTO::class with BotOfflineEventDroppedDTO.serializer()
BotReloginEventDTO::class with BotReloginEventDTO.serializer()
BotGroupPermissionChangeEventDTO::class with BotGroupPermissionChangeEventDTO.serializer()
BotMuteEventDTO::class with BotMuteEventDTO.serializer()
BotUnmuteEventDTO::class with BotUnmuteEventDTO.serializer()
BotJoinGroupEventDTO::class with BotJoinGroupEventDTO.serializer()
GroupNameChangeEventDTO::class with GroupNameChangeEventDTO.serializer()
GroupEntranceAnnouncementChangeEventDTO::class with GroupEntranceAnnouncementChangeEventDTO.serializer()
GroupMuteAllEventDTO::class with GroupMuteAllEventDTO.serializer()
GroupAllowAnonymousChatEventDTO::class with GroupAllowAnonymousChatEventDTO.serializer()
GroupAllowConfessTalkEventDTO::class with GroupAllowConfessTalkEventDTO.serializer()
GroupAllowMemberInviteEventDTO::class with GroupAllowMemberInviteEventDTO.serializer()
MemberJoinEventDTO::class with MemberJoinEventDTO.serializer()
MemberLeaveEventKickDTO::class with MemberLeaveEventKickDTO.serializer()
MemberLeaveEventQuitDTO::class with MemberLeaveEventQuitDTO.serializer()
MemberCardChangeEventDTO::class with MemberCardChangeEventDTO.serializer()
MemberSpecialTitleChangeEventDTO::class with MemberSpecialTitleChangeEventDTO.serializer()
MemberPermissionChangeEventDTO::class with MemberPermissionChangeEventDTO.serializer()
MemberMuteEventDTO::class with MemberMuteEventDTO.serializer()
MemberUnmuteEventDTO::class with MemberUnmuteEventDTO.serializer()
}
// Message Polymorphic
// polymorphic(MessageDTO.serializer()) {
// MessageSourceDTO::class with MessageSourceDTO.serializer()
// AtDTO::class with AtDTO.serializer()
// AtAllDTO::class with AtAllDTO.serializer()
// FaceDTO::class with FaceDTO.serializer()
// PlainDTO::class with PlainDTO.serializer()
// ImageDTO::class with ImageDTO.serializer()
// XmlDTO::class with XmlDTO.serializer()
// UnknownMessageDTO::class with UnknownMessageDTO.serializer()
// }
})
}

View File

@ -1,50 +0,0 @@
plugins {
kotlin("jvm")
java
id("com.github.johnrengelman.shadow")
}
version = "1.0.0"
val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext
val serializationVersion: String by rootProject.ext
val klockVersion: String by rootProject.ext
val ktorVersion: String by rootProject.ext
kotlin {
sourceSets {
all {
languageSettings.enableLanguageFeature("InlineClasses")
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
}
}
}
fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
dependencies {
api(project(":mirai-core"))
api(project(":mirai-console"))
runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE
api(kotlin("stdlib", kotlinVersion))
api(kotlinx("io-jvm", kotlinXIoVersion))
api(kotlinx("io", kotlinXIoVersion))
api(kotlinx("coroutines-io", coroutinesIoVersion))
api(kotlinx("coroutines-core", coroutinesVersion))
api("org.jsoup:jsoup:1.12.1")
api(group = "com.alibaba", name = "fastjson", version = "1.2.62")
}
tasks.withType<JavaCompile>() {
options.encoding = "UTF-8"
}

View File

@ -1,155 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.imageplugin
import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.registerCommand
import net.mamoe.mirai.console.plugins.Config
import net.mamoe.mirai.console.plugins.ConfigSection
import net.mamoe.mirai.console.plugins.PluginBase
import net.mamoe.mirai.console.plugins.withDefaultWriteSave
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.event.events.BotOnlineEvent
import net.mamoe.mirai.event.events.MemberPermissionChangeEvent
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeGroupMessages
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.sendTo
import net.mamoe.mirai.message.upload
import net.mamoe.mirai.message.uploadAsImage
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import org.jsoup.Jsoup
import java.awt.RenderingHints
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
class ImageSenderMain : PluginBase() {
lateinit var images: Config
lateinit var normal: List<ConfigSection>
lateinit var r18: List<ConfigSection>
val config by lazy {
loadConfig("setting.yml")
}
val Normal_Image_Trigger by config.withDefaultWriteSave { "色图" }
val R18_Image_Trigger by config.withDefaultWriteSave { "不够色" }
val Image_Resize_Max_Width_Height by config.withDefaultWriteSave { 800 }
val groupsAllowNormal by lazy {
config.getLongList("Allow_Normal_Image_Groups").toMutableList()
}
val groupsAllowR18 by lazy {
config.getLongList("Allow_R18_Image_Groups").toMutableList()
}
override fun onDisable() {
config["Allow_R18_Image_Groups"] = groupsAllowR18
config["Allow_Normal_Image_Groups"] = groupsAllowNormal
config.save()
}
override fun onEnable() {
logger.info("Image Sender plugin enabled")
registerCommands()
subscribeAlways<MemberPermissionChangeEvent> {
logger.info("${this.bot.uin} login succeed, it will be controlled by Image Sender Plugin")
this.bot.subscribeGroupMessages {
(contains(Normal_Image_Trigger)) {
sendImage(subject, normal.random())
}
(contains(R18_Image_Trigger)) {
sendImage(subject, r18.random())
}
}
}
}
private fun sendImage(contact: Contact, configSection: ConfigSection) {
launch {
try {
logger.info("正在推送图片")
getImage(
contact, configSection.getString("url"), configSection.getString("pid"), 800
).plus(configSection.getString("tags")).sendTo(contact)
} catch (e: Exception) {
contact.sendMessage(e.message ?: "unknown error")
}
}
}
private suspend fun getImage(contact: Contact, url: String, pid: String, maxWidthOrHeight: Int): Image {
val bodyStream = withTimeoutOrNull(20 * 1000) {
withContext(Dispatchers.IO) {
Jsoup
.connect(url)
.followRedirects(true)
.timeout(180_000)
.ignoreContentType(true)
.userAgent("Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; ja-jp) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27")
.referrer("https://www.pixiv.net/member_illust.php?mode=medium&illust_id=$pid")
.ignoreHttpErrors(true)
.maxBodySize(100000000)
.execute().also { check(it.statusCode() == 200) { "Failed to download image" } }
}
}?.bodyStream() ?: error("Failed to download image")
if (maxWidthOrHeight < 1) {
return bodyStream.uploadAsImage(contact)
}
val image = withContext(Dispatchers.IO) {
ImageIO.read(bodyStream)
}
if (image.width.coerceAtLeast(image.height) <= maxWidthOrHeight) {
return image.upload(contact)
}
val rate = (maxWidthOrHeight.toFloat() / image.width.coerceAtLeast(image.height))
val newWidth = (image.width * rate).toInt()
val newHeight = (image.height * rate).toInt()
return withContext(Dispatchers.IO) {
val dimg = BufferedImage(newWidth, newHeight, image.type)
val g = dimg.createGraphics()
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
g.drawImage(image, 0, 0, newWidth, newHeight, 0, 0, image.width, image.height, null)
g.dispose()
dimg
}.upload(contact)
}
override fun onLoad() {
logger.info("loading local image data")
try {
images = getResourcesConfig("data.yml")
} catch (e: Exception) {
e.printStackTrace()
logger.info("无法加载本地图片")
}
logger.info("本地图片版本" + images.getString("version"))
r18 = images.getConfigSectionList("R18")
normal = images.getConfigSectionList("normal")
logger.info("Normal * " + normal.size)
logger.info("R18 * " + r18.size)
}
fun registerCommands() {
registerCommand {
}
}
}

View File

@ -1,144 +0,0 @@
import com.alibaba.fastjson.JSONObject
import com.google.gson.JsonObject
import net.mamoe.mirai.console.plugins.*
import net.mamoe.mirai.utils.cryptor.contentToString
import org.jsoup.Connection
import org.jsoup.Jsoup
import java.io.File
import kotlin.concurrent.thread
object Data {
val section = (File(System.getProperty("user.dir") + "/setu.yml")).loadAsConfig()
val abstract = section.getStringList("abstract").toMutableList()
val R18 = section.getConfigSectionList("R18").toMutableList()
val normal = section.getConfigSectionList("normal").toMutableList()
fun init() {
section.setIfAbsent("abstract", mutableListOf<String>())
section.setIfAbsent("R18", mutableListOf<ConfigSection>())
section.setIfAbsent("Normal", mutableListOf<ConfigSection>())
}
fun save() {
section["abstract"] = abstract
section["R18"] = R18
section["normal"] = normal
section.save()
}
}
fun main() {
val abstract_file = (File(System.getProperty("user.dir") + "/abstractSetu.yml")).loadAsConfig()
abstract_file.setIfAbsent("R18", mutableListOf<ConfigSection>())
abstract_file.setIfAbsent("normal", mutableListOf<ConfigSection>())
val r18 = abstract_file.getConfigSectionList("R18").toMutableList()
val normal = abstract_file.getConfigSectionList("normal").toMutableList()
Data.R18.forEach {
val forbid = with(it.getString("tags")) {
this.contains("初音ミク") || this.contains("VOCALOID") || this.contains("Miku")
||
this.contains("东方") || this.contains("東方")
}
if (forbid) {
println("过滤掉了一张图")
} else {
r18.add(
ConfigSectionImpl().apply {
this["pid"] = it["pid"]!!
this["author"] = it["author"]!!
this["uid"] = it["uid"]!!
this["tags"] = it["tags"]!!
this["url"] = it["url"]!!
}
)
}
}
Data.normal.forEach {
val forbid = with(it.getString("tags")) {
this.contains("初音ミク") || this.contains("VOCALOID") || this.contains("Miku")
||
this.contains("东方") || this.contains("東方")
}
if (forbid) {
println("过滤掉了一张图")
} else {
normal.add(
ConfigSectionImpl().apply {
this["pid"] = it["pid"]!!
this["author"] = it["author"]!!
this["uid"] = it["uid"]!!
this["tags"] = it["tags"]!!
this["url"] = it["url"]!!
}
)
}
}
abstract_file.set("R18", r18)
abstract_file.set("normal", normal)
abstract_file.save()
/**
Data.init()
Runtime.getRuntime().addShutdownHook(thread(start = false) {
Data.save()
})
while (true){
try {
val val0 = JSONObject.parseObject(Jsoup
.connect("https://api.lolicon.app/setu/")
.ignoreContentType(true)
.method(Connection.Method.GET)
.data("r18","1")
.data("num","10")
.execute().body())
val val1 = val0.getJSONArray("data")
for(index in 0 until val1.size - 1){
val content = val1.getJSONObject(index)
val pid = content.getString("pid")
if(Data.abstract.contains(pid)){
println("获取到了一张重复图$pid")
continue
}
val configSection = ConfigSectionImpl()
val isR18 = content.getBoolean("r18")
configSection["author"] = content.getString("author")
configSection["pid"] = pid
configSection["uid"] = content.getInteger("uid")
configSection["width"] = content.getInteger("width")
configSection["height"] = content.getInteger("height")
configSection["tags"] = content.getJSONArray("tags").map {
it.toString()
}.joinToString(",")
configSection["url"] = content.getString("url")
if(isR18){
Data.R18.add(configSection)
print("获取到了一张R18")
}else{
Data.normal.add(configSection)
print("获取到了一张Normal")
}
Data.abstract.add(pid)
println(configSection.contentToString())
}
}catch (e:Exception){
println(e.message)
}
Data.save()
println("SAVED")
Thread.sleep(1000)
}
*/
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
name: ImageSender
main: net.mamoe.mirai.imageplugin.ImageSenderMain
version: 1.0.0
author: 不想写代码
info: a demo[hso] plugin of mirai