@acedatacloud/skills 2026.622.3 → 2026.622.4
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acedatacloud/skills",
|
|
3
|
-
"version": "2026.622.
|
|
3
|
+
"version": "2026.622.4",
|
|
4
4
|
"description": "Agent Skills for AceDataCloud AI services — music, image, video generation, LLM chat, web search. Compatible with Claude Code, GitHub Copilot, Gemini CLI, OpenAI Codex, and 30+ AI coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -25,6 +25,7 @@ from __future__ import annotations
|
|
|
25
25
|
import argparse
|
|
26
26
|
import gzip
|
|
27
27
|
import hashlib
|
|
28
|
+
import html as _html
|
|
28
29
|
import ipaddress
|
|
29
30
|
import json
|
|
30
31
|
import os
|
|
@@ -287,7 +288,7 @@ _IMG_SKIP = ("hdslb.com", "bilibili.com", "biliimg.com")
|
|
|
287
288
|
# matches both HTML <img src="..."> and markdown 
|
|
288
289
|
# <img ... src = "..."> / '...' (tolerates spaces and either quote style)
|
|
289
290
|
_HTML_IMG = re.compile(r"""(<img\b[^>]*?\bsrc\s*=\s*)(["'])(https?://[^"']+)(\2)""", re.IGNORECASE)
|
|
290
|
-
_MD_IMG = re.compile(r"!\[([^\]]
|
|
291
|
+
_MD_IMG = re.compile(r"!\[([^\]]{0,500})\]\((https?://[^)\s]{0,2000})\)") # bounded: no O(n²) on "![" * N
|
|
291
292
|
UPCOVER = "https://api.bilibili.com/x/article/creative/article/upcover"
|
|
292
293
|
|
|
293
294
|
|
|
@@ -429,6 +430,121 @@ def rehost_images(jar, csrf, content):
|
|
|
429
430
|
return content
|
|
430
431
|
|
|
431
432
|
|
|
433
|
+
# ── Markdown → HTML (专栏 body is HTML; convert if the caller passed Markdown) ──
|
|
434
|
+
|
|
435
|
+
# Bounded so a single token can't scan to EOF — keeps these linear (no O(n²)
|
|
436
|
+
# backtracking on malformed input like "![" * N). Real alt < 500 / URL < 2000.
|
|
437
|
+
_IMG_RE = re.compile(r"!\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)")
|
|
438
|
+
_LINK_RE = re.compile(r"(?<!!)\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)")
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _looks_like_markdown(s):
|
|
442
|
+
# Three-way classification (no full parser): block-level HTML ⇒ already an
|
|
443
|
+
# HTML document → pass through (even if it also contains markdown-looking
|
|
444
|
+
# text like **bold**); else markdown markers ⇒ render; else inline-only HTML
|
|
445
|
+
# ⇒ pass through; else plain text ⇒ render (md_to_html just wraps/escapes it).
|
|
446
|
+
s = s or ""
|
|
447
|
+
block_html = re.search(
|
|
448
|
+
r"</?(?:p|div|h[1-6]|ul|ol|li|table|thead|tbody|tr|td|th|blockquote|"
|
|
449
|
+
r"pre|figure|figcaption|section|article|header|footer|nav|aside|hr|"
|
|
450
|
+
r"main|details|summary)\b",
|
|
451
|
+
s, re.I,
|
|
452
|
+
)
|
|
453
|
+
if block_html:
|
|
454
|
+
return False
|
|
455
|
+
# ^-anchored (re.M) + horizontal-only whitespace + bounded {0,N} repeats so
|
|
456
|
+
# the scan can't backtrack across newlines or to EOF (no quadratic scanning).
|
|
457
|
+
has_md = re.search(
|
|
458
|
+
r"^#{1,6}\s|!\[[^\]]{0,500}\]\([^)]{0,2000}\)|^[ \t]*[-*+]\s|^[ \t]*\d+\.\s"
|
|
459
|
+
r"|\[[^\]]{0,500}\]\([^)]{0,2000}\)|`[^`]{1,500}`|\*\*[^*]{1,500}\*\*",
|
|
460
|
+
s, re.M,
|
|
461
|
+
)
|
|
462
|
+
if has_md:
|
|
463
|
+
return True
|
|
464
|
+
inline_html = re.search(
|
|
465
|
+
r"</?(?:a|strong|em|b|i|u|s|span|code|br|img|small|mark|sup|sub|"
|
|
466
|
+
r"video|audio|iframe)\b",
|
|
467
|
+
s, re.I,
|
|
468
|
+
)
|
|
469
|
+
return not inline_html
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _attr_url(u):
|
|
473
|
+
# Strip C0 controls/space FIRST, then allow only http/https/mailto — otherwise
|
|
474
|
+
# `\x01javascript:` would slip past a literal scheme check yet be re-normalised
|
|
475
|
+
# to javascript: by the browser. Finally neutralise attribute-breaking quotes.
|
|
476
|
+
u = re.sub(r"[\x00-\x20\x7f]", "", u or "")
|
|
477
|
+
m = re.match(r"(?i)([a-z][a-z0-9+.\-]*):", u)
|
|
478
|
+
if m and m.group(1).lower() not in ("http", "https", "mailto"):
|
|
479
|
+
return "#"
|
|
480
|
+
return u.replace('"', "%22").replace("'", "%27")
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _alt(s):
|
|
484
|
+
return (s or "").replace('"', """).replace("'", "'")
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _inline_md(t):
|
|
488
|
+
t = _html.escape(t, quote=False).replace("\x00", "")
|
|
489
|
+
# Stash code spans FIRST so emphasis/link/image markup inside `…` stays
|
|
490
|
+
# literal (e.g. `**x**` must render as text, not bold).
|
|
491
|
+
spans = []
|
|
492
|
+
|
|
493
|
+
def _stash(m):
|
|
494
|
+
spans.append("<code>" + m.group(1) + "</code>")
|
|
495
|
+
return f"\x00{len(spans) - 1}\x00"
|
|
496
|
+
|
|
497
|
+
t = re.sub(r"`([^`]+)`", _stash, t)
|
|
498
|
+
t = _IMG_RE.sub(lambda m: f'<img src="{_attr_url(m.group(2))}" alt="{_alt(m.group(1))}">', t)
|
|
499
|
+
t = _LINK_RE.sub(lambda m: f'<a href="{_attr_url(m.group(2))}">{m.group(1)}</a>', t)
|
|
500
|
+
t = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", t)
|
|
501
|
+
t = re.sub(r"(?<!\*)\*([^*\n]+)\*(?!\*)", r"<em>\1</em>", t)
|
|
502
|
+
t = re.sub(r"\x00(\d+)\x00", lambda m: spans[int(m.group(1))], t)
|
|
503
|
+
return t
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _md_block_to_html(b):
|
|
507
|
+
b = b.strip("\n")
|
|
508
|
+
if not b.strip():
|
|
509
|
+
return None
|
|
510
|
+
f = b.lstrip()
|
|
511
|
+
m = re.match(r"(#{1,6})\s+(.*)", f)
|
|
512
|
+
if m:
|
|
513
|
+
lvl = len(m.group(1))
|
|
514
|
+
return f"<h{lvl}>{_inline_md(m.group(2).strip())}</h{lvl}>"
|
|
515
|
+
if f.startswith(">"):
|
|
516
|
+
inner = re.sub(r"^>\s?", "", b, flags=re.M).replace("\n", " ")
|
|
517
|
+
return "<blockquote><p>" + _inline_md(inner) + "</p></blockquote>"
|
|
518
|
+
if re.match(r"[-*+]\s+", f):
|
|
519
|
+
items = [_inline_md(re.sub(r"^[-*+]\s+", "", ln)) for ln in b.split("\n") if ln.strip()]
|
|
520
|
+
return "<ul>" + "".join(f"<li>{x}</li>" for x in items) + "</ul>"
|
|
521
|
+
if re.match(r"\d+\.\s+", f):
|
|
522
|
+
items = [_inline_md(re.sub(r"^\d+\.\s+", "", ln)) for ln in b.split("\n") if ln.strip()]
|
|
523
|
+
return "<ol>" + "".join(f"<li>{x}</li>" for x in items) + "</ol>"
|
|
524
|
+
only = re.fullmatch(r"!\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)", f)
|
|
525
|
+
if only:
|
|
526
|
+
return f'<img src="{_attr_url(only.group(2))}" alt="{_alt(_html.escape(only.group(1), quote=False))}">'
|
|
527
|
+
return "<p>" + _inline_md(b.replace("\n", " ").strip()) + "</p>"
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def md_to_html(src):
|
|
531
|
+
src = (src or "").strip()
|
|
532
|
+
out = []
|
|
533
|
+
# Pull fenced code out FIRST (it may itself contain blank lines) so the
|
|
534
|
+
# blank-line block splitter below can never tear a ``` … ``` fence apart.
|
|
535
|
+
for i, part in enumerate(re.split(r"(?ms)^(```.*?(?:\n```[ \t]*$|\Z))", src)):
|
|
536
|
+
if i % 2 == 1: # fenced code segment
|
|
537
|
+
code = re.sub(r"\A```[^\n]*\n?", "", part)
|
|
538
|
+
code = re.sub(r"\n?```[ \t]*\Z", "", code)
|
|
539
|
+
out.append("<pre><code>" + _html.escape(code) + "</code></pre>")
|
|
540
|
+
continue
|
|
541
|
+
for b in re.split(r"\n[ \t]*\n", part): # blank-line split (no cross-newline \s*)
|
|
542
|
+
block = _md_block_to_html(b)
|
|
543
|
+
if block:
|
|
544
|
+
out.append(block)
|
|
545
|
+
return "\n".join(out)
|
|
546
|
+
|
|
547
|
+
|
|
432
548
|
def cmd_publish(jar, args):
|
|
433
549
|
if not args.title:
|
|
434
550
|
die("--title is required")
|
|
@@ -461,6 +577,11 @@ def cmd_publish(jar, args):
|
|
|
461
577
|
if not args.no_rehost_images:
|
|
462
578
|
content = rehost_images(jar, csrf, content)
|
|
463
579
|
|
|
580
|
+
# 专栏 content is HTML; if the caller passed Markdown, render it (else the
|
|
581
|
+
# article shows literal `##`/`![]()` and runs together as one paragraph).
|
|
582
|
+
if _looks_like_markdown(content):
|
|
583
|
+
content = md_to_html(content)
|
|
584
|
+
|
|
464
585
|
ref = "https://member.bilibili.com/"
|
|
465
586
|
base = {
|
|
466
587
|
"title": args.title, "content": content, "csrf": csrf,
|
|
@@ -28,6 +28,7 @@ import base64
|
|
|
28
28
|
import gzip
|
|
29
29
|
import hashlib
|
|
30
30
|
import hmac
|
|
31
|
+
import html as _html
|
|
31
32
|
import ipaddress
|
|
32
33
|
import json
|
|
33
34
|
import mimetypes
|
|
@@ -286,7 +287,7 @@ def cmd_article(jar, args):
|
|
|
286
287
|
# Signed signature → Huawei OBS multipart → csdnimg.cn URL. Mirrors our
|
|
287
288
|
# PlatformPublisher csdn.py flow.
|
|
288
289
|
|
|
289
|
-
_MD_IMG = re.compile(r"!\[([^\]]
|
|
290
|
+
_MD_IMG = re.compile(r"!\[([^\]]{0,500})\]\((https?://[^)\s]{0,2000})\)") # bounded: no O(n²) on "![" * N
|
|
290
291
|
_IMG_SKIP = ("csdnimg.cn", "csdn.net")
|
|
291
292
|
|
|
292
293
|
|
|
@@ -416,6 +417,126 @@ def rehost_images(jar, content):
|
|
|
416
417
|
return _MD_IMG.sub(repl, content)
|
|
417
418
|
|
|
418
419
|
|
|
420
|
+
# ── Markdown → HTML (CSDN renders the `content` HTML field, not the source) ──
|
|
421
|
+
|
|
422
|
+
# Bounded so a single token can't scan to EOF — keeps these linear (no O(n²)
|
|
423
|
+
# backtracking on malformed input like "![" * N). Real alt < 500 / URL < 2000.
|
|
424
|
+
_IMG_RE = re.compile(r"!\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)")
|
|
425
|
+
_LINK_RE = re.compile(r"(?<!!)\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)")
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _attr_url(u):
|
|
429
|
+
# Strip C0 controls/space FIRST, then allow only http/https/mailto — otherwise
|
|
430
|
+
# `\x01javascript:` would slip past a literal scheme check yet be re-normalised
|
|
431
|
+
# to javascript: by the browser. Finally neutralise attribute-breaking quotes.
|
|
432
|
+
u = re.sub(r"[\x00-\x20\x7f]", "", u or "")
|
|
433
|
+
m = re.match(r"(?i)([a-z][a-z0-9+.\-]*):", u)
|
|
434
|
+
if m and m.group(1).lower() not in ("http", "https", "mailto"):
|
|
435
|
+
return "#"
|
|
436
|
+
return u.replace('"', "%22").replace("'", "%27")
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _alt(s):
|
|
440
|
+
# alt text is already &/</>-escaped by the line escape; only the attribute
|
|
441
|
+
# quote can still break out, so neutralise it.
|
|
442
|
+
return (s or "").replace('"', """).replace("'", "'")
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _inline_md(t):
|
|
446
|
+
t = _html.escape(t, quote=False).replace("\x00", "")
|
|
447
|
+
# Stash code spans FIRST so emphasis/link/image markup inside `…` stays
|
|
448
|
+
# literal (e.g. `**x**` must render as text, not bold).
|
|
449
|
+
spans = []
|
|
450
|
+
|
|
451
|
+
def _stash(m):
|
|
452
|
+
spans.append("<code>" + m.group(1) + "</code>")
|
|
453
|
+
return f"\x00{len(spans) - 1}\x00"
|
|
454
|
+
|
|
455
|
+
t = re.sub(r"`([^`]+)`", _stash, t)
|
|
456
|
+
t = _IMG_RE.sub(lambda m: f'<img src="{_attr_url(m.group(2))}" alt="{_alt(m.group(1))}">', t)
|
|
457
|
+
t = _LINK_RE.sub(lambda m: f'<a href="{_attr_url(m.group(2))}">{m.group(1)}</a>', t)
|
|
458
|
+
t = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", t)
|
|
459
|
+
t = re.sub(r"(?<!\*)\*([^*\n]+)\*(?!\*)", r"<em>\1</em>", t)
|
|
460
|
+
t = re.sub(r"\x00(\d+)\x00", lambda m: spans[int(m.group(1))], t)
|
|
461
|
+
return t
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _md_block_to_html(b):
|
|
465
|
+
b = b.strip("\n")
|
|
466
|
+
if not b.strip():
|
|
467
|
+
return None
|
|
468
|
+
f = b.lstrip()
|
|
469
|
+
m = re.match(r"(#{1,6})\s+(.*)", f)
|
|
470
|
+
if m:
|
|
471
|
+
lvl = len(m.group(1))
|
|
472
|
+
return f"<h{lvl}>{_inline_md(m.group(2).strip())}</h{lvl}>"
|
|
473
|
+
if f.startswith(">"):
|
|
474
|
+
inner = re.sub(r"^>\s?", "", b, flags=re.M).replace("\n", " ")
|
|
475
|
+
return "<blockquote><p>" + _inline_md(inner) + "</p></blockquote>"
|
|
476
|
+
if re.match(r"[-*+]\s+", f):
|
|
477
|
+
items = [_inline_md(re.sub(r"^[-*+]\s+", "", ln)) for ln in b.split("\n") if ln.strip()]
|
|
478
|
+
return "<ul>" + "".join(f"<li>{x}</li>" for x in items) + "</ul>"
|
|
479
|
+
if re.match(r"\d+\.\s+", f):
|
|
480
|
+
items = [_inline_md(re.sub(r"^\d+\.\s+", "", ln)) for ln in b.split("\n") if ln.strip()]
|
|
481
|
+
return "<ol>" + "".join(f"<li>{x}</li>" for x in items) + "</ol>"
|
|
482
|
+
only = re.fullmatch(r"!\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)", f)
|
|
483
|
+
if only:
|
|
484
|
+
return f'<img src="{_attr_url(only.group(2))}" alt="{_alt(_html.escape(only.group(1), quote=False))}">'
|
|
485
|
+
return "<p>" + _inline_md(b.replace("\n", " ").strip()) + "</p>"
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def md_to_html(src):
|
|
489
|
+
"""Minimal stdlib Markdown→HTML for the platforms whose body is HTML.
|
|
490
|
+
Covers headings, paragraphs, standalone images, links, bold/italic/code,
|
|
491
|
+
fenced code, blockquotes, and ordered/unordered lists."""
|
|
492
|
+
src = (src or "").strip()
|
|
493
|
+
out = []
|
|
494
|
+
# Pull fenced code out FIRST (it may itself contain blank lines) so the
|
|
495
|
+
# blank-line block splitter below can never tear a ``` … ``` fence apart.
|
|
496
|
+
for i, part in enumerate(re.split(r"(?ms)^(```.*?(?:\n```[ \t]*$|\Z))", src)):
|
|
497
|
+
if i % 2 == 1: # fenced code segment
|
|
498
|
+
code = re.sub(r"\A```[^\n]*\n?", "", part)
|
|
499
|
+
code = re.sub(r"\n?```[ \t]*\Z", "", code)
|
|
500
|
+
out.append("<pre><code>" + _html.escape(code) + "</code></pre>")
|
|
501
|
+
continue
|
|
502
|
+
for b in re.split(r"\n[ \t]*\n", part): # blank-line split (no cross-newline \s*)
|
|
503
|
+
block = _md_block_to_html(b)
|
|
504
|
+
if block:
|
|
505
|
+
out.append(block)
|
|
506
|
+
return "\n".join(out)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _looks_like_markdown(s):
|
|
510
|
+
# Three-way classification (no full parser): block-level HTML ⇒ already an
|
|
511
|
+
# HTML document → pass through (even if it also contains markdown-looking
|
|
512
|
+
# text like **bold**); else markdown markers ⇒ render; else inline-only HTML
|
|
513
|
+
# ⇒ pass through; else plain text ⇒ render (md_to_html just wraps/escapes it).
|
|
514
|
+
s = s or ""
|
|
515
|
+
block_html = re.search(
|
|
516
|
+
r"</?(?:p|div|h[1-6]|ul|ol|li|table|thead|tbody|tr|td|th|blockquote|"
|
|
517
|
+
r"pre|figure|figcaption|section|article|header|footer|nav|aside|hr|"
|
|
518
|
+
r"main|details|summary)\b",
|
|
519
|
+
s, re.I,
|
|
520
|
+
)
|
|
521
|
+
if block_html:
|
|
522
|
+
return False
|
|
523
|
+
# ^-anchored (re.M) + horizontal-only whitespace + bounded {0,N} repeats so
|
|
524
|
+
# the scan can't backtrack across newlines or to EOF (no quadratic scanning).
|
|
525
|
+
has_md = re.search(
|
|
526
|
+
r"^#{1,6}\s|!\[[^\]]{0,500}\]\([^)]{0,2000}\)|^[ \t]*[-*+]\s|^[ \t]*\d+\.\s"
|
|
527
|
+
r"|\[[^\]]{0,500}\]\([^)]{0,2000}\)|`[^`]{1,500}`|\*\*[^*]{1,500}\*\*",
|
|
528
|
+
s, re.M,
|
|
529
|
+
)
|
|
530
|
+
if has_md:
|
|
531
|
+
return True
|
|
532
|
+
inline_html = re.search(
|
|
533
|
+
r"</?(?:a|strong|em|b|i|u|s|span|code|br|img|small|mark|sup|sub|"
|
|
534
|
+
r"video|audio|iframe)\b",
|
|
535
|
+
s, re.I,
|
|
536
|
+
)
|
|
537
|
+
return not inline_html
|
|
538
|
+
|
|
539
|
+
|
|
419
540
|
def cmd_publish(jar, args):
|
|
420
541
|
if not args.title:
|
|
421
542
|
die("--title is required")
|
|
@@ -450,8 +571,12 @@ def cmd_publish(jar, args):
|
|
|
450
571
|
status_code = 2 if args.draft_only else 0
|
|
451
572
|
body = {
|
|
452
573
|
"title": args.title,
|
|
574
|
+
# markdowncontent = the editor source; content = the RENDERED HTML CSDN
|
|
575
|
+
# actually displays. Sending raw markdown as `content` shows literal
|
|
576
|
+
# `##`/`![]()` and collapses newlines — it MUST be HTML. Already-HTML
|
|
577
|
+
# callers are passed through untouched (don't double-escape).
|
|
453
578
|
"markdowncontent": content,
|
|
454
|
-
"content": content,
|
|
579
|
+
"content": md_to_html(content) if _looks_like_markdown(content) else content,
|
|
455
580
|
"readType": "public",
|
|
456
581
|
"status": status_code,
|
|
457
582
|
"categories": "",
|