@zhin.js/adapter-icqq 1.0.21 → 1.0.23
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/CHANGELOG.md +20 -0
- package/README.md +301 -24
- package/lib/index.d.ts +23 -8
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +83 -79
- package/lib/index.js.map +1 -1
- package/package.json +6 -9
- package/src/index.ts +262 -235
package/src/index.ts
CHANGED
|
@@ -1,250 +1,277 @@
|
|
|
1
|
-
import { Config, Client, PrivateMessageEvent, GroupMessageEvent, Sendable, MessageElem} from "@icqqjs/icqq";
|
|
1
|
+
import { Config, Client, PrivateMessageEvent, GroupMessageEvent, Sendable, MessageElem } from "@icqqjs/icqq";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
SendContent,
|
|
14
|
-
segment
|
|
4
|
+
Bot,
|
|
5
|
+
usePlugin,
|
|
6
|
+
Adapter,
|
|
7
|
+
Plugin,
|
|
8
|
+
Message,
|
|
9
|
+
SendOptions,
|
|
10
|
+
MessageSegment,
|
|
11
|
+
SendContent,
|
|
12
|
+
segment,
|
|
15
13
|
} from "zhin.js";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
import { Router } from "@zhin.js/http";
|
|
15
|
+
|
|
16
|
+
declare module "zhin.js" {
|
|
17
|
+
namespace Plugin {
|
|
18
|
+
interface Contexts {
|
|
19
|
+
icqq: IcqqAdapter;
|
|
20
|
+
web: any;
|
|
21
|
+
router: Router;
|
|
19
22
|
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface RegisteredAdapters {
|
|
26
|
+
icqq: IcqqAdapter;
|
|
27
|
+
}
|
|
20
28
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
|
|
30
|
+
const plugin = usePlugin();
|
|
31
|
+
const { useContext } = plugin;
|
|
32
|
+
|
|
33
|
+
export interface IcqqBotConfig extends Config {
|
|
34
|
+
context: "icqq";
|
|
35
|
+
name: `${number}`;
|
|
36
|
+
password?: string;
|
|
37
|
+
scope?: string;
|
|
27
38
|
}
|
|
28
|
-
|
|
29
|
-
|
|
39
|
+
|
|
40
|
+
export interface IcqqBot {
|
|
41
|
+
$config: IcqqBotConfig;
|
|
30
42
|
}
|
|
31
|
-
export class IcqqBot extends Client implements Bot<PrivateMessageEvent|GroupMessageEvent,IcqqBotConfig>{
|
|
32
|
-
$connected?:boolean
|
|
33
|
-
constructor(config:IcqqBotConfig) {
|
|
34
|
-
if(!config.scope) config.scope='icqqjs'
|
|
35
|
-
if(!config.data_dir) config.data_dir=path.join(process.cwd(),'data')
|
|
36
|
-
if(config.scope.startsWith('@')) config.scope=config.scope.slice(1)
|
|
37
|
-
super(config);
|
|
38
|
-
this.$config=config
|
|
39
|
-
}
|
|
40
|
-
private handleIcqqMessage(msg: PrivateMessageEvent|GroupMessageEvent): void {
|
|
41
|
-
const message =this.$formatMessage(msg) ;
|
|
42
|
-
plugin.dispatch('message.receive',message)
|
|
43
|
-
plugin.logger.info(`${this.$config.name} recv ${message.$channel.type}(${message.$channel.id}):${segment.raw(message.$content)}`)
|
|
44
|
-
plugin.dispatch(`message.${message.$channel.type}.receive`,message)
|
|
45
|
-
}
|
|
46
|
-
async $connect(): Promise<void> {
|
|
47
|
-
this.on('message',this.handleIcqqMessage.bind(this))
|
|
48
|
-
this.on('system.login.device',async (e:unknown)=>{
|
|
49
|
-
await this.sendSmsCode()
|
|
50
|
-
plugin.logger.info('请输入短信验证码:')
|
|
51
|
-
process.stdin.once('data',(data)=>{
|
|
52
|
-
this.submitSmsCode(data.toString().trim())
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
this.on('system.login.qrcode',(e)=>{
|
|
56
|
-
plugin.logger.info(`取码地址:${e.image}\n请扫码完成后回车继续:`)
|
|
57
|
-
process.stdin.once('data',()=>{
|
|
58
|
-
this.login()
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
this.on('system.login.slider',(e)=>{
|
|
62
|
-
plugin.logger.info(`取码地址:${e.url}\n请输入滑块验证ticket:`)
|
|
63
|
-
process.stdin.once('data',(e)=>{
|
|
64
|
-
this.submitSlider(e.toString().trim())
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
return new Promise((resolve)=>{
|
|
68
|
-
this.once('system.online',()=>{
|
|
69
|
-
this.$connected=true;
|
|
70
|
-
resolve()
|
|
71
|
-
})
|
|
72
|
-
this.login(Number(this.$config.name),this.$config.password)
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
43
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
44
|
+
export class IcqqBot extends Client implements Bot<IcqqBotConfig, PrivateMessageEvent | GroupMessageEvent> {
|
|
45
|
+
$connected: boolean = false;
|
|
46
|
+
|
|
47
|
+
get $id() {
|
|
48
|
+
return this.$config.name;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
constructor(public adapter: IcqqAdapter, config: IcqqBotConfig) {
|
|
52
|
+
if (!config.scope) config.scope = "icqqjs";
|
|
53
|
+
if (!config.data_dir) config.data_dir = path.join(process.cwd(), "data");
|
|
54
|
+
if (config.scope.startsWith("@")) config.scope = config.scope.slice(1);
|
|
55
|
+
super(config);
|
|
56
|
+
this.$config = config;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private handleIcqqMessage(msg: PrivateMessageEvent | GroupMessageEvent): void {
|
|
60
|
+
const message = this.$formatMessage(msg);
|
|
61
|
+
this.adapter.emit("message.receive", message);
|
|
62
|
+
plugin.logger.debug(`${this.$config.name} recv ${message.$channel.type}(${message.$channel.id}):${segment.raw(message.$content)}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async $connect(): Promise<void> {
|
|
66
|
+
this.on("message", this.handleIcqqMessage.bind(this));
|
|
67
|
+
this.on("system.login.device", async (e: unknown) => {
|
|
68
|
+
await this.sendSmsCode();
|
|
69
|
+
plugin.logger.info("请输入短信验证码:");
|
|
70
|
+
process.stdin.once("data", (data) => {
|
|
71
|
+
this.submitSmsCode(data.toString().trim());
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
this.on("system.login.qrcode", (e) => {
|
|
75
|
+
plugin.logger.info(`取码地址:${e.image}\n请扫码完成后回车继续:`);
|
|
76
|
+
process.stdin.once("data", () => {
|
|
77
|
+
this.login();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
this.on("system.login.slider", (e) => {
|
|
81
|
+
plugin.logger.info(`取码地址:${e.url}\n请输入滑块验证ticket:`);
|
|
82
|
+
process.stdin.once("data", (e) => {
|
|
83
|
+
this.submitSlider(e.toString().trim());
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
this.once("system.online", () => {
|
|
88
|
+
this.$connected = true;
|
|
89
|
+
resolve();
|
|
90
|
+
});
|
|
91
|
+
this.login(Number(this.$config.name), this.$config.password);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async $disconnect(): Promise<void> {
|
|
96
|
+
await this.logout();
|
|
97
|
+
this.$connected = false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
$formatMessage(msg: PrivateMessageEvent | GroupMessageEvent) {
|
|
101
|
+
const result = Message.from(msg, {
|
|
102
|
+
$id: msg.message_id.toString(),
|
|
103
|
+
$adapter: "icqq" as const,
|
|
104
|
+
$bot: `${this.$config.name}`,
|
|
105
|
+
$sender: {
|
|
106
|
+
id: msg.sender.user_id.toString(),
|
|
107
|
+
name: msg.sender.nickname.toString(),
|
|
108
|
+
},
|
|
109
|
+
$channel: {
|
|
110
|
+
id: msg.message_type === "group" ? msg.group_id.toString() : msg.from_id.toString(),
|
|
111
|
+
type: msg.message_type,
|
|
112
|
+
},
|
|
113
|
+
$content: IcqqBot.toSegments(msg.message),
|
|
114
|
+
$raw: msg.raw_message,
|
|
115
|
+
$timestamp: msg.time,
|
|
116
|
+
$recall: async () => {
|
|
117
|
+
await this.$recallMessage(result.$id);
|
|
118
|
+
},
|
|
119
|
+
$reply: async (content: SendContent, quote?: boolean | string): Promise<string> => {
|
|
120
|
+
if (!Array.isArray(content)) content = [content];
|
|
121
|
+
if (quote) content.unshift({ type: "reply", data: { id: typeof quote === "boolean" ? result.$id : quote } });
|
|
122
|
+
return await this.$sendMessage({
|
|
123
|
+
...result.$channel,
|
|
124
|
+
context: "icqq",
|
|
125
|
+
bot: `${this.uin}`,
|
|
126
|
+
content,
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async $recallMessage(id: string): Promise<void> {
|
|
134
|
+
await this.deleteMsg(id);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async $sendMessage(options: SendOptions): Promise<string> {
|
|
138
|
+
switch (options.type) {
|
|
139
|
+
case "private": {
|
|
140
|
+
const result = await this.sendPrivateMsg(Number(options.id), IcqqBot.toSendable(options.content));
|
|
141
|
+
plugin.logger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(options.content)}`);
|
|
142
|
+
return result.message_id.toString();
|
|
143
|
+
}
|
|
144
|
+
case "group": {
|
|
145
|
+
const result = await this.sendGroupMsg(Number(options.id), IcqqBot.toSendable(options.content));
|
|
146
|
+
plugin.logger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(options.content)}`);
|
|
147
|
+
return result.message_id.toString();
|
|
148
|
+
}
|
|
149
|
+
default:
|
|
150
|
+
throw new Error(`unsupported channel type ${options.type}`);
|
|
133
151
|
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export namespace IcqqBot {
|
|
156
|
+
const allowTypes = ["text", "face", "image", "record", "audio", "dice", "rps", "video", "file", "location", "share", "json", "at", "reply", "long_msg", "button", "markdown", "xml"];
|
|
134
157
|
|
|
158
|
+
export function toSegments(message: Sendable): MessageSegment[] {
|
|
159
|
+
if (!Array.isArray(message)) message = [message];
|
|
160
|
+
return message
|
|
161
|
+
.filter((item, index) => {
|
|
162
|
+
return typeof item === "string" || item.type !== "long_msg" || index !== 0;
|
|
163
|
+
})
|
|
164
|
+
.map((item): MessageSegment => {
|
|
165
|
+
if (typeof item === "string") return { type: "text", data: { text: item } };
|
|
166
|
+
const { type, ...data } = item;
|
|
167
|
+
return { type, data };
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function toSendable(content: SendContent): Sendable {
|
|
172
|
+
if (!Array.isArray(content)) content = [content];
|
|
173
|
+
return content.map((seg): MessageElem => {
|
|
174
|
+
if (typeof seg === "string") return { type: "text", text: seg };
|
|
175
|
+
let { type, data } = seg;
|
|
176
|
+
if (typeof type === "function") type = type.name;
|
|
177
|
+
if (!allowTypes.includes(type)) return { type: "text", text: segment.toString(seg) };
|
|
178
|
+
return { type, ...data } as MessageElem;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
135
181
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
'video',
|
|
146
|
-
'file',
|
|
147
|
-
'location',
|
|
148
|
-
'share',
|
|
149
|
-
'json',
|
|
150
|
-
'at',
|
|
151
|
-
'reply',
|
|
152
|
-
'long_msg',
|
|
153
|
-
'button',
|
|
154
|
-
'markdown',
|
|
155
|
-
'xml',
|
|
156
|
-
]
|
|
157
|
-
export function toSegments(message:Sendable):MessageSegment[]{
|
|
158
|
-
if(!Array.isArray(message)) message=[message]
|
|
159
|
-
return message.filter((item,index)=>{
|
|
160
|
-
return typeof item==="string"||(item.type!=='long_msg'||index!==0)
|
|
161
|
-
}).map((item):MessageSegment=>{
|
|
162
|
-
if(typeof item==="string") return {type:'text',data:{text:item}}
|
|
163
|
-
const {type,...data}=item
|
|
164
|
-
return {type,data}
|
|
165
|
-
})
|
|
166
|
-
}
|
|
167
|
-
export function toSendable(content:SendContent):Sendable{
|
|
168
|
-
if(!Array.isArray(content)) content=[content]
|
|
169
|
-
return content.map((seg):MessageElem=>{
|
|
170
|
-
if(typeof seg==="string") return {type:'text',text:seg}
|
|
171
|
-
let {type,data}=seg
|
|
172
|
-
if(typeof type==='function') type=type.name
|
|
173
|
-
if(!allowTypes.includes(type)) return {type:'text',text:segment.toString(seg)}
|
|
174
|
-
return {type,...data} as MessageElem
|
|
175
|
-
})
|
|
176
|
-
}
|
|
182
|
+
|
|
183
|
+
class IcqqAdapter extends Adapter<IcqqBot> {
|
|
184
|
+
constructor(plugin: Plugin) {
|
|
185
|
+
super(plugin, "icqq", []);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
createBot(config: IcqqBotConfig): IcqqBot {
|
|
189
|
+
return new IcqqBot(this, config);
|
|
190
|
+
}
|
|
177
191
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
192
|
+
|
|
193
|
+
const { provide } = usePlugin();
|
|
194
|
+
|
|
195
|
+
provide({
|
|
196
|
+
name: "icqq",
|
|
197
|
+
description: "ICQQ Adapter",
|
|
198
|
+
mounted: async (p) => {
|
|
199
|
+
const adapter = new IcqqAdapter(p);
|
|
200
|
+
await adapter.start();
|
|
201
|
+
return adapter;
|
|
202
|
+
},
|
|
203
|
+
dispose: async (adapter) => {
|
|
204
|
+
await adapter.stop();
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
useContext("web", (web: any) => {
|
|
209
|
+
// 注册ICQQ适配器的客户端入口文件
|
|
210
|
+
const dispose = web.addEntry({
|
|
211
|
+
production: path.resolve(import.meta.dirname, "../dist/index.js"),
|
|
212
|
+
development: path.resolve(import.meta.dirname, "../client/index.tsx"),
|
|
213
|
+
});
|
|
214
|
+
return dispose;
|
|
187
215
|
});
|
|
188
|
-
|
|
189
|
-
|
|
216
|
+
|
|
217
|
+
useContext("router", async (router: Router) => {
|
|
218
|
+
const icqq = plugin.root.inject("icqq") as IcqqAdapter;
|
|
219
|
+
router.get("/api/icqq/bots", async (ctx: any) => {
|
|
220
|
+
try {
|
|
221
|
+
const bots = Array.from(icqq.bots.values());
|
|
222
|
+
|
|
223
|
+
if (bots.length === 0) {
|
|
224
|
+
ctx.body = {
|
|
225
|
+
success: true,
|
|
226
|
+
data: [],
|
|
227
|
+
message: "暂无ICQQ机器人实例",
|
|
228
|
+
};
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const result = bots.map((bot) => {
|
|
190
233
|
try {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
} catch (botError) {
|
|
216
|
-
// 单个机器人数据获取失败时的处理
|
|
217
|
-
// 获取机器人数据失败,返回错误状态
|
|
218
|
-
return {
|
|
219
|
-
name: bot.$config.name,
|
|
220
|
-
connected: false,
|
|
221
|
-
groupCount: 0,
|
|
222
|
-
friendCount: 0,
|
|
223
|
-
receiveCount: 0,
|
|
224
|
-
sendCount: 0,
|
|
225
|
-
loginMode: 'unknown',
|
|
226
|
-
status: 'error',
|
|
227
|
-
error: '数据获取失败'
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
ctx.body = {
|
|
233
|
-
success: true,
|
|
234
|
-
data: result,
|
|
235
|
-
timestamp: new Date().toISOString()
|
|
236
|
-
}
|
|
237
|
-
} catch (error) {
|
|
238
|
-
// ICQQ API调用失败
|
|
239
|
-
|
|
240
|
-
ctx.status = 500
|
|
241
|
-
ctx.body = {
|
|
242
|
-
success: false,
|
|
243
|
-
error: 'ICQQ_API_ERROR',
|
|
244
|
-
message: '获取机器人数据失败',
|
|
245
|
-
details: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined,
|
|
246
|
-
timestamp: new Date().toISOString()
|
|
247
|
-
}
|
|
234
|
+
return {
|
|
235
|
+
name: bot.$config.name,
|
|
236
|
+
connected: bot.$connected || false,
|
|
237
|
+
groupCount: bot.gl?.size || 0,
|
|
238
|
+
friendCount: bot.fl?.size || 0,
|
|
239
|
+
receiveCount: bot.stat?.recv_msg_cnt || 0,
|
|
240
|
+
sendCount: bot.stat?.sent_msg_cnt || 0,
|
|
241
|
+
loginMode: bot.$config.password ? "password" : "qrcode",
|
|
242
|
+
status: bot.$connected ? "online" : "offline",
|
|
243
|
+
lastActivity: new Date().toISOString(),
|
|
244
|
+
};
|
|
245
|
+
} catch (botError) {
|
|
246
|
+
// 单个机器人数据获取失败时的处理
|
|
247
|
+
return {
|
|
248
|
+
name: bot.$config.name,
|
|
249
|
+
connected: false,
|
|
250
|
+
groupCount: 0,
|
|
251
|
+
friendCount: 0,
|
|
252
|
+
receiveCount: 0,
|
|
253
|
+
sendCount: 0,
|
|
254
|
+
loginMode: "unknown",
|
|
255
|
+
status: "error",
|
|
256
|
+
error: "数据获取失败",
|
|
257
|
+
};
|
|
248
258
|
}
|
|
249
|
-
|
|
250
|
-
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
ctx.body = {
|
|
262
|
+
success: true,
|
|
263
|
+
data: result,
|
|
264
|
+
timestamp: new Date().toISOString(),
|
|
265
|
+
};
|
|
266
|
+
} catch (error) {
|
|
267
|
+
ctx.status = 500;
|
|
268
|
+
ctx.body = {
|
|
269
|
+
success: false,
|
|
270
|
+
error: "ICQQ_API_ERROR",
|
|
271
|
+
message: "获取机器人数据失败",
|
|
272
|
+
details: process.env.NODE_ENV === "development" ? (error as Error).message : undefined,
|
|
273
|
+
timestamp: new Date().toISOString(),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
});
|