Skip to content

Commit 5708d5d

Browse files
committed
🏰 upkeep
1 parent e0743f9 commit 5708d5d

File tree

8 files changed

+155
-75
lines changed

8 files changed

+155
-75
lines changed

Diff for: .gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,4 @@ dmypy.json
128128
# Pyre type checker
129129
.pyre/
130130

131-
.idea/
131+
.idea/

Diff for: .pre-commit-config.yaml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v2.3.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: 22.10.0
10+
hooks:
11+
- id: black
12+
- repo: https://github.com/pycqa/isort
13+
rev: 5.12.0
14+
hooks:
15+
- id: isort
16+
name: isort (python)

Diff for: README.md

+20-4
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@ Set the following environment variables by creating a **.env** file:
2424
PASSWORD=
2525

2626
## If you don't have a password
27-
Recently Substack has been setting up new accounts without a password. If you sign-out and sign back in it just uses your email address with a "magic" link.
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.
2830

2931
Set a password:
30-
- Sign-out of Substack
31-
- At the sign-in page click, "Sign in with password" under the `Email` text box
32-
- Then choose, "Set a new 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"
3336

3437
The .env file will be ignored by git but always be careful.
3538

@@ -100,3 +103,16 @@ api.prepublish_draft(draft.get("id"))
100103
api.publish_draft(draft.get("id"))
101104
```
102105

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+
```

Diff for: examples/draft.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ body:
4949
src: "EnDg65ISswg"
5050
9:
5151
type: "subscribeWidget"
52-
message: "Hello Everyone!!!"
52+
message: "Hello Everyone!!!"

Diff for: poetry.lock

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: pyproject.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "python-substack"
3-
version = "0.1.13"
3+
version = "0.1.14"
44
description = "A Python wrapper around the Substack API."
55
authors = ["Paolo Mazza <mazzapaolo2019@gmail.com>"]
66
license = "MIT"
@@ -28,4 +28,4 @@ PyYAML = "^6.0"
2828

2929
[build-system]
3030
requires = ["poetry-core>=1.0.0"]
31-
build-backend = "poetry.core.masonry.api"
31+
build-backend = "poetry.core.masonry.api"

Diff for: substack/api.py

+70-38
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
"""
2+
3+
API Wrapper
4+
5+
"""
6+
17
import base64
8+
import json
29
import logging
310
import os
411
from datetime import datetime
512
from urllib.parse import urljoin
6-
import json
713

814
import requests
915

