@mrlingxd/koishi-plugin-adapter-onebot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020-present Shigma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,36 @@
1
+ import { Bot, Context, Schema, Universal } from "koishi";
2
+ import * as OneBot from "../utils";
3
+ import { OneBotMessageEncoder } from "./message";
4
+ export declare class BaseBot<C extends Context = Context, T extends BaseBot.Config = BaseBot.Config> extends Bot<C, T> {
5
+ static MessageEncoder: typeof OneBotMessageEncoder;
6
+ static inject: string[];
7
+ parent?: BaseBot;
8
+ internal: OneBot.Internal;
9
+ createDirectChannel(userId: string): Promise<{
10
+ id: string;
11
+ type: Universal.Channel.Type;
12
+ }>;
13
+ getMessage(channelId: string, messageId: string): Promise<Universal.Message>;
14
+ deleteMessage(channelId: string, messageId: string): Promise<void>;
15
+ getLogin(): Promise<Universal.Login>;
16
+ getUser(userId: string): Promise<Universal.User>;
17
+ getFriendList(): Promise<{
18
+ data: Universal.User[];
19
+ }>;
20
+ handleFriendRequest(messageId: string, approve: boolean, comment?: string): Promise<void>;
21
+ handleGuildRequest(messageId: string, approve: boolean, comment?: string): Promise<void>;
22
+ handleGuildMemberRequest(messageId: string, approve: boolean, comment?: string): Promise<void>;
23
+ deleteFriend(userId: string): Promise<void>;
24
+ getMessageList(channelId: string, before?: string, direction?: Universal.Direction): Promise<{
25
+ data: Universal.Message[];
26
+ }>;
27
+ }
28
+ export declare namespace BaseBot {
29
+ interface Config {
30
+ advanced?: AdvancedConfig;
31
+ }
32
+ interface AdvancedConfig {
33
+ splitMixedContent?: boolean;
34
+ }
35
+ const AdvancedConfig: Schema<AdvancedConfig>;
36
+ }
@@ -0,0 +1,66 @@
1
+ import { Bot, Schema } from "koishi";
2
+ import * as OneBot from "../utils";
3
+ import { OneBotMessageEncoder, PRIVATE_PFX } from "./message";
4
+ export class BaseBot extends Bot {
5
+ static MessageEncoder = OneBotMessageEncoder;
6
+ static inject = ["http"];
7
+ parent;
8
+ internal;
9
+ async createDirectChannel(userId) {
10
+ return { id: `${PRIVATE_PFX}${userId}`, type: 1 /* Universal.Channel.Type.DIRECT */ };
11
+ }
12
+ async getMessage(channelId, messageId) {
13
+ const data = await this.internal.getMsg(messageId);
14
+ return await OneBot.adaptMessage(this, data);
15
+ }
16
+ async deleteMessage(channelId, messageId) {
17
+ await this.internal.deleteMsg(messageId);
18
+ }
19
+ async getLogin() {
20
+ const data = await this.internal.getLoginInfo();
21
+ this.user = OneBot.decodeUser(data);
22
+ return this.toJSON();
23
+ }
24
+ async getUser(userId) {
25
+ const data = await this.internal.getStrangerInfo(userId);
26
+ return OneBot.decodeUser(data);
27
+ }
28
+ async getFriendList() {
29
+ const data = await this.internal.getFriendList();
30
+ return { data: data.map(OneBot.decodeUser) };
31
+ }
32
+ async handleFriendRequest(messageId, approve, comment) {
33
+ await this.internal.setFriendAddRequest(messageId, approve, comment);
34
+ }
35
+ async handleGuildRequest(messageId, approve, comment) {
36
+ await this.internal.setGroupAddRequest(messageId, "invite", approve, comment);
37
+ }
38
+ async handleGuildMemberRequest(messageId, approve, comment) {
39
+ await this.internal.setGroupAddRequest(messageId, "add", approve, comment);
40
+ }
41
+ async deleteFriend(userId) {
42
+ await this.internal.deleteFriend(userId);
43
+ }
44
+ async getMessageList(channelId, before, direction = "before") {
45
+ if (direction !== "before")
46
+ throw new Error("Unsupported direction.");
47
+ // include `before` message
48
+ let list;
49
+ if (before) {
50
+ const msg = await this.internal.getMsg(before);
51
+ if (msg?.message_seq) {
52
+ list = (await this.internal.getGroupMsgHistory(Number(channelId), msg.message_seq)).messages;
53
+ }
54
+ }
55
+ else {
56
+ list = (await this.internal.getGroupMsgHistory(Number(channelId))).messages;
57
+ }
58
+ // 从旧到新
59
+ return { data: await Promise.all(list.map((item) => OneBot.adaptMessage(this, item))) };
60
+ }
61
+ }
62
+ (function (BaseBot) {
63
+ BaseBot.AdvancedConfig = Schema.object({
64
+ splitMixedContent: Schema.boolean().description("是否自动在混合内容间插入空格。").default(true)
65
+ }).description("高级设置");
66
+ })(BaseBot || (BaseBot = {}));
@@ -0,0 +1,13 @@
1
+ import { Dict, h } from "koishi";
2
+ export declare function CQCode(type: string, attrs: Dict<string>): string;
3
+ export interface CQCode {
4
+ type: string;
5
+ data: Dict<string>;
6
+ capture?: RegExpExecArray;
7
+ }
8
+ export declare namespace CQCode {
9
+ function escape(source: any, inline?: boolean): string;
10
+ function unescape(source: string): string;
11
+ function from(source: string): CQCode;
12
+ function parse(source: string | CQCode[]): h[];
13
+ }
@@ -0,0 +1,60 @@
1
+ import { h } from "koishi";
2
+ export function CQCode(type, attrs) {
3
+ if (type === "text")
4
+ return attrs.content;
5
+ let output = "[CQ:" + type;
6
+ for (const key in attrs) {
7
+ if (attrs[key])
8
+ output += `,${key}=${h.escape(attrs[key], true)}`;
9
+ }
10
+ return output + "]";
11
+ }
12
+ (function (CQCode) {
13
+ const ESCAPE_MAP = { "&": "&amp;", "[": "&#91;", "]": "&#93;" };
14
+ const UNESCAPE_MAP = Object.fromEntries(Object.entries(ESCAPE_MAP).map(([k, v]) => [v, k]));
15
+ function escape(source, inline = false) {
16
+ let result = String(source).replace(/[&[\]]/g, (m) => ESCAPE_MAP[m]);
17
+ if (inline) {
18
+ result = result.replace(/,/g, "&#44;");
19
+ result = result.replace(/(\ud83c[\udf00-\udfff])|(\ud83d[\udc00-\ude4f\ude80-\udeff])|[\u2600-\u2B55]/g, " ");
20
+ }
21
+ return result;
22
+ }
23
+ CQCode.escape = escape;
24
+ function unescape(source) {
25
+ return String(source).replace(/&#91;|&#93;|&#44;|&amp;/g, (m) => UNESCAPE_MAP[m] ?? m);
26
+ }
27
+ CQCode.unescape = unescape;
28
+ const pattern = /\[CQ:(\w+)((,\w+=[^,\]]*)*)\]/;
29
+ function from(source) {
30
+ const capture = pattern.exec(source);
31
+ if (!capture)
32
+ return null;
33
+ const [, type, attrs] = capture;
34
+ const data = Object.fromEntries((attrs?.slice(1).split(",") || []).map((str) => {
35
+ const index = str.indexOf("=");
36
+ return [str.slice(0, index), unescape(str.slice(index + 1))];
37
+ }));
38
+ return { type, data, capture };
39
+ }
40
+ CQCode.from = from;
41
+ function parse(source) {
42
+ if (typeof source !== "string") {
43
+ return source.map(({ type, data }) => h(type === "text" ? "text" : type, type === "text" ? { content: data.text } : data));
44
+ }
45
+ const elements = [];
46
+ let result;
47
+ while ((result = from(source))) {
48
+ const { type, data, capture } = result;
49
+ if (capture.index) {
50
+ elements.push(h("text", { content: unescape(source.slice(0, capture.index)) }));
51
+ }
52
+ elements.push(h(type, data));
53
+ source = source.slice(capture.index + capture[0].length);
54
+ }
55
+ if (source)
56
+ elements.push(h("text", { content: unescape(source) }));
57
+ return elements;
58
+ }
59
+ CQCode.parse = parse;
60
+ })(CQCode || (CQCode = {}));
@@ -0,0 +1,42 @@
1
+ import { Context, Schema, Session } from "koishi";
2
+ import { HttpServer } from "../http";
3
+ import { WsClient, WsServer } from "../ws";
4
+ import { BaseBot } from "./base";
5
+ import { QQGuildBot } from "./qqguild";
6
+ export * from "./base";
7
+ export * from "./cqcode";
8
+ export * from "./message";
9
+ export * from "./qqguild";
10
+ export declare class OneBotBot<C extends Context, T extends OneBotBot.Config = OneBotBot.Config> extends BaseBot<C, T> {
11
+ guildBot: QQGuildBot<C>;
12
+ constructor(ctx: C, config: T);
13
+ stop(): Promise<void>;
14
+ initialize(): Promise<void>;
15
+ setupGuildService(): Promise<void>;
16
+ getChannel(channelId: string): Promise<import("@satorijs/protocol").Channel>;
17
+ getGuild(guildId: string): Promise<import("@satorijs/protocol").Guild>;
18
+ getGuildList(): Promise<{
19
+ data: import("@satorijs/protocol").Guild[];
20
+ }>;
21
+ getChannelList(guildId: string): Promise<{
22
+ data: import("@satorijs/protocol").Channel[];
23
+ }>;
24
+ getGuildMember(guildId: string, userId: string): Promise<import("@satorijs/protocol").GuildMember>;
25
+ getGuildMemberList(guildId: string): Promise<{
26
+ data: import("@satorijs/protocol").GuildMember[];
27
+ }>;
28
+ kickGuildMember(guildId: string, userId: string, permanent?: boolean): Promise<void>;
29
+ muteGuildMember(guildId: string, userId: string, duration: number): Promise<void>;
30
+ muteChannel(channelId: string, guildId?: string, enable?: boolean): Promise<void>;
31
+ checkPermission(name: string, session: Partial<Session>): Promise<boolean>;
32
+ }
33
+ export declare namespace OneBotBot {
34
+ interface BaseConfig extends BaseBot.Config {
35
+ selfId: string;
36
+ password?: string;
37
+ token?: string;
38
+ }
39
+ const BaseConfig: Schema<BaseConfig>;
40
+ type Config = BaseConfig & (HttpServer.Options | WsServer.Options | WsClient.Options);
41
+ const Config: Schema<Config>;
42
+ }
@@ -0,0 +1,108 @@
1
+ import { noop, Schema } from "koishi";
2
+ import { HttpServer } from "../http";
3
+ import * as OneBot from "../utils";
4
+ import { WsClient, WsServer } from "../ws";
5
+ import { BaseBot } from "./base";
6
+ import { QQGuildBot } from "./qqguild";
7
+ export * from "./base";
8
+ export * from "./cqcode";
9
+ export * from "./message";
10
+ export * from "./qqguild";
11
+ export class OneBotBot extends BaseBot {
12
+ guildBot;
13
+ constructor(ctx, config) {
14
+ super(ctx, config, "onebot");
15
+ this.selfId = config.selfId;
16
+ this.internal = new OneBot.Internal(this);
17
+ this.user.avatar = `http://q.qlogo.cn/headimg_dl?dst_uin=${config.selfId}&spec=640`;
18
+ if (config.protocol === "http") {
19
+ ctx.plugin(HttpServer, this);
20
+ }
21
+ else if (config.protocol === "ws") {
22
+ ctx.plugin(WsClient, this);
23
+ }
24
+ else if (config.protocol === "ws-reverse") {
25
+ ctx.plugin(WsServer, this);
26
+ }
27
+ }
28
+ async stop() {
29
+ if (this.guildBot) {
30
+ // QQGuild stub bot should also be removed
31
+ delete this.ctx.bots[this.guildBot.sid];
32
+ }
33
+ await super.stop();
34
+ }
35
+ async initialize() {
36
+ await Promise.all([this.getLogin(), this.setupGuildService().catch(noop)]).then(() => this.online(), (error) => this.offline(error));
37
+ }
38
+ async setupGuildService() {
39
+ const profile = await this.internal.getGuildServiceProfile();
40
+ // guild service is not supported in this account
41
+ if (!profile?.tiny_id || profile.tiny_id === "0")
42
+ return;
43
+ this.ctx.plugin(QQGuildBot, {
44
+ profile,
45
+ parent: this,
46
+ advanced: this.config.advanced
47
+ });
48
+ }
49
+ async getChannel(channelId) {
50
+ const data = await this.internal.getGroupInfo(channelId);
51
+ return OneBot.adaptChannel(data);
52
+ }
53
+ async getGuild(guildId) {
54
+ const data = await this.internal.getGroupInfo(guildId);
55
+ return OneBot.adaptGuild(data);
56
+ }
57
+ async getGuildList() {
58
+ const data = await this.internal.getGroupList();
59
+ return { data: data.map(OneBot.adaptGuild) };
60
+ }
61
+ async getChannelList(guildId) {
62
+ return { data: [await this.getChannel(guildId)] };
63
+ }
64
+ async getGuildMember(guildId, userId) {
65
+ const data = await this.internal.getGroupMemberInfo(guildId, userId);
66
+ return OneBot.decodeGuildMember(data);
67
+ }
68
+ async getGuildMemberList(guildId) {
69
+ const data = await this.internal.getGroupMemberList(guildId);
70
+ return { data: data.map(OneBot.decodeGuildMember) };
71
+ }
72
+ async kickGuildMember(guildId, userId, permanent) {
73
+ return this.internal.setGroupKick(guildId, userId, permanent);
74
+ }
75
+ async muteGuildMember(guildId, userId, duration) {
76
+ return this.internal.setGroupBan(guildId, userId, Math.round(duration / 1000));
77
+ }
78
+ async muteChannel(channelId, guildId, enable) {
79
+ return this.internal.setGroupWholeBan(channelId, enable);
80
+ }
81
+ async checkPermission(name, session) {
82
+ if (name === "onebot.group.admin") {
83
+ return session.author?.roles?.[0] === "admin";
84
+ }
85
+ else if (name === "onebot.group.owner") {
86
+ return session.author?.roles?.[0] === "owner";
87
+ }
88
+ return super.checkPermission(name, session);
89
+ }
90
+ }
91
+ (function (OneBotBot) {
92
+ OneBotBot.BaseConfig = Schema.object({
93
+ selfId: Schema.string().description("机器人的账号。").required(),
94
+ token: Schema.string()
95
+ .role("secret")
96
+ .description("发送信息时用于验证的字段,应与 OneBot 配置文件中的 `access_token` 保持一致。"),
97
+ protocol: process.env.KOISHI_ENV === "browser"
98
+ ? Schema.const("ws").default("ws")
99
+ : Schema.union(["http", "ws", "ws-reverse"]).description("选择要使用的协议。").default("ws-reverse")
100
+ });
101
+ OneBotBot.Config = Schema.intersect([
102
+ OneBotBot.BaseConfig,
103
+ Schema.union([HttpServer.Options, WsClient.Options, WsServer.Options]),
104
+ Schema.object({
105
+ advanced: BaseBot.AdvancedConfig
106
+ })
107
+ ]);
108
+ })(OneBotBot || (OneBotBot = {}));
@@ -0,0 +1,25 @@
1
+ import { Context, h, MessageEncoder, Universal } from "koishi";
2
+ import { BaseBot } from "./base";
3
+ import { CQCode } from "./cqcode";
4
+ export interface Author extends Universal.User {
5
+ time?: string | number;
6
+ messageId?: string;
7
+ }
8
+ declare class State {
9
+ type: "message" | "forward" | "reply";
10
+ author: Partial<Author>;
11
+ children: CQCode[];
12
+ constructor(type: "message" | "forward" | "reply");
13
+ }
14
+ export declare const PRIVATE_PFX = "private:";
15
+ export declare class OneBotMessageEncoder<C extends Context = Context> extends MessageEncoder<C, BaseBot<C>> {
16
+ stack: State[];
17
+ children: CQCode[];
18
+ prepare(): Promise<void>;
19
+ forward(): Promise<void>;
20
+ flush(): Promise<void>;
21
+ private sendFile;
22
+ private text;
23
+ visit(element: h): Promise<void>;
24
+ }
25
+ export {};
@@ -0,0 +1,281 @@
1
+ import { MessageEncoder, pick } from "koishi";
2
+ import { fileURLToPath } from "node:url";
3
+ class State {
4
+ type;
5
+ author = {};
6
+ children = [];
7
+ constructor(type) {
8
+ this.type = type;
9
+ }
10
+ }
11
+ export const PRIVATE_PFX = "private:";
12
+ export class OneBotMessageEncoder extends MessageEncoder {
13
+ stack = [new State("message")];
14
+ children = [];
15
+ async prepare() {
16
+ super.prepare();
17
+ const { event: { channel } } = this.session;
18
+ if (!channel.type) {
19
+ channel.type = channel.id.startsWith(PRIVATE_PFX)
20
+ ? 1 /* Universal.Channel.Type.DIRECT */
21
+ : 0 /* Universal.Channel.Type.TEXT */;
22
+ }
23
+ if (!this.session.isDirect) {
24
+ this.session.guildId ??= this.channelId;
25
+ }
26
+ }
27
+ async forward() {
28
+ if (!this.stack[0].children.length)
29
+ return;
30
+ const session = this.bot.session();
31
+ session.content = "";
32
+ session.messageId =
33
+ this.session.event.channel.type === 1 /* Universal.Channel.Type.DIRECT */
34
+ ? "" +
35
+ (await this.bot.internal.sendPrivateForwardMsg(this.channelId.slice(PRIVATE_PFX.length), this.stack[0].children))
36
+ : "" + (await this.bot.internal.sendGroupForwardMsg(this.channelId, this.stack[0].children));
37
+ session.userId = this.bot.selfId;
38
+ session.channelId = this.session.channelId;
39
+ session.guildId = this.session.guildId;
40
+ session.isDirect = this.session.isDirect;
41
+ session.app.emit(session, "send", session);
42
+ this.results.push(session.event.message);
43
+ }
44
+ async flush() {
45
+ // trim start
46
+ while (true) {
47
+ const first = this.children[0];
48
+ if (first?.type !== "text")
49
+ break;
50
+ first.data.text = first.data.text.trimStart();
51
+ if (first.data.text)
52
+ break;
53
+ this.children.shift();
54
+ }
55
+ // trim end
56
+ while (true) {
57
+ const last = this.children[this.children.length - 1];
58
+ if (last?.type !== "text")
59
+ break;
60
+ last.data.text = last.data.text.trimEnd();
61
+ if (last.data.text)
62
+ break;
63
+ this.children.pop();
64
+ }
65
+ // flush
66
+ const { type, author } = this.stack[0];
67
+ if (!this.children.length && !author.messageId)
68
+ return;
69
+ if (type === "forward") {
70
+ if (author.messageId) {
71
+ this.stack[1].children.push({
72
+ type: "node",
73
+ data: {
74
+ id: author.messageId
75
+ }
76
+ });
77
+ }
78
+ else {
79
+ this.stack[1].children.push({
80
+ type: "node",
81
+ data: {
82
+ name: author.name || this.bot.user.name,
83
+ uin: author.id || this.bot.userId,
84
+ content: String(this.children),
85
+ time: `${Math.floor((+author.time || Date.now()) / 1000)}`
86
+ }
87
+ });
88
+ }
89
+ this.children = [];
90
+ return;
91
+ }
92
+ const session = this.bot.session();
93
+ session.content = "";
94
+ session.messageId = this.bot.parent
95
+ ? "" + (await this.bot.internal.sendGuildChannelMsg(this.session.guildId, this.channelId, this.children))
96
+ : this.session.event.channel.type === 1 /* Universal.Channel.Type.DIRECT */
97
+ ? "" + (await this.bot.internal.sendPrivateMsg(this.channelId.slice(PRIVATE_PFX.length), this.children))
98
+ : "" + (await this.bot.internal.sendGroupMsg(this.channelId, this.children));
99
+ session.userId = this.bot.selfId;
100
+ session.channelId = this.session.channelId;
101
+ session.guildId = this.session.guildId;
102
+ session.isDirect = this.session.isDirect;
103
+ session.app.emit(session, "send", session);
104
+ this.results.push(session.event.message);
105
+ this.children = [];
106
+ }
107
+ async sendFile(attrs) {
108
+ const src = attrs.src || attrs.url;
109
+ const name = attrs.title || (await this.bot.ctx.http.file(src)).filename;
110
+ // 本地文件路径
111
+ const file = src.startsWith("file:") ? fileURLToPath(src) : await this.bot.internal.downloadFile(src);
112
+ if (this.session.event.channel.type === 1 /* Universal.Channel.Type.DIRECT */) {
113
+ await this.bot.internal.uploadPrivateFile(this.channelId.slice(PRIVATE_PFX.length), file, name);
114
+ }
115
+ else {
116
+ await this.bot.internal.uploadGroupFile(this.channelId, file, name);
117
+ }
118
+ const session = this.bot.session();
119
+ // 相关 API 没有返回 message_id
120
+ session.messageId = "";
121
+ session.content = "";
122
+ session.userId = this.bot.selfId;
123
+ session.channelId = this.session.channelId;
124
+ session.guildId = this.session.guildId;
125
+ session.isDirect = this.session.isDirect;
126
+ session.app.emit(session, "send", session);
127
+ this.results.push(session.event.message);
128
+ }
129
+ text(text) {
130
+ this.children.push({ type: "text", data: { text } });
131
+ }
132
+ async visit(element) {
133
+ let { type, attrs, children } = element;
134
+ if (type === "text") {
135
+ this.text(attrs.content);
136
+ }
137
+ else if (type === "br") {
138
+ this.text("\n");
139
+ }
140
+ else if (type === "p") {
141
+ const prev = this.children[this.children.length - 1];
142
+ if (prev?.type === "text") {
143
+ if (!prev.data.text.endsWith("\n")) {
144
+ prev.data.text += "\n";
145
+ }
146
+ }
147
+ else {
148
+ this.text("\n");
149
+ }
150
+ await this.render(children);
151
+ this.text("\n");
152
+ }
153
+ else if (type === "at") {
154
+ if (attrs.type === "all") {
155
+ this.children.push({ type: "at", data: { qq: "all" } });
156
+ }
157
+ else {
158
+ this.children.push({ type: "at", data: { qq: attrs.id, name: attrs.name } });
159
+ }
160
+ }
161
+ else if (type === "sharp") {
162
+ if (attrs.id)
163
+ this.text(attrs.id);
164
+ }
165
+ else if (type === "face") {
166
+ if (attrs.platform && attrs.platform !== this.bot.platform) {
167
+ await this.render(children);
168
+ }
169
+ else {
170
+ this.children.push({ type: "face", data: { id: attrs.id } });
171
+ }
172
+ }
173
+ else if (type === "a") {
174
+ await this.render(children);
175
+ // https://github.com/koishijs/koishi-plugin-adapter-onebot/issues/23
176
+ if (attrs.href)
177
+ this.text(`(${attrs.href})`);
178
+ }
179
+ else if (["video", "audio", "image", "img"].includes(type)) {
180
+ if (type === "video" || type === "audio")
181
+ await this.flush();
182
+ if (type === "audio")
183
+ type = "record";
184
+ if (type === "img")
185
+ type = "image";
186
+ attrs = { ...attrs };
187
+ attrs.file = attrs.src || attrs.url;
188
+ delete attrs.src;
189
+ delete attrs.url;
190
+ if (attrs.cache) {
191
+ attrs.cache = 1;
192
+ }
193
+ else {
194
+ attrs.cache = 0;
195
+ }
196
+ // https://github.com/koishijs/koishi-plugin-adapter-onebot/issues/30
197
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
198
+ const cap = /^data:([\w/.+-]+);base64,/.exec(attrs.file);
199
+ if (cap)
200
+ attrs.file = "base64://" + attrs.file.slice(cap[0].length);
201
+ this.children.push({ type, data: attrs });
202
+ }
203
+ else if (type === "file") {
204
+ await this.flush();
205
+ await this.sendFile(attrs);
206
+ }
207
+ else if (type === "onebot:music") {
208
+ await this.flush();
209
+ this.children.push({ type: "music", data: attrs });
210
+ }
211
+ else if (type === "onebot:tts") {
212
+ await this.flush();
213
+ this.children.push({ type: "tts", data: attrs });
214
+ }
215
+ else if (type === "onebot:poke") {
216
+ await this.flush();
217
+ this.children.push({ type: "poke", data: attrs });
218
+ }
219
+ else if (type === "onebot:gift") {
220
+ await this.flush();
221
+ this.children.push({ type: "gift", data: attrs });
222
+ }
223
+ else if (type === "onebot:share") {
224
+ await this.flush();
225
+ this.children.push({ type: "share", data: attrs });
226
+ }
227
+ else if (type === "onebot:json") {
228
+ await this.flush();
229
+ this.children.push({ type: "json", data: attrs });
230
+ }
231
+ else if (type === "onebot:xml") {
232
+ await this.flush();
233
+ this.children.push({ type: "xml", data: attrs });
234
+ }
235
+ else if (type === "onebot:cardimage") {
236
+ await this.flush();
237
+ this.children.push({ type: "cardimage", data: attrs });
238
+ }
239
+ else if (type === "author") {
240
+ Object.assign(this.stack[0].author, attrs);
241
+ }
242
+ else if (type === "figure" && !this.bot.parent) {
243
+ await this.flush();
244
+ this.stack.unshift(new State("forward"));
245
+ await this.render(children);
246
+ await this.flush();
247
+ this.stack.shift();
248
+ await this.forward();
249
+ }
250
+ else if (type === "figure") {
251
+ await this.render(children);
252
+ await this.flush();
253
+ }
254
+ else if (type === "quote") {
255
+ await this.flush();
256
+ this.children.push({ type: "reply", data: attrs });
257
+ }
258
+ else if (type === "message") {
259
+ await this.flush();
260
+ // qqguild does not support forward messages
261
+ if ("forward" in attrs && !this.bot.parent) {
262
+ this.stack.unshift(new State("forward"));
263
+ await this.render(children);
264
+ await this.flush();
265
+ this.stack.shift();
266
+ await this.forward();
267
+ }
268
+ else if ("id" in attrs) {
269
+ this.stack[0].author.messageId = attrs.id.toString();
270
+ }
271
+ else {
272
+ Object.assign(this.stack[0].author, pick(attrs, ["userId", "username", "nickname", "time"]));
273
+ await this.render(children);
274
+ await this.flush();
275
+ }
276
+ }
277
+ else {
278
+ await this.render(children);
279
+ }
280
+ }
281
+ }