@alemonjs/qq-bot 2.1.0 → 2.1.1
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/README.md +2 -0
- package/lib/config.d.ts +1 -0
- package/lib/format.d.ts +5 -0
- package/lib/format.js +94 -0
- package/lib/sends.js +61 -3
- package/lib/types.d.ts +48 -0
- package/lib/types.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
package/lib/config.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export declare const platform = "qq-bot";
|
|
|
3
3
|
export type Options = {
|
|
4
4
|
master_key?: string[];
|
|
5
5
|
master_id?: string[];
|
|
6
|
+
markdownToText?: boolean;
|
|
6
7
|
} & sdkOptions;
|
|
7
8
|
export declare const getQQBotConfig: () => Options;
|
|
8
9
|
export declare const getMaster: (UserId: string) => readonly [boolean, string];
|
package/lib/format.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DataEnums, DataMarkDown } from 'alemonjs';
|
|
2
|
+
export declare const markdownToText: (items: DataMarkDown["value"]) => string;
|
|
3
|
+
export declare const buttonsToText: (rows: any[]) => string;
|
|
4
|
+
export declare const markdownRawToText: (raw: string) => string;
|
|
5
|
+
export declare const dataEnumToText: (item: DataEnums) => string;
|
package/lib/format.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const markdownToText = (items) => {
|
|
2
|
+
return items
|
|
3
|
+
.map(item => {
|
|
4
|
+
switch (item.type) {
|
|
5
|
+
case 'MD.text':
|
|
6
|
+
return item.value;
|
|
7
|
+
case 'MD.title':
|
|
8
|
+
return `【${item.value}】\n`;
|
|
9
|
+
case 'MD.subtitle':
|
|
10
|
+
return `〖${item.value}〗\n`;
|
|
11
|
+
case 'MD.bold':
|
|
12
|
+
case 'MD.italic':
|
|
13
|
+
case 'MD.italicStar':
|
|
14
|
+
case 'MD.strikethrough':
|
|
15
|
+
return item.value;
|
|
16
|
+
case 'MD.link': {
|
|
17
|
+
const v = item.value;
|
|
18
|
+
return `${v.text}( ${v.url} )`;
|
|
19
|
+
}
|
|
20
|
+
case 'MD.image':
|
|
21
|
+
return '[图片]';
|
|
22
|
+
case 'MD.list':
|
|
23
|
+
return (item.value
|
|
24
|
+
.map(li => {
|
|
25
|
+
if (typeof li.value === 'object') {
|
|
26
|
+
return `${li.value.index}. ${li.value.text ?? ''}`;
|
|
27
|
+
}
|
|
28
|
+
return `· ${li.value}`;
|
|
29
|
+
})
|
|
30
|
+
.join('\n') + '\n');
|
|
31
|
+
case 'MD.blockquote':
|
|
32
|
+
return `> ${item.value}\n`;
|
|
33
|
+
case 'MD.divider':
|
|
34
|
+
return '————————\n';
|
|
35
|
+
case 'MD.newline':
|
|
36
|
+
return '\n';
|
|
37
|
+
case 'MD.code':
|
|
38
|
+
return item.value;
|
|
39
|
+
case 'MD.mention':
|
|
40
|
+
if (item.value === 'everyone') {
|
|
41
|
+
return '@全体成员';
|
|
42
|
+
}
|
|
43
|
+
return `@${item.value ?? ''}`;
|
|
44
|
+
case 'MD.content':
|
|
45
|
+
return item.value;
|
|
46
|
+
case 'MD.button':
|
|
47
|
+
return `[${item.value}]`;
|
|
48
|
+
default:
|
|
49
|
+
return String(item?.value ?? '');
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.join('');
|
|
53
|
+
};
|
|
54
|
+
const buttonsToText = (rows) => {
|
|
55
|
+
return rows
|
|
56
|
+
.map((row) => row.value.map((btn) => `[${btn.value}]`).join(' '))
|
|
57
|
+
.join('\n');
|
|
58
|
+
};
|
|
59
|
+
const markdownRawToText = (raw) => {
|
|
60
|
+
let text = raw;
|
|
61
|
+
text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, '[图片]');
|
|
62
|
+
text = text.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1');
|
|
63
|
+
text = text.replace(/^#{1,6}\s+/gm, '');
|
|
64
|
+
text = text.replace(/(\*{3}|_{3})([^*_]+)\1/g, '$2');
|
|
65
|
+
text = text.replace(/(\*{2}|_{2})([^*_]+)\1/g, '$2');
|
|
66
|
+
text = text.replace(/(?<!\*)\*(?!\*)([^*]+)(?<!\*)\*(?!\*)/g, '$1');
|
|
67
|
+
text = text.replace(/(?<!_)_(?!_)([^_]+)(?<!_)_(?!_)/g, '$1');
|
|
68
|
+
text = text.replace(/~~([^~]+)~~/g, '$1');
|
|
69
|
+
text = text.replace(/`([^`]+)`/g, '$1');
|
|
70
|
+
text = text.replace(/```[\s\S]*?```/g, match => {
|
|
71
|
+
return match.replace(/```\w*\n?/g, '').trim();
|
|
72
|
+
});
|
|
73
|
+
text = text.replace(/^>\s+/gm, '');
|
|
74
|
+
text = text.replace(/^[-*_]{3,}\s*$/gm, '————————');
|
|
75
|
+
text = text.replace(/^[\s]*[-*+]\s+/gm, '· ');
|
|
76
|
+
text = text.replace(/^[\s]*(\d+)\.\s+/gm, '$1. ');
|
|
77
|
+
return text.trim();
|
|
78
|
+
};
|
|
79
|
+
const dataEnumToText = (item) => {
|
|
80
|
+
switch (item.type) {
|
|
81
|
+
case 'MarkdownOriginal':
|
|
82
|
+
return markdownRawToText(String(item.value));
|
|
83
|
+
case 'Attachment':
|
|
84
|
+
return `[附件${item.options?.filename ? ': ' + item.options.filename : ''}]`;
|
|
85
|
+
case 'Audio':
|
|
86
|
+
return '[音频]';
|
|
87
|
+
case 'Video':
|
|
88
|
+
return '[视频]';
|
|
89
|
+
default:
|
|
90
|
+
return '';
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export { buttonsToText, dataEnumToText, markdownRawToText, markdownToText };
|
package/lib/sends.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import { createResult, ResultCode } from 'alemonjs';
|
|
3
3
|
import axios from 'axios';
|
|
4
|
+
import { dataEnumToText, markdownToText, buttonsToText } from './format.js';
|
|
5
|
+
import { getQQBotConfig } from './config.js';
|
|
4
6
|
|
|
5
7
|
const MAX_BUTTON_ROWS = 5;
|
|
6
8
|
const MAX_BUTTONS_PER_ROW = 5;
|
|
@@ -162,7 +164,13 @@ const formatMention = (item, mode) => {
|
|
|
162
164
|
return '';
|
|
163
165
|
};
|
|
164
166
|
const extractContent = (val, mode) => {
|
|
165
|
-
|
|
167
|
+
const nativeTypes = new Set([
|
|
168
|
+
'Mention', 'Text', 'Link',
|
|
169
|
+
'Image', 'ImageFile', 'ImageURL',
|
|
170
|
+
'Markdown', 'BT.group', 'ButtonTemplate',
|
|
171
|
+
'Ark.list', 'Ark.Card', 'Ark.BigCard'
|
|
172
|
+
]);
|
|
173
|
+
const nativeText = val
|
|
166
174
|
.filter(item => item.type === 'Mention' || item.type === 'Text' || item.type === 'Link')
|
|
167
175
|
.map(item => {
|
|
168
176
|
if (item.type === 'Link') {
|
|
@@ -177,6 +185,12 @@ const extractContent = (val, mode) => {
|
|
|
177
185
|
return '';
|
|
178
186
|
})
|
|
179
187
|
.join('');
|
|
188
|
+
const fallbackText = val
|
|
189
|
+
.filter(item => !nativeTypes.has(item.type))
|
|
190
|
+
.map(item => dataEnumToText(item))
|
|
191
|
+
.filter(Boolean)
|
|
192
|
+
.join('\n');
|
|
193
|
+
return [nativeText, fallbackText].filter(Boolean).join('\n');
|
|
180
194
|
};
|
|
181
195
|
const buildBaseParams = (tag, messageId, interactionTag) => {
|
|
182
196
|
if (tag === interactionTag) {
|
|
@@ -288,23 +302,53 @@ const resolveRichMediaUrl = async (images, uploadMedia) => {
|
|
|
288
302
|
}
|
|
289
303
|
return undefined;
|
|
290
304
|
};
|
|
305
|
+
const flattenMdToText = (content, val) => {
|
|
306
|
+
const mdItems = val.filter(item => item.type === 'Markdown');
|
|
307
|
+
const btnItems = val.filter(item => item.type === 'BT.group');
|
|
308
|
+
const parts = [content];
|
|
309
|
+
for (const item of mdItems) {
|
|
310
|
+
if (item.type === 'Markdown' && typeof item.value !== 'string') {
|
|
311
|
+
parts.push(markdownToText(item.value));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
for (const item of btnItems) {
|
|
315
|
+
if (item.type === 'BT.group' && typeof item.value !== 'string') {
|
|
316
|
+
parts.push(buttonsToText(item.value));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return parts.filter(Boolean).join('\n');
|
|
320
|
+
};
|
|
291
321
|
const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMessage, label) => {
|
|
322
|
+
const config = getQQBotConfig();
|
|
323
|
+
const mdToText = config.markdownToText === true;
|
|
292
324
|
const images = filterImages(val);
|
|
293
325
|
if (images.length > 0) {
|
|
294
326
|
const url = await resolveRichMediaUrl(images, uploadMedia);
|
|
295
327
|
if (!url) {
|
|
296
328
|
return [createResult(ResultCode.Fail, '图片上传失败', null)];
|
|
297
329
|
}
|
|
330
|
+
const imgContent = flattenMdToText(content, val);
|
|
298
331
|
const res = await sendMessage({
|
|
299
|
-
content,
|
|
332
|
+
content: imgContent,
|
|
300
333
|
media: { file_info: url },
|
|
301
334
|
msg_type: 7,
|
|
302
335
|
...baseParams
|
|
303
336
|
});
|
|
304
337
|
return [createResult(ResultCode.Ok, label, { id: res.id })];
|
|
305
338
|
}
|
|
339
|
+
if (mdToText) {
|
|
340
|
+
const textContent = flattenMdToText(content, val);
|
|
341
|
+
if (textContent) {
|
|
342
|
+
const res = await sendMessage({ content: textContent, msg_type: 0, ...baseParams });
|
|
343
|
+
return [createResult(ResultCode.Ok, label, { id: res.id })];
|
|
344
|
+
}
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
306
347
|
const mdParams = buildMdAndButtonsParams(val);
|
|
307
348
|
if (mdParams) {
|
|
349
|
+
if (mdParams.markdown?.content && content) {
|
|
350
|
+
mdParams.markdown.content = content + '\n' + mdParams.markdown.content;
|
|
351
|
+
}
|
|
308
352
|
const res = await sendMessage({ content, msg_type: 2, ...mdParams, ...baseParams });
|
|
309
353
|
return [createResult(ResultCode.Ok, label, { id: res.id })];
|
|
310
354
|
}
|
|
@@ -346,14 +390,28 @@ const resolveImageBuffer = async (images) => {
|
|
|
346
390
|
return null;
|
|
347
391
|
};
|
|
348
392
|
const sendGuildMessage = async (content, val, baseParams, sendMessage, label) => {
|
|
393
|
+
const config = getQQBotConfig();
|
|
394
|
+
const mdToText = config.markdownToText === true;
|
|
349
395
|
const images = filterImages(val);
|
|
350
396
|
if (images.length > 0) {
|
|
351
397
|
const imageBuffer = await resolveImageBuffer(images);
|
|
352
|
-
const
|
|
398
|
+
const imgContent = flattenMdToText(content, val);
|
|
399
|
+
const res = await sendMessage({ content: imgContent, ...baseParams }, imageBuffer);
|
|
353
400
|
return [createResult(ResultCode.Ok, label, { id: res?.id })];
|
|
354
401
|
}
|
|
402
|
+
if (mdToText) {
|
|
403
|
+
const textContent = flattenMdToText(content, val);
|
|
404
|
+
if (textContent) {
|
|
405
|
+
const res = await sendMessage({ content: textContent, ...baseParams });
|
|
406
|
+
return [createResult(ResultCode.Ok, label, { id: res?.id })];
|
|
407
|
+
}
|
|
408
|
+
return [];
|
|
409
|
+
}
|
|
355
410
|
const mdParams = buildMdAndButtonsParams(val);
|
|
356
411
|
if (mdParams) {
|
|
412
|
+
if (mdParams.markdown?.content && content) {
|
|
413
|
+
mdParams.markdown.content = content + '\n' + mdParams.markdown.content;
|
|
414
|
+
}
|
|
357
415
|
const res = await sendMessage({ content: '', ...mdParams, ...baseParams });
|
|
358
416
|
return [createResult(ResultCode.Ok, label, { id: res.id })];
|
|
359
417
|
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type DataArkListTip = {
|
|
2
|
+
type: 'Ark.listTip';
|
|
3
|
+
value: {
|
|
4
|
+
desc: string;
|
|
5
|
+
prompt: string;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
export type DataArkListItem = {
|
|
9
|
+
type: 'Ark.listItem';
|
|
10
|
+
value: string | {
|
|
11
|
+
title: string;
|
|
12
|
+
link: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export type DataArkListContent = {
|
|
16
|
+
type: 'Ark.listContent';
|
|
17
|
+
value: DataArkListItem[];
|
|
18
|
+
};
|
|
19
|
+
export type DataArkList = {
|
|
20
|
+
type: 'Ark.list';
|
|
21
|
+
value: [DataArkListTip, DataArkListContent];
|
|
22
|
+
};
|
|
23
|
+
export type DataArkCard = {
|
|
24
|
+
type: 'Ark.Card';
|
|
25
|
+
value: {
|
|
26
|
+
title: string;
|
|
27
|
+
cover: string;
|
|
28
|
+
link: string;
|
|
29
|
+
subtitle: string;
|
|
30
|
+
decs: string;
|
|
31
|
+
prompt: string;
|
|
32
|
+
metadecs: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
export type DataArkBigCard = {
|
|
36
|
+
type: 'Ark.BigCard';
|
|
37
|
+
value: {
|
|
38
|
+
title: string;
|
|
39
|
+
subtitle: string;
|
|
40
|
+
cover: string;
|
|
41
|
+
link: string;
|
|
42
|
+
prompt: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export type DataButtonTemplate = {
|
|
46
|
+
type: 'ButtonTemplate';
|
|
47
|
+
value: string;
|
|
48
|
+
};
|
package/lib/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|