node-karin 0.4.1 → 0.5.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.
@@ -17,6 +17,17 @@ multi_progress: false
17
17
  # 控制台触发插件日志颜色 十六进制 默认#FFFF00 不支持热更新
18
18
  log_color: "#E1D919"
19
19
 
20
+ # input适配器配置 以下所有配置均不支持热更新
21
+ AdapterInput:
22
+ # 是否启用
23
+ enable: true
24
+ # 是否将语音、图片、视频消息转为文件 转为文件后可通过url访问
25
+ msgToFile: true
26
+ # url访问token 如果为 AdapterInput 每次启动后会重新生成
27
+ token: "AdapterInput"
28
+ # 访问ip
29
+ ip: 127.0.0.1
30
+
20
31
  # ffmpeg配置 用于音视频处理
21
32
  ffmpeg_path:
22
33
  ffprobe_path:
@@ -0,0 +1,63 @@
1
+ import { KarinAdapter } from '../../types/adapter.js';
2
+ import { contact, KarinElement } from '../../types/index.js';
3
+ /**
4
+ * - 标准输入输出适配器
5
+ */
6
+ export declare class AdapterInput implements KarinAdapter {
7
+ #private;
8
+ socket: WebSocket;
9
+ account: KarinAdapter['account'];
10
+ adapter: KarinAdapter['adapter'];
11
+ version: KarinAdapter['version'];
12
+ constructor();
13
+ get self_id(): string;
14
+ stdin(): void;
15
+ logger(level: 'info' | 'error' | 'trace' | 'debug' | 'mark' | 'warn' | 'fatal', ...args: any[]): void;
16
+ GetVersion(): Promise<{
17
+ name: string;
18
+ app_name: string;
19
+ version: string;
20
+ }>;
21
+ SendMessage(_contact: contact, elements: Array<KarinElement>): Promise<{
22
+ message_id: string;
23
+ }>;
24
+ getAvatarUrl(): string;
25
+ getGroupAvatar(): string;
26
+ GetCurrentAccount(): Promise<{
27
+ account_uid: string;
28
+ account_uin: string;
29
+ account_name: string;
30
+ }>;
31
+ GetEssenceMessageList(): Promise<any>;
32
+ DownloadForwardMessage(): Promise<any>;
33
+ SetEssenceMessage(): Promise<any>;
34
+ DeleteEssenceMessage(): Promise<any>;
35
+ SetFriendApplyResult(): Promise<any>;
36
+ SetGroupApplyResultRequest(): Promise<any>;
37
+ SetInvitedJoinGroupResult(): Promise<any>;
38
+ ReactMessageWithEmojiRequest(): Promise<any>;
39
+ UploadPrivateFile(): Promise<any>;
40
+ UploadGroupFile(): Promise<any>;
41
+ UploadForwardMessage(): Promise<any>;
42
+ sendForwardMessage(): Promise<any>;
43
+ SendMessageByResId(): Promise<any>;
44
+ RecallMessage(): Promise<any>;
45
+ GetMessage(): Promise<any>;
46
+ GetHistoryMessage(): Promise<any>;
47
+ VoteUser(): Promise<any>;
48
+ KickMember(): Promise<any>;
49
+ BanMember(): Promise<any>;
50
+ SetGroupWholeBan(): Promise<any>;
51
+ SetGroupAdmin(): Promise<any>;
52
+ ModifyMemberCard(): Promise<any>;
53
+ ModifyGroupName(): Promise<any>;
54
+ LeaveGroup(): Promise<any>;
55
+ SetGroupUniqueTitle(): Promise<any>;
56
+ GetStrangerProfileCard(): Promise<any>;
57
+ GetFriendList(): Promise<any>;
58
+ GetGroupInfo(): Promise<any>;
59
+ GetGroupList(): Promise<any>;
60
+ GetGroupMemberInfo(): Promise<any>;
61
+ GetGroupMemberList(): Promise<any>;
62
+ GetGroupHonor(): Promise<any>;
63
+ }
@@ -0,0 +1,188 @@
1
+ import fs from 'fs';
2
+ import { randomUUID } from 'crypto';
3
+ import { listener } from '../../core/index.js';
4
+ import { KarinMessage } from '../../event/index.js';
5
+ import { config, common, YamlEditor } from '../../utils/index.js';
6
+ const { enable, msgToFile, token: oldToken, ip } = config.Config.AdapterInput;
7
+ let token = oldToken;
8
+ if (oldToken === 'AdapterInput') {
9
+ try {
10
+ token = randomUUID();
11
+ const yaml = new YamlEditor('./config/config/config.yaml');
12
+ const data = yaml.get('AdapterInput');
13
+ if (!data) {
14
+ const yaml1 = new YamlEditor('./config/defSet/config.yaml');
15
+ const data1 = yaml1.get('AdapterInput');
16
+ data1.token = token;
17
+ yaml.set('AdapterInput', data1);
18
+ }
19
+ else {
20
+ data.token = token;
21
+ yaml.set('AdapterInput', data);
22
+ }
23
+ yaml.save();
24
+ }
25
+ catch (e) {
26
+ logger.error('AdapterInput token更换失败,请手动更换token');
27
+ }
28
+ }
29
+ // 清空文件夹
30
+ fs.readdirSync('./temp/input').forEach((file) => {
31
+ fs.unlinkSync(`./temp/input/${file}`);
32
+ });
33
+ /**
34
+ * - 标准输入输出适配器
35
+ */
36
+ export class AdapterInput {
37
+ #stdin;
38
+ socket;
39
+ account;
40
+ adapter;
41
+ version;
42
+ constructor() {
43
+ this.#stdin = false;
44
+ this.account = { uid: 'input', uin: 'input', name: 'input' };
45
+ this.adapter = { id: 'shell', name: 'input', type: 'internal', sub_type: 'internal', start_time: Date.now(), connect: '' };
46
+ this.version = { name: 'input', app_name: 'input', version: '1.0.0' };
47
+ }
48
+ get self_id() {
49
+ return this.account.uid;
50
+ }
51
+ stdin() {
52
+ if (this.#stdin)
53
+ return;
54
+ this.#stdin = true;
55
+ process.stdin.on('data', data => this.#input(data.toString()));
56
+ process.once('stdin.close', () => process.stdin.removeAllListeners('data'));
57
+ }
58
+ logger(level, ...args) {
59
+ logger.bot(level, this.account.uid || this.account.uin, ...args);
60
+ }
61
+ async #input(elements) {
62
+ const message = {
63
+ event: 'message',
64
+ self_id: 'input',
65
+ user_id: 'input',
66
+ time: Date.now(),
67
+ message_id: `input.${Date.now()}`,
68
+ message_seq: '',
69
+ sender: {
70
+ uid: 'input',
71
+ uin: 'input',
72
+ nick: 'input',
73
+ role: 'member',
74
+ },
75
+ elements: [{ type: 'text', text: elements }],
76
+ contact: {
77
+ scene: 'private',
78
+ peer: 'input',
79
+ sub_peer: '',
80
+ },
81
+ group_id: '',
82
+ raw_message: elements,
83
+ };
84
+ const e = new KarinMessage(message);
85
+ e.bot = this;
86
+ /**
87
+ * 快速回复 开发者不应该使用这个方法,应该使用由karin封装过后的reply方法
88
+ */
89
+ e.replyCallback = async (elements) => {
90
+ this.SendMessage(e.contact, elements);
91
+ return { message_id: e.message_id };
92
+ };
93
+ listener.emit('message', e);
94
+ }
95
+ async #MsgToFile(type, file) {
96
+ if (!msgToFile)
97
+ return '';
98
+ // 判断是否为string 如果是则继续判断是否为url、path
99
+ if (typeof file === 'string') {
100
+ if (file.startsWith('http'))
101
+ return file;
102
+ if (common.exists(file))
103
+ return file;
104
+ }
105
+ const buffer = await common.buffer(file);
106
+ // 生成文件名 根据type生成不同的文件后缀
107
+ const name = `${Date.now()}.${type === 'image' ? 'jpg' : type === 'voice' ? 'mp3' : 'file'}`;
108
+ // 写入文件
109
+ fs.writeFileSync(`./temp/input/${name}`, buffer);
110
+ return `[${type === 'image' ? '图片' : '语音'}: http://${ip}:${config.Server.http.port}/api/input?name=${name}&token=${token} ]`;
111
+ }
112
+ async GetVersion() {
113
+ const data = this.version;
114
+ delete data.name;
115
+ return data;
116
+ }
117
+ async SendMessage(_contact, elements) {
118
+ const text = [];
119
+ for (const v of elements) {
120
+ switch (v.type) {
121
+ case 'at':
122
+ text.push(`@${v.uid}`);
123
+ break;
124
+ case 'face':
125
+ text.push(`[表情:${v.id}]`);
126
+ break;
127
+ case 'text':
128
+ text.push(v.text);
129
+ break;
130
+ case 'image':
131
+ case 'voice':
132
+ text.push(await this.#MsgToFile(v.type, v.file));
133
+ break;
134
+ default:
135
+ text.push(`[未知消息类型:${JSON.stringify(v)}]`);
136
+ }
137
+ }
138
+ this.logger('info', text.join(''));
139
+ return { message_id: 'input' };
140
+ }
141
+ getAvatarUrl() {
142
+ return 'https://p.qlogo.cn/gh/967068507/967068507/0';
143
+ }
144
+ getGroupAvatar() {
145
+ return 'https://p.qlogo.cn/gh/967068507/967068507/0';
146
+ }
147
+ async GetCurrentAccount() {
148
+ return { account_uid: 'input', account_uin: 'input', account_name: 'input' };
149
+ }
150
+ async GetEssenceMessageList() { throw new Error('Method not implemented.'); }
151
+ async DownloadForwardMessage() { throw new Error('Method not implemented.'); }
152
+ async SetEssenceMessage() { throw new Error('Method not implemented.'); }
153
+ async DeleteEssenceMessage() { throw new Error('Method not implemented.'); }
154
+ async SetFriendApplyResult() { throw new Error('Method not implemented.'); }
155
+ async SetGroupApplyResultRequest() { throw new Error('Method not implemented.'); }
156
+ async SetInvitedJoinGroupResult() { throw new Error('Method not implemented.'); }
157
+ async ReactMessageWithEmojiRequest() { throw new Error('Method not implemented.'); }
158
+ async UploadPrivateFile() { throw new Error('Method not implemented.'); }
159
+ async UploadGroupFile() { throw new Error('Method not implemented.'); }
160
+ async UploadForwardMessage() { throw new Error('Method not implemented.'); }
161
+ async sendForwardMessage() { throw new Error('Method not implemented.'); }
162
+ async SendMessageByResId() { throw new Error('Method not implemented.'); }
163
+ async RecallMessage() { throw new Error('Method not implemented.'); }
164
+ async GetMessage() { throw new Error('Method not implemented.'); }
165
+ async GetHistoryMessage() { throw new Error('Method not implemented.'); }
166
+ async VoteUser() { throw new Error('Method not implemented.'); }
167
+ async KickMember() { throw new Error('Method not implemented.'); }
168
+ async BanMember() { throw new Error('Method not implemented.'); }
169
+ async SetGroupWholeBan() { throw new Error('Method not implemented.'); }
170
+ async SetGroupAdmin() { throw new Error('Method not implemented.'); }
171
+ async ModifyMemberCard() { throw new Error('Method not implemented.'); }
172
+ async ModifyGroupName() { throw new Error('Method not implemented.'); }
173
+ async LeaveGroup() { throw new Error('Method not implemented.'); }
174
+ async SetGroupUniqueTitle() { throw new Error('Method not implemented.'); }
175
+ async GetStrangerProfileCard() { throw new Error('Method not implemented.'); }
176
+ async GetFriendList() { throw new Error('Method not implemented.'); }
177
+ async GetGroupInfo() { throw new Error('Method not implemented.'); }
178
+ async GetGroupList() { throw new Error('Method not implemented.'); }
179
+ async GetGroupMemberInfo() { throw new Error('Method not implemented.'); }
180
+ async GetGroupMemberList() { throw new Error('Method not implemented.'); }
181
+ async GetGroupHonor() { throw new Error('Method not implemented.'); }
182
+ }
183
+ if (enable) {
184
+ const bot = new AdapterInput();
185
+ bot.stdin();
186
+ /** 注册bot */
187
+ listener.emit('bot', { type: 'internal', bot });
188
+ }
@@ -1,17 +1,13 @@
1
1
  import WebSocket from 'ws';
2
2
  import { IncomingMessage } from 'http';
3
3
  import { KarinAdapter } from '../../types/adapter.js';
4
- import { Scene, contact, OneBot11Api, KarinElement, OneBot11Segment, CustomNodeSegment, OneBot11ApiParamsType } from '../../types/index.js';
4
+ import { contact, OneBot11Api, KarinElement, OneBot11Segment, CustomNodeSegment, OneBot11ApiParamsType, GroupInfo, KarinNodeElement } from '../../types/index.js';
5
5
  /**
6
6
  * @class OneBot11
7
7
  * @extends KarinAdapter
8
8
  */
9
9
  export declare class OneBot11 implements KarinAdapter {
10
10
  #private;
11
- /**
12
- * 机器人QQ号
13
- */
14
- self_id: string;
15
11
  /**
16
12
  * - 重连次数 仅正向ws使用
17
13
  */
@@ -30,6 +26,7 @@ export declare class OneBot11 implements KarinAdapter {
30
26
  * @param connect - WebSocket连接地址
31
27
  */
32
28
  client(connect: string): Promise<void>;
29
+ get self_id(): string;
33
30
  /**
34
31
  * 获取当前登录号信息
35
32
  */
@@ -38,121 +35,14 @@ export declare class OneBot11 implements KarinAdapter {
38
35
  get isInit(): Promise<unknown>;
39
36
  /**
40
37
  * onebot11转karin
41
- * @param {Array<{type: string, data: any}>} data onebot11格式消息
42
38
  * @return karin格式消息
43
39
  * */
44
- AdapterConvertKarin(data: Array<OneBot11Segment>): (import("../../types/index.js").TextElement | import("../../types/index.js").AtElement | import("../../types/index.js").FaceElement | import("../../types/index.js").ReplyElement | import("../../types/index.js").ImageElement | import("../../types/index.js").VoiceElement | import("../../types/index.js").VideoElement | import("../../types/index.js").PokeElement | import("../../types/index.js").LocationElement | import("../../types/index.js").ForwardElement | import("../../types/index.js").ContactElement | import("../../types/index.js").JsonElement | import("../../types/index.js").XmlElement)[];
40
+ AdapterConvertKarin(data: Array<OneBot11Segment>): Array<KarinElement>;
45
41
  /**
46
42
  * karin转onebot11
47
43
  * @param data karin格式消息
48
- * @return {Array<{type: string, data: any}>} onebot11格式消息
49
44
  * */
50
- KarinConvertAdapter(data: Array<KarinElement>): (import("../../types/index.js").BubbleFaceElement | import("../../types/index.js").RecordElement | import("../../types/index.js").BasketballElement | import("../../types/index.js").DiceElement | import("../../types/index.js").RpsElement | import("../../types/index.js").WeatherElement | import("../../types/index.js").LocationElement | import("../../types/index.js").ShareElement | import("../../types/index.js").GiftElement | import("../../types/index.js").MarketFaceElement | import("../../types/index.js").ContactElement | import("../../types/index.js").RowElement | import("../../types/index.js").LongMsgElement | {
51
- type: string;
52
- data: {
53
- text: string;
54
- id?: undefined;
55
- qq?: undefined;
56
- file?: undefined;
57
- data?: undefined;
58
- magic?: undefined;
59
- type?: undefined;
60
- };
61
- } | {
62
- type: string;
63
- data: {
64
- id: number;
65
- text?: undefined;
66
- qq?: undefined;
67
- file?: undefined;
68
- data?: undefined;
69
- magic?: undefined;
70
- type?: undefined;
71
- };
72
- } | {
73
- type: string;
74
- data: {
75
- qq: string;
76
- text?: undefined;
77
- id?: undefined;
78
- file?: undefined;
79
- data?: undefined;
80
- magic?: undefined;
81
- type?: undefined;
82
- };
83
- } | {
84
- type: string;
85
- data: {
86
- id: string;
87
- text?: undefined;
88
- qq?: undefined;
89
- file?: undefined;
90
- data?: undefined;
91
- magic?: undefined;
92
- type?: undefined;
93
- };
94
- } | {
95
- type: "file" | "video" | "image";
96
- data: {
97
- file: string;
98
- text?: undefined;
99
- id?: undefined;
100
- qq?: undefined;
101
- data?: undefined;
102
- magic?: undefined;
103
- type?: undefined;
104
- };
105
- } | {
106
- type: "json" | "xml";
107
- data: {
108
- data: string;
109
- text?: undefined;
110
- id?: undefined;
111
- qq?: undefined;
112
- file?: undefined;
113
- magic?: undefined;
114
- type?: undefined;
115
- };
116
- } | {
117
- type: string;
118
- data: {
119
- file: string;
120
- magic: boolean;
121
- text?: undefined;
122
- id?: undefined;
123
- qq?: undefined;
124
- data?: undefined;
125
- type?: undefined;
126
- };
127
- } | {
128
- type: "markdown";
129
- data: {
130
- content: string;
131
- custom_template_id: string;
132
- params: Array<{
133
- key: string;
134
- values: Array<string>;
135
- }>;
136
- text?: undefined;
137
- id?: undefined;
138
- qq?: undefined;
139
- file?: undefined;
140
- data?: undefined;
141
- magic?: undefined;
142
- type?: undefined;
143
- };
144
- } | {
145
- type: string;
146
- data: {
147
- type: number;
148
- id: number;
149
- text?: undefined;
150
- qq?: undefined;
151
- file?: undefined;
152
- data?: undefined;
153
- magic?: undefined;
154
- };
155
- })[];
45
+ KarinConvertAdapter(data: Array<KarinElement>): Array<OneBot11Segment>;
156
46
  /**
157
47
  * 专属当前Bot的日志打印方法
158
48
  */
@@ -202,36 +92,30 @@ export declare class OneBot11 implements KarinAdapter {
202
92
  * @param elements - nodes
203
93
  * @returns - 资源id
204
94
  * */
205
- UploadForwardMessage(contact: {
206
- scene: Scene;
207
- peer: string;
208
- }, elements: any[]): Promise<any>;
95
+ UploadForwardMessage(contact: contact, elements: KarinNodeElement[]): Promise<any>;
209
96
  /**
210
97
  * 通过资源id发送转发消息
211
98
  * @param contact - 联系人信息
212
99
  * @param id - 资源id
213
100
  * */
214
- SendMessageByResId(contact: {
215
- scene: Scene;
216
- peer: string;
217
- }, id: any): Promise<{
101
+ SendMessageByResId(contact: contact, id: string): Promise<{
218
102
  message_id: any;
219
103
  message_time: number;
220
104
  }>;
221
105
  /**
222
106
  * 撤回消息
223
- * @param {null} [_contact] - ob11无需提供contact参数
107
+ * @param _contact - ob11无需提供contact参数
224
108
  * @param message_id - 消息ID
225
109
  * @returns {Promise<null>}
226
110
  */
227
111
  RecallMessage(_contact: contact, message_id: string): Promise<any>;
228
112
  /**
229
113
  * 获取消息
230
- * @param {null} [_contact] - ob11无需提供contact参数
114
+ * @param _contact - ob11无需提供contact参数
231
115
  * @param message_id - 消息ID
232
116
  * @returns {Promise<object>} - 消息内容
233
117
  */
234
- GetMessage(_contact: any, message_id: any): Promise<any>;
118
+ GetMessage(_contact: contact, message_id: string): Promise<any>;
235
119
  /**
236
120
  * 获取msg_id获取历史消息
237
121
  * @description 此api各平台实现不同,暂时废弃
@@ -242,7 +126,7 @@ export declare class OneBot11 implements KarinAdapter {
242
126
  message_seq: any;
243
127
  contact: contact;
244
128
  sender: any;
245
- elements: (import("../../types/index.js").TextElement | import("../../types/index.js").AtElement | import("../../types/index.js").FaceElement | import("../../types/index.js").ReplyElement | import("../../types/index.js").ImageElement | import("../../types/index.js").VoiceElement | import("../../types/index.js").VideoElement | import("../../types/index.js").PokeElement | import("../../types/index.js").LocationElement | import("../../types/index.js").ForwardElement | import("../../types/index.js").ContactElement | import("../../types/index.js").JsonElement | import("../../types/index.js").XmlElement)[];
129
+ elements: KarinElement[];
246
130
  }[]>;
247
131
  /**
248
132
  * 获取合并转发消息
@@ -340,18 +224,8 @@ export declare class OneBot11 implements KarinAdapter {
340
224
  * 获取群信息
341
225
  * @param group_id - 群号
342
226
  * @param no_cache - 是否不使用缓存
343
- * @returns {Promise<IGroupInfo>} - 群信息
344
- */
345
- GetGroupInfo(group_id: string, no_cache?: boolean): Promise<{
346
- group_id: any;
347
- group_name: any;
348
- group_remark: any;
349
- max_member_count: any;
350
- member_count: any;
351
- group_uin: any;
352
- admins: any;
353
- owner: any;
354
- }>;
227
+ */
228
+ GetGroupInfo(group_id: string, no_cache?: boolean): Promise<GroupInfo>;
355
229
  /**
356
230
  * 获取群列表
357
231
  */
@@ -12,10 +12,6 @@ export class OneBot11 {
12
12
  * 是否初始化
13
13
  */
14
14
  #init = false;
15
- /**
16
- * 机器人QQ号
17
- */
18
- self_id;
19
15
  /**
20
16
  * - 重连次数 仅正向ws使用
21
17
  */
@@ -25,7 +21,6 @@ export class OneBot11 {
25
21
  adapter;
26
22
  version;
27
23
  constructor() {
28
- this.self_id = '';
29
24
  this.index = 0;
30
25
  this.account = { uid: '', uin: '', name: '' };
31
26
  this.adapter = { id: 'QQ', name: 'OneBot11', type: 'ws', sub_type: 'internal', start_time: Date.now(), connect: '' };
@@ -108,6 +103,9 @@ export class OneBot11 {
108
103
  await this.getSelf();
109
104
  this.#init = true;
110
105
  }
106
+ get self_id() {
107
+ return this.account.uid || this.account.uin;
108
+ }
111
109
  /**
112
110
  * 获取当前登录号信息
113
111
  */
@@ -537,7 +535,6 @@ export class OneBot11 {
537
535
  }
538
536
  /**
539
537
  * onebot11转karin
540
- * @param {Array<{type: string, data: any}>} data onebot11格式消息
541
538
  * @return karin格式消息
542
539
  * */
543
540
  AdapterConvertKarin(data) {
@@ -593,12 +590,9 @@ export class OneBot11 {
593
590
  /**
594
591
  * karin转onebot11
595
592
  * @param data karin格式消息
596
- * @return {Array<{type: string, data: any}>} onebot11格式消息
597
593
  * */
598
594
  KarinConvertAdapter(data) {
599
595
  const elements = [];
600
- // const selfUin = this.account.uin
601
- // const selfNick = this.account.name
602
596
  for (const i of data) {
603
597
  switch (i.type) {
604
598
  case 'text':
@@ -619,37 +613,35 @@ export class OneBot11 {
619
613
  elements.push({ type: i.type, data: { file: i.file } });
620
614
  break;
621
615
  }
622
- case 'xml':
616
+ case 'xml': {
617
+ elements.push({ type: 'xml', data: { data: i.data } });
618
+ break;
619
+ }
623
620
  case 'json': {
624
- elements.push({ type: i.type, data: { data: i.data } });
621
+ elements.push({ type: 'json', data: { data: i.data } });
625
622
  break;
626
623
  }
627
- // case 'node': {
628
- // let { type, user_id = selfUin, nickname = selfNick, content } = i
629
- // content = this.KarinConvertAdapter(content)
630
- // elements.push({ type, data: { uin: user_id, name: nickname, content } })
631
- // break
632
- // }
633
624
  case 'forward': {
634
625
  elements.push({ type: 'forward', data: { id: i.res_id } });
635
626
  break;
636
627
  }
628
+ case 'record':
637
629
  case 'voice': {
638
630
  elements.push({ type: 'record', data: { file: i.file, magic: i.magic || false } });
639
631
  break;
640
632
  }
641
633
  case 'music': {
642
- // if (i.platform) {
643
- // elements.push({ type: 'music', data: { type: i.platform, id: i.id } })
644
- // } else {
645
- // const { url, audio, title, content, image } = i
646
- // elements.push({ type: 'music', data: { type: 'custom', url, audio, title, content, image } })
647
- // }
634
+ if (i.id) {
635
+ elements.push({ type: 'music', data: { type: i.platform, id: i.id } });
636
+ }
637
+ else {
638
+ const { url, audio, title, author, pic } = i;
639
+ elements.push({ type: 'music', data: { type: 'custom', url, audio, title, content: author, image: pic } });
640
+ }
648
641
  break;
649
642
  }
650
643
  case 'button': {
651
- // todo
652
- // elements.push({ type: 'button', data: { buttons: i.buttons } })
644
+ elements.push({ type: 'button', data: i.data });
653
645
  break;
654
646
  }
655
647
  case 'markdown': {
@@ -657,19 +649,51 @@ export class OneBot11 {
657
649
  elements.push({ type, data: { ...data } });
658
650
  break;
659
651
  }
660
- // case 'rows': {
661
- // for (const val of i.rows) {
662
- // elements.push({ type: 'button', data: { buttons: val.buttons } })
663
- // }
664
- // break
665
- // }
652
+ case 'rows': {
653
+ for (const val of i.rows) {
654
+ elements.push({ type: 'button', data: val.data });
655
+ }
656
+ break;
657
+ }
666
658
  case 'poke': {
667
659
  elements.push({ type: 'poke', data: { type: i.poke_type, id: i.id } });
668
660
  break;
669
661
  }
662
+ case 'bubble_face': {
663
+ elements.push({ type: 'bubble_face', data: { id: i.id, count: i.count } });
664
+ break;
665
+ }
666
+ case 'contact': {
667
+ elements.push({ type: 'contact', data: { type: i.scene, id: i.peer } });
668
+ break;
669
+ }
670
+ case 'location': {
671
+ elements.push({ type: 'location', data: { lat: i.lat, lon: i.lon, title: i.title, content: i.address } });
672
+ break;
673
+ }
674
+ case 'long_msg':
675
+ case 'basketball':
676
+ case 'dice':
677
+ case 'market_face':
678
+ case 'rps': {
679
+ elements.push({ type: i.type, data: { id: i.id } });
680
+ break;
681
+ }
682
+ case 'gift': {
683
+ elements.push({ type: 'gift', data: { qq: i.qq, id: i.id } });
684
+ break;
685
+ }
686
+ case 'share': {
687
+ elements.push({ type: 'share', data: { url: i.url, title: i.title, content: i.content, image: i.image } });
688
+ break;
689
+ }
690
+ case 'weather': {
691
+ elements.push({ type: 'weather', data: { city: i.city, type: i.type } });
692
+ break;
693
+ }
670
694
  default: {
671
695
  elements.push(i);
672
- logger.info(i);
696
+ break;
673
697
  }
674
698
  }
675
699
  }
@@ -750,7 +774,14 @@ export class OneBot11 {
750
774
  }
751
775
  const { scene, peer } = contact;
752
776
  const message_type = scene === 'group' ? 'group_id' : 'user_id';
753
- const messages = this.KarinConvertAdapter(elements);
777
+ const messages = [];
778
+ const selfUin = this.account.uin;
779
+ const selfNick = this.account.name;
780
+ for (const i of elements) {
781
+ const { type, user_id, nickname, content: contents } = i;
782
+ const content = this.KarinConvertAdapter(contents);
783
+ messages.push({ type, data: { uin: user_id || selfUin, name: nickname || selfNick, content } });
784
+ }
754
785
  const params = { [message_type]: String(peer), messages };
755
786
  return await this.SendApi('send_forward_msg', params);
756
787
  }
@@ -770,7 +801,7 @@ export class OneBot11 {
770
801
  }
771
802
  /**
772
803
  * 撤回消息
773
- * @param {null} [_contact] - ob11无需提供contact参数
804
+ * @param _contact - ob11无需提供contact参数
774
805
  * @param message_id - 消息ID
775
806
  * @returns {Promise<null>}
776
807
  */
@@ -779,7 +810,7 @@ export class OneBot11 {
779
810
  }
780
811
  /**
781
812
  * 获取消息
782
- * @param {null} [_contact] - ob11无需提供contact参数
813
+ * @param _contact - ob11无需提供contact参数
783
814
  * @param message_id - 消息ID
784
815
  * @returns {Promise<object>} - 消息内容
785
816
  */
@@ -991,22 +1022,8 @@ export class OneBot11 {
991
1022
  * 获取群信息
992
1023
  * @param group_id - 群号
993
1024
  * @param no_cache - 是否不使用缓存
994
- * @returns {Promise<IGroupInfo>} - 群信息
995
1025
  */
996
1026
  async GetGroupInfo(group_id, no_cache = false) {
997
- /**
998
- * @type {{
999
- * group_id: number,
1000
- * group_name: string,
1001
- * group_memo: string,
1002
- * group_remark: string,
1003
- * group_create_time: number,
1004
- * group_level: number,
1005
- * member_count: number,
1006
- * max_member_count: number,
1007
- * admins: number[]
1008
- * }}
1009
- */
1010
1027
  const groupInfo = await this.SendApi('get_group_info', { group_id, no_cache });
1011
1028
  return {
1012
1029
  group_id: groupInfo.group_id,
@@ -7,3 +7,4 @@ export * from './plugin.app.js';
7
7
  export * from './plugin.loader.js';
8
8
  export * from './process.js';
9
9
  export * from './server.js';
10
+ import '../adapter/input/index.js';
package/lib/core/index.js CHANGED
@@ -7,3 +7,4 @@ export * from './plugin.app.js'
7
7
  export * from './plugin.loader.js'
8
8
  export * from './process.js'
9
9
  export * from './server.js'
10
+ import '../adapter/input/index.js'
@@ -2,6 +2,8 @@ import { EventEmitter } from 'events'
2
2
  import { pluginLoader } from './plugin.loader.js'
3
3
  import { common, logger, config } from '../utils/index.js'
4
4
  import { MessageHandler } from '../event/message.handler.js'
5
+ import NoticeHandler from '../event/notice.handler.js'
6
+ import RequestHandler from '../event/request.handler.js'
5
7
  /**
6
8
  * 监听器管理
7
9
  */
@@ -33,11 +35,13 @@ class Listeners extends EventEmitter {
33
35
  this.addAdapter(data)
34
36
  })
35
37
  this.on('bot', data => {
36
- this.addBot(data)
38
+ if (!this.addBot(data)) { return }
37
39
  logger.info(`[机器人][注册][${data.type}] ` + logger.green(`[account:${data.bot.account.uid || data.bot.account.uin}(${data.bot.account.name})]`))
38
40
  this.emit('karin:online', data.bot.account.uid || data.bot.account.uin)
39
41
  })
40
42
  this.on('message', data => new MessageHandler(data))
43
+ this.on('notice', data => new NoticeHandler(data))
44
+ this.on('request', data => new RequestHandler(data))
41
45
  }
42
46
 
43
47
  /**
@@ -47,7 +51,7 @@ class Listeners extends EventEmitter {
47
51
  this.index++
48
52
  const index = this.index
49
53
  if (!data.bot) {
50
- logger.error('[Bot管理][注册] 注册失败: Bot实例不能为空')
54
+ logger.error('[Bot管理][注册] 注册失败: Bot实例不能为空', JSON.stringify(data))
51
55
  return false
52
56
  }
53
57
  this.list.push({ index, type: data.type, bot: data.bot })
@@ -76,6 +76,32 @@ export const server = new (class Server {
76
76
  process.exit()
77
77
  }
78
78
  })
79
+ /** 控制台适配器 */
80
+ this.app.get('/api/input', (req, res) => {
81
+ const name = req.query.name
82
+ const token = req.query.token
83
+ if (!name || !token) {
84
+ logger.error('[HTTP][input] 缺少参数', req.query)
85
+ return res.status(403).json({ error: '禁止访问', message: '缺少参数' })
86
+ }
87
+ // 禁止键入向上级目录
88
+ if (name.includes('/')) {
89
+ logger.error('[HTTP][input] 无效的文件名', name)
90
+ return res.status(403).json({ error: '禁止访问', message: '无效的文件名' })
91
+ }
92
+ const CfgToken = config.Config.AdapterInput.token
93
+ if (CfgToken === 'AdapterInput' || CfgToken !== token) {
94
+ logger.error('[HTTP][input] 无效的令牌', token)
95
+ return res.status(403).json({ error: '禁止访问', message: '无效的令牌' })
96
+ }
97
+ const file = process.cwd() + `/temp/input/${name}`
98
+ if (!fs.existsSync(file)) {
99
+ logger.error('[HTTP][input] 文件不存在', file)
100
+ return res.status(404).json({ error: '文件不存在', message: '找不到指定文件' })
101
+ }
102
+ logger.info(`${logger.yellow('[HTTP][input]')} ${logger.green(token)} file:${file}`)
103
+ res.sendFile(file)
104
+ })
79
105
  /** 监听端口 */
80
106
  const { host, port } = config.Server.http
81
107
  this.server.listen(port, host, () => {
@@ -120,7 +120,7 @@ export default class EventHandler {
120
120
  if (this.e.isGroup) {
121
121
  review.GroupMsgPrint(this.e) && logger.bot('info', this.e.self_id, `${logger.green(`Send Group ${this.e.group_id}: `)}${ReplyLog}`)
122
122
  } else {
123
- logger.bot('info', this.e.self_id, `${logger.green(`Send private ${this.e.user_id}: `)}${ReplyLog}`)
123
+ this.e.self_id === 'input' && logger.bot('info', this.e.self_id, `${logger.green(`Send private ${this.e.user_id}: `)}${ReplyLog}`)
124
124
  }
125
125
  let message_id = ''
126
126
  try {
@@ -101,7 +101,7 @@ export class KarinEvent {
101
101
  constructor ({ event, self_id, user_id, group_id = '', time, contact, sender, sub_event, event_id }) {
102
102
  this.self_id = self_id
103
103
  this.user_id = user_id
104
- this.group_id = group_id
104
+ this.group_id = contact.scene === 'group' ? (contact.peer || group_id) : group_id
105
105
  this.time = time
106
106
  this.event = event
107
107
  this.event_id = event_id
package/lib/index.d.ts CHANGED
@@ -29,13 +29,14 @@ export declare const Renderer: {
29
29
  export declare const Cfg: {
30
30
  dir: string;
31
31
  _path: string;
32
- _pathDef: string;
32
+ npmCfgDir: string;
33
33
  change: Map<string, any>;
34
34
  watcher: Map<string, any>;
35
35
  review: boolean;
36
36
  logger: import("log4js").Logger;
37
37
  initCfg(): Promise<void>;
38
38
  getPlugins(): string[];
39
+ checkPath(path: string): void;
39
40
  dirPath(name: string, plugins: string[]): Promise<void>;
40
41
  timeout(type?: "ws" | "grpc"): number;
41
42
  readonly redis: import("./types/index.js").Redis;
@@ -50,7 +50,7 @@ export interface KarinAdapter {
50
50
  /**
51
51
  * - 适配器名称
52
52
  */
53
- name: 'ICQQ' | 'OneBot11' | 'OntBot12' | 'Kritor' | string;
53
+ name: 'ICQQ' | 'OneBot11' | 'OntBot12' | 'Kritor' | 'input' | string;
54
54
  /**
55
55
  * - 适配器类型
56
56
  */
@@ -130,6 +130,27 @@ export interface Config {
130
130
  * 管理员列表
131
131
  */
132
132
  admin: string[];
133
+ /**
134
+ *
135
+ */
136
+ AdapterInput: {
137
+ /**
138
+ * - 是否开启input适配器
139
+ */
140
+ enable: boolean;
141
+ /**
142
+ * - 是否将语音、图片、视频消息转为文件 转为文件后可通过url访问
143
+ */
144
+ msgToFile: boolean;
145
+ /**
146
+ * - url访问token 如果为 AdapterInput 每次启动后会重新生成
147
+ */
148
+ token: string;
149
+ /**
150
+ * - ip地址
151
+ */
152
+ ip: string;
153
+ };
133
154
  /**
134
155
  * 黑名单相关
135
156
  */
@@ -77,7 +77,7 @@ export declare const common: {
77
77
  * @param - 为true时,http地址会直接返回,否则会下载文件并转换为base64字符串
78
78
  * @returns 返回base64字符串
79
79
  */
80
- base64(file: string | Buffer | Readable, options?: {
80
+ base64(file: any, options?: {
81
81
  http: boolean;
82
82
  }): Promise<string>;
83
83
  /**
@@ -88,12 +88,11 @@ export declare const common: {
88
88
  stream(stream: Readable): Promise<Buffer>;
89
89
  /**
90
90
  * 将文件转换为Buffer对象
91
- * @param {string|Buffer|http|stream.Readable} file - 文件路径或Buffer对象、可读流对象、http地址、base64://字符串
92
- * @param {object} options - 附加数据
93
- * @param {boolean} options.http - 为true时,http地址会直接返回,否则会下载文件并转换为Buffer对象
94
- * @returns {Promise<Buffer>} - 返回Buffer对象
91
+ * @param file - 文件路径或Buffer对象、可读流对象、http地址、base64://字符串
92
+ * @param options - 选项
93
+ * @returns - 返回Buffer对象
95
94
  */
96
- buffer(file: string | Buffer | Readable, options?: {
95
+ buffer(file: any, options?: {
97
96
  http: boolean;
98
97
  }): Promise<Buffer | Error | string>;
99
98
  /**
@@ -233,10 +233,9 @@ export const common = new (class Common {
233
233
 
234
234
  /**
235
235
  * 将文件转换为Buffer对象
236
- * @param {string|Buffer|http|stream.Readable} file - 文件路径或Buffer对象、可读流对象、http地址、base64://字符串
237
- * @param {object} options - 附加数据
238
- * @param {boolean} options.http - 为true时,http地址会直接返回,否则会下载文件并转换为Buffer对象
239
- * @returns {Promise<Buffer>} - 返回Buffer对象
236
+ * @param file - 文件路径或Buffer对象、可读流对象、http地址、base64://字符串
237
+ * @param options - 选项
238
+ * @returns - 返回Buffer对象
240
239
  */
241
240
  async buffer (file, options = { http: false }) {
242
241
  if (typeof file !== 'string') {
@@ -6,7 +6,7 @@ import { Redis, App, Config, Server, Package, GroupCfg } from '../types/index.js
6
6
  export declare const config: {
7
7
  dir: string;
8
8
  _path: string;
9
- _pathDef: string;
9
+ npmCfgDir: string;
10
10
  change: Map<string, any>;
11
11
  watcher: Map<string, any>;
12
12
  review: boolean;
@@ -14,6 +14,10 @@ export declare const config: {
14
14
  /** 初始化配置 */
15
15
  initCfg(): Promise<void>;
16
16
  getPlugins(): string[];
17
+ /**
18
+ * 检查路径是否存在 不存在则创建
19
+ */
20
+ checkPath(path: string): void;
17
21
  /**
18
22
  * 为每一个插件建立对应的文件夹
19
23
  */
@@ -6,7 +6,7 @@ import { fs, yaml as Yaml, chokidar } from '../modules.js'
6
6
  export const config = new (class Cfg {
7
7
  dir
8
8
  _path
9
- _pathDef
9
+ npmCfgDir
10
10
  change
11
11
  watcher
12
12
  review
@@ -14,7 +14,7 @@ export const config = new (class Cfg {
14
14
  constructor () {
15
15
  this.dir = karinDir
16
16
  this._path = process.cwd() + '/config'
17
- this._pathDef = this.dir + '/config/defSet'
17
+ this.npmCfgDir = this.dir + '/config/defSet'
18
18
  /** 缓存 */
19
19
  this.change = new Map()
20
20
  /** 监听文件 */
@@ -26,18 +26,23 @@ export const config = new (class Cfg {
26
26
 
27
27
  /** 初始化配置 */
28
28
  async initCfg () {
29
- if (!fs.existsSync(this._path)) { fs.mkdirSync(this._path) }
30
- this._path = process.cwd() + '/config/config'
31
- if (!fs.existsSync(this._path)) { fs.mkdirSync(this._path) }
32
- const files = fs.readdirSync(this._pathDef).filter(file => file.endsWith('.yaml'))
33
- for (const file of files) {
34
- const path = `${this._path}/${file}`
35
- const pathDef = `${this._pathDef}/${file}`
36
- if (!fs.existsSync(path)) { fs.copyFileSync(pathDef, path) }
29
+ const list = [
30
+ this._path,
31
+ this._path + '/config',
32
+ process.cwd() + '/temp/input',
33
+ './plugins',
34
+ './plugins/karin-plugin-example',
35
+ ]
36
+ list.forEach(path => this.checkPath(path))
37
+ if (this.npmCfgDir !== (this._path + '/defSet').replace(/\\/g, '/')) {
38
+ const files = fs.readdirSync(this.npmCfgDir).filter(file => file.endsWith('.yaml'))
39
+ files.forEach(file => {
40
+ const path = `${this._path}/config/${file}`
41
+ const pathDef = `${this.npmCfgDir}/${file}`
42
+ if (!fs.existsSync(path)) { fs.copyFileSync(pathDef, path) }
43
+ })
37
44
  }
38
45
  // 创建插件文件夹文件夹
39
- if (!fs.existsSync('./plugins')) { fs.mkdirSync('./plugins') }
40
- if (!fs.existsSync('./plugins/karin-plugin-example')) { fs.mkdirSync('./plugins/karin-plugin-example') }
41
46
  const plugins = this.getPlugins()
42
47
  this.dirPath('data', plugins)
43
48
  this.dirPath('temp', plugins)
@@ -52,15 +57,22 @@ export const config = new (class Cfg {
52
57
  return files.filter(file => file.isDirectory() && (file.name.startsWith('karin-plugin-'))).map(dir => dir.name)
53
58
  }
54
59
 
60
+ /**
61
+ * 检查路径是否存在 不存在则创建
62
+ */
63
+ checkPath (path) {
64
+ if (!fs.existsSync(path)) { fs.mkdirSync(path) }
65
+ }
66
+
55
67
  /**
56
68
  * 为每一个插件建立对应的文件夹
57
69
  */
58
70
  async dirPath (name, plugins) {
59
71
  name = `./${name}`
60
- if (!fs.existsSync(name)) { fs.mkdirSync(name) }
72
+ this.checkPath(name)
61
73
  for (const plugin of plugins) {
62
74
  const path = `${name}/${plugin}`
63
- if (!fs.existsSync(path)) { fs.mkdirSync(path) }
75
+ this.checkPath(path)
64
76
  }
65
77
  }
66
78
 
@@ -14,7 +14,7 @@ export declare class YamlEditor {
14
14
  * @param path - 路径,用点号分隔,例如:'a.b.c'
15
15
  * @param value - 要设置的值
16
16
  */
17
- set(path: string | string[], value: string): null | undefined;
17
+ set(path: string | string[], value: string | boolean): null | undefined;
18
18
  /**
19
19
  * 向指定路径添加新值
20
20
  * @param path - 路径,用点号分隔,例如:'a.b.c'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-karin",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "基于 Kritor 进行开发的nodejs机器人框架",
6
6
  "homepage": "https://github.com/KarinJS/Karin",
@@ -59,6 +59,7 @@
59
59
  "dependencies": {
60
60
  "@grpc/grpc-js": "1.10.10",
61
61
  "@grpc/proto-loader": "0.7.13",
62
+ "@inquirer/prompts": "^5.0.7",
62
63
  "art-template": "4.13.2",
63
64
  "axios": "1.7.2",
64
65
  "chalk": "5.3.0",