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 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;
@@ -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
- if (tweetData.possibly_sensitive && !config.allow_sensitive) {
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
- extraSendPromises.push(session.send(`文件大小超限 (${sizeMB} MB > ${maxMB} MB)`));
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
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "koishi-plugin-share-links-analysis",
3
3
  "description": "自用插件",
4
4
  "license": "MIT",
5
- "version": "0.5.4",
5
+ "version": "0.6.1",
6
6
  "main": "lib/index.js",
7
7
  "typings": "lib/index.d.ts",
8
8
  "files": [