koishi-plugin-share-links-analysis 0.6.3 → 0.7.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/lib/core.d.ts CHANGED
@@ -1,6 +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
+ export declare const parsers_str: ("bilibili" | "xiaohongshu" | "twitter" | "xiaoheihe")[];
4
4
  /**
5
5
  * 从文本中解析出所有支持的链接
6
6
  * @param content 消息内容
package/lib/core.js CHANGED
@@ -41,9 +41,10 @@ exports.init = init;
41
41
  const Bilibili = __importStar(require("./parsers/bilibili"));
42
42
  const Xiaohongshu = __importStar(require("./parsers/xiaohongshu"));
43
43
  const Twitter = __importStar(require("./parsers/twitter"));
44
+ const Xiaoheihe = __importStar(require("./parsers/xiaoheihe"));
44
45
  // 定义所有支持的解析器
45
- const parsers = [Bilibili, Xiaohongshu, Twitter];
46
- exports.parsers_str = ['bilibili', 'xiaohongshu', 'twitter'];
46
+ const parsers = [Bilibili, Xiaohongshu, Twitter, Xiaoheihe];
47
+ exports.parsers_str = parsers.map(p => p.name);
47
48
  /**
48
49
  * 从文本中解析出所有支持的链接
49
50
  * @param content 消息内容
package/lib/index.js CHANGED
@@ -35,6 +35,7 @@ exports.Config = koishi_1.Schema.intersect([
35
35
  koishi_1.Schema.const("forward").description("合并转发"),
36
36
  koishi_1.Schema.const("mixed").description("混合发送"),
37
37
  ]).default("forward").description("发送模式"),
38
+ usingLocal: koishi_1.Schema.boolean().default(false).description("使用本地文件(关闭后代理设置无效)"),
38
39
  sendFiles: koishi_1.Schema.boolean().default(true).description("是否发送文件(视频等)"),
39
40
  sendLinks: koishi_1.Schema.boolean().default(false).description("是否附加直链(仅对合并发送有效)"),
40
41
  }).description("基础设置"),
@@ -108,7 +109,8 @@ function apply(ctx, config) {
108
109
  if (!await (0, utils_1.isUserAdmin)(session, session.userId))
109
110
  return '权限不足';
110
111
  if (parser) {
111
- if (!core_1.parsers_str.includes(parser))
112
+ const isValidParser = (name) => core_1.parsers_str.includes(name);
113
+ if (!isValidParser(parser))
112
114
  return '请输入正确的解析器名称';
113
115
  if (!value)
114
116
  return '请输入正确的模式';
@@ -192,9 +194,6 @@ function apply(ctx, config) {
192
194
  lastProcessedUrls[channelId][link.url] = now;
193
195
  await sendResult(session, config, result, logger);
194
196
  }
195
- else if (config.showError) {
196
- await session.send(`无法解析链接:${link.url}。可能是不支持的类型或链接有误。`);
197
- }
198
197
  linkCount++;
199
198
  }
200
199
  });
@@ -1,5 +1,6 @@
1
1
  import { Context, Session } from 'koishi';
2
2
  import { Link, ParsedInfo, PluginConfig } from '../types';
3
+ export declare const name = "bilibili";
3
4
  /**
4
5
  * 在文本中匹配B站链接 (长链/短链/纯BV号)
5
6
  * @param content 消息内容
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  // src/parsers/bilibili.ts
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.name = void 0;
4
5
  exports.match = match;
5
6
  exports.process = process;
6
7
  const utils_1 = require("../utils");
8
+ exports.name = "bilibili";
7
9
  const linkRules = [
8
10
  {
9
11
  pattern: /(?:https?:\/\/)?(?:www\.bilibili\.com\/video\/)(([ab]v[0-9a-zA-Z]+))/gi,
@@ -36,7 +38,7 @@ function match(content) {
36
38
  continue;
37
39
  seen.add(url);
38
40
  results.push({
39
- platform: 'bilibili',
41
+ platform: exports.name,
40
42
  type,
41
43
  id,
42
44
  url,
@@ -52,7 +54,7 @@ function match(content) {
52
54
  continue;
53
55
  seen.add(url);
54
56
  results.push({
55
- platform: 'bilibili',
57
+ platform: exports.name,
56
58
  type: 'video',
57
59
  id: videoId,
58
60
  url,
@@ -69,7 +71,7 @@ function match(content) {
69
71
  * @returns 处理后的标准格式对象
70
72
  */
