python-substack 0.1.27__tar.gz → 0.3.0__tar.gz

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.
@@ -0,0 +1,284 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-substack
3
+ Version: 0.3.0
4
+ Summary: Write and safely manage Substack drafts from Markdown with Python, CLI, and MCP.
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: substack,substack-api,cli,newsletter,publishing,automation,mcp
8
+ Author: Paolo Mazza
9
+ Author-email: mazzapaolo2019@gmail.com
10
+ Requires-Python: >=3.10,<4.0
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Communications :: Email
21
+ Classifier: Topic :: Internet :: WWW/HTTP
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Provides-Extra: mcp
24
+ Requires-Dist: PyYAML (>=6.0,<7.0)
25
+ Requires-Dist: fastmcp (>=3.1.1,<4.0.0) ; extra == "mcp"
26
+ Requires-Dist: markdown-it-py (>=3.0,<4.0)
27
+ Requires-Dist: mdit-py-plugins (>=0.5,<0.7)
28
+ Requires-Dist: python-dotenv (>=1.2.1,<2.0.0)
29
+ Requires-Dist: requests (>=2.32.0,<3.0.0)
30
+ Project-URL: Changelog, https://github.com/ma2za/python-substack/blob/main/CHANGELOG.md
31
+ Project-URL: Documentation, https://ma2za.github.io/python-substack/
32
+ Project-URL: Homepage, https://github.com/ma2za/python-substack
33
+ Project-URL: Issues, https://github.com/ma2za/python-substack/issues
34
+ Project-URL: Repository, https://github.com/ma2za/python-substack
35
+ Description-Content-Type: text/markdown
36
+
37
+ # Python Substack
38
+
39
+ Write Substack posts in Markdown and safely create, inspect, schedule, and
40
+ publish them through Python, a command-line interface, or MCP.
41
+
42
+ [![PyPI](https://img.shields.io/pypi/v/python-substack)](https://pypi.org/project/python-substack/)
43
+ [![Python](https://img.shields.io/pypi/pyversions/python-substack)](https://pypi.org/project/python-substack/)
44
+ [![Tests](https://github.com/ma2za/python-substack/actions/workflows/ci.yml/badge.svg)](https://github.com/ma2za/python-substack/actions/workflows/ci.yml)
45
+ [![Release](https://github.com/ma2za/python-substack/actions/workflows/ci_publish.yml/badge.svg)](https://github.com/ma2za/python-substack/actions/workflows/ci_publish.yml)
46
+ [![License](https://img.shields.io/pypi/l/python-substack)](LICENSE)
47
+ [![Downloads](https://static.pepy.tech/badge/python-substack/month)](https://pepy.tech/project/python-substack)
48
+
49
+ [Documentation](https://ma2za.github.io/python-substack/) ·
50
+ [Getting started](https://ma2za.github.io/python-substack/getting-started.html) ·
51
+ [PyPI](https://pypi.org/project/python-substack/)
52
+
53
+ > [!IMPORTANT]
54
+ > Creating and publishing are separate operations. `substack drafts create`
55
+ > always creates an unpublished draft. It never schedules, sends, publishes,
56
+ > or deletes content.
57
+
58
+ ## From Markdown to a Substack draft
59
+
60
+ Install the package:
61
+
62
+ ```bash
63
+ pip install python-substack
64
+ ```
65
+
66
+ Check the selected account and publication:
67
+
68
+ ```bash
69
+ substack status
70
+ ```
71
+
72
+ Create a safe unpublished draft, then publish only when it is ready:
73
+
74
+ ```bash
75
+ substack drafts create post.md
76
+ substack drafts publish 12345 --no-send
77
+ ```
78
+
79
+ Publishing and deletion require confirmation. Noninteractive and JSON
80
+ workflows must pass `--yes` explicitly.
81
+
82
+ Markdown source:
83
+
84
+ ![Markdown before conversion](docs/before.png)
85
+
86
+ Substack result:
87
+
88
+ ![Substack after conversion](docs/after.png)
89
+
90
+ ## What it supports
91
+
92
+ - Create rich Substack drafts from Markdown.
93
+ - Upload local images referenced by Markdown.
94
+ - Set audience, comment permissions, SEO metadata, slug, sections, and tags.
95
+ - List and inspect publications and drafts.
96
+ - Schedule, unschedule, publish, and delete drafts with explicit safeguards.
97
+ - Use stable JSON envelopes in scripts and automation.
98
+ - Authenticate with browser cookies or email and password.
99
+ - Use the same publishing workflow from Python or an optional MCP server.
100
+
101
+ ## Setup
102
+
103
+ Copy `.env.example` to `.env` and configure one authentication method:
104
+
105
+ ```env
106
+ EMAIL=
107
+ PASSWORD=
108
+ PUBLICATION_URL=
109
+ COOKIES_PATH=
110
+ COOKIES_STRING=
111
+ ```
112
+
113
+ Cookie authentication is usually more reliable when Substack requires captcha
114
+ or magic-link sign-in. See
115
+ [Authentication](docs/authentication.md) for cookie export instructions and
116
+ account-selection details.
117
+
118
+ Verify the installation without authenticating:
119
+
120
+ ```bash
121
+ substack --version
122
+ substack --help
123
+ ```
124
+
125
+ ## CLI
126
+
127
+ Create a draft with metadata:
128
+
129
+ ```bash
130
+ substack --json drafts create post.md \
131
+ --title "My Post" \
132
+ --subtitle "Optional subtitle" \
133
+ --tag python \
134
+ --tag substack \
135
+ --slug my-post \
136
+ --search-engine-title "SEO title" \
137
+ --search-engine-description "SEO description"
138
+ ```
139
+
140
+ Inspect publications and drafts:
141
+
142
+ ```bash
143
+ substack publications list
144
+ substack drafts list --limit 10
145
+ substack drafts get 12345
146
+ substack --publication-url https://example.substack.com drafts list
147
+ ```
148
+
149
+ Manage scheduling:
150
+
151
+ ```bash
152
+ substack drafts schedule 12345 --at 2026-08-01T09:00:00+03:00
153
+ substack drafts unschedule 12345
154
+ ```
155
+
156
+ Publish or delete intentionally:
157
+
158
+ ```bash
159
+ substack drafts publish 12345 --no-send
160
+ substack drafts delete 12345 --yes
161
+ ```
162
+
163
+ Global options such as `--json`, `--cookies`, and `--publication-url` must
164
+ appear before the command:
165
+
166
+ ```bash
167
+ substack --json drafts list
168
+ substack --cookies cookies.json --json status
169
+ ```
170
+
171
+ The original standalone commands remain supported. See
172
+ [Legacy CLI commands](docs/legacy-cli.md).
173
+
174
+ ## Python
175
+
176
+ ```python
177
+ import os
178
+
179
+ from dotenv import load_dotenv
180
+ from substack import Api
181
+
182
+ load_dotenv()
183
+
184
+ api = Api(
185
+ email=os.getenv("EMAIL"),
186
+ password=os.getenv("PASSWORD"),
187
+ publication_url=os.getenv("PUBLICATION_URL"),
188
+ )
189
+
190
+ result = api.create_draft_from_markdown(
191
+ title="Shipping with Python",
192
+ subtitle="A short note from a script",
193
+ markdown="""
194
+ # Hello
195
+
196
+ This draft was created from **Markdown**.
197
+
198
+ ![Alt text](https://example.com/image.png "Image caption")
199
+ """,
200
+ tags=["python", "automation"],
201
+ slug="shipping-with-python",
202
+ )
203
+
204
+ print(result["draft"]["id"])
205
+ ```
206
+
207
+ `create_draft_from_markdown` creates a draft by default. It publishes only when
208
+ `publish=True` is passed.
209
+
210
+ For direct ProseMirror node construction, see the
211
+ [low-level Python API](docs/low-level-api.md). YAML workflows are documented in
212
+ [YAML drafts](docs/yaml.md).
213
+
214
+ ## Markdown
215
+
216
+ Supported Markdown includes headings, paragraphs, bold, italic, inline code,
217
+ strikethrough, superscript, subscript, links, images, linked images, image
218
+ captions, code blocks, blockquotes, ordered and unordered lists, horizontal
219
+ rules, footnotes, LaTeX math, pull quotes, and callouts.
220
+
221
+ ```python
222
+ from substack.post import Post
223
+
224
+ post = Post("Title", "Subtitle", user_id=1)
225
+ post.from_markdown(
226
+ """
227
+ # Heading
228
+
229
+ Paragraph with **bold**, *italic*, `code`, and [links](https://example.com).
230
+ """
231
+ )
232
+ ```
233
+
234
+ Pass `api=` to upload local images while rendering:
235
+
236
+ ```python
237
+ post.from_markdown(markdown_content, api=api)
238
+ ```
239
+
240
+ See the complete [Markdown reference](docs/markdown.md).
241
+
242
+ ## MCP
243
+
244
+ Install and run the optional MCP server:
245
+
246
+ ```bash
247
+ pip install "python-substack[mcp]"
248
+ substack-mcp
249
+ ```
250
+
251
+ The MCP tools use the same environment variables and SDK behavior as the CLI.
252
+ See [MCP server](docs/mcp.md) for the tool list and safety notes.
253
+
254
+ ## Project documentation
255
+
256
+ - [Documentation site](https://ma2za.github.io/python-substack/)
257
+ - [Installation and first draft](docs/getting-started.md)
258
+ - [Unified CLI](docs/cli.md)
259
+ - [Python SDK](docs/python-sdk.md)
260
+ - [Authentication](docs/authentication.md)
261
+ - [Markdown reference](docs/markdown.md)
262
+ - [Legacy CLI commands](docs/legacy-cli.md)
263
+ - [Low-level Python API](docs/low-level-api.md)
264
+ - [YAML drafts](docs/yaml.md)
265
+ - [MCP server](docs/mcp.md)
266
+ - [Safety and publishing behavior](docs/safety.md)
267
+ - [Troubleshooting](docs/troubleshooting.md)
268
+ - [Compatibility policy](docs/compatibility.md)
269
+ - [Contributing](CONTRIBUTING.md)
270
+ - [Security policy](SECURITY.md)
271
+ - [Changelog](CHANGELOG.md)
272
+
273
+ ## Compatibility
274
+
275
+ The project preserves existing Python APIs, console commands, CLI behavior,
276
+ environment variables, JSON keys, and MCP tool signatures through the 1.x
277
+ series. Additive capabilities may be introduced. See the
278
+ [compatibility policy](docs/compatibility.md).
279
+
280
+ ## Disclaimer
281
+
282
+ This project is not affiliated with Substack. It uses undocumented Substack
283
+ interfaces that may change without notice.
284
+
@@ -0,0 +1,247 @@
1
+ # Python Substack
2
+
3
+ Write Substack posts in Markdown and safely create, inspect, schedule, and
4
+ publish them through Python, a command-line interface, or MCP.
5
+
6
+ [![PyPI](https://img.shields.io/pypi/v/python-substack)](https://pypi.org/project/python-substack/)
7
+ [![Python](https://img.shields.io/pypi/pyversions/python-substack)](https://pypi.org/project/python-substack/)
8
+ [![Tests](https://github.com/ma2za/python-substack/actions/workflows/ci.yml/badge.svg)](https://github.com/ma2za/python-substack/actions/workflows/ci.yml)
9
+ [![Release](https://github.com/ma2za/python-substack/actions/workflows/ci_publish.yml/badge.svg)](https://github.com/ma2za/python-substack/actions/workflows/ci_publish.yml)
10
+ [![License](https://img.shields.io/pypi/l/python-substack)](LICENSE)
11
+ [![Downloads](https://static.pepy.tech/badge/python-substack/month)](https://pepy.tech/project/python-substack)
12
+
13
+ [Documentation](https://ma2za.github.io/python-substack/) ·
14
+ [Getting started](https://ma2za.github.io/python-substack/getting-started.html) ·
15
+ [PyPI](https://pypi.org/project/python-substack/)
16
+
17
+ > [!IMPORTANT]
18
+ > Creating and publishing are separate operations. `substack drafts create`
19
+ > always creates an unpublished draft. It never schedules, sends, publishes,
20
+ > or deletes content.
21
+
22
+ ## From Markdown to a Substack draft
23
+
24
+ Install the package:
25
+
26
+ ```bash
27
+ pip install python-substack
28
+ ```
29
+
30
+ Check the selected account and publication:
31
+
32
+ ```bash
33
+ substack status
34
+ ```
35
+
36
+ Create a safe unpublished draft, then publish only when it is ready:
37
+
38
+ ```bash
39
+ substack drafts create post.md
40
+ substack drafts publish 12345 --no-send
41
+ ```
42
+
43
+ Publishing and deletion require confirmation. Noninteractive and JSON
44
+ workflows must pass `--yes` explicitly.
45
+
46
+ Markdown source:
47
+
48
+ ![Markdown before conversion](docs/before.png)
49
+
50
+ Substack result:
51
+
52
+ ![Substack after conversion](docs/after.png)
53
+
54
+ ## What it supports
55
+
56
+ - Create rich Substack drafts from Markdown.
57
+ - Upload local images referenced by Markdown.
58
+ - Set audience, comment permissions, SEO metadata, slug, sections, and tags.
59
+ - List and inspect publications and drafts.
60
+ - Schedule, unschedule, publish, and delete drafts with explicit safeguards.
61
+ - Use stable JSON envelopes in scripts and automation.
62
+ - Authenticate with browser cookies or email and password.
63
+ - Use the same publishing workflow from Python or an optional MCP server.
64
+
65
+ ## Setup
66
+
67
+ Copy `.env.example` to `.env` and configure one authentication method:
68
+
69
+ ```env
70
+ EMAIL=
71
+ PASSWORD=
72
+ PUBLICATION_URL=
73
+ COOKIES_PATH=
74
+ COOKIES_STRING=
75
+ ```
76
+
77
+ Cookie authentication is usually more reliable when Substack requires captcha
78
+ or magic-link sign-in. See
79
+ [Authentication](docs/authentication.md) for cookie export instructions and
80
+ account-selection details.
81
+
82
+ Verify the installation without authenticating:
83
+
84
+ ```bash
85
+ substack --version
86
+ substack --help
87
+ ```
88
+
89
+ ## CLI
90
+
91
+ Create a draft with metadata:
92
+
93
+ ```bash
94
+ substack --json drafts create post.md \
95
+ --title "My Post" \
96
+ --subtitle "Optional subtitle" \
97
+ --tag python \
98
+ --tag substack \
99
+ --slug my-post \
100
+ --search-engine-title "SEO title" \
101
+ --search-engine-description "SEO description"
102
+ ```
103
+
104
+ Inspect publications and drafts:
105
+
106
+ ```bash
107
+ substack publications list
108
+ substack drafts list --limit 10
109
+ substack drafts get 12345
110
+ substack --publication-url https://example.substack.com drafts list
111
+ ```
112
+
113
+ Manage scheduling:
114
+
115
+ ```bash
116
+ substack drafts schedule 12345 --at 2026-08-01T09:00:00+03:00
117
+ substack drafts unschedule 12345
118
+ ```
119
+
120
+ Publish or delete intentionally:
121
+
122
+ ```bash
123
+ substack drafts publish 12345 --no-send
124
+ substack drafts delete 12345 --yes
125
+ ```
126
+
127
+ Global options such as `--json`, `--cookies`, and `--publication-url` must
128
+ appear before the command:
129
+
130
+ ```bash
131
+ substack --json drafts list
132
+ substack --cookies cookies.json --json status
133
+ ```
134
+
135
+ The original standalone commands remain supported. See
136
+ [Legacy CLI commands](docs/legacy-cli.md).
137
+
138
+ ## Python
139
+
140
+ ```python
141
+ import os
142
+
143
+ from dotenv import load_dotenv
144
+ from substack import Api
145
+
146
+ load_dotenv()
147
+
148
+ api = Api(
149
+ email=os.getenv("EMAIL"),
150
+ password=os.getenv("PASSWORD"),
151
+ publication_url=os.getenv("PUBLICATION_URL"),
152
+ )
153
+
154
+ result = api.create_draft_from_markdown(
155
+ title="Shipping with Python",
156
+ subtitle="A short note from a script",
157
+ markdown="""
158
+ # Hello
159
+
160
+ This draft was created from **Markdown**.
161
+
162
+ ![Alt text](https://example.com/image.png "Image caption")
163
+ """,
164
+ tags=["python", "automation"],
165
+ slug="shipping-with-python",
166
+ )
167
+
168
+ print(result["draft"]["id"])
169
+ ```
170
+
171
+ `create_draft_from_markdown` creates a draft by default. It publishes only when
172
+ `publish=True` is passed.
173
+
174
+ For direct ProseMirror node construction, see the
175
+ [low-level Python API](docs/low-level-api.md). YAML workflows are documented in
176
+ [YAML drafts](docs/yaml.md).
177
+
178
+ ## Markdown
179
+
180
+ Supported Markdown includes headings, paragraphs, bold, italic, inline code,
181
+ strikethrough, superscript, subscript, links, images, linked images, image
182
+ captions, code blocks, blockquotes, ordered and unordered lists, horizontal
183
+ rules, footnotes, LaTeX math, pull quotes, and callouts.
184
+
185
+ ```python
186
+ from substack.post import Post
187
+
188
+ post = Post("Title", "Subtitle", user_id=1)
189
+ post.from_markdown(
190
+ """
191
+ # Heading
192
+
193
+ Paragraph with **bold**, *italic*, `code`, and [links](https://example.com).
194
+ """
195
+ )
196
+ ```
197
+
198
+ Pass `api=` to upload local images while rendering:
199
+
200
+ ```python
201
+ post.from_markdown(markdown_content, api=api)
202
+ ```
203
+
204
+ See the complete [Markdown reference](docs/markdown.md).
205
+
206
+ ## MCP
207
+
208
+ Install and run the optional MCP server:
209
+
210
+ ```bash
211
+ pip install "python-substack[mcp]"
212
+ substack-mcp
213
+ ```
214
+
215
+ The MCP tools use the same environment variables and SDK behavior as the CLI.
216
+ See [MCP server](docs/mcp.md) for the tool list and safety notes.
217
+
218
+ ## Project documentation
219
+
220
+ - [Documentation site](https://ma2za.github.io/python-substack/)
221
+ - [Installation and first draft](docs/getting-started.md)
222
+ - [Unified CLI](docs/cli.md)
223
+ - [Python SDK](docs/python-sdk.md)
224
+ - [Authentication](docs/authentication.md)
225
+ - [Markdown reference](docs/markdown.md)
226
+ - [Legacy CLI commands](docs/legacy-cli.md)
227
+ - [Low-level Python API](docs/low-level-api.md)
228
+ - [YAML drafts](docs/yaml.md)
229
+ - [MCP server](docs/mcp.md)
230
+ - [Safety and publishing behavior](docs/safety.md)
231
+ - [Troubleshooting](docs/troubleshooting.md)
232
+ - [Compatibility policy](docs/compatibility.md)
233
+ - [Contributing](CONTRIBUTING.md)
234
+ - [Security policy](SECURITY.md)
235
+ - [Changelog](CHANGELOG.md)
236
+
237
+ ## Compatibility
238
+
239
+ The project preserves existing Python APIs, console commands, CLI behavior,
240
+ environment variables, JSON keys, and MCP tool signatures through the 1.x
241
+ series. Additive capabilities may be introduced. See the
242
+ [compatibility policy](docs/compatibility.md).
243
+
244
+ ## Disclaimer
245
+
246
+ This project is not affiliated with Substack. It uses undocumented Substack
247
+ interfaces that may change without notice.
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "python-substack"
3
- version = "0.1.27"
4
- description = "A Python SDK and CLI for managing Substack publications and drafts."
3
+ version = "0.3.0"
4
+ description = "Write and safely manage Substack drafts from Markdown with Python, CLI, and MCP."
5
5
  authors = ["Paolo Mazza <mazzapaolo2019@gmail.com>"]
6
6
  license = "MIT"
7
7
  packages = [
@@ -16,7 +16,7 @@ homepage = "https://github.com/ma2za/python-substack"
16
16
 
17
17
  keywords = ["substack", "substack-api", "cli", "newsletter", "publishing", "automation", "mcp"]
18
18
  classifiers = [
19
- "Development Status :: 3 - Alpha",
19
+ "Development Status :: 4 - Beta",
20
20
  "Intended Audience :: Developers",
21
21
  "Programming Language :: Python :: 3",
22
22
  "Programming Language :: Python :: 3.10",
@@ -29,6 +29,7 @@ classifiers = [
29
29
  ]
30
30
 
31
31
  [tool.poetry.urls]
32
+ "Documentation" = "https://ma2za.github.io/python-substack/"
32
33
  "Changelog" = "https://github.com/ma2za/python-substack/blob/main/CHANGELOG.md"
33
34
  "Issues" = "https://github.com/ma2za/python-substack/issues"
34
35
 
@@ -47,6 +48,7 @@ mcp = ["fastmcp"]
47
48
 
48
49
  [tool.poetry.group.dev.dependencies]
49
50
  pytest = "^9.1.1"
51
+ pre-commit = "^4.6.1"
50
52
 
51
53
  [tool.pytest.ini_options]
52
54
  markers = [
@@ -3,9 +3,11 @@
3
3
  __author__ = "Paolo Mazza"
4
4
  __email__ = "mazzapaolo2019@gmail.com"
5
5
  __license__ = "MIT License"
6
- __version__ = "0.1.27"
6
+ __version__ = "0.3.0"
7
7
  __url__ = "https://github.com/ma2za/python-substack"
8
8
  __download_url__ = "https://pypi.python.org/pypi/python-substack"
9
- __description__ = "A Python SDK and CLI for managing Substack publications and drafts"
9
+ __description__ = (
10
+ "Write and safely manage Substack drafts from Markdown with Python, CLI, and MCP."
11
+ )
10
12
 
11
13
  from .api import Api
@@ -348,7 +348,6 @@ class Api:
348
348
  return Api._handle_response(response=response)
349
349
 
350
350
  def get_publication_subscriber_count(self):
351
-
352
351
  """
353
352
  Get subscriber count.
354
353