@sovovs/bycli 2.1.14 → 2.1.16

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.
@@ -22,8 +22,21 @@ export function wechatArticleToMarkdown({ html, title, accountName, author, publ
22
22
  .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
23
23
  .replace(/"/g, '&quot;').replace(/'/g, '&#39;')
24
24
  .replace(/([\\`*_[\]{}()#+.!|>~-])/g, '\\$1');
25
+ // A URL must not go through `safe()`: escaping `. _ - ( ) #` and entity-encoding
26
+ // `&` corrupts the link so it can no longer be copied or followed. Rendered as an
27
+ // autolink it only needs whitespace and control characters dropped, `<`/`>`
28
+ // percent-encoded so the autolink cannot be closed early, and an http(s) scheme
29
+ // allowlist to reject `javascript:` and other hostile schemes.
30
+ const safeUrl = value => {
31
+ const raw = [...String(value || '')]
32
+ .filter(char => char > ' ' && char.codePointAt(0) !== 127)
33
+ .join('');
34
+ if (!/^https?:\/\//i.test(raw)) return '';
35
+ return raw.replace(/</g, '%3C').replace(/>/g, '%3E');
36
+ };
37
+ const articleUrl = safeUrl(url);
25
38
  const metadata = [accountName && `> 公众号: ${safe(accountName)}`, author && `> 作者: ${safe(author)}`,
26
39
  publishedAt && `> 发布时间: ${safe(publishedAt)}`, digest && `> 摘要: ${safe(digest)}`,
27
- url && `> 原文链接: ${safe(url)}`].filter(Boolean);
40
+ articleUrl && `> 原文链接: <${articleUrl}>`].filter(Boolean);
28
41
  return [`# ${safe(title || 'Untitled')}`, ...metadata, '', '---', '', markdown, ''].join('\n');
29
42
  }
@@ -212,6 +212,13 @@ function convertToMarkdown(contentHtml, codeBlocks, configure, cleanSelectors, s
212
212
  md = md.replace(/^[ \t]*[-·][ \t]*$/gm, '');
213
213
  md = md.replace(/^[ \t]+$/gm, '');
214
214
  md = md.replace(/[ \t]+$/gm, '');
215
+ // Bare-text URLs in adjacent inline nodes (WeChat renders each on its own
216
+ // visual line but emits no separator) fuse into one unusable string like
217
+ // `https://a/Xhttps://b/Y`. Split consecutive bare URLs onto their own lines.
218
+ // A URL whose own query carries another URL (`?next=https://…`, common in
219
+ // redirect wrappers) must stay intact, so skip breaks that follow a query or
220
+ // path delimiter. Markdown destinations are already bounded by `()`/`<>`.
221
+ md = md.replace(/(https?:\/\/[^\s<>()[\]]*?)(?=https?:\/\/)/gi, (match) => (/[=?&/:,;-]$/.test(match) ? match : `${match}\n`));
215
222
  md = md.replace(/\n{3,}/g, '\n\n');
216
223
  return md;
217
224
  }
@@ -329,13 +336,20 @@ export async function downloadArticle(data, options) {
329
336
  // Shape: `# Title\n[> meta\n...]\n---\n\n<markdown>` — exactly one blank
330
337
  // line separates every section, so we never produce ≥3 consecutive newlines.
331
338
  const headerValue = (value) => secureMarkdown ? escapeMarkdownText(value) : value;
339
+ // The source URL must not go through escapeMarkdownText: escaping `. _ - ( ) #`
340
+ // and entity-encoding `&` leaves a link that can no longer be copied or
341
+ // followed. safeHttpUrl already allowlists http(s) and percent-encodes `<`/`>`
342
+ // via URL normalization, so the autolink cannot be closed early; an empty
343
+ // return means a hostile or malformed URL and the line is dropped entirely.
344
+ const headerUrl = (value) => secureMarkdown ? safeHttpUrl(value) : value;
332
345
  const headerLines = [`# ${headerValue(data.title)}`];
333
346
  if (data.author)
334
347
  headerLines.push(`> ${labels.author}: ${headerValue(data.author)}`);
335
348
  if (data.publishTime)
336
349
  headerLines.push(`> ${labels.publishTime}: ${headerValue(data.publishTime)}`);
337
- if (data.sourceUrl)
338
- headerLines.push(`> ${labels.sourceUrl}: ${headerValue(data.sourceUrl)}`);
350
+ const sourceUrl = data.sourceUrl ? headerUrl(data.sourceUrl) : '';
351
+ if (sourceUrl)
352
+ headerLines.push(`> ${labels.sourceUrl}: <${sourceUrl}>`);
339
353
  const frontmatter = headerLines.join('\n') + '\n\n---\n\n';
340
354
  const fullContent = frontmatter + markdown;
341
355
  const size = Buffer.byteLength(fullContent, 'utf-8');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sovovs/bycli",
3
- "version": "2.1.14",
3
+ "version": "2.1.16",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },