python-substack 0.1.13__tar.gz → 0.1.14__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.
- {python_substack-0.1.13 → python_substack-0.1.14}/PKG-INFO +40 -9
- {python_substack-0.1.13 → python_substack-0.1.14}/README.md +39 -8
- {python_substack-0.1.13 → python_substack-0.1.14}/pyproject.toml +2 -2
- {python_substack-0.1.13 → python_substack-0.1.14}/substack/api.py +196 -18
- {python_substack-0.1.13 → python_substack-0.1.14}/substack/post.py +38 -22
- {python_substack-0.1.13 → python_substack-0.1.14}/LICENSE +0 -0
- {python_substack-0.1.13 → python_substack-0.1.14}/substack/__init__.py +0 -0
- {python_substack-0.1.13 → python_substack-0.1.14}/substack/exceptions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-substack
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14
|
|
4
4
|
Summary: A Python wrapper around the Substack API.
|
|
5
5
|
Home-page: https://github.com/ma2za/python-substack
|
|
6
6
|
License: MIT
|
|
@@ -44,15 +44,19 @@ You can install python-substack using:
|
|
|
44
44
|
|
|
45
45
|
Set the following environment variables by creating a **.env** file:
|
|
46
46
|
|
|
47
|
-
PUBLICATION_URL=https://ma2za.substack.com
|
|
48
47
|
EMAIL=
|
|
49
48
|
PASSWORD=
|
|
50
|
-
USER_ID=
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
## If you don't have a password
|
|
51
|
+
|
|
52
|
+
Recently Substack has been setting up new accounts without a password. If you sign-out and sign back in it just uses
|
|
53
|
+
your email address with a "magic" link.
|
|
54
|
+
|
|
55
|
+
Set a password:
|
|
56
|
+
|
|
57
|
+
- Sign-out of Substack
|
|
58
|
+
- At the sign-in page click, "Sign in with password" under the `Email` text box
|
|
59
|
+
- Then choose, "Set a new password"
|
|
56
60
|
|
|
57
61
|
The .env file will be ignored by git but always be careful.
|
|
58
62
|
|
|
@@ -71,13 +75,24 @@ from substack.post import Post
|
|
|
71
75
|
api = Api(
|
|
72
76
|
email=os.getenv("EMAIL"),
|
|
73
77
|
password=os.getenv("PASSWORD"),
|
|
74
|
-
publication_url=os.getenv("PUBLICATION_URL"),
|
|
75
78
|
)
|
|
76
79
|
|
|
80
|
+
user_id = api.get_user_id()
|
|
81
|
+
|
|
82
|
+
# Switch Publications - The library defaults to your users primary publication. You can retrieve all your publications and change which one you want to use.
|
|
83
|
+
|
|
84
|
+
# primary publication
|
|
85
|
+
user_publication = api.get_user_primary_publication()
|
|
86
|
+
# all publications
|
|
87
|
+
user_publications = api.get_user_publications()
|
|
88
|
+
|
|
89
|
+
# This step is only necessary if you are not using your primary publication
|
|
90
|
+
# api.change_publication(user_publication)
|
|
91
|
+
|
|
77
92
|
post = Post(
|
|
78
93
|
title="How to publish a Substack post using the Python API",
|
|
79
94
|
subtitle="This post was published using the Python API",
|
|
80
|
-
user_id=
|
|
95
|
+
user_id=user_id
|
|
81
96
|
)
|
|
82
97
|
|
|
83
98
|
post.add({'type': 'paragraph', 'content': 'This is how you add a new paragraph to your post!'})
|
|
@@ -91,6 +106,9 @@ post.add({'type': "paragraph",
|
|
|
91
106
|
post.add({'type': 'paragraph', 'content': [
|
|
92
107
|
{'content': "View Link", 'marks': [{'type': "link", 'href': 'https://whoraised.substack.com/'}]}]})
|
|
93
108
|
|
|
109
|
+
# set paywall boundary
|
|
110
|
+
post.add({'type': 'paywall'})
|
|
111
|
+
|
|
94
112
|
# add image
|
|
95
113
|
post.add({'type': 'captionedImage', 'src': "https://media.tenor.com/7B4jMa-a7bsAAAAC/i-am-batman.gif"})
|
|
96
114
|
|
|
@@ -109,4 +127,17 @@ api.prepublish_draft(draft.get("id"))
|
|
|
109
127
|
api.publish_draft(draft.get("id"))
|
|
110
128
|
```
|
|
111
129
|
|
|
130
|
+
# Contributing
|
|
131
|
+
|
|
132
|
+
Install pre-commit:
|
|
133
|
+
|
|
134
|
+
```shell
|
|
135
|
+
pip install pre-commit
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Set up pre-commit
|
|
139
|
+
|
|
140
|
+
```shell
|
|
141
|
+
pre-commit install
|
|
142
|
+
```
|
|
112
143
|
|
|
@@ -20,15 +20,19 @@ You can install python-substack using:
|
|
|
20
20
|
|
|
21
21
|
Set the following environment variables by creating a **.env** file:
|
|
22
22
|
|
|
23
|
-
PUBLICATION_URL=https://ma2za.substack.com
|
|
24
23
|
EMAIL=
|
|
25
24
|
PASSWORD=
|
|
26
|
-
USER_ID=
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
## If you don't have a password
|
|
27
|
+
|
|
28
|
+
Recently Substack has been setting up new accounts without a password. If you sign-out and sign back in it just uses
|
|
29
|
+
your email address with a "magic" link.
|
|
30
|
+
|
|
31
|
+
Set a password:
|
|
32
|
+
|
|
33
|
+
- Sign-out of Substack
|
|
34
|
+
- At the sign-in page click, "Sign in with password" under the `Email` text box
|
|
35
|
+
- Then choose, "Set a new password"
|
|
32
36
|
|
|
33
37
|
The .env file will be ignored by git but always be careful.
|
|
34
38
|
|
|
@@ -47,13 +51,24 @@ from substack.post import Post
|
|
|
47
51
|
api = Api(
|
|
48
52
|
email=os.getenv("EMAIL"),
|
|
49
53
|
password=os.getenv("PASSWORD"),
|
|
50
|
-
publication_url=os.getenv("PUBLICATION_URL"),
|
|
51
54
|
)
|
|
52
55
|
|
|
56
|
+
user_id = api.get_user_id()
|
|
57
|
+
|
|
58
|
+
# Switch Publications - The library defaults to your users primary publication. You can retrieve all your publications and change which one you want to use.
|
|
59
|
+
|
|
60
|
+
# primary publication
|
|
61
|
+
user_publication = api.get_user_primary_publication()
|
|
62
|
+
# all publications
|
|
63
|
+
user_publications = api.get_user_publications()
|
|
64
|
+
|
|
65
|
+
# This step is only necessary if you are not using your primary publication
|
|
66
|
+
# api.change_publication(user_publication)
|
|
67
|
+
|
|
53
68
|
post = Post(
|
|
54
69
|
title="How to publish a Substack post using the Python API",
|
|
55
70
|
subtitle="This post was published using the Python API",
|
|
56
|
-
user_id=
|
|
71
|
+
user_id=user_id
|
|
57
72
|
)
|
|
58
73
|
|
|
59
74
|
post.add({'type': 'paragraph', 'content': 'This is how you add a new paragraph to your post!'})
|
|
@@ -67,6 +82,9 @@ post.add({'type': "paragraph",
|
|
|
67
82
|
post.add({'type': 'paragraph', 'content': [
|
|
68
83
|
{'content': "View Link", 'marks': [{'type': "link", 'href': 'https://whoraised.substack.com/'}]}]})
|
|
69
84
|
|
|
85
|
+
# set paywall boundary
|
|
86
|
+
post.add({'type': 'paywall'})
|
|
87
|
+
|
|
70
88
|
# add image
|
|
71
89
|
post.add({'type': 'captionedImage', 'src': "https://media.tenor.com/7B4jMa-a7bsAAAAC/i-am-batman.gif"})
|
|
72
90
|
|
|
@@ -85,3 +103,16 @@ api.prepublish_draft(draft.get("id"))
|
|
|
85
103
|
api.publish_draft(draft.get("id"))
|
|
86
104
|
```
|
|
87
105
|
|
|
106
|
+
# Contributing
|
|
107
|
+
|
|
108
|
+
Install pre-commit:
|
|
109
|
+
|
|
110
|
+
```shell
|
|
111
|
+
pip install pre-commit
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Set up pre-commit
|
|
115
|
+
|
|
116
|
+
```shell
|
|
117
|
+
pre-commit install
|
|
118
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-substack"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.14"
|
|
4
4
|
description = "A Python wrapper around the Substack API."
|
|
5
5
|
authors = ["Paolo Mazza <mazzapaolo2019@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -28,4 +28,4 @@ PyYAML = "^6.0"
|
|
|
28
28
|
|
|
29
29
|
[build-system]
|
|
30
30
|
requires = ["poetry-core>=1.0.0"]
|
|
31
|
-
build-backend = "poetry.core.masonry.api"
|
|
31
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
API Wrapper
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
|
|
1
7
|
import base64
|
|
8
|
+
import json
|
|
2
9
|
import logging
|
|
3
10
|
import os
|
|
4
11
|
from datetime import datetime
|
|
@@ -21,12 +28,13 @@ class Api:
|
|
|
21
28
|
"""
|
|
22
29
|
|
|
23
30
|
def __init__(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
self,
|
|
32
|
+
email=None,
|
|
33
|
+
password=None,
|
|
34
|
+
cookies_path=None,
|
|
35
|
+
base_url=None,
|
|
36
|
+
publication_url=None,
|
|
37
|
+
debug=False,
|
|
30
38
|
):
|
|
31
39
|
"""
|
|
32
40
|
|
|
@@ -37,12 +45,15 @@ class Api:
|
|
|
37
45
|
Args:
|
|
38
46
|
email:
|
|
39
47
|
password:
|
|
48
|
+
cookies_path
|
|
49
|
+
To re-use your session without logging in each time, you can save your cookies to a json file and
|
|
50
|
+
then load them in the next session.
|
|
51
|
+
Make sure to re-save your cookies, as they do update over time.
|
|
40
52
|
base_url:
|
|
41
53
|
The base URL to use to contact the Substack API.
|
|
42
54
|
Defaults to https://substack.com/api/v1.
|
|
43
55
|
"""
|
|
44
56
|
self.base_url = base_url or "https://substack.com/api/v1"
|
|
45
|
-
self.publication_url = urljoin(publication_url, "api/v1")
|
|
46
57
|
|
|
47
58
|
if debug:
|
|
48
59
|
logging.basicConfig()
|
|
@@ -50,8 +61,42 @@ class Api:
|
|
|
50
61
|
|
|
51
62
|
self._session = requests.Session()
|
|
52
63
|
|
|
53
|
-
|
|
64
|
+
# Load cookies from file if provided
|
|
65
|
+
# Helps with Captcha errors by reusing cookies from "local" auth, then switching to running code in the cloud
|
|
66
|
+
if cookies_path is not None:
|
|
67
|
+
with open(cookies_path) as f:
|
|
68
|
+
cookies = json.load(f)
|
|
69
|
+
self._session.cookies.update(cookies)
|
|
70
|
+
|
|
71
|
+
elif email is not None and password is not None:
|
|
54
72
|
self.login(email, password)
|
|
73
|
+
else:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
"Must provide email and password or cookies_path to authenticate."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
user_publication = None
|
|
79
|
+
# if the user provided a publication url, then use that
|
|
80
|
+
if publication_url:
|
|
81
|
+
import re
|
|
82
|
+
|
|
83
|
+
# Regular expression to extract subdomain name
|
|
84
|
+
match = re.search(r"https://(.*).substack.com", publication_url.lower())
|
|
85
|
+
subdomain = match.group(1) if match else None
|
|
86
|
+
|
|
87
|
+
user_publications = self.get_user_publications()
|
|
88
|
+
# search through publications to find the publication with the matching subdomain
|
|
89
|
+
for publication in user_publications:
|
|
90
|
+
if publication["subdomain"] == subdomain:
|
|
91
|
+
# set the current publication to the users publication
|
|
92
|
+
user_publication = publication
|
|
93
|
+
break
|
|
94
|
+
else:
|
|
95
|
+
# get the users primary publication
|
|
96
|
+
user_publication = self.get_user_primary_publication()
|
|
97
|
+
|
|
98
|
+
# set the current publication to the users primary publication
|
|
99
|
+
self.change_publication(user_publication)
|
|
55
100
|
|
|
56
101
|
def login(self, email, password) -> dict:
|
|
57
102
|
"""
|
|
@@ -73,8 +118,37 @@ class Api:
|
|
|
73
118
|
"redirect": "/",
|
|
74
119
|
},
|
|
75
120
|
)
|
|
121
|
+
|
|
76
122
|
return Api._handle_response(response=response)
|
|
77
123
|
|
|
124
|
+
def signin_for_pub(self, publication):
|
|
125
|
+
"""
|
|
126
|
+
Complete the signin process
|
|
127
|
+
"""
|
|
128
|
+
response = self._session.get(
|
|
129
|
+
f"https://substack.com/sign-in?redirect=%2F&for_pub={publication['subdomain']}",
|
|
130
|
+
)
|
|
131
|
+
return Api._handle_response(response=response)
|
|
132
|
+
|
|
133
|
+
def change_publication(self, publication):
|
|
134
|
+
"""
|
|
135
|
+
Change the publication URL
|
|
136
|
+
"""
|
|
137
|
+
self.publication_url = urljoin(publication["publication_url"], "api/v1")
|
|
138
|
+
|
|
139
|
+
# sign-in to the publication
|
|
140
|
+
self.signin_for_pub(publication)
|
|
141
|
+
|
|
142
|
+
def export_cookies(self, path: str = "cookies.json"):
|
|
143
|
+
"""
|
|
144
|
+
Export cookies to a json file.
|
|
145
|
+
Args:
|
|
146
|
+
path: path to the json file
|
|
147
|
+
"""
|
|
148
|
+
cookies = self._session.cookies.get_dict()
|
|
149
|
+
with open(path, "w") as f:
|
|
150
|
+
json.dump(cookies, f)
|
|
151
|
+
|
|
78
152
|
@staticmethod
|
|
79
153
|
def _handle_response(response: requests.Response):
|
|
80
154
|
"""
|
|
@@ -92,6 +166,82 @@ class Api:
|
|
|
92
166
|
except ValueError:
|
|
93
167
|
raise SubstackRequestException("Invalid Response: %s" % response.text)
|
|
94
168
|
|
|
169
|
+
def get_user_id(self):
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
profile = self.get_user_profile()
|
|
176
|
+
user_id = profile["id"]
|
|
177
|
+
|
|
178
|
+
return user_id
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def get_publication_url(publication: dict) -> str:
|
|
182
|
+
"""
|
|
183
|
+
Gets the publication url
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
publication:
|
|
187
|
+
"""
|
|
188
|
+
custom_domain = publication["custom_domain"]
|
|
189
|
+
if not custom_domain:
|
|
190
|
+
publication_url = f"https://{publication['subdomain']}.substack.com"
|
|
191
|
+
else:
|
|
192
|
+
publication_url = f"https://{custom_domain}"
|
|
193
|
+
|
|
194
|
+
return publication_url
|
|
195
|
+
|
|
196
|
+
def get_user_primary_publication(self):
|
|
197
|
+
"""
|
|
198
|
+
Gets the users primary publication
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
profile = self.get_user_profile()
|
|
202
|
+
primary_publication = profile["primaryPublication"]
|
|
203
|
+
primary_publication["publication_url"] = self.get_publication_url(
|
|
204
|
+
primary_publication
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return primary_publication
|
|
208
|
+
|
|
209
|
+
def get_user_publications(self):
|
|
210
|
+
"""
|
|
211
|
+
Gets the users publications
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
profile = self.get_user_profile()
|
|
215
|
+
|
|
216
|
+
# Loop through users "publicationUsers" list, and return a list
|
|
217
|
+
# of dictionaries of "name", and "subdomain", and "id"
|
|
218
|
+
user_publications = []
|
|
219
|
+
for publication in profile["publicationUsers"]:
|
|
220
|
+
pub = publication["publication"]
|
|
221
|
+
pub["publication_url"] = self.get_publication_url(pub)
|
|
222
|
+
user_publications.append(pub)
|
|
223
|
+
|
|
224
|
+
return user_publications
|
|
225
|
+
|
|
226
|
+
def get_user_profile(self):
|
|
227
|
+
"""
|
|
228
|
+
Gets the users profile
|
|
229
|
+
"""
|
|
230
|
+
response = self._session.get(f"{self.base_url}/user/profile/self")
|
|
231
|
+
|
|
232
|
+
return Api._handle_response(response=response)
|
|
233
|
+
|
|
234
|
+
def get_user_settings(self):
|
|
235
|
+
"""
|
|
236
|
+
Get list of users.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
|
|
240
|
+
"""
|
|
241
|
+
response = self._session.get(f"{self.base_url}/settings")
|
|
242
|
+
|
|
243
|
+
return Api._handle_response(response=response)
|
|
244
|
+
|
|
95
245
|
def get_publication_users(self):
|
|
96
246
|
"""
|
|
97
247
|
Get list of users.
|
|
@@ -111,9 +261,29 @@ class Api:
|
|
|
111
261
|
Returns:
|
|
112
262
|
|
|
113
263
|
"""
|
|
114
|
-
response = self._session.get(
|
|
264
|
+
response = self._session.get(
|
|
265
|
+
f"{self.publication_url}/publication_launch_checklist"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
return Api._handle_response(response=response)["subscriberCount"]
|
|
269
|
+
|
|
270
|
+
def get_published_posts(
|
|
271
|
+
self, offset=0, limit=25, order_by="post_date", order_direction="desc"
|
|
272
|
+
):
|
|
273
|
+
"""
|
|
274
|
+
Get list of published posts for the publication.
|
|
275
|
+
"""
|
|
276
|
+
response = self._session.get(
|
|
277
|
+
f"{self.publication_url}/post_management/published",
|
|
278
|
+
params={
|
|
279
|
+
"offset": offset,
|
|
280
|
+
"limit": limit,
|
|
281
|
+
"order_by": order_by,
|
|
282
|
+
"order_direction": order_direction,
|
|
283
|
+
},
|
|
284
|
+
)
|
|
115
285
|
|
|
116
|
-
return Api._handle_response(response=response)
|
|
286
|
+
return Api._handle_response(response=response)
|
|
117
287
|
|
|
118
288
|
def get_posts(self) -> dict:
|
|
119
289
|
"""
|
|
@@ -142,6 +312,14 @@ class Api:
|
|
|
142
312
|
)
|
|
143
313
|
return Api._handle_response(response=response)
|
|
144
314
|
|
|
315
|
+
def get_draft(self, draft_id):
|
|
316
|
+
"""
|
|
317
|
+
Gets a draft given it's id.
|
|
318
|
+
|
|
319
|
+
"""
|
|
320
|
+
response = self._session.get(f"{self.publication_url}/drafts/{draft_id}")
|
|
321
|
+
return Api._handle_response(response=response)
|
|
322
|
+
|
|
145
323
|
def delete_draft(self, draft_id):
|
|
146
324
|
"""
|
|
147
325
|
|
|
@@ -166,11 +344,7 @@ class Api:
|
|
|
166
344
|
response = self._session.post(f"{self.publication_url}/drafts", json=body)
|
|
167
345
|
return Api._handle_response(response=response)
|
|
168
346
|
|
|
169
|
-
def put_draft(
|
|
170
|
-
self,
|
|
171
|
-
draft,
|
|
172
|
-
**kwargs
|
|
173
|
-
) -> dict:
|
|
347
|
+
def put_draft(self, draft, **kwargs) -> dict:
|
|
174
348
|
"""
|
|
175
349
|
|
|
176
350
|
Args:
|
|
@@ -202,7 +376,7 @@ class Api:
|
|
|
202
376
|
return Api._handle_response(response=response)
|
|
203
377
|
|
|
204
378
|
def publish_draft(
|
|
205
|
-
|
|
379
|
+
self, draft, send: bool = True, share_automatically: bool = False
|
|
206
380
|
) -> dict:
|
|
207
381
|
"""
|
|
208
382
|
|
|
@@ -320,7 +494,7 @@ class Api:
|
|
|
320
494
|
page_output = self.get_category(category_id, category_type, page)
|
|
321
495
|
publications.extend(page_output.get("publications", []))
|
|
322
496
|
if (
|
|
323
|
-
|
|
497
|
+
limit is not None and limit <= len(publications)
|
|
324
498
|
) or not page_output.get("more", False):
|
|
325
499
|
publications = publications[:limit]
|
|
326
500
|
break
|
|
@@ -358,5 +532,9 @@ class Api:
|
|
|
358
532
|
f"{self.publication_url}/subscriptions",
|
|
359
533
|
)
|
|
360
534
|
content = Api._handle_response(response=response)
|
|
361
|
-
sections = [
|
|
535
|
+
sections = [
|
|
536
|
+
p.get("sections")
|
|
537
|
+
for p in content.get("publications")
|
|
538
|
+
if p.get("hostname") in self.publication_url
|
|
539
|
+
]
|
|
362
540
|
return sections[0]
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
Post Utilities
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
|
|
1
7
|
import json
|
|
2
8
|
from typing import Dict
|
|
3
9
|
|
|
@@ -7,13 +13,19 @@ from substack.exceptions import SectionNotExistsException
|
|
|
7
13
|
|
|
8
14
|
|
|
9
15
|
class Post:
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
Post utility class
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
|
|
10
22
|
def __init__(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
self,
|
|
24
|
+
title: str,
|
|
25
|
+
subtitle: str,
|
|
26
|
+
user_id,
|
|
27
|
+
audience: str = None,
|
|
28
|
+
write_comment_permissions: str = None,
|
|
17
29
|
):
|
|
18
30
|
"""
|
|
19
31
|
|
|
@@ -143,20 +155,20 @@ class Post:
|
|
|
143
155
|
return self
|
|
144
156
|
|
|
145
157
|
def captioned_image(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
self,
|
|
159
|
+
src: str,
|
|
160
|
+
fullscreen: bool = False,
|
|
161
|
+
imageSize: str = "normal",
|
|
162
|
+
height: int = 819,
|
|
163
|
+
width: int = 1456,
|
|
164
|
+
resizeWidth: int = 728,
|
|
165
|
+
bytes: str = None,
|
|
166
|
+
alt: str = None,
|
|
167
|
+
title: str = None,
|
|
168
|
+
type: str = None,
|
|
169
|
+
href: str = None,
|
|
170
|
+
belowTheFold: bool = False,
|
|
171
|
+
internalRedirect: str = None,
|
|
160
172
|
):
|
|
161
173
|
"""
|
|
162
174
|
|
|
@@ -252,7 +264,7 @@ class Post:
|
|
|
252
264
|
return self
|
|
253
265
|
|
|
254
266
|
def remove_last_paragraph(self):
|
|
255
|
-
""" """
|
|
267
|
+
"""Remove last paragraph"""
|
|
256
268
|
del self.draft_body.get("content")[-1]
|
|
257
269
|
|
|
258
270
|
def get_draft(self):
|
|
@@ -282,7 +294,11 @@ class Post:
|
|
|
282
294
|
Subscribe for free to receive new posts and support my work."""
|
|
283
295
|
|
|
284
296
|
subscribe = self.draft_body["content"][-1]
|
|
285
|
-
subscribe["attrs"] = {
|
|
297
|
+
subscribe["attrs"] = {
|
|
298
|
+
"url": "%%checkout_url%%",
|
|
299
|
+
"text": "Subscribe",
|
|
300
|
+
"language": "en",
|
|
301
|
+
}
|
|
286
302
|
subscribe["content"] = [
|
|
287
303
|
{
|
|
288
304
|
"type": "ctaCaption",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|