python-substack 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
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.4.0.dist-info → python_substack-0.5.0.dist-info}/METADATA +2 -2
- python_substack-0.5.0.dist-info/RECORD +13 -0
- substack/__init__.py +13 -13
- substack/api.py +835 -792
- substack/cli.py +7 -1
- substack/exceptions.py +44 -32
- python_substack-0.4.0.dist-info/RECORD +0 -13
- {python_substack-0.4.0.dist-info → python_substack-0.5.0.dist-info}/WHEEL +0 -0
- {python_substack-0.4.0.dist-info → python_substack-0.5.0.dist-info}/entry_points.txt +0 -0
- {python_substack-0.4.0.dist-info → python_substack-0.5.0.dist-info}/licenses/LICENSE +0 -0
substack/cli.py
CHANGED
|
@@ -18,7 +18,7 @@ class CLIUsageError(Exception):
|
|
|
18
18
|
pass
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def _api_from_env(cookies_path=None, publication_url=None):
|
|
21
|
+
def _api_from_env(cookies_path=None, publication_url=None, timeout=None):
|
|
22
22
|
load_dotenv()
|
|
23
23
|
|
|
24
24
|
cookies_path = cookies_path or os.getenv("COOKIES_PATH")
|
|
@@ -30,12 +30,14 @@ def _api_from_env(cookies_path=None, publication_url=None):
|
|
|
30
30
|
cookies_path=cookies_path,
|
|
31
31
|
cookies_string=cookies_string,
|
|
32
32
|
publication_url=publication_url,
|
|
33
|
+
timeout=timeout,
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
return Api(
|
|
36
37
|
email=os.getenv("EMAIL"),
|
|
37
38
|
password=os.getenv("PASSWORD"),
|
|
38
39
|
publication_url=publication_url,
|
|
40
|
+
timeout=timeout,
|
|
39
41
|
)
|
|
40
42
|
|
|
41
43
|
|
|
@@ -370,6 +372,9 @@ def _build_parser():
|
|
|
370
372
|
parser.add_argument(
|
|
371
373
|
"--publication-url", help="Override PUBLICATION_URL for this command."
|
|
372
374
|
)
|
|
375
|
+
parser.add_argument(
|
|
376
|
+
"--timeout", type=float, help="Timeout in seconds for API requests."
|
|
377
|
+
)
|
|
373
378
|
parser.add_argument("--json", action="store_true", dest="json_output")
|
|
374
379
|
parser.add_argument("--version", action="version", version=__version__)
|
|
375
380
|
|
|
@@ -450,6 +455,7 @@ def main(argv=None):
|
|
|
450
455
|
api = _api_from_env(
|
|
451
456
|
cookies_path=args.cookies,
|
|
452
457
|
publication_url=args.publication_url,
|
|
458
|
+
timeout=args.timeout,
|
|
453
459
|
)
|
|
454
460
|
args.handler(api, args)
|
|
455
461
|
except CLIUsageError as exc:
|
substack/exceptions.py
CHANGED
|
@@ -1,32 +1,44 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _redact_message(text: str) -> str:
|
|
6
|
+
if not text:
|
|
7
|
+
return text
|
|
8
|
+
# Redact common cookie-like values and session tokens
|
|
9
|
+
# e.g., s%3A... or s:... which are typical for express session cookies
|
|
10
|
+
text = re.sub(r"s%3A[a-zA-Z0-9_\-\.\%]+", "[REDACTED_COOKIE]", text)
|
|
11
|
+
text = re.sub(r"s:[a-zA-Z0-9_\-\.\%]+", "[REDACTED_COOKIE]", text)
|
|
12
|
+
return text
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SubstackAPIException(Exception):
|
|
16
|
+
def __init__(self, status_code, text):
|
|
17
|
+
text = _redact_message(text)
|
|
18
|
+
try:
|
|
19
|
+
json_res = json.loads(text)
|
|
20
|
+
except ValueError:
|
|
21
|
+
self.message = f"Invalid JSON error message from Substack: {text}"
|
|
22
|
+
else:
|
|
23
|
+
self.message = ", ".join(
|
|
24
|
+
list(
|
|
25
|
+
map(lambda error: error.get("msg", ""), json_res.get("errors", []))
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
self.message = self.message or json_res.get("error", "")
|
|
29
|
+
self.status_code = status_code
|
|
30
|
+
|
|
31
|
+
def __str__(self):
|
|
32
|
+
return f"APIError(code={self.status_code}): {self.message}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SubstackRequestException(Exception):
|
|
36
|
+
def __init__(self, message):
|
|
37
|
+
self.message = _redact_message(message)
|
|
38
|
+
|
|
39
|
+
def __str__(self):
|
|
40
|
+
return f"SubstackRequestException: {self.message}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SectionNotExistsException(SubstackRequestException):
|
|
44
|
+
pass
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
substack/__init__.py,sha256=jF2QV8aP2zgNMtGcOY7FlX1cZdnUDHoON4qzOiQpqrA,437
|
|
2
|
-
substack/api.py,sha256=5dIijlGDZI5bPgX9skTzytDkY1IO6etvCv6PbNFUjyQ,23212
|
|
3
|
-
substack/cli.py,sha256=JBCsCdmthjzTeDpOYBLeVKOdNYjBrPuY1o_CIsAIekk,21712
|
|
4
|
-
substack/exceptions.py,sha256=BbP5W5UpzFcM5SYIxx6snWD_Rmj7F_YjYIYC_r03gZY,911
|
|
5
|
-
substack/mdrender.py,sha256=QGJkdp1isFmhVyTRMogIabAK8OO2xQh9CrjnXZvFTY4,9308
|
|
6
|
-
substack/nodes.py,sha256=fTsGO0-lTztcXiloIjXrHVnJes9EcltGcrX-mEV2ceQ,5004
|
|
7
|
-
substack/post.py,sha256=qDXu-xzO3nRXtahc-yG2EhxKAXB2hG_Z1nLa55UMBGg,19697
|
|
8
|
-
substack_mcp/mcp_server.py,sha256=3VTSSsiAmr4btImWpfoEl7irpTgqI-FH4q5r9r3u3w8,10949
|
|
9
|
-
python_substack-0.4.0.dist-info/METADATA,sha256=CYRO_5NDtuzFfyyjI8CNZCLogy2NyMGGbyUBIdSgS5A,8405
|
|
10
|
-
python_substack-0.4.0.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
|
|
11
|
-
python_substack-0.4.0.dist-info/entry_points.txt,sha256=MKPjaBUd-0PtvxsBviStsVq1c0h8JZ_qUoYEsBK1xJc,236
|
|
12
|
-
python_substack-0.4.0.dist-info/licenses/LICENSE,sha256=L6jk148I5HhhVbfUvkO3EO7eAoU5zToLio4-ApkCkxg,1062
|
|
13
|
-
python_substack-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|