Skip to content

Commit 81c0fc2

Browse files
committed
✨ Add Markdown support for post creation and example script
1 parent 81c7feb commit 81c0fc2

3 files changed

Lines changed: 429 additions & 2 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ post.add({"type": "captionedImage", "src": image.get("url")})
100100
embedded = api.publication_embed("https://jackio.substack.com/")
101101
post.add({"type": "embeddedPublication", "url": embedded})
102102

103+
# create post from Markdown
104+
markdown_content = """
105+
# My Heading
106+
107+
This is a paragraph with **bold** and *italic* text.
108+
109+
![Image Alt](https://example.com/image.jpg)
110+
"""
111+
post.from_markdown(markdown_content, api=api)
112+
103113
draft = api.post_draft(post.get_draft())
104114

105115
# set section (can only be done after first posting the draft)

examples/publish_markdown.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""
2+
Example: Publishing a post from Markdown content
3+
4+
This example demonstrates how to use the new Markdown support
5+
to create Substack posts from Markdown files.
6+
7+
This example reads from README.md to test the Markdown parsing.
8+
"""
9+
10+
import argparse
11+
import os
12+
from pathlib import Path
13+
from dotenv import load_dotenv
14+
15+
from substack import Api
16+
from substack.post import Post
17+
18+
load_dotenv()
19+
20+
if __name__ == "__main__":
21+
parser = argparse.ArgumentParser()
22+
parser.add_argument(
23+
"-m",
24+
"--markdown",
25+
default="README.md",
26+
required=False,
27+
help="Markdown file to publish (default: README.md).",
28+
type=str,
29+
)
30+
parser.add_argument(
31+
"--publish", help="Publish the draft.", action="store_true", default=False
32+
)
33+
parser.add_argument(
34+
"--cookies",
35+
help="Path to cookies JSON file for authentication (optional, can also be set via COOKIES_PATH or COOKIES_STRING env vars).",
36+
type=str,
37+
default=None,
38+
)
39+
args = parser.parse_args()
40+
41+
cookies_path = args.cookies or os.getenv("COOKIES_PATH")
42+
cookies_string = os.getenv("COOKIES_STRING")
43+
44+
# Initialize API
45+
api = Api(
46+
email=os.getenv("EMAIL") if not cookies_path and not cookies_string else None,
47+
password=os.getenv("PASSWORD") if not cookies_path and not cookies_string else None,
48+
cookies_path=cookies_path,
49+
cookies_string=cookies_string,
50+
publication_url=os.getenv("PUBLICATION_URL"),
51+
)
52+
53+
user_id = api.get_user_id()
54+
55+
# Determine the markdown file path
56+
markdown_path = Path(args.markdown)
57+
if not markdown_path.is_absolute():
58+
# If relative path, try relative to current directory first, then parent directory
59+
if markdown_path.exists():
60+
pass # Use as-is
61+
else:
62+
# Try relative to parent directory (for README.md in project root)
63+
markdown_path = Path(__file__).parent.parent / args.markdown
64+
65+
if not markdown_path.exists():
66+
print(f"Error: Markdown file not found at {markdown_path}")
67+
exit(1)
68+
69+
with open(markdown_path, "r", encoding="utf-8") as f:
70+
markdown_content = f.read()
71+
72+
# Extract title from first heading (if it starts with #)
73+
title = "Python Substack"
74+
subtitle = "Markdown Test Post"
75+
76+
lines = markdown_content.split("\n")
77+
for line in lines:
78+
if line.startswith("# "):
79+
title = line[2:].strip()
80+
break
81+
elif line.startswith("#"):
82+
# Skip badge lines and other non-title content
83+
continue
84+
85+
# Create a post
86+
post = Post(
87+
title=title,
88+
subtitle=subtitle,
89+
user_id=user_id,
90+
)
91+
92+
# Parse and add Markdown content
93+
print(f"Parsing Markdown from {markdown_path}...")
94+
post.from_markdown(markdown_content, api=api)
95+
96+
# Create draft
97+
print("Creating draft...")
98+
draft = api.post_draft(post.get_draft())
99+
100+
if args.publish:
101+
print("Preparing to publish...")
102+
api.prepublish_draft(draft.get("id"))
103+
print("Publishing...")
104+
api.publish_draft(draft.get("id"))
105+
print("Post published successfully!")
106+
else:
107+
print(f"Draft created with ID: {draft.get('id')}")
108+
print(f"Title: {title}")
109+
print(f"Subtitle: {subtitle}")
110+
print("Use --publish flag to publish the draft.")
111+

0 commit comments

Comments
 (0)