koishi-plugin-share-links-analysis 0.5.0 → 0.5.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.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Context, Session } from 'koishi';
2
2
  import { Link, PluginConfig, ParsedInfo } from './types';
3
+ export declare const parsers_str: string[];
3
4
  /**
4
5
  * 从文本中解析出所有支持的链接
5
6
  * @param content 消息内容
package/lib/core.js CHANGED
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  };
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.parsers_str = void 0;
37
38
  exports.resolveLinks = resolveLinks;
38
39
  exports.processLink = processLink;
39
40
  exports.init = init;
@@ -42,6 +43,7 @@ const Xiaohongshu = __importStar(require("./parsers/xiaohongshu"));
42
43
  const Twitter = __importStar(require("./parsers/twitter"));
43
44
  // 定义所有支持的解析器
44
45
  const parsers = [Bilibili, Xiaohongshu, Twitter];
46
+ exports.parsers_str = ['bilibili', 'xiaohongshu', 'twitter'];
45
47
  /**
46
48
  * 从文本中解析出所有支持的链接
47
49
  * @param content 消息内容
package/lib/index.js CHANGED
@@ -24,7 +24,7 @@ exports.Config = koishi_1.Schema.intersect([
24
24
  koishi_1.Schema.const('1').description('低清晰度优先'),
25
25
  koishi_1.Schema.const('2').description('高清晰度优先'),
26
26
  ]).role('radio').default('1').description("发送的视频清晰度优先策略"),
27
- Max_size: koishi_1.Schema.number().default(5).description("允许发送的最大文件大小(Mb)"),
27
+ Max_size: koishi_1.Schema.number().default(20).description("允许发送的最大文件大小(Mb)"),
28
28
  Max_size_tip: koishi_1.Schema.string().default('文件体积过大,策略已阻止发送').description("对大文件的文字提示内容"),
29
29
  MinimumTimeInterval: koishi_1.Schema.number().default(600).description("若干秒内不再处理相同链接,防止刷屏").min(1),
30
30
  waitTip_Switch: koishi_1.Schema.union([
@@ -53,8 +53,11 @@ exports.Config = koishi_1.Schema.intersect([
53
53
  useNumeral: koishi_1.Schema.boolean().default(true).description("使用格式化数字 (如 10000 -> 1万)"),
54
54
  showError: koishi_1.Schema.boolean().default(false).description("当链接不正确时提醒发送者"),
55
55
  allow_sensitive: koishi_1.Schema.boolean().default(false).description("允许NSFW内容"),
56
- proxy: koishi_1.Schema.string().description("代理设置"),
57
56
  }).description("高级解析设置"),
57
+ koishi_1.Schema.object({
58
+ 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)]))),
60
+ }).description("代理设置"),
58
61
  koishi_1.Schema.object({
59
62
  onebotReadDir: koishi_1.Schema.string().description('OneBot 实现 (如 NapCat) 所在的容器或环境提供的路径前缀。').default("/app/.config/QQ/NapCat/temp"),
60
63
  localDownloadDir: koishi_1.Schema.string().description('与上述路径对应的、Koishi 所在的容器或主机可以访问的路径前缀。').default("/koishi/data/temp"),
package/lib/types.d.ts CHANGED
@@ -27,6 +27,7 @@ export interface PluginConfig {
27
27
  showError: boolean;
28
28
  allow_sensitive: boolean;
29
29
  proxy: string;
30
+ proxy_settings: object;
30
31
  onebotReadDir: string;
31
32
  localDownloadDir: string;
32
33
  userAgent: string;
package/lib/utils.d.ts CHANGED
@@ -8,6 +8,6 @@ import { Logger, Session } from "koishi";
8
8
  */
9
9
  export declare function numeral(num: number, config: PluginConfig): string;
10
10
  export declare function escapeHtml(str: string): string;
11
- export declare function getFileSize(url: string, proxy: string | undefined, userAgent: string | undefined): Promise<number | null>;
11
+ export declare function getFileSize(url: string, proxy: string | undefined, userAgent: string | undefined, logger: Logger): Promise<number | null>;
12
12
  export declare function sendResult_plain(session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger): Promise<void>;
13
13
  export declare function sendResult_forward(session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger, mixed_sending?: boolean): Promise<void>;
package/lib/utils.js CHANGED
@@ -170,7 +170,22 @@ async function downloadAndMapUrl(url, proxy, userAgent, localDownloadDir, onebot
170
170
  });
171
171
  });
172
172
  }
