Skip to content

Commit 7566473

Browse files
authored
feat: preserve Substack widgets in Markdown (fixes #64) (#76)
1 parent 0c7f5d8 commit 7566473

5 files changed

Lines changed: 507 additions & 271 deletions

File tree

‎.pre-commit-config.yaml‎

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
repos:
2-
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v6.0.0
4-
hooks:
5-
- id: check-yaml
6-
- id: end-of-file-fixer
7-
- id: trailing-whitespace
8-
- repo: https://github.com/psf/black
9-
rev: 26.5.1
10-
hooks:
11-
- id: black
12-
- repo: https://github.com/pycqa/isort
13-
rev: 8.0.1
14-
hooks:
15-
- id: isort
16-
name: isort (python)
17-
args: ["--profile", "black"]
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v6.0.0
4+
hooks:
5+
- id: check-yaml
6+
- id: end-of-file-fixer
7+
- id: trailing-whitespace
8+
- repo: https://github.com/psf/black
9+
rev: 26.5.1
10+
hooks:
11+
- id: black
12+
- repo: https://github.com/pycqa/isort
13+
rev: 9.0.1
14+
hooks:
15+
- id: isort
16+
name: isort (python)
17+
args: ["--profile", "black"]

‎substack/api.py‎

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ def update_draft_from_markdown(
478478
draft_section_id: int = None,
479479
tags=None,
480480
dry_run: bool = False,
481+
allow_unsupported_change: bool = False,
481482
) -> dict:
482483
"""
483484
Update an existing draft body from Markdown, with optional metadata changes.
@@ -497,12 +498,7 @@ def update_draft_from_markdown(
497498
if not isinstance(draft_body, dict):
498499
raise ValueError("Malformed draft body: draft_body must be a JSON object")
499500

500-
_, unsupported_nodes = document_to_markdown(draft_body)
501-
if unsupported_nodes:
502-
raise ValueError(
503-
"Refusing to update: remote draft contains unsupported Substack nodes. "
504-
"Export it first or remove the nodes manually to avoid data loss."
505-
)
501+
_, remote_unsupported = document_to_markdown(draft_body)
506502

507503
post = Post(
508504
title=draft.get("title", ""),
@@ -515,6 +511,25 @@ def update_draft_from_markdown(
515511
)
516512
post.from_markdown(markdown, api=self)
517513

514+
_, submitted_unsupported = document_to_markdown(post.draft_body)
515+
516+
if not allow_unsupported_change:
517+
remote_serialized = [
518+
json.dumps(n, ensure_ascii=False, separators=(",", ":"), sort_keys=True)
519+
for n in remote_unsupported
520+
]
521+
submitted_serialized = [
522+
json.dumps(n, ensure_ascii=False, separators=(",", ":"), sort_keys=True)
523+
for n in submitted_unsupported
524+
]
525+
if sorted(remote_serialized) != sorted(submitted_serialized):
526+
raise ValueError(
527+
"Refusing to update: remote unsupported nodes are missing, duplicated, stale, or altered. "
528+
"Export the draft first or set allow_unsupported_change=True to proceed."
529+
)
530+
531+
unsupported_nodes = remote_unsupported
532+
518533
update_payload = {"draft_body": json.dumps(post.draft_body)}
519534

520535
if subtitle is not None:

‎substack/cli.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,9 @@ def _drafts_create(api, args):
310310

311311

312312
def _drafts_update(api, args):
313+
if args.allow_unsupported_change and not args.yes:
314+
raise CLIUsageError("--yes is required when using --allow-unsupported-change")
315+
313316
if not args.yes and (args.json_output or not sys.stdin.isatty()):
314317
raise CLIUsageError("--yes is required in non-interactive or JSON mode")
315318

@@ -347,6 +350,7 @@ def _drafts_update(api, args):
347350
draft_section_id=args.draft_section_id,
348351
tags=args.tags,
349352
dry_run=args.dry_run,
353+
allow_unsupported_change=args.allow_unsupported_change,
350354
)
351355

352356
if args.json_output:
@@ -508,6 +512,7 @@ def _build_parser():
508512
drafts_update.add_argument("--draft-section-id", type=int)
509513
drafts_update.add_argument("--tag", action="append", dest="tags", metavar="TAG")
510514
drafts_update.add_argument("--dry-run", action="store_true")
515+
drafts_update.add_argument("--allow-unsupported-change", action="store_true")
511516
drafts_update.add_argument("--yes", action="store_true")
512517
drafts_update.set_defaults(handler=_drafts_update)
513518

0 commit comments

Comments
 (0)