@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/lib/utils.js ADDED
@@ -0,0 +1,297 @@
1
+ import { h, hyphenate, omit } from "koishi";
2
+ import * as qface from "qface";
3
+ import { CQCode } from "./bot";
4
+ export * from "./types";
5
+ export const decodeUser = (user) => ({
6
+ id: user.tiny_id || user.user_id.toString(),
7
+ name: user.nickname,
8
+ userId: user.tiny_id || user.user_id.toString(),
9
+ avatar: user.user_id ? `http://q.qlogo.cn/headimg_dl?dst_uin=${user.user_id}&spec=640` : undefined,
10
+ username: user.nickname
11
+ });
12
+ export const decodeGuildMember = (user) => ({
13
+ user: decodeUser(user),
14
+ nick: user.card,
15
+ roles: [user.role]
16
+ });
17
+ export const adaptQQGuildMemberInfo = (user) => ({
18
+ user: {
19
+ id: user.tiny_id,
20
+ name: user.nickname,
21
+ isBot: user.role_name === "机器人"
22
+ },
23
+ name: user.nickname,
24
+ roles: user.role_name ? [user.role_name] : []
25
+ });
26
+ export const adaptQQGuildMemberProfile = (user) => ({
27
+ user: {
28
+ id: user.tiny_id,
29
+ name: user.nickname,
30
+ isBot: user.roles?.some((r) => r.role_name === "机器人")
31
+ },
32
+ name: user.nickname,
33
+ roles: user.roles?.map((r) => r.role_name) || []
34
+ });
35
+ export async function adaptMessage(bot, data, message = {}, payload = message) {
36
+ message.id = message.messageId = data.message_id.toString();
37
+ // message content
38
+ const chain = CQCode.parse(data.message);
39
+ if (bot.config.advanced.splitMixedContent) {
40
+ chain.forEach((item, index) => {
41
+ if (item.type !== "image")
42
+ return;
43
+ const left = chain[index - 1];
44
+ if (left && left.type === "text" && left.attrs.content.trimEnd() === left.attrs.content) {
45
+ left.attrs.content += " ";
46
+ }
47
+ const right = chain[index + 1];
48
+ if (right && right.type === "text" && right.attrs.content.trimStart() === right.attrs.content) {
49
+ right.attrs.content = " " + right.attrs.content;
50
+ }
51
+ });
52
+ }
53
+ message.elements = h.transform(chain, {
54
+ at(attrs) {
55
+ if (attrs.qq !== "all")
56
+ return h.at(attrs.qq, { name: attrs.name });
57
+ return h("at", { type: "all" });
58
+ },
59
+ face({ id }) {
60
+ const name = qface.get(id)?.QDes.slice(1);
61
+ return h("face", { id, name, platform: bot.platform }, [h.image(qface.getUrl(id))]);
62
+ },
63
+ image(attrs) {
64
+ return h("img", {
65
+ src: attrs.url || attrs.file,
66
+ ...omit(attrs, ["url"])
67
+ });
68
+ },
69
+ record(attrs) {
70
+ return h("audio", {
71
+ src: attrs.url || attrs.file,
72
+ ...omit(attrs, ["url"])
73
+ });
74
+ },
75
+ video(attrs) {
76
+ return h("video", {
77
+ src: attrs.url || attrs.file,
78
+ ...omit(attrs, ["url"])
79
+ });
80
+ },
81
+ file(attrs) {
82
+ return h("file", {
83
+ src: attrs.url || attrs.file,
84
+ ...omit(attrs, ["url"])
85
+ });
86
+ }
87
+ });
88
+ const [guildId, channelId] = decodeGuildChannelId(data);
89
+ if (message.elements[0]?.type === "reply") {
90
+ const reply = message.elements.shift();
91
+ message.quote = await bot.getMessage(channelId, reply.attrs.id).catch((error) => {
92
+ bot.logger.warn(error);
93
+ return undefined;
94
+ });
95
+ }
96
+ message.content = message.elements.join("");
97
+ if (!payload)
98
+ return message;
99
+ payload.user = decodeUser(data.sender);
100
+ payload.member = decodeGuildMember(data.sender);
101
+ payload.timestamp = data.time * 1000;
102
+ payload.guild = guildId && { id: guildId };
103
+ payload.channel = channelId && {
104
+ id: channelId,
105
+ type: guildId ? 0 /* Universal.Channel.Type.TEXT */ : 1 /* Universal.Channel.Type.DIRECT */
106
+ };
107
+ return message;
108
+ }
109
+ const decodeGuildChannelId = (data) => {
110
+ if (data.guild_id) {
111
+ return [data.guild_id, data.channel_id];
112
+ }
113
+ else if (data.group_id) {
114
+ return [data.group_id.toString(), data.group_id.toString()];
115
+ }
116
+ else {
117
+ return [undefined, "private:" + data.sender.user_id];
118
+ }
119
+ };
120
+ export const adaptGuild = (info) => {
121
+ if (info.guild_id) {
122
+ const guild = info;
123
+ return {
124
+ id: guild.guild_id,
125
+ name: guild.guild_name
126
+ };
127
+ }
128
+ else {
129
+ const group = info;
130
+ return {
131
+ id: group.group_id.toString(),
132
+ name: group.group_name
133
+ };
134
+ }
135
+ };
136
+ export const adaptChannel = (info) => {
137
+ if (info.channel_id) {
138
+ const channel = info;
139
+ return {
140
+ id: channel.channel_id,
141
+ name: channel.channel_name,
142
+ type: 0 /* Universal.Channel.Type.TEXT */
143
+ };
144
+ }
145
+ else {
146
+ const group = info;
147
+ return {
148
+ id: group.group_id.toString(),
149
+ name: group.group_name,
150
+ type: 0 /* Universal.Channel.Type.TEXT */
151
+ };
152
+ }
153
+ };
154
+ export async function dispatchSession(bot, data) {
155
+ if (data.self_tiny_id) {
156
+ // don't dispatch any guild message without guild initialization
157
+ bot = bot["guildBot"];
158
+ if (!bot)
159
+ return;
160
+ }
161
+ const session = await adaptSession(bot, data);
162
+ if (!session)
163
+ return;
164
+ session.setInternal("onebot", data);
165
+ bot.dispatch(session);
166
+ }
167
+ export async function adaptSession(bot, data) {
168
+ const session = bot.session();
169
+ session.selfId = data.self_tiny_id ? data.self_tiny_id : "" + data.self_id;
170
+ session.type = data.post_type;
171
+ if (data.post_type === "message" || data.post_type === "message_sent") {
172
+ await adaptMessage(bot, data, (session.event.message = {}), session.event);
173
+ if (data.post_type === "message_sent" && !session.guildId) {
174
+ session.channelId = "private:" + data.target_id;
175
+ }
176
+ session.type = "message";
177
+ session.subtype = data.message_type === "guild" ? "group" : data.message_type;
178
+ session.isDirect = data.message_type === "private";
179
+ session.subsubtype = data.message_type;
180
+ return session;
181
+ }
182
+ session.subtype = data.sub_type;
183
+ if (data.user_id)
184
+ session.userId = "" + data.user_id;
185
+ if (data.group_id)
186
+ session.guildId = session.channelId = "" + data.group_id;
187
+ if (data.guild_id)
188
+ session.guildId = "" + data.guild_id;
189
+ if (data.channel_id)
190
+ session.channelId = "" + data.channel_id;
191
+ if (data.target_id)
192
+ session["targetId"] = "" + data.target_id;
193
+ if (data.operator_id)
194
+ session.operatorId = "" + data.operator_id;
195
+ if (data.message_id)
196
+ session.messageId = "" + data.message_id;
197
+ if (data.post_type === "request") {
198
+ session.content = data.comment;
199
+ session.messageId = data.flag;
200
+ if (data.request_type === "friend") {
201
+ session.type = "friend-request";
202
+ session.channelId = `private:${session.userId}`;
203
+ }
204
+ else if (data.sub_type === "add") {
205
+ session.type = "guild-member-request";
206
+ }
207
+ else {
208
+ session.type = "guild-request";
209
+ }
210
+ }
211
+ else if (data.post_type === "notice") {
212
+ switch (data.notice_type) {
213
+ case "group_recall":
214
+ session.type = "message-deleted";
215
+ session.subtype = "group";
216
+ break;
217
+ case "friend_recall":
218
+ session.type = "message-deleted";
219
+ session.subtype = "private";
220
+ session.channelId = `private:${session.userId}`;
221
+ break;
222
+ // from go-cqhttp source code, but not mentioned in official docs
223
+ case "guild_channel_recall":
224
+ session.type = "message-deleted";
225
+ session.subtype = "guild";
226
+ break;
227
+ case "friend_add":
228
+ session.type = "friend-added";
229
+ break;
230
+ case "group_admin":
231
+ session.type = "guild-member";
232
+ session.subtype = "role";
233
+ break;
234
+ case "group_ban":
235
+ session.type = "guild-member";
236
+ session.subtype = "ban";
237
+ break;
238
+ case "group_decrease":
239
+ session.type = session.userId === session.selfId ? "guild-deleted" : "guild-member-removed";
240
+ session.subtype = session.userId === session.operatorId ? "active" : "passive";
241
+ break;
242
+ case "group_increase":
243
+ session.type = session.userId === session.selfId ? "guild-added" : "guild-member-added";
244
+ session.subtype = session.userId === session.operatorId ? "active" : "passive";
245
+ break;
246
+ case "group_card":
247
+ session.type = "guild-member-updated";
248
+ session.subtype = "nickname";
249
+ break;
250
+ case "notify":
251
+ session.type = "notice";
252
+ session.subtype = hyphenate(data.sub_type);
253
+ if (session.subtype === "poke") {
254
+ session.channelId ||= `private:${session.userId}`;
255
+ }
256
+ else if (session.subtype === "honor") {
257
+ session.subsubtype = hyphenate(data.honor_type);
258
+ }
259
+ break;
260
+ case "message_reactions_updated":
261
+ session.type = "onebot";
262
+ session.subtype = "message-reactions-updated";
263
+ break;
264
+ case "channel_created":
265
+ session.type = "onebot";
266
+ session.subtype = "channel-created";
267
+ break;
268
+ case "channel_updated":
269
+ session.type = "onebot";
270
+ session.subtype = "channel-updated";
271
+ break;
272
+ case "channel_destroyed":
273
+ session.type = "onebot";
274
+ session.subtype = "channel-destroyed";
275
+ break;
276
+ // https://github.com/koishijs/koishi-plugin-adapter-onebot/issues/33
277
+ // case 'offline_file':
278
+ // session.elements = [h('file', data.file)]
279
+ // session.type = 'message'
280
+ // session.subtype = 'private'
281
+ // session.isDirect = true
282
+ // session.subsubtype = 'offline-file-added'
283
+ // break
284
+ // case 'group_upload':
285
+ // session.elements = [h('file', data.file)]
286
+ // session.type = 'message'
287
+ // session.subtype = 'group'
288
+ // session.subsubtype = 'guild-file-added'
289
+ // break
290
+ default:
291
+ return;
292
+ }
293
+ }
294
+ else
295
+ return;
296
+ return session;
297
+ }
package/lib/ws.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { WebSocketLayer } from "@koishijs/plugin-server";
2
+ import { Adapter, Context, HTTP, Logger, Schema, Universal } from "koishi";
3
+ import { OneBotBot } from "./bot";
4
+ interface SharedConfig<T = "ws" | "ws-reverse"> {
5
+ protocol: T;
6
+ responseTimeout?: number;
7
+ }
8
+ export declare class WsClient<C extends Context = Context> extends Adapter.WsClient<C, OneBotBot<C, any>> {
9
+ fork(ctx: C, bot: OneBotBot<C, OneBotBot.BaseConfig & WsClient.Options>): Promise<void>;
10
+ accept(socket: Universal.WebSocket): void;
11
+ prepare(): any;
12
+ disconnect(bot: OneBotBot<C>): Promise<void>;
13
+ }
14
+ export declare namespace WsClient {
15
+ interface Options extends SharedConfig<"ws">, HTTP.Config, Adapter.WsClientConfig {
16
+ }
17
+ const Options: Schema<Options>;
18
+ }
19
+ export declare class WsServer<C extends Context> extends Adapter<C, OneBotBot<C, OneBotBot.BaseConfig & WsServer.Options>> {
20
+ static inject: string[];
21
+ logger: Logger;
22
+ wsServer?: WebSocketLayer;
23
+ constructor(ctx: C, bot: OneBotBot<C>);
24
+ disconnect(bot: OneBotBot<C>): Promise<void>;
25
+ }
26
+ export declare namespace WsServer {
27
+ interface Options extends SharedConfig<"ws-reverse"> {
28
+ path?: string;
29
+ }
30
+ const Options: Schema<Options>;
31
+ }
32
+ export declare function accept(socket: Universal.WebSocket, bot: OneBotBot<Context, any>): void;
33
+ export {};
package/lib/ws.js ADDED
@@ -0,0 +1,122 @@
1
+ import { Adapter, HTTP, Schema, Time } from "koishi";
2
+ import { dispatchSession, TimeoutError } from "./utils";
3
+ let counter = 0;
4
+ const listeners = {};
5
+ export class WsClient extends Adapter.WsClient {
6
+ async fork(ctx, bot) {
7
+ super.fork(ctx, bot);
8
+ }
9
+ accept(socket) {
10
+ accept(socket, this.bot);
11
+ }
12
+ prepare() {
13
+ const { token, endpoint } = this.bot.config;
14
+ const http = this.ctx.http.extend(this.bot.config);
15
+ if (token)
16
+ http.config.headers.Authorization = `Bearer ${token}`;
17
+ return http.ws(endpoint);
18
+ }
19
+ async disconnect(bot) {
20
+ bot.status = 4 /* Universal.Status.RECONNECT */;
21
+ }
22
+ }
23
+ (function (WsClient) {
24
+ WsClient.Options = Schema.intersect([
25
+ Schema.object({
26
+ protocol: Schema.const("ws").required(process.env.KOISHI_ENV !== "browser"),
27
+ responseTimeout: Schema.natural()
28
+ .role("time")
29
+ .default(Time.minute)
30
+ .description("等待响应的时间 (单位为毫秒)。")
31
+ }).description("连接设置"),
32
+ HTTP.createConfig(true),
33
+ Adapter.WsClientConfig
34
+ ]);
35
+ })(WsClient || (WsClient = {}));
36
+ const kSocket = Symbol("socket");
37
+ export class WsServer extends Adapter {
38
+ static inject = ["server"];
39
+ logger;
40
+ wsServer;
41
+ constructor(ctx, bot) {
42
+ super(ctx);
43
+ this.logger = ctx.logger("onebot");
44
+ const { path = "/onebot" } = bot.config;
45
+ this.wsServer = ctx.server.ws(path, (socket, { headers }) => {
46
+ this.logger.debug("connected with", headers);
47
+ if (headers["x-client-role"] !== "Universal") {
48
+ return socket.close(1008, "invalid x-client-role");
49
+ }
50
+ const selfId = headers["x-self-id"].toString();
51
+ const bot = this.bots.find((bot) => bot.selfId === selfId);
52
+ if (!bot)
53
+ return socket.close(1008, "invalid x-self-id");
54
+ bot[kSocket] = socket;
55
+ accept(socket, bot);
56
+ });
57
+ ctx.on("dispose", () => {
58
+ this.logger.debug("ws server closing");
59
+ this.wsServer.close();
60
+ });
61
+ }
62
+ async disconnect(bot) {
63
+ bot[kSocket]?.close();
64
+ bot[kSocket] = null;
65
+ bot.status = 4 /* Universal.Status.RECONNECT */;
66
+ }
67
+ }
68
+ (function (WsServer) {
69
+ WsServer.Options = Schema.object({
70
+ protocol: Schema.const("ws-reverse").required(process.env.KOISHI_ENV === "browser"),
71
+ path: Schema.string().description("服务器监听的路径。").default("/onebot"),
72
+ responseTimeout: Schema.natural().role("time").default(Time.minute).description("等待响应的时间 (单位为毫秒)。")
73
+ }).description("连接设置");
74
+ })(WsServer || (WsServer = {}));
75
+ export function accept(socket, bot) {
76
+ socket.addEventListener("message", async ({ data }) => {
77
+ let parsed;
78
+ try {
79
+ parsed = JSON.parse(data.toString());
80
+ }
81
+ catch {
82
+ return bot.logger.warn("cannot parse message", data);
83
+ }
84
+ if ("post_type" in parsed) {
85
+ bot.logger.debug("[receive] %o", parsed);
86
+ await dispatchSession(bot, parsed);
87
+ }
88
+ else if (parsed.echo in listeners) {
89
+ listeners[parsed.echo](parsed);
90
+ delete listeners[parsed.echo];
91
+ }
92
+ });
93
+ socket.addEventListener("close", () => {
94
+ delete bot.internal._request;
95
+ Object.keys(listeners).forEach((echo) => {
96
+ delete listeners[Number(echo)];
97
+ });
98
+ });
99
+ bot.internal._request = (action, params) => {
100
+ const echo = ++counter;
101
+ const data = { action, params, echo };
102
+ return Promise.race([
103
+ new Promise((resolve, reject) => {
104
+ listeners[echo] = resolve;
105
+ try {
106
+ socket.send(JSON.stringify(data));
107
+ }
108
+ catch (error) {
109
+ delete listeners[echo];
110
+ reject(error);
111
+ }
112
+ }),
113
+ new Promise((_, reject) => {
114
+ setTimeout(() => {
115
+ delete listeners[echo];
116
+ reject(new TimeoutError(params, action));
117
+ }, bot.config.responseTimeout);
118
+ })
119
+ ]);
120
+ };
121
+ bot.initialize();
122
+ }
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@mrlingxd/koishi-plugin-adapter-onebot",
3
+ "version": "0.1.0",
4
+ "description": "OneBot Adapter for Koishi",
5
+ "main": "lib/index.js",
6
+ "typings": "lib/index.d.ts",
7
+ "files": [
8
+ "lib"
9
+ ],
10
+ "contributors": [
11
+ "MrlingXD <90316914+wling-art@users.noreply.github.com>"
12
+ ],
13
+ "license": "MIT",
14
+ "homepage": "https://github.com/wling-art/koishi-plugin-adapter-onebot",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/wling-art/koishi-plugin-adapter-onebot.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/wling-art/koishi-plugin-adapter-onebot/issues"
21
+ },
22
+ "keywords": [
23
+ "bot",
24
+ "chatbot",
25
+ "koishi",
26
+ "plugin",
27
+ "adapter",
28
+ "onebot",
29
+ "im",
30
+ "chat"
31
+ ],
32
+ "koishi": {
33
+ "description": {
34
+ "en": "OneBot Adapter for Koishi",
35
+ "zh": "OneBot 适配器"
36
+ },
37
+ "service": {
38
+ "implements": [
39
+ "adapter"
40
+ ]
41
+ }
42
+ },
43
+ "scripts": {
44
+ "build": "atsc -b",
45
+ "lint": "eslint --ext=ts --cache"
46
+ },
47
+ "devDependencies": {
48
+ "@cordisjs/eslint-config": "^1.1.1",
49
+ "@koishijs/plugin-server": "^3.2.7",
50
+ "@types/node": "^25.0.3",
51
+ "atsc": "^2.1.0",
52
+ "esbuild": "^0.27.2",
53
+ "esbuild-register": "^3.6.0",
54
+ "eslint": "^9.39.2",
55
+ "koishi": "^4.18.9",
56
+ "typescript": "^5.9.3",
57
+ "yml-register": "^1.2.5"
58
+ },
59
+ "peerDependencies": {
60
+ "koishi": "^4.18.6"
61
+ },
62
+ "dependencies": {
63
+ "qface": "^1.4.1"
64
+ }
65
+ }
package/readme.md ADDED
@@ -0,0 +1,153 @@
1
+ # koishi-plugin-adapter-onebot
2
+
3
+ > 本项目基于 [koishijs/koishi-plugin-adapter-onebot](https://github.com/koishijs/koishi-plugin-adapter-onebot) fork,<br>
4
+ > 用于优化和补充原仓库功能,便于后续维护和更新。
5
+
6
+ 适用于 [Koishi](https://koishi.chat/) 的 OneBot 适配器。
7
+
8
+ [OneBot](https://github.com/howmanybots/onebot) 是一个聊天机器人应用接口标准。
9
+
10
+ ## 配置项
11
+
12
+ ### config.protocol
13
+
14
+ - 可选值: http, ws, ws-reverse
15
+
16
+ 要使用的协议类型。
17
+
18
+ ### config.token
19
+
20
+ - 类型:`string`
21
+
22
+ 发送信息时用于验证的字段。
23
+
24
+ ### config.endpoint
25
+
26
+ - 类型:`string`
27
+
28
+ 如果使用了 HTTP,则该配置将作为发送信息的服务端;如果使用了 WebSocket,则该配置将作为监听事件和发送信息的服务端。
29
+
30
+ ### config.proxyAgent
31
+
32
+ - 类型: `string`
33
+ - 默认值: [`app.config.request.proxyAgent`](https://koishi.chat/zh-CN/api/core/app.html#options-request-proxyagent)
34
+
35
+ 请求时默认使用的网络代理。
36
+
37
+ ### config.path
38
+
39
+ - 类型:`string`
40
+ - 默认值:`'/onebot'`
41
+
42
+ 服务器监听的路径。仅用于 HTTP 或 WS Reverse 通信方式。
43
+
44
+ ### config.secret
45
+
46
+ - 类型:`string`
47
+
48
+ 接收信息时用于验证的字段,应与 OneBot 的 `secret` 配置保持一致。
49
+
50
+ ## 内部 API
51
+
52
+ 你可以通过 `bot.internal` 或 `session.onebot` 访问内部 API,参见 [访问内部接口](https://koishi.chat/zh-CN/guide/adapter/bot.html#internal-access)。
53
+
54
+ ### OneBot v11 标准 API
55
+
56
+ - [`onebot.sendPrivateMsg()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#send_private_msg-发送私聊消息)
57
+ - [`onebot.sendGroupMsg()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#send_group_msg-发送群消息)
58
+ - [`onebot.deleteMsg()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#delete_msg-撤回消息)
59
+ - [`onebot.getMsg()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_msg-获取消息)
60
+ - [`onebot.getForwardMsg()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_forward_msg-获取合并转发消息)
61
+ - [`onebot.sendLike()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#send_like-发送好友赞)
62
+ - [`onebot.setGroupKick()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_kick-群组踢人)
63
+ - [`onebot.setGroupBan()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_ban-群组单人禁言)
64
+ - [`onebot.setGroupAnonymousBan()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_anonymous_ban-群组匿名用户禁言)
65
+ - [`onebot.setGroupWholeBan()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_whole_ban-群组全员禁言)
66
+ - [`onebot.setGroupAdmin()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_admin-群组设置管理员)
67
+ - [`onebot.setGroupAnonymous()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_anonymous-群组匿名)
68
+ - [`onebot.setGroupCard()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_card-设置群名片群备注)
69
+ - [`onebot.setGroupName()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_name-设置群名)
70
+ - [`onebot.setGroupLeave()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_leave-退出群组)
71
+ - [`onebot.setGroupSpecialTitle()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_special_title-设置群组专属头衔)
72
+ - [`onebot.setFriendAddRequest()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_friend_add_request-处理加好友请求)
73
+ - [`onebot.setGroupAddRequest()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_add_request-处理加群请求邀请)
74
+ - [`onebot.getLoginInfo()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_login_info-获取登录号信息)
75
+ - [`onebot.getStrangerInfo()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_stranger_info-获取陌生人信息)
76
+ - [`onebot.getFriendList()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_friend_list-获取好友列表)
77
+ - [`onebot.getGroupInfo()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_group_info-获取群信息)
78
+ - [`onebot.getGroupList()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_group_list-获取群列表)
79
+ - [`onebot.getGroupMemberInfo()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_group_member_info-获取群成员信息)
80
+ - [`onebot.getGroupMemberList()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_group_member_list-获取群成员列表)
81
+ - [`onebot.getGroupHonorInfo()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_group_honor_info-获取群荣誉信息)
82
+ - [`onebot.getCookies()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_cookies-获取-cookies)
83
+ - [`onebot.getCsrfToken()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_csrf_token-获取-csrf-token)
84
+ - [`onebot.getCredentials()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_credentials-获取-qq-相关接口凭证)
85
+ - [`onebot.getRecord()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_record-获取语音)
86
+ - [`onebot.getImage()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_image-获取图片)
87
+ - [`onebot.canSendImage()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#can_send_image-检查是否可以发送图片)
88
+ - [`onebot.canSendRecord()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#can_send_record-检查是否可以发送语音)
89
+ - [`onebot.getStatus()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_status-获取运行状态)
90
+ - [`onebot.getVersionInfo()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_version_info-获取版本信息)
91
+ - [`onebot.setRestart()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_restart-重启-onebot-实现)
92
+ - [`onebot.cleanCache()`](https://github.com/botuniverse/onebot-11/blob/master/api/public.md#clean_cache-清理缓存)
93
+
94
+ ### go-cqhttp 扩展 API
95
+
96
+ - [`onebot.sendGroupForwardMsg()`](https://docs.go-cqhttp.org/api/#发送合并转发-群聊)
97
+ - [`onebot.markMsgAsRead()`](https://docs.go-cqhttp.org/api/#标记消息已读)
98
+ - [`onebot.sendGroupSign()`](https://docs.go-cqhttp.org/api/#群打卡)
99
+ - [`onebot.qidianGetAccountInfo()`](https://docs.go-cqhttp.org/api/#获取企点账号信息)
100
+ - [`onebot.setQqProfile()`](https://docs.go-cqhttp.org/api/#设置登录号资料)
101
+ - [`onebot.getUnidirectionalFriendList()`](https://docs.go-cqhttp.org/api/#获取单向好友列表)
102
+ - [`onebot.deleteFriend()`](https://docs.go-cqhttp.org/api/#删除好友)
103
+ - [`onebot.setGroupPortrait()`](https://docs.go-cqhttp.org/api/#设置群头像)
104
+ - [`onebot.getWordSlices()`](https://docs.go-cqhttp.org/api/#获取中文分词-隐藏-api)
105
+ - [`onebot.ocrImage()`](https://docs.go-cqhttp.org/api/#图片-ocr)
106
+ - [`onebot.getGroupSystemMsg()`](https://docs.go-cqhttp.org/api/#获取群系统消息)
107
+ - [`onebot.uploadPrivateFile()`](https://docs.go-cqhttp.org/api/#上传私聊文件)
108
+ - [`onebot.uploadGroupFile()`](https://docs.go-cqhttp.org/api/#上传群文件)
109
+ - [`onebot.getGroupFileSystemInfo()`](https://docs.go-cqhttp.org/api/#获取群文件系统信息)
110
+ - [`onebot.getGroupRootFiles()`](https://docs.go-cqhttp.org/api/#获取群根目录文件列表)
111
+ - [`onebot.getGroupFilesByFolder()`](https://docs.go-cqhttp.org/api/#获取群子目录文件列表)
112
+ - [`onebot.createGroupFileFolder()`](https://docs.go-cqhttp.org/api/#创建群文件文件夹)
113
+ - [`onebot.deleteGroupFolder()`](https://docs.go-cqhttp.org/api/#删除群文件文件夹)
114
+ - [`onebot.deleteGroupFile()`](https://docs.go-cqhttp.org/api/#删除群文件)
115
+ - [`onebot.getGroupFileUrl()`](https://docs.go-cqhttp.org/api/#获取群文件资源链接)
116
+ - [`onebot.getGroupAtAllRemain()`](https://docs.go-cqhttp.org/api/#获取群-全体成员-剩余次数)
117
+ - [`onebot.getVipInfo()`](https://github.com/Mrs4s/go-cqhttp/blob/master/docs/cqhttp.md?plain=1#L1081)
118
+ - [`onebot.sendGroupNotice()`](https://docs.go-cqhttp.org/api/#发送群公告)
119
+ - [`onebot.getGroupNotice()`](https://docs.go-cqhttp.org/api/#获取群公告)
120
+ - [`onebot.reloadEventFilter()`](https://docs.go-cqhttp.org/api/#重载事件过滤器)
121
+ - [`onebot.downloadFile()`](https://docs.go-cqhttp.org/api/#下载文件到缓存目录)
122
+ - [`onebot.getOnlineClients()`](https://docs.go-cqhttp.org/api/#获取当前账号在线客户端列表)
123
+ - [`onebot.getGroupMsgHistory()`](https://docs.go-cqhttp.org/api/#获取群消息历史记录)
124
+ - [`onebot.setEssenceMsg()`](https://docs.go-cqhttp.org/api/#设置精华消息)
125
+ - [`onebot.deleteEssenceMsg()`](https://docs.go-cqhttp.org/api/#移出精华消息)
126
+ - [`onebot.getEssenceMsgList()`](https://docs.go-cqhttp.org/api/#获取精华消息列表)
127
+ - [`onebot.checkUrlSafely()`](https://docs.go-cqhttp.org/api/#检查链接安全性)
128
+ - [`onebot.getModelShow()`](https://docs.go-cqhttp.org/api/#获取在线机型)
129
+ - [`onebot.setModelShow()`](https://docs.go-cqhttp.org/api/#设置在线机型)
130
+ - [`onebot.delete_unidirectional_friend()`](https://docs.go-cqhttp.org/api/#删除单向好友)
131
+ - [`onebot.send_private_forward_msg()`](https://docs.go-cqhttp.org/api/#发送合并转发-好友)
132
+
133
+ ### 频道 API
134
+
135
+ - [`onebot.getGuildServiceProfile()`](https://docs.go-cqhttp.org/api/guild.html#获取频道系统内bot的资料)
136
+ - [`onebot.getGuildList()`](https://docs.go-cqhttp.org/api/guild.html#获取频道列表)
137
+ - [`onebot.getGuildMetaByGuest()`](https://docs.go-cqhttp.org/api/guild.html#通过访客获取频道元数据)
138
+ - [`onebot.getGuildChannelList()`](https://docs.go-cqhttp.org/api/guild.html#获取子频道列表)
139
+ - [`onebot.getGuildMembers()`](https://docs.go-cqhttp.org/api/guild.html#获取频道成员列表)
140
+ - [`onebot.sendGuildChannelMsg()`](https://docs.go-cqhttp.org/api/guild.html#发送信息到子频道)
141
+
142
+ ## 许可证
143
+
144
+ 使用 [MIT](./LICENSE) 许可证发布。
145
+
146
+ ```
147
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
148
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
149
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
150
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
151
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
152
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
153
+ ```