173
- async function getFileSize(url, proxy, userAgent) {
173
+ async function getFileSize(url, proxy, userAgent, logger) {
174
+ try {
175
+ // 先尝试HEAD请求(标准方式)
176
+ const headSize = await tryHeadRequest(url, proxy, userAgent, logger);
177
+ if (headSize !== null) {
178
+ return headSize;
179
+ }
180
+ // HEAD失败,尝试GET请求获取部分内容
181
+ return await tryGetRequestForSize(url, proxy, userAgent, logger);
182
+ }
183
+ catch (e) {
184
+ logger.warn(`获取文件大小失败: ${url}`, e);
185
+ return null;
186
+ }
187
+ }
188
+ async function tryHeadRequest(url, proxy, userAgent, logger) {
174
189
  return new Promise((resolve) => {
175
190
  const u = new url_1.URL(url);
176
191
  const agent = getProxyAgent(proxy, url);
@@ -180,7 +195,8 @@ async function getFileSize(url, proxy, userAgent) {
180
195
  method: 'HEAD',
181
196
  timeout: 10000,
182
197
  headers: {
183
- 'User-Agent': userAgent
198
+ 'User-Agent': userAgent,
199
+ 'Referer': 'https://www.bilibili.com/'
184
200
  }
185
201
  }, (res) => {
186
202
  const len = res.headers['content-length'];
@@ -192,11 +208,61 @@ async function getFileSize(url, proxy, userAgent) {
192
208
  }
193
209
  req.destroy();
194
210
  });
195
- req.on('error', () => {
211
+ req.on('error', (err) => {
212
+ logger.warn(`HEAD请求失败: ${url}`, err);
213
+ req.destroy();
214
+ resolve(null);
215
+ });
216
+ req.on('timeout', () => {
217
+ logger.warn(`HEAD请求超时: ${url}`);
218
+ req.destroy();
219
+ resolve(null);
220
+ });
221
+ });
222
+ }
223
+ async function tryGetRequestForSize(url, proxy, userAgent, logger) {
224
+ proxy = undefined;
225
+ return new Promise((resolve) => {
226
+ const u = new url_1.URL(url);
227
+ const agent = getProxyAgent(proxy, url);
228
+ const getter = u.protocol === 'https:' ? require('https').get : require('http').get;
229
+ const req = getter(url, {
230
+ agent,
231
+ timeout: 15000,
232
+ headers: {
233
+ 'User-Agent': userAgent,
234
+ 'Range': 'bytes=0-1023' // 只请求前1KB
235
+ }
236
+ }, (res) => {
237
+ const contentRange = res.headers['content-range'];
238
+ if (contentRange) {
239
+ // 从 Content-Range 头获取总大小,例如: "bytes 0-1023/12345678"
240
+ const match = contentRange.match(/\/(\d+)$/);
241
+ if (match) {
242
+ resolve(parseInt(match[1], 10));
243
+ req.destroy();
244
+ return;
245
+ }
246
+ }
247
+ const len = res.headers['content-length'];
248
+ if (len && /^\d+$/.test(len)) {
249
+ resolve(parseInt(len, 10));
250
+ }
251
+ else {
252
+ resolve(null);
253
+ }
254
+ // 读取少量数据后关闭连接
255
+ res.on('data', () => {
256
+ req.destroy();
257
+ });
258
+ });
259
+ req.on('error', (err) => {
260
+ logger.warn(`GET请求获取大小失败: ${url}`, err);
196
261
  req.destroy();
197
262
  resolve(null);
198
263
  });
199
264
  req.on('timeout', () => {
265
+ logger.warn(`GET请求获取大小超时: ${url}`);
200
266
  req.destroy();
201
267
  resolve(null);
202
268
  });
@@ -211,10 +277,15 @@ async function sendResult_plain(session, config, result, logger) {
211
277
  let mediaCoverUrl = result.coverUrl;
212
278
  let mediaVideoUrl = result.videoUrl || null;
213
279
  let mediaMainbody = result.mainbody;
280
+ let proxy = undefined;
281
+ if (config.proxy_settings[result.platform]) {
282
+ proxy = config.proxy;
283
+ logger.info("正在使用代理");
284
+ }
214
285
  // --- 下载封面 ---
215
286
  if (result.coverUrl) {
216
287
  try {
217
- mediaCoverUrl = await downloadAndMapUrl(result.coverUrl, config.proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
288
+ mediaCoverUrl = await downloadAndMapUrl(result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
218
289
  if (config.logLevel === 'full')
219
290
  logger.info(`封面已下载: ${mediaCoverUrl}`);
220
291
  }
@@ -226,7 +297,7 @@ async function sendResult_plain(session, config, result, logger) {
226
297
  // --- 视频:先检查大小 ---
227
298
  let videoExceedsLimit = false;
228
299
  if (result.videoUrl) {
229
- const sizeBytes = await getFileSize(result.videoUrl, config.proxy, config.userAgent);
300
+ const sizeBytes = await getFileSize(result.videoUrl, proxy, config.userAgent, logger);
230
301
  const maxBytes = config.Max_size !== undefined ? config.Max_size * 1024 * 1024 : undefined;
231
302
  // 日志用 MB(保留 2 位小数)
232
303
  const formatMB = (bytes) => (bytes / (1024 * 1024)).toFixed(2);
@@ -246,7 +317,7 @@ async function sendResult_plain(session, config, result, logger) {
246
317
  else {
247
318
  // 大小合规,执行下载
248
319
  try {
249
- mediaVideoUrl = await downloadAndMapUrl(result.videoUrl, config.proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
320
+ mediaVideoUrl = await downloadAndMapUrl(result.videoUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
250
321
  if (config.logLevel === 'full') {
251
322
  logger.info(`视频已下载 (${sizeMB} MB): ${mediaVideoUrl}`);
252
323
  }
@@ -265,7 +336,7 @@ async function sendResult_plain(session, config, result, logger) {
265
336
  await Promise.all(imgMatches.map(async (match) => {
266
337
  const remoteUrl = match[1];
267
338
  try {
268
- const localUrl = await downloadAndMapUrl(remoteUrl, config.proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
339
+ const localUrl = await downloadAndMapUrl(remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
269
340
  urlMap[remoteUrl] = localUrl;
270
341
  if (config.logLevel === 'full')
271
342
  logger.info(`正文图片已下载: ${localUrl}`);
@@ -326,10 +397,15 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
326
397
  let mediaCoverUrl = result.coverUrl;
327
398
  let mediaVideoUrl = result.videoUrl || null;
328
399
  let mediaMainbody = result.mainbody;
400
+ let proxy = undefined;
401
+ if (config.proxy_settings[result.platform]) {
402
+ proxy = config.proxy;
403
+ logger.info("正在使用代理");
404
+ }
329
405
  // --- 封面 ---
330
406
  if (result.coverUrl) {
331
407
  try {
332
- mediaCoverUrl = await downloadAndMapUrl(result.coverUrl, config.proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
408
+ mediaCoverUrl = await downloadAndMapUrl(result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
333
409
  }
334
410
  catch (e) {
335
411
  logger.warn('封面下载失败', e);
@@ -339,7 +415,7 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
339
415
  // --- 视频大小检查 + 下载 ---
340
416
  let videoExceedsLimit = false;
341
417
  if (result.videoUrl) {
342
- const sizeBytes = await getFileSize(result.videoUrl, config.proxy, config.userAgent);
418
+ const sizeBytes = await getFileSize(result.videoUrl, proxy, config.userAgent, logger);
343
419
  const maxBytes = config.Max_size !== undefined ? config.Max_size * 1024 * 1024 : undefined;
344
420
  const formatMB = (bytes) => (bytes / (1024 * 1024)).toFixed(2);
345
421
  if (sizeBytes === null) {
@@ -357,7 +433,7 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
357
433
  }
358
434
  else {
359
435
  try {
360
- mediaVideoUrl = await downloadAndMapUrl(result.videoUrl, config.proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
436
+ mediaVideoUrl = await downloadAndMapUrl(result.videoUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
361
437
  if (config.logLevel === 'full') {
362
438
  logger.info(`视频已下载 (${sizeMB} MB): ${mediaVideoUrl}`);
363
439
  }
@@ -375,7 +451,7 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
375
451
  const urlMap = {};
376
452
  await Promise.all(imgUrls.map(async (url) => {
377
453
  try {
378
- urlMap[url] = await downloadAndMapUrl(url, config.proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
454
+ urlMap[url] = await downloadAndMapUrl(url, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
379
455
  }
380
456
  catch (e) {
381
457
  logger.warn(`正文图片下载失败: ${url}`, e);
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.0",
5
+ "version": "0.5.1",
6
6
  "main": "lib/index.js",
7
7
  "typings": "lib/index.d.ts",
8
8
  "files": [
@@ -1,46 +0,0 @@
1
- {
2
- allSameType: true,
3
- article: null,
4
- combinedMediaUrl: null,
5
- communityNote: null,
6
- conversationID: '1988209837298377142',
7
- date: 'Tue Nov 11 11:38:59 +0000 2025',
8
- date_epoch: 1762861139,
9
- fetched_on: 1763038701,
10
- hasMedia: true,
11
- hashtags: [],
12
- lang: 'zxx',
13
- likes: 80,
14
- mediaURLs: [
15
- 'https://pbs.twimg.com/media/G5eKTetbcAE9wtk.jpg'
16
- ],
17
- media_extended: [
18
- {
19
- altText: null,
20
- id_str: '1988209827773116417',
21
- size: {
22
- height: 1840,
23
- width: 1840
24
- },
25
- thumbnail_url: 'https://pbs.twimg.com/media/G5eKTetbcAE9wtk.jpg',
26
- type: 'image',
27
- url: 'https://pbs.twimg.com/media/G5eKTetbcAE9wtk.jpg'
28
- }
29
- ],
30
- pollData: null,
31
- possibly_sensitive: false,
32
- qrt: null,
33
- qrtURL: null,
34
- replies: 5,
35
- replyingTo: null,
36
- replyingToID: null,
37
- retweet: null,
38
- retweetURL: null,
39
- retweets: 4,
40
- text: 'https://t.co/b8AyvpjhAE',
41
- tweetID: '1988209837298377142',
42
- tweetURL: 'https://twitter.com/moyushuang_/status/1988209837298377142',
43
- user_name: '魔芋凉粉',
44
- user_profile_image_url: 'https://pbs.twimg.com/profile_images/1830265587676962816/KM46JNnI_normal.jpg',
45
- user_screen_name: 'moyushuang_'
46
- }