@@ -22,13 +28,13 @@ class Api:
2228
"""
2329

2430
def __init__(
25-
self,
26-
email=None,
27-
password=None,
28-
cookies_path=None,
29-
base_url=None,
30-
publication_url=None,
31-
debug=False,
31+
self,
32+
email=None,
33+
password=None,
34+
cookies_path=None,
35+
base_url=None,
36+
publication_url=None,
37+
debug=False,
3238
):
3339
"""
3440
@@ -40,7 +46,8 @@ def __init__(
4046
email:
4147
password:
4248
cookies_path
43-
To re-use your session without logging in each time, you can save your cookies to a json file and then load them in the next session.
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.
4451
Make sure to re-save your cookies, as they do update over time.
4552
base_url:
4653
The base URL to use to contact the Substack API.
@@ -57,27 +64,30 @@ def __init__(
5764
# Load cookies from file if provided
5865
# Helps with Captcha errors by reusing cookies from "local" auth, then switching to running code in the cloud
5966
if cookies_path is not None:
60-
with open(cookies_path, "r") as f:
67+
with open(cookies_path) as f:
6168
cookies = json.load(f)
6269
self._session.cookies.update(cookies)
6370

6471
elif email is not None and password is not None:
6572
self.login(email, password)
6673
else:
67-
raise ValueError("Must provide email and password or cookies_path to authenticate.")
74+
raise ValueError(
75+
"Must provide email and password or cookies_path to authenticate."
76+
)
6877

78+
user_publication = None
6979
# if the user provided a publication url, then use that
7080
if publication_url:
7181
import re
7282

7383
# Regular expression to extract subdomain name
7484
match = re.search(r"https://(.*).substack.com", publication_url.lower())
7585
subdomain = match.group(1) if match else None
76-
86+
7787
user_publications = self.get_user_publications()
7888
# search through publications to find the publication with the matching subdomain
7989
for publication in user_publications:
80-
if publication['subdomain'] == subdomain:
90+
if publication["subdomain"] == subdomain:
8191
# set the current publication to the users publication
8292
user_publication = publication
8393
break
@@ -110,20 +120,21 @@ def login(self, email, password) -> dict:
110120
)
111121

112122
return Api._handle_response(response=response)
113-
123+
114124
def signin_for_pub(self, publication):
115125
"""
116126
Complete the signin process
117127
"""
118128
response = self._session.get(
119129
f"https://substack.com/sign-in?redirect=%2F&for_pub={publication['subdomain']}",
120130
)
131+
return Api._handle_response(response=response)
121132

122133
def change_publication(self, publication):
123134
"""
124135
Change the publication URL
125136
"""
126-
self.publication_url = urljoin(publication['publication_url'], "api/v1")
137+
self.publication_url = urljoin(publication["publication_url"], "api/v1")
127138

128139
# sign-in to the publication
129140
self.signin_for_pub(publication)
@@ -156,16 +167,25 @@ def _handle_response(response: requests.Response):
156167
raise SubstackRequestException("Invalid Response: %s" % response.text)
157168

158169
def get_user_id(self):
170+
"""
171+
172+
Returns:
173+
174+
"""
159175
profile = self.get_user_profile()
160-
user_id = profile['id']
176+
user_id = profile["id"]
161177

162178
return user_id
163-
164-
def get_publication_url(self, publication):
179+
180+
@staticmethod
181+
def get_publication_url(publication: dict) -> str:
165182
"""
166183
Gets the publication url
184+
185+
Args:
186+
publication:
167187
"""
168-
custom_domain = publication['custom_domain']
188+
custom_domain = publication["custom_domain"]
169189
if not custom_domain:
170190
publication_url = f"https://{publication['subdomain']}.substack.com"
171191
else:
@@ -179,8 +199,10 @@ def get_user_primary_publication(self):
179199
"""
180200

181201
profile = self.get_user_profile()
182-
primary_publication = profile['primaryPublication']
183-
primary_publication['publication_url'] = self.get_publication_url(primary_publication)
202+
primary_publication = profile["primaryPublication"]
203+
primary_publication["publication_url"] = self.get_publication_url(
204+
primary_publication
205+
)
184206

185207
return primary_publication
186208

@@ -191,11 +213,12 @@ def get_user_publications(self):
191213

192214
profile = self.get_user_profile()
193215

194-
# Loop through users "publicationUsers" list, and return a list of dictionaries of "name", and "subdomain", and "id"
216+
# Loop through users "publicationUsers" list, and return a list
217+
# of dictionaries of "name", and "subdomain", and "id"
195218
user_publications = []
196-
for publication in profile['publicationUsers']:
197-
pub = publication['publication']
198-
pub['publication_url'] = self.get_publication_url(pub)
219+
for publication in profile["publicationUsers"]:
220+
pub = publication["publication"]
221+
pub["publication_url"] = self.get_publication_url(pub)
199222
user_publications.append(pub)
200223

201224
return user_publications
@@ -218,7 +241,7 @@ def get_user_settings(self):
218241
response = self._session.get(f"{self.base_url}/settings")
219242

220243
return Api._handle_response(response=response)
221-
244+
222245
def get_publication_users(self):
223246
"""
224247
Get list of users.
@@ -238,17 +261,26 @@ def get_publication_subscriber_count(self):
238261
Returns:
239262
240263
"""
241-
response = self._session.get(f"{self.publication_url}/publication_launch_checklist")
264+
response = self._session.get(
265+
f"{self.publication_url}/publication_launch_checklist"
266+
)
242267

243-
return Api._handle_response(response=response)['subscriberCount']
268+
return Api._handle_response(response=response)["subscriberCount"]
244269

245-
def get_published_posts(self, offset=0, limit=25, order_by="post_date", order_direction="desc"):
270+
def get_published_posts(
271+
self, offset=0, limit=25, order_by="post_date", order_direction="desc"
272+
):
246273
"""
247274
Get list of published posts for the publication.
248275
"""
249276
response = self._session.get(
250277
f"{self.publication_url}/post_management/published",
251-
params={"offset": offset, "limit": limit, "order_by": order_by, "order_direction": order_direction},
278+
params={
279+
"offset": offset,
280+
"limit": limit,
281+
"order_by": order_by,
282+
"order_direction": order_direction,
283+
},
252284
)
253285

254286
return Api._handle_response(response=response)
@@ -312,11 +344,7 @@ def post_draft(self, body) -> dict:
312344
response = self._session.post(f"{self.publication_url}/drafts", json=body)
313345
return Api._handle_response(response=response)
314346

315-
def put_draft(
316-
self,
317-
draft,
318-
**kwargs
319-
) -> dict:
347+
def put_draft(self, draft, **kwargs) -> dict:
320348
"""
321349
322350
Args:
@@ -348,7 +376,7 @@ def prepublish_draft(self, draft) -> dict:
348376
return Api._handle_response(response=response)
349377

350378
def publish_draft(
351-
self, draft, send: bool = True, share_automatically: bool = False
379+
self, draft, send: bool = True, share_automatically: bool = False
352380
) -> dict:
353381
"""
354382
@@ -466,7 +494,7 @@ def get_single_category(self, category_id, category_type, page=None, limit=None)
466494
page_output = self.get_category(category_id, category_type, page)
467495
publications.extend(page_output.get("publications", []))
468496
if (
469-
limit is not None and limit <= len(publications)
497+
limit is not None and limit <= len(publications)
470498
) or not page_output.get("more", False):
471499
publications = publications[:limit]
472500
break
@@ -504,5 +532,9 @@ def get_sections(self):
504532
f"{self.publication_url}/subscriptions",
505533
)
506534
content = Api._handle_response(response=response)
507-
sections = [p.get("sections") for p in content.get("publications") if p.get("hostname") in self.publication_url]
535+
sections = [
536+
p.get("sections")
537+
for p in content.get("publications")
538+
if p.get("hostname") in self.publication_url
539+
]
508540
return sections[0]

0 commit comments

Comments
 (0)