@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/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
- Bot,
5
- usePlugin,
6
- useContext,
7
- Adapter,
8
- Plugin,
9
- registerAdapter,
10
- Message,
11
- SendOptions,
12
- MessageSegment,
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
- declare module '@zhin.js/types'{
17
- interface RegisteredAdapters{
18
- icqq:Adapter<IcqqBot>
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
- const plugin =usePlugin()
22
- export interface IcqqBotConfig extends Bot.Config,Config{
23
- context:'icqq'
24
- name:`${number}`
25
- password?:string
26
- scope?:string
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
- export interface IcqqBot{
29
- $config:IcqqBotConfig
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
- async $disconnect(): Promise<void> {
77
- await this.logout()
78
- this.$connected=false;
79
- }
80
- $formatMessage(msg:PrivateMessageEvent|GroupMessageEvent){
81
- const result= Message.from(msg,{
82
- $id: msg.message_id.toString(),
83
- $adapter:'icqq',
84
- $bot:`${this.$config.name}`,
85
- $sender:{
86
- id:msg.sender.user_id.toString(),
87
- name:msg.sender.nickname.toString(),
88
- },
89
- $channel:{
90
- id:msg.message_type==='group'?msg.group_id.toString():msg.from_id.toString(),
91
- type:msg.message_type
92
- },
93
- $content: IcqqBot.toSegments(msg.message),
94
- $raw: msg.raw_message,
95
- $timestamp: msg.time,
96
- $recall: async () => {
97
- await this.$recallMessage(result.$id)
98
- },
99
- $reply:async (content: SendContent, quote?: boolean|string):Promise<string>=> {
100
- if(!Array.isArray(content)) content=[content];
101
- if(quote) content.unshift({type:'reply',data:{id:typeof quote==="boolean"?result.$id:quote}})
102
- return await this.$sendMessage({
103
- ...result.$channel,
104
- context:'icqq',
105
- bot:`${this.uin}`,
106
- content
107
- })
108
- }
109
- })
110
- return result
111
- }
112
- async $recallMessage(id:string):Promise<void> {
113
- await this.deleteMsg(id)
114
- }
115
- async $sendMessage(options: SendOptions): Promise<string> {
116
- options=await plugin.app.handleBeforeSend(options)
117
- switch (options.type){
118
- case 'private':{
119
- const result= await this.sendPrivateMsg(Number(options.id),IcqqBot.toSendable(options.content))
120
- plugin.logger.info(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(options.content)}`)
121
- return result.message_id.toString()
122
- break;
123
- }
124
- case "group":{
125
- const result=await this.sendGroupMsg(Number(options.id),IcqqBot.toSendable(options.content))
126
- plugin.logger.info(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(options.content)}`)
127
- return result.message_id.toString()
128
- break;
129
- }
130
- default:
131
- throw new Error(`unsupported channel type ${options.type}`)
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
- export namespace IcqqBot{
137
- const allowTypes=[
138
- 'text',
139
- 'face',
140
- 'image',
141
- 'record',
142
- 'audio',
143
- 'dice',
144
- 'rps',
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
- registerAdapter(new Adapter('icqq',IcqqBot))
179
-
180
- useContext('web', (web) => {
181
- // 注册ICQQ适配器的客户端入口文件
182
- const dispose = web.addEntry({
183
- production: path.resolve(import.meta.dirname, '../dist/index.js'),
184
- development: path.resolve(import.meta.dirname, '../client/index.tsx')
185
- })
186
- return dispose
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
- useContext('router','icqq', (router,icqq) => {
189
- router.get('/api/icqq/bots', async (ctx) => {
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
- const bots = Array.from(icqq.bots.values())
192
-
193
- if (bots.length === 0) {
194
- ctx.body = {
195
- success: true,
196
- data: [],
197
- message: '暂无ICQQ机器人实例'
198
- }
199
- return
200
- }
201
-
202
- const result = bots.map(bot => {
203
- try {
204
- return {
205
- name: bot.$config.name,
206
- connected: bot.$connected || false,
207
- groupCount: bot.gl?.size || 0,
208
- friendCount: bot.fl?.size || 0,
209
- receiveCount: bot.stat?.recv_msg_cnt || 0,
210
- sendCount: bot.stat?.sent_msg_cnt || 0,
211
- loginMode: bot.$config.password ? 'password' : 'qrcode',
212
- status: bot.$connected ? 'online' : 'offline',
213
- lastActivity: new Date().toISOString()
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
+ });