koishi-plugin-share-links-analysis 0.13.2 → 0.14.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.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 shangxue
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2024 shangxue
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,71 +1,71 @@
1
- # koishi-plugin-share-links-analysis
2
-
3
- # 视频链接解析插件说明 📺✨
4
-
5
- 本插件为用户提供便捷的分享链接链接解析服务,让聊天体验更加丰富多彩。
6
-
7
- ---
8
-
9
- ## 鸣谢 💖
10
-
11
- 本插件的解析能力与稳定运行,离不开以下开源项目及社区文档的强大支撑。特此向这些项目及其维护者致以最诚挚的感谢:
12
-
13
- - [koishi-plugin-bilibili-videolink-analysis](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/bilibili-videolink-analysis)
14
- - [koishi-plugin-bili-parser](https://github.com/summonhim/koishi-plugin-bili-parser)
15
- - [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect)
16
- - [koishi-plugin-xiaohongshu](https://www.npmjs.com/package/koishi-plugin-xiaohongshu)
17
- 以及解析方式原作者:[@MuJie](https://mu-jie.cc/)
18
- - [BetterTwitFix](https://github.com/dylanpdx/BetterTwitFix)
19
- - [yt-dlp](https://github.com/yt-dlp/yt-dlp)
20
- - [NeteaseCloudMusicApiEnhanced](https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced)
21
-
22
- ---
23
-
24
- ## 🚀 YouTube 解析后端部署指南 (必看)
25
-
26
- 由于 YouTube 引入了极其严苛的反爬机制,传统的直连解析已全部失效。为了保证解析的稳定,本插件采用了 **“前端 Koishi + 后端 Python 微服务”** 的分离架构。
27
-
28
- Python 后端利用 `yt-dlp` 的最新特性,通过调用本地 Node.js 实时计算 YouTube 签名,并已内置自动热更新。
29
-
30
- ### 部署环境要求
31
- - **Python 3.8+**
32
- - **Node.js** (⚠️ 必须安装!`yt-dlp` 需要调用 Node 环境来执行 JS 脚本破解签名)
33
- - **一个能够正常访问 YouTube 的网络代理** (强烈建议使用流媒体解锁节点)
34
-
35
- ### 部署步骤
36
-
37
- **1. 下载后端脚本**
38
- 将本仓库内的 `yt_server.py` 下载到你的服务器上:
39
- ```bash
40
- wget https://raw.githubusercontent.com/furryaxw/share-links-analysis/Master/yt_server.py
41
- ```
42
-
43
- **2. 安装 Python 依赖**
44
- ```bash
45
- pip install httpx fastapi uvicorn yt-dlp
46
- ```
47
-
48
- **3. 配置网络代理 (重要)**
49
- 请使用编辑器(如 vim 或 nano)打开 `yt_server.py`,在文件顶部找到代理配置区域,**将地址修改为你自己的真实代理**:
50
- ```python
51
- # =====================================
52
- # 配置代理
53
- proxy = "http://127.0.0.1:7890" # 👈 在这里填入你的代理IP和端口
54
- # =====================================
55
- ```
56
-
57
- **4. 启动微服务**
58
- ```bash
59
- python yt_server.py
60
- ```
61
- *(注:由于脚本内置了自动更新热重启逻辑,强烈建议直接使用上述命令启动,或者使用 `pm2 start yt_server.py --name yt-api --interpreter python3` 进行后台进程守护。)*
62
-
63
- **5. 在 Koishi 中配置**
64
- 确保 Python 服务成功运行(默认监听 `12001` 端口)后,回到 Koishi 控制台的本插件配置页。
65
- 在 `youtube_pythonApiUrl` 配置项中填入你的后端地址:
66
- ```text
67
- http://127.0.0.1:12001/api/parse
68
- ```
69
- *(如果你的 Python 服务部署在其他服务器上,请将 `127.0.0.1` 替换为对应的服务器 IP)*
70
-
71
- 🎉 配置完成!现在你可以尽情享受丝滑的 YouTube 视频与 Shorts 解析了!
1
+ # koishi-plugin-share-links-analysis
2
+
3
+ # 视频链接解析插件说明 📺✨
4
+
5
+ 本插件为用户提供便捷的分享链接链接解析服务,让聊天体验更加丰富多彩。
6
+
7
+ ---
8
+
9
+ ## 鸣谢 💖
10
+
11
+ 本插件的解析能力与稳定运行,离不开以下开源项目及社区文档的强大支撑。特此向这些项目及其维护者致以最诚挚的感谢:
12
+
13
+ - [koishi-plugin-bilibili-videolink-analysis](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/bilibili-videolink-analysis)
14
+ - [koishi-plugin-bili-parser](https://github.com/summonhim/koishi-plugin-bili-parser)
15
+ - [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect)
16
+ - [koishi-plugin-xiaohongshu](https://www.npmjs.com/package/koishi-plugin-xiaohongshu)
17
+ 以及解析方式原作者:[@MuJie](https://mu-jie.cc/)
18
+ - [BetterTwitFix](https://github.com/dylanpdx/BetterTwitFix)
19
+ - [yt-dlp](https://github.com/yt-dlp/yt-dlp)
20
+ - [NeteaseCloudMusicApiEnhanced](https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced)
21
+
22
+ ---
23
+
24
+ ## 🚀 YouTube 解析后端部署指南 (必看)
25
+
26
+ 由于 YouTube 引入了极其严苛的反爬机制,传统的直连解析已全部失效。为了保证解析的稳定,本插件采用了 **“前端 Koishi + 后端 Python 微服务”** 的分离架构。
27
+
28
+ Python 后端利用 `yt-dlp` 的最新特性,通过调用本地 Node.js 实时计算 YouTube 签名,并已内置自动热更新。
29
+
30
+ ### 部署环境要求
31
+ - **Python 3.8+**
32
+ - **Node.js** (⚠️ 必须安装!`yt-dlp` 需要调用 Node 环境来执行 JS 脚本破解签名)
33
+ - **一个能够正常访问 YouTube 的网络代理** (强烈建议使用流媒体解锁节点)
34
+
35
+ ### 部署步骤
36
+
37
+ **1. 下载后端脚本**
38
+ 将本仓库内的 `yt_server.py` 下载到你的服务器上:
39
+ ```bash
40
+ wget https://raw.githubusercontent.com/furryaxw/share-links-analysis/Master/yt_server.py
41
+ ```
42
+
43
+ **2. 安装 Python 依赖**
44
+ ```bash
45
+ pip install httpx fastapi uvicorn yt-dlp
46
+ ```
47
+
48
+ **3. 配置网络代理 (重要)**
49
+ 请使用编辑器(如 vim 或 nano)打开 `yt_server.py`,在文件顶部找到代理配置区域,**将地址修改为你自己的真实代理**:
50
+ ```python
51
+ # =====================================
52
+ # 配置代理
53
+ proxy = "http://127.0.0.1:7890" # 👈 在这里填入你的代理IP和端口
54
+ # =====================================
55
+ ```
56
+
57
+ **4. 启动微服务**
58
+ ```bash
59
+ python yt_server.py
60
+ ```
61
+ *(注:由于脚本内置了自动更新热重启逻辑,强烈建议直接使用上述命令启动,或者使用 `pm2 start yt_server.py --name yt-api --interpreter python3` 进行后台进程守护。)*
62
+
63
+ **5. 在 Koishi 中配置**
64
+ 确保 Python 服务成功运行(默认监听 `12001` 端口)后,回到 Koishi 控制台的本插件配置页。
65
+ 在 `youtube_pythonApiUrl` 配置项中填入你的后端地址:
66
+ ```text
67
+ http://127.0.0.1:12001/api/parse
68
+ ```
69
+ *(如果你的 Python 服务部署在其他服务器上,请将 `127.0.0.1` 替换为对应的服务器 IP)*
70
+
71
+ 🎉 配置完成!现在你可以尽情享受丝滑的 YouTube 视频与 Shorts 解析了!
package/lib/index.js CHANGED
@@ -301,6 +301,98 @@ function apply(ctx, config) {
301
301
  await ctx.database.remove('sla_file_cache', {});
302
302
  return '缓存及对应文件已清理。';
303
303
  });
304
+ cmd.subcommand('.checkcache <url:string>', '查看缓存数据状态', { authority: 2 })
305
+ .action(async ({ session }, url) => {
306
+ if (!session)
307
+ return '会话不可用。';
308
+ if (!config.enableCache)
309
+ return '缓存功能未启用。';
310
+ const links = await (0, core_1.resolveLinks)(url, ctx, config);
311
+ if (links.length === 0)
312
+ return '未在该链接中识别到支持的内容。';
313
+ const link = links[0];
314
+ const cacheKey = `${link.platform}:${link.id}`;
315
+ const cached = await ctx.database.get('sla_parse_cache', cacheKey);
316
+ if (cached.length === 0)
317
+ return `未找到缓存数据: ${cacheKey}`;
318
+ const entry = cached[0];
319
+ const ageMs = Date.now() - entry.created_at;
320
+ const isExpiredL1 = config.cacheExpiration > 0 && (ageMs > config.cacheExpiration * 60 * 60 * 1000);
321
+ const isExpiredL2 = config.optimisticExpiration > 0 && (ageMs > config.optimisticExpiration * 60 * 60 * 1000);
322
+ if (isExpiredL2)
323
+ return `缓存数据已完全过期: ${cacheKey}`;
324
+ const cacheTimeStr = new Date(entry.created_at).toLocaleString('zh-CN', { hour12: false });
325
+ const result = { ...entry.data };
326
+ if (isExpiredL1 && config.optimisticCache) {
327
+ result.mainbody = (result.mainbody || '') + `\n\n[📦 L2 缓存 | 缓存时间: ${cacheTimeStr}]`;
328
+ }
329
+ else {
330
+ result.mainbody = (result.mainbody || '') + `\n\n[📦 L1 缓存 | 缓存时间: ${cacheTimeStr}]`;
331
+ }
332
+ const sendStats = { downloadTime: 0, sendTime: 0, errors: [] };
333
+ await (0, utils_1.sendResult)(ctx, session, config, result, logger, sendStats);
334
+ return;
335
+ });
336
+ cmd.subcommand('.forceparse <url:string>', '忽略缓存强制解析链接', { authority: 2 })
337
+ .action(async ({ session }, url) => {
338
+ if (!session)
339
+ return '会话不可用。';
340
+ const links = await (0, core_1.resolveLinks)(url, ctx, config);
341
+ if (links.length === 0)
342
+ return '未在该链接中识别到支持的内容。';
343
+ const link = links[0];
344
+ if (config.waitTip_Switch)
345
+ await session.send(config.waitTip_Switch);
346
+ const result = await (0, core_1.processLink)(ctx, config, link, session);
347
+ if (!result)
348
+ return `解析失败: ${link.platform}/${link.type}:${link.id}`;
349
+ const cacheKey = `${link.platform}:${link.id}`;
350
+ if (config.enableCache) {
351
+ await ctx.database.upsert('sla_parse_cache', [{
352
+ key: cacheKey,
353
+ data: result,
354
+ created_at: Date.now()
355
+ }]);
356
+ }
357
+ const sendStats = { downloadTime: 0, sendTime: 0, errors: [] };
358
+ await (0, utils_1.sendResult)(ctx, session, config, result, logger, sendStats);
359
+ return;
360
+ });
361
+ cmd.subcommand('.directlink <url:string> [quality:string]', '获取视频/音频直链', { authority: 1 })
362
+ .action(async ({ session }, url, quality) => {
363
+ if (!session)
364
+ return '会话不可用。';
365
+ const links = await (0, core_1.resolveLinks)(url, ctx, config);
366
+ if (links.length === 0)
367
+ return '未在该链接中识别到支持的内容。';
368
+ const link = links[0];
369
+ let clarity = config.Video_ClarityPriority;
370
+ if (quality) {
371
+ const q = quality.trim().toLowerCase();
372
+ if (q === 'high' || q === 'h' || q === '高' || q === '高清晰度') {
373
+ clarity = '2';
374
+ }
375
+ else if (q === 'low' || q === 'l' || q === '低' || q === '低清晰度') {
376
+ clarity = '1';
377
+ }
378
+ else {
379
+ return `无效的画质参数: ${quality}。可用: high/h/高清晰度, low/l/低清晰度`;
380
+ }
381
+ }
382
+ const modifiedConfig = { ...config, Video_ClarityPriority: clarity, Max_size: Number.MAX_SAFE_INTEGER };
383
+ const result = await (0, core_1.processLink)(ctx, modifiedConfig, link, session);
384
+ if (!result)
385
+ return `解析失败: ${link.platform}/${link.type}:${link.id}`;
386
+ if (result.files.length === 0) {
387
+ return `未获取到直链: ${link.platform}/${link.type}:${link.id}\n该内容类型可能不支持直链获取。`;
388
+ }
389
+ const clarityLabel = clarity === '2' ? '高清晰度优先' : '低清晰度优先';
390
+ let output = `直链结果 (${link.platform}/${link.type}:${link.id}):\n画质策略: ${clarityLabel}\n标题: ${result.title}\n`;
391
+ for (const [i, file] of result.files.entries()) {
392
+ output += `\n[${i + 1}] ${file.type}: ${file.url}`;
393
+ }
394
+ return output;
395
+ });
304
396
  cmd.subcommand('.refresh', '强制刷新所有 Cookie', { authority: 3 })
305
397
  .action(async ({ session }) => {
306
398
  await session?.send('🔄 开始执行 Cookie 全量刷新流程...');
@@ -530,9 +622,15 @@ function apply(ctx, config) {
530
622
  }
531
623
  }
532
624
  catch (e) {
625
+ let mergeErrDetail = e.message || String(e);
626
+ if (e.cause) {
627
+ const causeCode = e.cause.code || '';
628
+ const causeMsg = e.cause.message || String(e.cause);
629
+ mergeErrDetail += ` [cause: ${causeCode ? causeCode + ': ' : ''}${causeMsg}]`;
630
+ }
533
631
  // 其他合并请求抛错时触发回退
534
632
  if (optimisticData) {
535
- logger.warn(`合并任务失败,触发乐观缓存回退: ${cacheKey}`);
633
+ logger.warn(`合并任务失败,触发乐观缓存回退: ${cacheKey} | Error: ${mergeErrDetail}`);
536
634
  result = optimisticData;
537
635
  const cacheTimeStr = new Date(optimisticTime).toLocaleString('zh-CN', { hour12: false });
538
636
  result.mainbody = (result.mainbody || '') + `\n\n[⚠️ 并发请求异常,回退 L2 乐观缓存 | 缓存时间: ${cacheTimeStr}]`;
@@ -573,15 +671,22 @@ function apply(ctx, config) {
573
671
  return res;
574
672
  }
575
673
  catch (e) {
674
+ // 构建包含根因的详细错误信息
675
+ let errDetail = e.message || String(e);
676
+ if (e.cause) {
677
+ const causeCode = e.cause.code || '';
678
+ const causeMsg = e.cause.message || String(e.cause);
679
+ errDetail += ` [cause: ${causeCode ? causeCode + ': ' : ''}${causeMsg}]`;
680
+ }
576
681
  // 抛出异常(网络错误/封禁)时触发乐观回退
577
682
  if (optimisticData) {
578
- logger.warn(`解析抛出异常,触发乐观缓存回退: ${cacheKey} | Error: ${e.message}`);
683
+ logger.warn(`解析抛出异常,触发乐观缓存回退: ${cacheKey} | Error: ${errDetail}`);
579
684
  const cacheTimeStr = new Date(optimisticTime).toLocaleString('zh-CN', { hour12: false });
580
685
  optimisticData.mainbody = (optimisticData.mainbody || '') + `\n\n[⚠️ 接口触发异常,回退 L2 乐观缓存 | 缓存时间: ${cacheTimeStr}]`;
581
686
  optimisticData._isOptimisticFallback = true;
582
687
  return optimisticData;
583
688
  }
584
- logger.warn(`解析任务出错: ${e}`);
689
+ logger.warn(`解析任务出错: ${errDetail}`);
585
690
  throw e;
586
691
  }
587
692
  finally {
@@ -619,9 +724,19 @@ function apply(ctx, config) {
619
724
  }
620
725
  catch (e) {
621
726
  status = "error";
622
- errorMsg = e.message || String(e);
727
+ // 构建包含根因的详细错误信息
728
+ let details = e.message || String(e);
729
+ if (e.cause) {
730
+ const causeCode = e.cause.code || '';
731
+ const causeMsg = e.cause.message || String(e.cause);
732
+ details += ` [cause: ${causeCode ? causeCode + ': ' : ''}${causeMsg}]`;
733
+ }
734
+ if (e.response?.status) {
735
+ details += ` [HTTP ${e.response.status}]`;
736
+ }
737
+ errorMsg = details;
623
738
  errorStack = e.stack || String(e);
624
- logger.warn(`处理异常: ${e}`);
739
+ logger.warn(`处理异常: ${details}`);
625
740
  }
626
741
  finally {
627
742
  // 4. 上报针对性排障数据
@@ -665,7 +780,7 @@ function apply(ctx, config) {
665
780
  ...resultFeatures,
666
781
  // === 6. 错误追踪 ===
667
782
  error_msg: errorMsg,
668
- error_stack: status === 'error' ? truncatedStack : "",
783
+ error_stack: truncatedStack,
669
784
  // === 7. 耗时拆解 (性能瓶颈定位) ===
670
785
  time_total_ms: totalTime,
671
786
  time_parse_ms: parseTime, // 解析器发请求拉取数据的耗时
@@ -238,7 +238,9 @@ async function handleSyndicationFallback(ctx, config, tweetId, session) {
238
238
  const reqOptions = {
239
239
  headers: {
240
240
  'User-Agent': config.userAgent,
241
- 'Accept': '*/*'
241
+ 'Accept': '*/*',
242
+ 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
243
+ 'Referer': 'https://platform.twitter.com/'
242
244
  }
243
245
  };
244
246
  if (config.proxy)
@@ -298,7 +300,10 @@ async function handleSyndicationFallback(ctx, config, tweetId, session) {
298
300
  };
299
301
  }
300
302
  catch (e) {
301
- logger.error(`Syndication fallback failed: ${e.message}`);
303
+ const causeDetail = e.cause ? ` (cause: ${e.cause.code || e.cause.message || e.cause})` : '';
304
+ logger.error(`Syndication fallback failed: ${e.message}${causeDetail}`);
305
+ if (e.response?.status)
306
+ logger.error(` HTTP status: ${e.response.status}`);
302
307
  // 抛出错误让外部统一处理 404 等信息
303
308
  throw e;
304
309
  }
@@ -372,16 +377,21 @@ async function process(ctx, config, link, session) {
372
377
  // 404 通常意味着推文真的没了
373
378
  const isNotFound = error.response?.status === 404;
374
379
  if (!isNotFound) {
375
- logger.warn(`VxTwitter API 失败 (${error.message}),尝试 Syndication API Fallback...`);
380
+ const causeDetail = error.cause ? ` (cause: ${error.cause.code || error.cause.message || error.cause})` : '';
381
+ logger.warn(`VxTwitter API 失败 (${error.message}${causeDetail}),尝试 Syndication API Fallback...`);
376
382
  try {
377
383
  return await handleSyndicationFallback(ctx, config, tweetId, session);
378
384
  }
379
385
  catch (fallbackError) {
380
- logger.error(`Fallback 失败: ${fallbackError.message}`);
386
+ const fbCauseDetail = fallbackError.cause ? ` (cause: ${fallbackError.cause.code || fallbackError.cause.message || fallbackError.cause})` : '';
387
+ logger.error(`Fallback 失败: ${fallbackError.message}${fbCauseDetail}`);
388
+ logger.error(`Twitter 解析失败: 主 API 和 Fallback 均失败`);
381
389
  }
382
390
  }
391
+ else {
392
+ logger.error(`Twitter 解析失败: ${error.message}`);
393
+ }
383
394
  // 最终错误处理
384
- logger.error(`Twitter 解析失败: ${error.message}`);
385
395
  if (error.response?.status === 404) {
386
396
  await session.send('推文不存在或已被删除');
387
397
  }
@@ -9,11 +9,11 @@ const utils_1 = require("../utils");
9
9
  exports.name = "xiaoheihe";
10
10
  const linkRules = [
11
11
  {
12
- pattern: /https?:\/\/api\.xiaoheihe\.cn\/v3\/bbs\/app\/api\/web\/share\?[^"'\s]*?\blink_id=(\d+)/gi,
12
+ pattern: /https?:\/\/api\.xiaoheihe\.cn\/v3\/bbs\/app\/api\/web\/share\?[^"'\s]*?\blink_id=([a-zA-Z0-9]+)/gi,
13
13
  type: "bbs_api",
14
14
  },
15
15
  {
16
- pattern: /https?:\/\/www\.xiaoheihe\.cn\/app\/bbs\/link\/(\d+)/gi,
16
+ pattern: /https?:\/\/www\.xiaoheihe\.cn\/app\/bbs\/link\/([a-zA-Z0-9]+)/gi,
17
17
  type: "bbs",
18
18
  }
19
19
  ];
package/lib/utils.js CHANGED
@@ -641,7 +641,7 @@ async function isUserAdmin(session, userId) {
641
641
  if (!memberInfo)
642
642
  return false;
643
643
  const adminRoles = ["owner", "admin", "administrator"];
644
- const memberRoles = [...(memberInfo.roles || [])].flat().filter(Boolean);
644
+ const memberRoles = (memberInfo.roles || []).map(r => r.name).filter((n) => !!n);
645
645
  for (const role of memberRoles) {
646
646
  if (adminRoles.includes(role.toLowerCase()))
647
647
  return true;
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.13.2",
5
+ "version": "0.14.0",
6
6
  "main": "lib/index.js",
7
7
  "typings": "lib/index.d.ts",
8
8
  "files": [