koishi-plugin-share-links-analysis 0.5.4 → 0.6.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/lib/core.js +2 -0
- package/lib/index.js +80 -2
- package/lib/parsers/twitter.js +2 -1
- package/lib/parsers/xiaohongshu.js +3 -0
- package/lib/types.d.ts +2 -1
- package/lib/utils.d.ts +6 -1
- package/lib/utils.js +56 -1
- package/package.json +1 -1
package/lib/core.js
CHANGED
|
@@ -75,7 +75,9 @@ async function processLink(ctx, config, link, session) {
|
|
|
75
75
|
}
|
|
76
76
|
async function init(ctx, config) {
|
|
77
77
|
for (const parser of parsers) {
|
|
78
|
+
// @ts-ignore
|
|
78
79
|
if (typeof parser.init === 'function') {
|
|
80
|
+
// @ts-ignore
|
|
79
81
|
await parser.init(ctx, config);
|
|
80
82
|
}
|
|
81
83
|
}
|
package/lib/index.js
CHANGED
|
@@ -52,12 +52,15 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
52
52
|
parseLimit: koishi_1.Schema.number().default(3).description("单对话多链接解析上限"),
|
|
53
53
|
useNumeral: koishi_1.Schema.boolean().default(true).description("使用格式化数字 (如 10000 -> 1万)"),
|
|
54
54
|
showError: koishi_1.Schema.boolean().default(false).description("当链接不正确时提醒发送者"),
|
|
55
|
-
allow_sensitive: koishi_1.Schema.boolean().default(false).description("允许NSFW内容"),
|
|
56
55
|
}).description("高级解析设置"),
|
|
57
56
|
koishi_1.Schema.object({
|
|
58
57
|
proxy: koishi_1.Schema.string().description("代理设置"),
|
|
59
|
-
proxy_settings: koishi_1.Schema.object(Object.fromEntries(core_1.parsers_str.map(parser => [parser, koishi_1.Schema.boolean().default(false)]))),
|
|
58
|
+
proxy_settings: koishi_1.Schema.object(Object.fromEntries(core_1.parsers_str.map(parser => [parser, koishi_1.Schema.boolean().default(false).description(`对${parser}使用代理`)]))),
|
|
60
59
|
}).description("代理设置"),
|
|
60
|
+
koishi_1.Schema.object({
|
|
61
|
+
default_parsers: koishi_1.Schema.object(Object.fromEntries(core_1.parsers_str.map(parser => [parser, koishi_1.Schema.boolean().default(true).description(`启用${parser}解析器`)]))),
|
|
62
|
+
allow_sensitive: koishi_1.Schema.boolean().default(false).description("允许NSFW内容"),
|
|
63
|
+
}).description("默认解析器设置"),
|
|
61
64
|
koishi_1.Schema.object({
|
|
62
65
|
onebotReadDir: koishi_1.Schema.string().description('OneBot 实现 (如 NapCat) 所在的容器或环境提供的路径前缀。').default("/app/.config/QQ/NapCat/temp"),
|
|
63
66
|
localDownloadDir: koishi_1.Schema.string().description('与上述路径对应的、Koishi 所在的容器或主机可以访问的路径前缀。').default("/koishi/data/temp"),
|
|
@@ -72,12 +75,78 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
72
75
|
}).description("调试设置"),
|
|
73
76
|
]);
|
|
74
77
|
function apply(ctx, config) {
|
|
78
|
+
// @ts-ignore
|
|
75
79
|
ctx.model.extend('sla_cookie_cache', {
|
|
76
80
|
platform: 'string', // 平台名称,如 'xiaohongshu'
|
|
77
81
|
cookie: 'text', // 存储的 cookie 字符串
|
|
78
82
|
}, {
|
|
79
83
|
primary: 'platform' // 使用平台名称作为主键
|
|
80
84
|
});
|
|
85
|
+
// @ts-ignore
|
|
86
|
+
ctx.model.extend('sla_group_settings', {
|
|
87
|
+
guildId: 'string',
|
|
88
|
+
custom_parsers: 'json',
|
|
89
|
+
nsfw_enabled: 'boolean',
|
|
90
|
+
}, {
|
|
91
|
+
primary: 'guildId',
|
|
92
|
+
});
|
|
93
|
+
// 注册指令
|
|
94
|
+
const cmd = ctx.command('share', '分享解析插件配置', { authority: 1 })
|
|
95
|
+
.action(async ({ session }) => {
|
|
96
|
+
if (!session?.guildId || !session?.userId)
|
|
97
|
+
return '该指令只能在群组中使用。';
|
|
98
|
+
const { parsers, nsfw } = await (0, utils_1.getEffectiveSettings)(ctx, session.guildId, config);
|
|
99
|
+
const parserList = Object.entries(parsers)
|
|
100
|
+
.map(([name, enabled]) => `${enabled ? '✅' : '❌'} ${name}`)
|
|
101
|
+
.join('\n');
|
|
102
|
+
return `当前解析器状态:\n${parserList}\nNSFW 内容:${nsfw ? '✅' : '❌'}`.trim();
|
|
103
|
+
});
|
|
104
|
+
cmd.subcommand('.parsers [parser:string] [mode:string]', '查看/管理当前群的解析器状态', { authority: 1 })
|
|
105
|
+
.action(async ({ session }, parser, value) => {
|
|
106
|
+
if (!session?.guildId || !session?.userId)
|
|
107
|
+
return '该指令只能在群组中使用。';
|
|
108
|
+
if (!await (0, utils_1.isUserAdmin)(session, session.userId))
|
|
109
|
+
return '权限不足';
|
|
110
|
+
if (parser) {
|
|
111
|
+
if (!core_1.parsers_str.includes(parser))
|
|
112
|
+
return '请输入正确的解析器名称';
|
|
113
|
+
if (!value)
|
|
114
|
+
return '请输入正确的模式';
|
|
115
|
+
const mode = value.trim().toLowerCase() === 'true';
|
|
116
|
+
// @ts-ignore
|
|
117
|
+
const data = await ctx.database.get('sla_group_settings', session.guildId);
|
|
118
|
+
// @ts-ignore
|
|
119
|
+
const final_parsers = { ...data[0]?.custom_parsers, ...{ [parser]: mode } };
|
|
120
|
+
const record = { guildId: session.guildId, custom_parsers: final_parsers };
|
|
121
|
+
// @ts-ignore
|
|
122
|
+
await ctx.database.upsert('sla_group_settings', [record]);
|
|
123
|
+
}
|
|
124
|
+
await session.execute('share');
|
|
125
|
+
});
|
|
126
|
+
cmd.subcommand('.nsfw [value:string]', '设置是否允许 NSFW 内容', { authority: 1 })
|
|
127
|
+
.action(async ({ session }, value) => {
|
|
128
|
+
if (!session?.guildId || !session?.userId)
|
|
129
|
+
return '该指令只能在群组中使用。';
|
|
130
|
+
if (!await (0, utils_1.isUserAdmin)(session, session.userId))
|
|
131
|
+
return '权限不足';
|
|
132
|
+
if (value) {
|
|
133
|
+
const mode = value.trim().toLowerCase() === 'true';
|
|
134
|
+
const record = { guildId: session.guildId, nsfw_enabled: mode };
|
|
135
|
+
// @ts-ignore
|
|
136
|
+
await ctx.database.upsert('sla_group_settings', [record]);
|
|
137
|
+
}
|
|
138
|
+
await session.execute('share');
|
|
139
|
+
});
|
|
140
|
+
cmd.subcommand('.reset', '重置为全局默认设置', { authority: 1 })
|
|
141
|
+
.action(async ({ session }) => {
|
|
142
|
+
if (!session?.guildId || !session?.userId)
|
|
143
|
+
return '该指令只能在群组中使用。';
|
|
144
|
+
if (!await (0, utils_1.isUserAdmin)(session, session.userId))
|
|
145
|
+
return '权限不足';
|
|
146
|
+
// @ts-ignore
|
|
147
|
+
await ctx.database.remove('sla_group_settings', { guildId: session.guildId });
|
|
148
|
+
return '已重置为全局默认设置。';
|
|
149
|
+
});
|
|
81
150
|
const logger = ctx.logger('share-links-analysis');
|
|
82
151
|
const lastProcessedUrls = {};
|
|
83
152
|
ctx.on('ready', async () => {
|
|
@@ -94,6 +163,15 @@ function apply(ctx, config) {
|
|
|
94
163
|
return next();
|
|
95
164
|
let linkCount = 0;
|
|
96
165
|
for (const link of links) {
|
|
166
|
+
if (session.guildId) {
|
|
167
|
+
const settings = await (0, utils_1.getEffectiveSettings)(ctx, session.guildId, config);
|
|
168
|
+
if (!settings.parsers[link.platform])
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
if (!config.default_parsers[link.platform])
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
97
175
|
if (linkCount >= config.parseLimit) {
|
|
98
176
|
await session.send("已达到单次解析上限…");
|
|
99
177
|
break;
|
package/lib/parsers/twitter.js
CHANGED
|
@@ -82,7 +82,8 @@ async function process(ctx, config, link, session) {
|
|
|
82
82
|
await session.send(`Twitter API 错误: ${tweetData.error}`);
|
|
83
83
|
return null;
|
|
84
84
|
}
|
|
85
|
-
|
|
85
|
+
const enable_nsfw = await (0, utils_1.getEffectiveSettings)(ctx, session.guildId, config);
|
|
86
|
+
if (tweetData.possibly_sensitive && !enable_nsfw) {
|
|
86
87
|
await session.send('潜在的不合规内容,已停止发送');
|
|
87
88
|
return null;
|
|
88
89
|
}
|
|
@@ -112,6 +112,7 @@ async function init(ctx, config) {
|
|
|
112
112
|
const filteredCookies = finalCookies.filter((c) => c.name !== 'acw_tc');
|
|
113
113
|
// 使用过滤后的 cookie 数组来生成字符串
|
|
114
114
|
const cookieString = filteredCookies.map((c) => `${c.name}=${c.value}`).join('; ');
|
|
115
|
+
// @ts-ignore
|
|
115
116
|
await ctx.database.upsert('sla_cookie_cache', [{ platform: platformId, cookie: cookieString }]);
|
|
116
117
|
logger.info('成功执行两步刷新策略并缓存了小红书 Cookie!');
|
|
117
118
|
return true;
|
|
@@ -200,7 +201,9 @@ async function process(ctx, config, link, session) {
|
|
|
200
201
|
if (config.logLevel === 'full')
|
|
201
202
|
logger.info(`正在抓取小红书页面: ${urlToFetch}`);
|
|
202
203
|
try {
|
|
204
|
+
// @ts-ignore
|
|
203
205
|
const dbCache = await ctx.database.get('sla_cookie_cache', platformId);
|
|
206
|
+
// @ts-ignore
|
|
204
207
|
let currentCookie = (dbCache && dbCache.length > 0) ? dbCache[0].cookie : '';
|
|
205
208
|
const requestHeaders = {
|
|
206
209
|
'User-Agent': config.userAgent,
|
package/lib/types.d.ts
CHANGED
|
@@ -30,9 +30,10 @@ export interface PluginConfig {
|
|
|
30
30
|
parseLimit: number;
|
|
31
31
|
useNumeral: boolean;
|
|
32
32
|
showError: boolean;
|
|
33
|
-
allow_sensitive: boolean;
|
|
34
33
|
proxy: string;
|
|
35
34
|
proxy_settings: object;
|
|
35
|
+
default_parsers: object;
|
|
36
|
+
allow_sensitive: boolean;
|
|
36
37
|
onebotReadDir: string;
|
|
37
38
|
localDownloadDir: string;
|
|
38
39
|
userAgent: string;
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ParsedInfo, PluginConfig } from './types';
|
|
2
|
-
import { Logger, Session } from "koishi";
|
|
2
|
+
import { Context, Logger, Session } from "koishi";
|
|
3
3
|
/**
|
|
4
4
|
* 将数字格式化为易读的字符串(如 万、亿)
|
|
5
5
|
* @param num 数字
|
|
@@ -9,5 +9,10 @@ import { Logger, Session } from "koishi";
|
|
|
9
9
|
export declare function numeral(num: number, config: PluginConfig): string;
|
|
10
10
|
export declare function escapeHtml(str: string): string;
|
|
11
11
|
export declare function getFileSize(url: string, proxy: string | undefined, userAgent: string | undefined, logger: Logger): Promise<number | null>;
|
|
12
|
+
export declare function getEffectiveSettings(ctx: Context, guildId: string | undefined, config: PluginConfig): Promise<{
|
|
13
|
+
parsers: any;
|
|
14
|
+
nsfw: any;
|
|
15
|
+
}>;
|
|
16
|
+
export declare function isUserAdmin(session: Session, userId: string): Promise<boolean>;
|
|
12
17
|
export declare function sendResult_plain(session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger): Promise<void>;
|
|
13
18
|
export declare function sendResult_forward(session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger, mixed_sending?: boolean): Promise<void>;
|
package/lib/utils.js
CHANGED
|
@@ -39,6 +39,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.numeral = numeral;
|
|
40
40
|
exports.escapeHtml = escapeHtml;
|
|
41
41
|
exports.getFileSize = getFileSize;
|
|
42
|
+
exports.getEffectiveSettings = getEffectiveSettings;
|
|
43
|
+
exports.isUserAdmin = isUserAdmin;
|
|
42
44
|
exports.sendResult_plain = sendResult_plain;
|
|
43
45
|
exports.sendResult_forward = sendResult_forward;
|
|
44
46
|
const koishi_1 = require("koishi");
|
|
@@ -268,6 +270,48 @@ async function tryGetRequestForSize(url, proxy, userAgent, logger) {
|
|
|
268
270
|
});
|
|
269
271
|
});
|
|
270
272
|
}
|
|
273
|
+
async function getEffectiveSettings(ctx, guildId, config) {
|
|
274
|
+
if (guildId == undefined) {
|
|
275
|
+
return {
|
|
276
|
+
parsers: config.default_parsers,
|
|
277
|
+
nsfw: config.allow_sensitive
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
// @ts-ignore
|
|
281
|
+
const data = await ctx.database.get('sla_group_settings', guildId);
|
|
282
|
+
const record = data[0];
|
|
283
|
+
// 合并:自定义设置覆盖默认
|
|
284
|
+
// @ts-ignore
|
|
285
|
+
const effectiveParsers = { ...config.default_parsers, ...record?.custom_parsers ? record.custom_parsers : {} };
|
|
286
|
+
// @ts-ignore
|
|
287
|
+
const nsfw_enabled = record?.nsfw_enabled ? record.nsfw_enabled : config.allow_sensitive;
|
|
288
|
+
return {
|
|
289
|
+
parsers: effectiveParsers,
|
|
290
|
+
nsfw: nsfw_enabled
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
async function isUserAdmin(session, userId) {
|
|
294
|
+
if (!session.guildId)
|
|
295
|
+
return false;
|
|
296
|
+
// @ts-ignore
|
|
297
|
+
if (session.user?.authority >= 3)
|
|
298
|
+
return true;
|
|
299
|
+
try {
|
|
300
|
+
const memberInfo = await session.bot.getGuildMember(session.guildId, userId);
|
|
301
|
+
if (!memberInfo)
|
|
302
|
+
return false;
|
|
303
|
+
const adminRoles = ["owner", "admin", "administrator"];
|
|
304
|
+
const memberRoles = [...(memberInfo.roles || [])].flat().filter(Boolean);
|
|
305
|
+
for (const role of memberRoles) {
|
|
306
|
+
if (adminRoles.includes(role.toLowerCase()))
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
271
315
|
async function sendResult_plain(session, config, result, logger) {
|
|
272
316
|
if (config.logLevel === 'full') {
|
|
273
317
|
logger.info('进入普通发送');
|
|
@@ -495,7 +539,18 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
|
|
|
495
539
|
if (config.logLevel !== 'none') {
|
|
496
540
|
const sizeMB = (sizeBytes / (1024 * 1024)).toFixed(2);
|
|
497
541
|
const maxMB = config.Max_size.toFixed(2);
|
|
498
|
-
|
|
542
|
+
forwardNodes.push({
|
|
543
|
+
type: 'node',
|
|
544
|
+
data: {
|
|
545
|
+
user_id: session.selfId,
|
|
546
|
+
nickname: '分享助手',
|
|
547
|
+
content: {
|
|
548
|
+
type: 'text', data: {
|
|
549
|
+
text: `文件大小超限 (${sizeMB} MB > ${maxMB} MB)`
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
});
|
|
499
554
|
logger.info(`文件大小超限 (${sizeMB} MB > ${maxMB} MB),跳过: ${remoteUrl}`);
|
|
500
555
|
}
|
|
501
556
|
}
|