Skip to content

Commit 00a7581

Browse files
committed
feat: implement image-sync safety with v2 markers (release 0.9.1)
1 parent 9452bdb commit 00a7581

12 files changed

Lines changed: 1028 additions & 124 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,46 @@
11
# Changelog
2-
3-
## 0.9.0
4-
5-
### Added
6-
7-
- Preserve image editor attributes through Markdown export and update with
8-
`python-substack-image:v1` markers.
9-
- Add `allow_image_replacement=False` and
10-
`substack drafts update --allow-image-replacement` for explicit image
11-
replacement authorization.
12-
13-
### Fixed
14-
15-
- Preserve absolute and home-relative local Markdown image paths during upload,
16-
retain existing root-relative asset fallback, and report missing files or
17-
failed uploads instead of saving broken image sources.
18-
19-
- Return draft records from the Substack drafts response envelope so
20-
`drafts list` works with the current API response.
2+
3+
## 0.9.1
4+
5+
### Added
6+
7+
- Export `python-substack-image:v2` markers immediately after every image,
8+
binding the image to its original remote source and preserving non-owned
9+
editor attributes including alignment, dimensions, watermark, and unknown
10+
attributes.
11+
- Resolve V2 markers by their bound source during draft updates, allowing changed
12+
Markdown image sources to safely retain original editor metadata.
13+
- Reject corrupt, stale, duplicate, reused, or non-adjacent image markers.
14+
- Continue supporting V1 image markers when the submitted image source resolves
15+
uniquely to a single remote image.
16+
17+
### Dependencies
18+
19+
- Bump `pyjwt` from 2.13.0 to 2.15.0 (#80).
20+
- Bump `fastmcp` from 4.0.5 to 4.0.10 (#81).
21+
22+
### Improved
23+
24+
- Modernize `pyproject.toml` to PEP 621 metadata standard, eliminating Poetry 2 packaging warnings.
25+
26+
## 0.9.0
27+
28+
### Added
29+
30+
- Preserve image editor attributes through Markdown export and update with
31+
`python-substack-image:v1` markers.
32+
- Add `allow_image_replacement=False` and
33+
`substack drafts update --allow-image-replacement` for explicit image
34+
replacement authorization.
35+
36+
### Fixed
37+
38+
- Preserve absolute and home-relative local Markdown image paths during upload,
39+
retain existing root-relative asset fallback, and report missing files or
40+
failed uploads instead of saving broken image sources.
41+
42+
- Return draft records from the Substack drafts response envelope so
43+
`drafts list` works with the current API response.
2144

2245
## 0.8.0
2346

@@ -79,7 +102,7 @@
79102
- Seven new MCP tools for read operations and safe publishing: `get_status`, `list_publications`, `list_drafts`, `get_draft`, `schedule_draft`, `unschedule_draft`, and `publish_draft_checked`.
80103
- Pre-publish validation, interactive confirmation requirements, and no-email defaults for the new `publish_draft_checked` MCP tool.
81104
- Verified MCP client configuration examples in `docs/mcp.md`.
82-
- Documentation for local agent workflows.
105+
- Documentation for local agent workflows.
83106

84107
### Improved
85108

‎docs/markdown.md‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,5 +204,18 @@ explicitly authorizes that change with `allow_unsupported_change=True` or
204204
`--allow-unsupported-change --yes`.
205205

206206
Export preserves the Markdown meaning of supported images: source, alt text,
207-
link, and plain-text caption. Substack-only image layout attributes are not a
208-
Markdown contract.
207+
link, and plain-text caption. In addition, images are exported with an immediate:
208+
209+
```text
210+
<!-- python-substack-image:v2 BASE64URL_JSON -->
211+
```
212+
213+
The payload binds the image to its original remote source and preserves
214+
editor layout and non-owned attributes (such as alignment, dimensions,
215+
watermark, and unrecognized attributes). Markdown remains authoritative for
216+
`src`, `alt`, `href`, and caption. Changed image sources resolve to their bound
217+
remote image and restore those attributes safely. Stale, corrupt, duplicate,
218+
reused, or non-adjacent markers are rejected. Unmatched image replacements require
219+
`allow_image_replacement=True` or `--allow-image-replacement --yes`. Legacy `v1`
220+
image markers remain supported when the image source resolves to exactly one
221+
remote image.

‎docs/releases/0.9.1.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
layout: default
3+
title: Release 0.9.1
4+
parent: Releases
5+
---
6+
7+
# Release 0.9.1
8+
9+
## Added
10+
11+
- Export `python-substack-image:v2` markers immediately following every image,
12+
binding the image to its original remote source and preserving non-owned
13+
editor attributes including alignment, dimensions, watermark, and unknown
14+
attributes.
15+
- Resolve V2 markers during draft updates by bound original source so changed
16+
image sources in Markdown safely retain editor attributes.
17+
- Reject corrupt, stale, duplicate, reused, or non-adjacent image markers.
18+
- Maintain backwards compatibility for existing V1 image markers when the
19+
submitted image source resolves uniquely to a remote image.
20+
21+
## Dependencies
22+
23+
- Bump `pyjwt` from 2.13.0 to 2.15.0 (#80).
24+
- Bump `fastmcp` from 4.0.5 to 4.0.10 (#81).
25+
26+
## Improved
27+
28+
- Modernize `pyproject.toml` to PEP 621 metadata standard, eliminating Poetry 2 packaging warnings.

‎poetry.lock‎

Lines changed: 9 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎pyproject.toml‎

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
1-
[tool.poetry]
1+
[project]
22
name = "python-substack"
3-
version = "0.9.0"
3+
version = "0.9.1"
44
description = "Write and safely manage Substack drafts from Markdown with Python, CLI, and MCP."
5-
authors = ["Paolo Mazza <mazzapaolo2019@gmail.com>"]
6-
license = "MIT"
7-
packages = [
8-
{ include = "substack" },
9-
{ include = "substack_mcp" }
5+
authors = [
6+
{ name = "Paolo Mazza", email = "mazzapaolo2019@gmail.com" }
107
]
11-
8+
license = "MIT"
129
readme = "README.md"
13-
14-
repository = "https://github.com/ma2za/python-substack"
15-
homepage = "https://github.com/ma2za/python-substack"
16-
10+
requires-python = ">=3.10,<4.0"
1711
keywords = ["substack", "substack-api", "cli", "newsletter", "publishing", "automation", "mcp"]
1812
classifiers = [
1913
"Development Status :: 4 - Beta",
@@ -27,24 +21,38 @@ classifiers = [
2721
"Topic :: Internet :: WWW/HTTP",
2822
"Topic :: Software Development :: Libraries :: Python Modules"
2923
]
24+
dependencies = [
25+
"requests>=2.32.0,<3.0.0",
26+
"python-dotenv>=1.2.1,<2.0.0",
27+
"PyYAML>=6.0,<7.0",
28+
"markdown-it-py>=3,<5",
29+
"mdit-py-plugins>=0.5,<0.7"
30+
]
31+
32+
[project.optional-dependencies]
33+
mcp = [
34+
"fastmcp>=3.1.1,<5.0.0"
35+
]
3036

31-
[tool.poetry.urls]
37+
[project.urls]
3238
"Documentation" = "https://ma2za.github.io/python-substack/"
3339
"Changelog" = "https://github.com/ma2za/python-substack/blob/main/CHANGELOG.md"
3440
"Issues" = "https://github.com/ma2za/python-substack/issues"
41+
"Homepage" = "https://github.com/ma2za/python-substack"
42+
"Repository" = "https://github.com/ma2za/python-substack"
3543

36-
[tool.poetry.dependencies]
37-
python = "<4.0,>=3.10"
38-
39-
requests = "^2.32.0"
40-
python-dotenv = "^1.2.1"
41-
PyYAML = "^6.0"
42-
markdown-it-py = ">=3,<5"
43-
mdit-py-plugins = ">=0.5,<0.7"
44-
fastmcp = { version = ">=3.1.1,<5.0.0", optional = true }
44+
[project.scripts]
45+
substack = "substack.cli:main"
46+
substack-auth-check = "substack.cli:auth_check"
47+
substack-publish-markdown = "substack.cli:publish_markdown"
48+
substack-publish-yaml = "substack.cli:publish_yaml"
49+
substack-mcp = "substack_mcp.mcp_server:main"
4550

46-
[tool.poetry.extras]
47-
mcp = ["fastmcp"]
51+
[tool.poetry]
52+
packages = [
53+
{ include = "substack" },
54+
{ include = "substack_mcp" }
55+
]
4856

4957
[tool.poetry.group.dev.dependencies]
5058
pytest = "^9.1.1"
@@ -56,13 +64,6 @@ markers = [
5664
"live: opt-in tests that call the live Substack service",
5765
]
5866

59-
[tool.poetry.scripts]
60-
substack = "substack.cli:main"
61-
substack-auth-check = "substack.cli:auth_check"
62-
substack-publish-markdown = "substack.cli:publish_markdown"
63-
substack-publish-yaml = "substack.cli:publish_yaml"
64-
substack-mcp = "substack_mcp.mcp_server:main"
65-
6667
[build-system]
67-
requires = ["poetry-core>=1.0.0"]
68+
requires = ["poetry-core>=2.0.0"]
6869
build-backend = "poetry.core.masonry.api"

‎substack/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
__author__ = "Paolo Mazza"
44
__email__ = "mazzapaolo2019@gmail.com"
55
__license__ = "MIT License"
6-
__version__ = "0.9.0"
6+
__version__ = "0.9.1"
77
__url__ = "https://github.com/ma2za/python-substack"
88
__download_url__ = "https://pypi.python.org/pypi/python-substack"
99
__description__ = (

0 commit comments

Comments
 (0)