71
73
  async function process(ctx, config, link, session) {
72
- const logger = ctx.logger('share-links-analysis:bilibili');
74
+ const logger = ctx.logger(`share-links-analysis:${exports.name}`);
73
75
  let videoId = link.id;
74
76
  let videoIdType = link.type;
75
77
  if (link.type === 'short') {
@@ -180,7 +182,7 @@ async function process(ctx, config, link, session) {
180
182
  files = [{ type: "video", url: videoUrl }];
181
183
  }
182
184
  return {
183
- platform: 'bilibili',
185
+ platform: exports.name,
184
186
  title: data.title,
185
187
  authorName: data.owner.name,
186
188
  mainbody: (0, utils_1.escapeHtml)(data.desc),
@@ -1,5 +1,6 @@
1
1
  import { Context, Session } from 'koishi';
2
2
  import { PluginConfig, ParsedInfo, Link } from '../types';
3
+ export declare const name = "twitter";
3
4
  /**
4
5
  * 在文本中匹配 Twitter/X 链接
5
6
  */
@@ -1,13 +1,12 @@
1
1
  "use strict";
2
2
  // src/parsers/twitter.ts
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.name = void 0;
4
5
  exports.match = match;
5
6
  exports.process = process;
6
7
  const koishi_1 = require("koishi");
7
8
  const utils_1 = require("../utils");
8
- // ======================
9
- // 链接匹配规则
10
- // ======================
9
+ exports.name = "twitter";
11
10
  const linkRules = [
12
11
  {
13
12
  pattern: /https?:\/\/(?:www\.)?(?:twitter\.com|x\.com|mobile\.twitter\.com)\/([\w-]+)\/status\/(\d+)/gi,
@@ -42,7 +41,7 @@ function match(content) {
42
41
  continue;
43
42
  seen.add(cleanUrl);
44
43
  results.push({
45
- platform: 'twitter',
44
+ platform: exports.name,
46
45
  type: rule.type,
47
46
  id: tweetId,
48
47
  url: cleanUrl,
@@ -55,7 +54,7 @@ function match(content) {
55
54
  * 处理单条 Twitter 链接
56
55
  */
57
56
  async function process(ctx, config, link, session) {
58
- const logger = ctx.logger('twitter:process');
57
+ const logger = ctx.logger(`share-links-analysis:${exports.name}`);
59
58
  let apiUrl;
60
59
  if (link.type === 'short') {
61
60
  // 短链接需先解析,但 vxtwitter 支持直接转换
@@ -113,7 +112,7 @@ async function process(ctx, config, link, session) {
113
112
  }
114
113
  }
115
114
  return {
116
- platform: 'twitter',
115
+ platform: exports.name,
117
116
  title: `@${tweetData.user_screen_name} 的推文`,
118
117
  authorName: tweetData.user_name || tweetData.user_screen_name,
119
118
  mainbody: mainbody,
@@ -161,7 +160,6 @@ function parseMedia(tweetData) {
161
160
  preview_url: media.thumbnail_url,
162
161
  duration: media.duration_millis ? media.duration_millis / 1000 : undefined
163
162
  });
164
- continue;
165
163
  }
166
164
  }
167
165
  return { images, videos };
@@ -0,0 +1,5 @@
1
+ import { Context, Session } from 'koishi';
2
+ import { PluginConfig, ParsedInfo, Link } from '../types';
3
+ export declare const name = "xiaoheihe";
4
+ export declare function match(content: string): Link[];
5
+ export declare function process(ctx: Context, config: PluginConfig, link: Link, session: Session): Promise<ParsedInfo | null>;
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ // src/parsers/xiaoheihe.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.name = void 0;
5
+ exports.match = match;
6
+ exports.process = process;
7
+ const koishi_1 = require("koishi");
8
+ const utils_1 = require("../utils");
9
+ exports.name = "xiaoheihe";
10
+ const linkRules = [
11
+ {
12
+ pattern: /https?:\/\/api.xiaoheihe\.cn\/v3\/bbs\/app\/api\/web\/share\?link_id=\w+/gi,
13
+ type: "bbs_api",
14
+ },
15
+ {
16
+ pattern: /https?:\/\/www.xiaoheihe\.cn\/app\/bbs\/link\/\w+/gi,
17
+ type: "bbs",
18
+ }
19
+ ];
20
+ function match(content) {
21
+ const results = [];
22
+ for (const rule of linkRules) {
23
+ const match = content.match(rule.pattern);
24
+ if (match) {
25
+ for (const fullUrl of match) {
26
+ const id = fullUrl.match(/\w+$/)?.[0];
27
+ if (id) {
28
+ results.push({
29
+ platform: exports.name,
30
+ type: rule.type,
31
+ id,
32
+ url: `https://www.xiaoheihe.cn/app/bbs/link/${id}`,
33
+ });
34
+ }
35
+ }
36
+ }
37
+ }
38
+ return results;
39
+ }
40
+ async function process(ctx, config, link, session) {
41
+ const logger = ctx.logger(`share-links-analysis:${exports.name}`);
42
+ const url = link.url;
43
+ let page = null;
44
+ try {
45
+ page = await ctx.puppeteer.page();
46
+ await page.setUserAgent(config.userAgent);
47
+ // 设置请求拦截(阻止非必要资源加载加速解析)
48
+ await page.setRequestInterception(true);
49
+ page.on('request', (req) => {
50
+ const resourceType = req.resourceType();
51
+ // 必须加载的资源
52
+ if (url.includes('/app/community/detail/') ||
53
+ url.includes('heybox-bbs') ||
54
+ url.includes('heybox-common') ||
55
+ resourceType === 'xhr' ||
56
+ resourceType === 'script') {
57
+ req.continue();
58
+ return;
59
+ }
60
+ // 阻止非必要资源
61
+ if (['image', 'stylesheet', 'font', 'media'].includes(resourceType) &&
62
+ !url.includes('avatar') &&
63
+ !url.includes('thumb')) {
64
+ req.abort();
65
+ }
66
+ else {
67
+ req.continue();
68
+ }
69
+ });
70
+ // 导航到目标页面
71
+ const response = await Promise.race([
72
+ page.goto(link.url, { waitUntil: 'networkidle2', timeout: 10000 }),
73
+ new Promise((_, reject) => setTimeout(() => reject(new Error('页面加载超时')), 10000))
74
+ ]);
75
+ if (!response || response.status() !== 200) {
76
+ throw new Error(`页面加载失败,状态码: ${response?.status() || '未知'}`);
77
+ }
78
+ // 智能等待 - 适配两种布局
79
+ await Promise.race([
80
+ page.waitForFunction(() => {
81
+ return document.querySelector('.hb-bbs-image-text') ||
82
+ document.querySelector('.hb-bbs-post');
83
+ }, { timeout: 10000 }),
84
+ new Promise((_, reject) => setTimeout(() => reject(new Error('核心内容容器未找到')), 10000))
85
+ ]);
86
+ // 全面解析页面内容
87
+ const postData = await page.evaluate(() => {
88
+ // 1. 检测页面类型
89
+ const isImageTextType = !!document.querySelector('.hb-bbs-image-text');
90
+ const isPostType = !!document.querySelector('.hb-bbs-post');
91
+ if (!isImageTextType && !isPostType) {
92
+ throw new Error('不支持的页面结构');
93
+ }
94
+ // 2. 基础数据解析(通用)
95
+ let title = '';
96
+ let username = '未知用户';
97
+ let level = 'Lv.0';
98
+ let time = '未知时间';
99
+ let ip = '未知地区';
100
+ const tags = [];
101
+ const contentBlocks = [];
102
+ let authorSection = null;
103
+ let coverImage = '';
104
+ // 3. 操作数据解析(通用)
105
+ let likeCount = '0';
106
+ let favoriteCount = '0';
107
+ let commentCount = '0';
108
+ const operationBox = document.querySelector('.link-reply__operation-box');
109
+ if (operationBox) {
110
+ const buttons = operationBox.querySelectorAll('button');
111
+ buttons.forEach((button) => {
112
+ const icon = button.querySelector('i');
113
+ const countSpan = button.querySelector('.link-reply__operation-desc');
114
+ const count = countSpan?.textContent?.trim() || '0';
115
+ if (icon?.classList.contains('heybox-bbs_thumbs-up')) {
116
+ likeCount = count;
117
+ }
118
+ else if (icon?.classList.contains('heybox-common_star')) {
119
+ favoriteCount = count;
120
+ }
121
+ else if (icon?.classList.contains('heybox-bbs_comment')) {
122
+ commentCount = count;
123
+ }
124
+ });
125
+ }
126
+ // 4. 按类型解析内容
127
+ if (isImageTextType) {
128
+ // ===== 旧结构解析 (.hb-bbs-image-text) =====
129
+ const container = document.querySelector('.hb-bbs-image-text');
130
+ // 标题
131
+ title = container.querySelector('.section-title__content')?.textContent?.trim() || '无标题';
132
+ // 作者信息
133
+ authorSection = container.querySelector('.link-section-user');
134
+ if (authorSection) {
135
+ username = authorSection.querySelector('.link-user__username')?.textContent?.trim() || '未知用户';
136
+ level = authorSection.querySelector('.level-tag__wrapper')?.textContent?.trim() || 'Lv.0';
137
+ time = authorSection.querySelector('.link-data__time')?.textContent?.trim() || '未知时间';
138
+ ip = authorSection.querySelector('.link-data__ip')?.textContent?.trim() || '未知地区';
139
+ }
140
+ // 标签
141
+ container.querySelectorAll('.link-section-tags .content-tag-text').forEach(tag => {
142
+ const text = tag.textContent?.trim();
143
+ if (text)
144
+ tags.push(text);
145
+ });
146
+ // 内容
147
+ const mainContent = container.querySelector('.image-text__content')?.textContent?.trim() || '';
148
+ contentBlocks.push({ type: 'text', content: mainContent });
149
+ // 图片(轮播图)
150
+ container.querySelectorAll('.header-image__item-image img').forEach((img, index) => {
151
+ const src = img.src.replace(/\?.*$/, '');
152
+ contentBlocks.push({ type: 'image', content: src });
153
+ });
154
+ }
155
+ else if (isPostType) {
156
+ const container = document.querySelector('.hb-bbs-post');
157
+ const postContainer = container.querySelector('.post__container');
158
+ // 标题
159
+ title = postContainer.querySelector('.section-title__content')?.textContent?.trim() || '无标题';
160
+ // 作者信息
161
+ authorSection = postContainer.querySelector('.link-section-user');
162
+ if (authorSection) {
163
+ username = authorSection.querySelector('.link-user__username')?.textContent?.trim() || '未知用户';
164
+ level = authorSection.querySelector('.level-tag__wrapper')?.textContent?.trim() || 'Lv.0';
165
+ // 时间/IP 在子元素中
166
+ const metaData = authorSection.querySelector('.user-info__line-2');
167
+ if (metaData) {
168
+ time = metaData.querySelector('.link-data__time')?.textContent?.trim() || '未知时间';
169
+ ip = metaData.querySelector('.link-data__ip')?.textContent?.replace('·', '').trim() || '未知地区';
170
+ }
171
+ }
172
+ // 标签(使用第一个标签区域)
173
+ postContainer.querySelectorAll('.link-section-tags:first-of-type .content-tag-text').forEach(tag => {
174
+ const text = tag.textContent?.trim();
175
+ if (text)
176
+ tags.push(text);
177
+ });
178
+ // 封面图片
179
+ const headerImageContainer = container.querySelector('.post__header-image');
180
+ if (headerImageContainer) {
181
+ const headerImage = headerImageContainer.querySelector('img');
182
+ if (headerImage) {
183
+ coverImage = headerImage.src.replace(/\?.*$/, '');
184
+ }
185
+ }
186
+ // 正文内容
187
+ const contentContainer = postContainer.querySelector('.post__content');
188
+ if (contentContainer) {
189
+ // 遍历所有子节点保持顺序
190
+ Array.from(contentContainer.childNodes).forEach(node => {
191
+ if (node.nodeType !== Node.ELEMENT_NODE)
192
+ return;
193
+ const el = node;
194
+ // 文本段落
195
+ if (el.matches('p.com-text, p.com-origin-source')) {
196
+ let text = el.textContent?.trim() || '';
197
+ // 特殊处理来源声明
198
+ if (el.classList.contains('com-origin-source')) {
199
+ text = `🔖 ${text}`;
200
+ }
201
+ if (text)
202
+ contentBlocks.push({ type: 'text', content: text });
203
+ }
204
+ // 图片
205
+ else if (el.matches('div.com-img, div.hb-cpt__image')) {
206
+ // 优先查找带'show'类的图片
207
+ const img = el.querySelector('img.hb-cpt__image-elem.show') ||
208
+ el.querySelector('img.hb-cpt__image-elem');
209
+ if (img) {
210
+ const src = img.src.replace(/\?.*$/, '');
211
+ contentBlocks.push({ type: 'image', content: src });
212
+ }
213
+ }
214
+ });
215
+ }
216
+ }
217
+ return {
218
+ isImageTextType,
219
+ isPostType,
220
+ title,
221
+ username,
222
+ level,
223
+ time,
224
+ ip,
225
+ tags,
226
+ contentBlocks,
227
+ likeCount,
228
+ favoriteCount,
229
+ commentCount,
230
+ coverImage
231
+ };
232
+ });
233
+ if (!postData)
234
+ throw new Error('未找到有效内容');
235
+ // 确保页面关闭
236
+ if (page)
237
+ await page.close().catch(() => {
238
+ });
239
+ // 5. 构建消息
240
+ let mainbody = "";
241
+ // 内容块
242
+ postData.contentBlocks.forEach((block) => {
243
+ if (block.type === 'text') {
244
+ mainbody += (0, utils_1.escapeHtml)(block.content + "\n");
245
+ }
246
+ else if (block.type === 'image') {
247
+ mainbody += koishi_1.h.image(block.content).toString();
248
+ }
249
+ });
250
+ const tag = postData.tags.length ? `标签:${postData.tags.join(' | ')}\n` : '';
251
+ const status = `点赞: ${(0, utils_1.numeral)(postData.likeCount, config)} | 收藏: ${(0, utils_1.numeral)(postData.favoriteCount, config)} | 评论: ${(0, utils_1.numeral)(postData.commentCount, config)}`;
252
+ return {
253
+ platform: exports.name,
254
+ title: postData.title,
255
+ authorName: postData.username,
256
+ mainbody: mainbody + tag,
257
+ sourceUrl: link.url,
258
+ stats: postData.isImageTextType ? status : "",
259
+ coverUrl: postData.coverImage,
260
+ files: []
261
+ };
262
+ }
263
+ catch (error) {
264
+ logger.error('解析失败:', error);
265
+ return null;
266
+ }
267
+ }
@@ -1,5 +1,6 @@
1
1
  import { Context, Session } from 'koishi';
2
2
  import { Link, ParsedInfo, PluginConfig } from '../types';
3
+ export declare const name = "xiaohongshu";
3
4
  /**
4
5
  * 在文本中匹配小红书链接 (长链接或短链接)
5
6
  * @param content 消息内容
@@ -1,12 +1,14 @@
1
1
  "use strict";
2
2
  // src/parsers/xiaohongshu.ts
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.name = void 0;
4
5
  exports.match = match;
5
6
  exports.init = init;
6
7
  exports.process = process;
7
8
  const koishi_1 = require("koishi");
8
9
  const cheerio_1 = require("cheerio");
9
10
  const utils_1 = require("../utils");
11
+ exports.name = "xiaohongshu";
10
12
  const linkRules = [
11
13
  {
12
14
  pattern: /(?:https?:\/\/)?(?:www\.xiaohongshu\.com\/discovery\/item\/)([\w?=&\-.%]+)/gi,
@@ -45,7 +47,7 @@ function match(content) {
45
47
  continue;
46
48
  seen.add(url);
47
49
  results.push({
48
- platform: 'xiaohongshu',
50
+ platform: exports.name,
49
51
  type,
50
52
  id: cleanId,
51
53
  url,
@@ -61,7 +63,6 @@ function match(content) {
61
63
  */
62
64
  async function init(ctx, config) {
63
65
  const logger = ctx.logger('share-links-analysis:xiaohongshu');
64
- const platformId = 'xiaohongshu';
65
66
  if (!ctx.puppeteer) {
66
67
  logger.warn('Puppeteer 服务未启用,无法自动刷新 Cookie。');
67
68
  return false;
@@ -136,8 +137,7 @@ async function init(ctx, config) {
136
137
  * @returns 处理后的标准格式对象
137
138
  */
138
139
  async function process(ctx, config, link, session) {
139
- const logger = ctx.logger('share-links-analysis:xiaohongshu');
140
- const platformId = 'xiaohongshu';
140
+ const logger = ctx.logger(`share-links-analysis:${exports.name}`);
141
141
  // 步骤一:从原始分享链接中提取 xsec_token
142
142
  let token = null;
143
143
  try {
@@ -271,7 +271,7 @@ async function process(ctx, config, link, session) {
271
271
  files = [{ type: "video", url: videoUrl }];
272
272
  }
273
273
  return {
274
- platform: 'xiaohongshu',
274
+ platform: exports.name,
275
275
  title: noteData.title,
276
276
  authorName: noteData.user.nickname,
277
277
  mainbody: mainbody,
package/lib/types.d.ts CHANGED
@@ -24,6 +24,7 @@ export interface PluginConfig {
24
24
  Min_Interval: number;
25
25
  waitTip_Switch: false | string;
26
26
  useForward: 'plain' | 'forward' | 'mixed';
27
+ usingLocal: boolean;
27
28
  sendFiles: boolean;
28
29
  sendLinks: boolean;
29
30
  format: string;
@@ -122,3 +123,22 @@ export interface XhsInitialState {
122
123
  };
123
124
  };
124
125
  }
126
+ export interface ContentBlock {
127
+ type: 'text' | 'image';
128
+ content: string;
129
+ }
130
+ export interface XiaoHeiHePostData {
131
+ isImageTextType: boolean;
132
+ isPostType: boolean;
133
+ title: string;
134
+ username: string;
135
+ level: string;
136
+ time: string;
137
+ ip: string;
138
+ tags: string[];
139
+ contentBlocks: ContentBlock[];
140
+ likeCount: string;
141
+ favoriteCount: string;
142
+ commentCount: string;
143
+ coverImage: string;
144
+ }
package/lib/utils.js CHANGED
@@ -327,14 +327,19 @@ async function sendResult_plain(session, config, result, logger) {
327
327
  }
328
328
  // --- 下载封面 ---
329
329
  if (result.coverUrl) {
330
- try {
331
- mediaCoverUrl = await downloadAndMapUrl(result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
332
- if (config.logLevel === 'full')
333
- logger.info(`封面已下载: ${mediaCoverUrl}`);
330
+ if (config.usingLocal) {
331
+ try {
332
+ mediaCoverUrl = await downloadAndMapUrl(result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
333
+ if (config.logLevel === 'full')
334
+ logger.info(`封面已下载: ${mediaCoverUrl}`);
335
+ }
336
+ catch (e) {
337
+ logger.warn(`封面下载失败: ${result.coverUrl}`, e);
338
+ mediaCoverUrl = result.coverUrl;
339
+ }
334
340
  }
335
- catch (e) {
336
- logger.warn(`封面下载失败: ${result.coverUrl}`, e);
337
- mediaCoverUrl = '';
341
+ else {
342
+ mediaCoverUrl = result.coverUrl;
338
343
  }
339
344
  }
340
345
  // --- 下载 mainbody 中的图片 ---
@@ -343,14 +348,19 @@ async function sendResult_plain(session, config, result, logger) {
343
348
  const urlMap = {};
344
349
  await Promise.all(imgMatches.map(async (match) => {
345
350
  const remoteUrl = match[1];
346
- try {
347
- const localUrl = await downloadAndMapUrl(remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
348
- urlMap[remoteUrl] = localUrl;
349
- if (config.logLevel === 'full')
350
- logger.info(`正文图片已下载: ${localUrl}`);
351
+ if (config.usingLocal) {
352
+ try {
353
+ const localUrl = await downloadAndMapUrl(remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
354
+ urlMap[remoteUrl] = localUrl;
355
+ if (config.logLevel === 'full')
356
+ logger.info(`正文图片已下载: ${localUrl}`);
357
+ }
358
+ catch (e) {
359
+ logger.warn(`正文图片下载失败: ${remoteUrl}`, e);
360
+ }
351
361
  }
352
- catch (e) {
353
- logger.warn(`正文图片下载失败: ${remoteUrl}`, e);
362
+ else {
363
+ urlMap[remoteUrl] = remoteUrl;
354
364
  }
355
365
  }));
356
366
  mediaMainbody = result.mainbody;
@@ -399,7 +409,9 @@ async function sendResult_plain(session, config, result, logger) {
399
409
  }
400
410
  if (shouldSend) {
401
411
  try {
402
- const localUrl = await downloadAndMapUrl(remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
412
+ let localUrl = remoteUrl;
413
+ if (config.usingLocal)
414
+ localUrl = await downloadAndMapUrl(remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
403
415
  if (!localUrl)
404
416
  continue;
405
417
  let element = null;
@@ -449,12 +461,17 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
449
461
  }
450
462
  // --- 封面 ---
451
463
  if (result.coverUrl) {
452
- try {
453
- mediaCoverUrl = await downloadAndMapUrl(result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
464
+ if (config.usingLocal) {
465
+ try {
466
+ mediaCoverUrl = await downloadAndMapUrl(result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
467
+ }
468
+ catch (e) {
469
+ logger.warn('封面下载失败', e);
470
+ mediaCoverUrl = '';
471
+ }
454
472
  }
455
- catch (e) {
456
- logger.warn('封面下载失败', e);
457
- mediaCoverUrl = '';
473
+ else {
474
+ mediaCoverUrl = result.coverUrl;
458
475
  }
459
476
  }
460
477
  // --- mainbody 图片 ---
@@ -462,11 +479,16 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
462
479
  const imgUrls = [...result.mainbody.matchAll(/<img\s[^>]*src\s*=\s*["']?([^"'>\s]+)["']?/gi)].map(m => m[1]);
463
480
  const urlMap = {};
464
481
  await Promise.all(imgUrls.map(async (url) => {
465
- try {
466
- urlMap[url] = await downloadAndMapUrl(url, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
482
+ if (config.usingLocal) {
483
+ try {
484
+ urlMap[url] = await downloadAndMapUrl(url, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
485
+ }
486
+ catch (e) {
487
+ logger.warn(`正文图片下载失败: ${url}`, e);
488
+ }
467
489
  }
468
- catch (e) {
469
- logger.warn(`正文图片下载失败: ${url}`, e);
490
+ else {
491
+ urlMap[url] = url;
470
492
  }
471
493
  }));
472
494
  mediaMainbody = result.mainbody;
@@ -557,7 +579,9 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
557
579
  }
558
580
  if (shouldInclude) {
559
581
  try {
560
- const localUrl = await downloadAndMapUrl(remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
582
+ let localUrl = remoteUrl;
583
+ if (config.usingLocal)
584
+ localUrl = await downloadAndMapUrl(remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
561
585
  if (!localUrl)
562
586
  continue;
563
587
  if (!mixed_sending) {
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.6.3",
5
+ "version": "0.7.0",
6
6
  "main": "lib/index.js",
7
7
  "typings": "lib/index.d.ts",
8
8
  "files": [