drp-dev 1.0.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.
Files changed (53) hide show
  1. drp_dev-1.0.0/LICENSE +31 -0
  2. drp_dev-1.0.0/PKG-INFO +96 -0
  3. drp_dev-1.0.0/PYPI_DEV_README.md +53 -0
  4. drp_dev-1.0.0/README.md +68 -0
  5. drp_dev-1.0.0/cli/__init__.py +30 -0
  6. drp_dev-1.0.0/cli/__main__.py +3 -0
  7. drp_dev-1.0.0/cli/api/__init__.py +18 -0
  8. drp_dev-1.0.0/cli/api/actions.py +205 -0
  9. drp_dev-1.0.0/cli/api/auth.py +40 -0
  10. drp_dev-1.0.0/cli/api/file.py +340 -0
  11. drp_dev-1.0.0/cli/api/helpers.py +28 -0
  12. drp_dev-1.0.0/cli/api/text.py +137 -0
  13. drp_dev-1.0.0/cli/commands/__init__.py +0 -0
  14. drp_dev-1.0.0/cli/commands/_context.py +44 -0
  15. drp_dev-1.0.0/cli/commands/ask.py +110 -0
  16. drp_dev-1.0.0/cli/commands/cache.py +88 -0
  17. drp_dev-1.0.0/cli/commands/collection.py +219 -0
  18. drp_dev-1.0.0/cli/commands/cp.py +62 -0
  19. drp_dev-1.0.0/cli/commands/edit.py +93 -0
  20. drp_dev-1.0.0/cli/commands/get.py +344 -0
  21. drp_dev-1.0.0/cli/commands/load.py +58 -0
  22. drp_dev-1.0.0/cli/commands/lock.py +85 -0
  23. drp_dev-1.0.0/cli/commands/ls.py +203 -0
  24. drp_dev-1.0.0/cli/commands/manage.py +90 -0
  25. drp_dev-1.0.0/cli/commands/save.py +32 -0
  26. drp_dev-1.0.0/cli/commands/send.py +76 -0
  27. drp_dev-1.0.0/cli/commands/serve.py +103 -0
  28. drp_dev-1.0.0/cli/commands/setup.py +306 -0
  29. drp_dev-1.0.0/cli/commands/shell.py +549 -0
  30. drp_dev-1.0.0/cli/commands/status.py +181 -0
  31. drp_dev-1.0.0/cli/commands/token.py +123 -0
  32. drp_dev-1.0.0/cli/commands/upload.py +327 -0
  33. drp_dev-1.0.0/cli/completion.py +297 -0
  34. drp_dev-1.0.0/cli/config.py +81 -0
  35. drp_dev-1.0.0/cli/crash_reporter.py +168 -0
  36. drp_dev-1.0.0/cli/drp.py +447 -0
  37. drp_dev-1.0.0/cli/format.py +105 -0
  38. drp_dev-1.0.0/cli/path_check.py +57 -0
  39. drp_dev-1.0.0/cli/progress.py +107 -0
  40. drp_dev-1.0.0/cli/prompt.py +102 -0
  41. drp_dev-1.0.0/cli/session.py +154 -0
  42. drp_dev-1.0.0/cli/smart_parse.py +207 -0
  43. drp_dev-1.0.0/cli/spinner.py +98 -0
  44. drp_dev-1.0.0/cli/timing.py +97 -0
  45. drp_dev-1.0.0/cli/version_check.py +150 -0
  46. drp_dev-1.0.0/drp_dev.egg-info/PKG-INFO +96 -0
  47. drp_dev-1.0.0/drp_dev.egg-info/SOURCES.txt +51 -0
  48. drp_dev-1.0.0/drp_dev.egg-info/dependency_links.txt +1 -0
  49. drp_dev-1.0.0/drp_dev.egg-info/entry_points.txt +2 -0
  50. drp_dev-1.0.0/drp_dev.egg-info/requires.txt +21 -0
  51. drp_dev-1.0.0/drp_dev.egg-info/top_level.txt +1 -0
  52. drp_dev-1.0.0/pyproject.toml +76 -0
  53. drp_dev-1.0.0/setup.cfg +4 -0
drp_dev-1.0.0/LICENSE ADDED
@@ -0,0 +1,31 @@
1
+ Copyright (c) 2026 Victorio Nascimento
2
+
3
+ Source Available License
4
+
5
+ Permission is granted to use, copy, modify, and run this software
6
+ solely for personal use or internal use within a single organization,
7
+ provided the service is not accessible to users outside that organization.
8
+
9
+ You may NOT, without a separate written commercial license from the author:
10
+
11
+ 1. Operate any instance of this software that accepts connections,
12
+ uploads, or downloads from users outside your own organization,
13
+ regardless of whether access is free, paid, invite-only, or
14
+ otherwise restricted.
15
+
16
+ 2. Offer any service — commercial or free — whose functionality is
17
+ substantially derived from this software to any person or entity
18
+ outside your organization.
19
+
20
+ 3. Sublicense, resell, or redistribute this software or any
21
+ derivative work in a manner that grants others rights beyond
22
+ those granted here.
23
+
24
+ 4. Remove or alter this license notice in any copy or derivative work.
25
+
26
+
27
+ For commercial licensing: nasci.victorio@gmail.com
28
+
29
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30
+ EXPRESS OR IMPLIED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
31
+ CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM USE OF THIS SOFTWARE.
drp_dev-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: drp-dev
3
+ Version: 1.0.0
4
+ Summary: Drop text and files from the command line — get a link instantly.
5
+ Author: Vic Nas
6
+ License: MIT
7
+ Project-URL: Homepage, https://drp.fyi
8
+ Project-URL: Repository, https://github.com/vicnasdev/drp
9
+ Project-URL: Issues, https://github.com/vicnasdev/drp/issues
10
+ Keywords: cli,file-sharing,pastebin,drops
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
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: Topic :: Utilities
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: requests>=2.28
24
+ Provides-Extra: completion
25
+ Requires-Dist: argcomplete>=3.1; extra == "completion"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest; extra == "dev"
28
+ Requires-Dist: pytest-django; extra == "dev"
29
+ Requires-Dist: pytest-asyncio; extra == "dev"
30
+ Requires-Dist: pytest-cov; extra == "dev"
31
+ Requires-Dist: django>=5.0; extra == "dev"
32
+ Requires-Dist: python-dotenv; extra == "dev"
33
+ Requires-Dist: dj-database-url; extra == "dev"
34
+ Requires-Dist: whitenoise; extra == "dev"
35
+ Requires-Dist: gunicorn; extra == "dev"
36
+ Requires-Dist: requests; extra == "dev"
37
+ Requires-Dist: psycopg2-binary>=2.9; extra == "dev"
38
+ Requires-Dist: resend; extra == "dev"
39
+ Requires-Dist: boto3; extra == "dev"
40
+ Requires-Dist: markdown; extra == "dev"
41
+ Requires-Dist: argcomplete>=3.1; extra == "dev"
42
+ Dynamic: license-file
43
+
44
+ # drp-dev — share text & files from your terminal
45
+
46
+ > **Development build** — may be unstable. For stable releases: `pipx install drp`.
47
+ >
48
+ > **Website:** [dev.drp.fyi](https://dev.drp.fyi) — dev server for testing.
49
+
50
+ ```bash
51
+ pipx install drp-dev
52
+ drp setup
53
+ drp up "hello world" # → https://dev.drp.fyi/a1b2c3/
54
+ ```
55
+
56
+ ## Features
57
+
58
+ | Feature | Command |
59
+ |---|---|
60
+ | Upload text | `drp up "hello"` or `echo text \| drp up` |
61
+ | Upload file | `drp up report.pdf` |
62
+ | Custom key | `drp up logo.png -k logo` |
63
+ | Burn after reading | `drp up "secret" --burn` |
64
+ | Password protect | `drp up data.csv --password` |
65
+ | Retrieve text | `drp get <key>` |
66
+ | Download file | `drp get -f <key>` |
67
+ | List drops | `drp ls` |
68
+ | Organize | `drp collection new "notes"` |
69
+ | Interactive shell | `drp shell` |
70
+ | Lock existing drop | `drp lock <key>` |
71
+
72
+ **Embed anywhere:** `dev.drp.fyi/embed/<key>/` (iframes, markdown)
73
+ **Raw access:** `dev.drp.fyi/raw/<key>/` (curl, scripts, CI)
74
+
75
+ ## Plans
76
+
77
+ Anonymous uploads work instantly — no account needed. [Sign up free](https://drp.fyi/auth/register/) for longer expiry, or upgrade to [Starter/Pro](https://drp.fyi/help/plans/) for collections, passwords, API tokens, server-side uploads, and more.
78
+
79
+ ## Stable vs Dev
80
+
81
+ | | `drp` (stable) | `drp-dev` (dev) |
82
+ |---|---|---|
83
+ | **Source** | `main` branch | `dev` branch |
84
+ | **Install** | `pipx install drp` | `pipx install drp-dev` |
85
+
86
+ ## Links
87
+
88
+ - **Website:** [dev.drp.fyi](https://dev.drp.fyi)
89
+ - **Docs:** [drp.fyi/help](https://drp.fyi/help/)
90
+ - **CLI reference:** [drp.fyi/help/cli](https://drp.fyi/help/cli/)
91
+ - **Stable package:** [pypi.org/project/drp](https://pypi.org/project/drp/)
92
+ - **Source:** [github.com/vicnasdev/drp](https://github.com/vicnasdev/drp)
93
+
94
+ ## License
95
+
96
+ CLI: MIT.
@@ -0,0 +1,53 @@
1
+ # drp-dev — share text & files from your terminal
2
+
3
+ > **Development build** — may be unstable. For stable releases: `pipx install drp`.
4
+ >
5
+ > **Website:** [dev.drp.fyi](https://dev.drp.fyi) — dev server for testing.
6
+
7
+ ```bash
8
+ pipx install drp-dev
9
+ drp setup
10
+ drp up "hello world" # → https://dev.drp.fyi/a1b2c3/
11
+ ```
12
+
13
+ ## Features
14
+
15
+ | Feature | Command |
16
+ |---|---|
17
+ | Upload text | `drp up "hello"` or `echo text \| drp up` |
18
+ | Upload file | `drp up report.pdf` |
19
+ | Custom key | `drp up logo.png -k logo` |
20
+ | Burn after reading | `drp up "secret" --burn` |
21
+ | Password protect | `drp up data.csv --password` |
22
+ | Retrieve text | `drp get <key>` |
23
+ | Download file | `drp get -f <key>` |
24
+ | List drops | `drp ls` |
25
+ | Organize | `drp collection new "notes"` |
26
+ | Interactive shell | `drp shell` |
27
+ | Lock existing drop | `drp lock <key>` |
28
+
29
+ **Embed anywhere:** `dev.drp.fyi/embed/<key>/` (iframes, markdown)
30
+ **Raw access:** `dev.drp.fyi/raw/<key>/` (curl, scripts, CI)
31
+
32
+ ## Plans
33
+
34
+ Anonymous uploads work instantly — no account needed. [Sign up free](https://drp.fyi/auth/register/) for longer expiry, or upgrade to [Starter/Pro](https://drp.fyi/help/plans/) for collections, passwords, API tokens, server-side uploads, and more.
35
+
36
+ ## Stable vs Dev
37
+
38
+ | | `drp` (stable) | `drp-dev` (dev) |
39
+ |---|---|---|
40
+ | **Source** | `main` branch | `dev` branch |
41
+ | **Install** | `pipx install drp` | `pipx install drp-dev` |
42
+
43
+ ## Links
44
+
45
+ - **Website:** [dev.drp.fyi](https://dev.drp.fyi)
46
+ - **Docs:** [drp.fyi/help](https://drp.fyi/help/)
47
+ - **CLI reference:** [drp.fyi/help/cli](https://drp.fyi/help/cli/)
48
+ - **Stable package:** [pypi.org/project/drp](https://pypi.org/project/drp/)
49
+ - **Source:** [github.com/vicnasdev/drp](https://github.com/vicnasdev/drp)
50
+
51
+ ## License
52
+
53
+ CLI: MIT.
@@ -0,0 +1,68 @@
1
+ # [drp](https://drp.fyi) — instant clipboard & file sharing
2
+
3
+ ![version](https://img.shields.io/github/v/tag/vicnasdev/drp)
4
+ ![license](https://img.shields.io/badge/CLI-MIT-green)
5
+ ![python](https://img.shields.io/badge/python-3.10+-blue)
6
+
7
+ Share text, code snippets, and files from your terminal in seconds. Get a link, share it anywhere.
8
+
9
+ **[drp.fyi](https://drp.fyi)** — try it now, no account needed.
10
+
11
+ ```bash
12
+ pipx install drp-dev
13
+ drp setup
14
+ drp up "hello world" # → https://drp.fyi/a1b2c3/
15
+ ```
16
+
17
+ ## What can drp do?
18
+
19
+ ```bash
20
+ drp up notes.md # upload a file → get a link
21
+ echo "secret" | drp up --burn # burn after first view
22
+ drp up photo.png -k avatar # custom key → drp.fyi/f/avatar/
23
+ drp get mykey # fetch text back
24
+ drp get -f report # download a file
25
+ drp ls # list your drops
26
+ drp lock mykey # password-protect (paid)
27
+ drp shell # interactive REPL
28
+ ```
29
+
30
+ **Embed anywhere** — images, iframes, raw text:
31
+ ```
32
+ https://drp.fyi/embed/mykey/ ← embeddable viewer
33
+ https://drp.fyi/raw/mykey/ ← plain text (curl-friendly)
34
+ ![img](https://drp.fyi/raw/avatar/) ← markdown image
35
+ ```
36
+
37
+ **Collections** — organize drops into folders with sub-collections, shareable URLs, and shell navigation.
38
+
39
+ **Plans** — anonymous drops work instantly. [Sign up free](https://drp.fyi/auth/register/) for longer expiry, or go [Starter/Pro](https://drp.fyi/help/plans/) for collections, passwords, API tokens, and more.
40
+
41
+ ## Links
42
+
43
+ - **Website:** [drp.fyi](https://drp.fyi)
44
+ - **Help & docs:** [drp.fyi/help](https://drp.fyi/help/)
45
+ - **Plans & pricing:** [drp.fyi/help/plans](https://drp.fyi/help/plans/)
46
+ - **CLI reference:** [drp.fyi/help/cli](https://drp.fyi/help/cli/)
47
+ - **PyPI (stable):** [pypi.org/project/drp](https://pypi.org/project/drp/)
48
+ - **PyPI (dev):** [pypi.org/project/drp-dev](https://pypi.org/project/drp-dev/)
49
+
50
+ ## Self-hosting
51
+
52
+ > Source-available for personal/internal use — see [LICENSE](LICENSE).
53
+
54
+ [![Deploy on Railway](https://railway.app/button.svg)](https://railway.com?referralCode=ZIdvo-)
55
+
56
+ See [drp.fyi/help](https://drp.fyi/help/) for setup instructions and environment variables.
57
+
58
+ ### Commercial License
59
+
60
+ Want to deploy drp for your organization? A **Commercial Self-Hosted License** is available.
61
+
62
+ [Purchase & generate your license PDF →](https://drp.fyi/billing/licensing/)
63
+ See [COMMERCIAL.md](COMMERCIAL.md) for full terms.
64
+
65
+ ## License
66
+
67
+ Server: source-available, personal/internal use only — see [LICENSE](LICENSE).
68
+ CLI (`cli/`): MIT.
@@ -0,0 +1,30 @@
1
+ """drp CLI — command-line tool for drp.
2
+
3
+ Drop, share, and manage text snippets and files from the terminal.
4
+ https://drp.fyi
5
+ """
6
+
7
+ # VERSION file holds major.minor (e.g. "1.0") — only bumped manually.
8
+ # CI queries PyPI for latest published version, bumps patch +1,
9
+ # and seds this line to a literal before building: __version__ = '1.0.42'
10
+ # Nothing committed back — merges between dev↔main stay clean.
11
+ # Fallback: importlib.metadata for installed packages.
12
+
13
+ def _resolve_version():
14
+ from pathlib import Path
15
+ vf = Path(__file__).resolve().parent.parent / 'VERSION'
16
+ if vf.is_file():
17
+ return vf.read_text().strip()
18
+ try:
19
+ from importlib.metadata import version
20
+ return version('drp-dev')
21
+ except Exception:
22
+ pass
23
+ try:
24
+ from importlib.metadata import version
25
+ return version('drp')
26
+ except Exception:
27
+ return '0.0.0'
28
+
29
+ __version__ = '1.0.0'
30
+ DEFAULT_HOST = 'https://drp.fyi'
@@ -0,0 +1,3 @@
1
+ from cli.drp import main
2
+
3
+ main()
@@ -0,0 +1,18 @@
1
+ """
2
+ Public API surface for the drp CLI.
3
+ Import from here to keep command modules clean.
4
+ """
5
+
6
+ from .auth import get_csrf, login
7
+ from .text import upload_text, get_clipboard
8
+ from .file import upload_file, get_file, upload_from_url
9
+ from .actions import delete, rename, renew, list_drops, key_exists, save_bookmark
10
+ from .helpers import slug, err, ok
11
+
12
+ __all__ = [
13
+ 'get_csrf', 'login',
14
+ 'upload_text', 'get_clipboard',
15
+ 'upload_file', 'get_file', 'upload_from_url',
16
+ 'delete', 'rename', 'renew', 'list_drops', 'key_exists', 'save_bookmark',
17
+ 'slug', 'err', 'ok',
18
+ ]
@@ -0,0 +1,205 @@
1
+ """
2
+ Drop action API calls: delete, rename, renew, list, key_exists, save_bookmark.
3
+
4
+ URL conventions:
5
+ Clipboard: /key/delete|rename|renew|save/
6
+ File: /f/key/delete|rename|renew|save/
7
+
8
+ Return value conventions for callers (manage.py):
9
+ rename() → new_key string on success
10
+ False on a known/reported error (404, 409, 403 …)
11
+ None on an unexpected error (network, bad JSON, etc.)
12
+ delete() → True / False (unchanged)
13
+ renew() → (expires_at, renewals) / (None, None) (unchanged)
14
+ """
15
+
16
+ from .auth import get_csrf
17
+ from .helpers import err
18
+
19
+
20
+ def _url(host, ns, key, action):
21
+ if ns == 'f':
22
+ return f'{host}/f/{key}/{action}/'
23
+ return f'{host}/{key}/{action}/'
24
+
25
+
26
+ def delete(host, session, key, ns='c'):
27
+ csrf = get_csrf(host, session)
28
+ try:
29
+ res = session.delete(
30
+ _url(host, ns, key, 'delete'),
31
+ headers={'X-CSRFToken': csrf},
32
+ timeout=10,
33
+ )
34
+ if res.ok:
35
+ return True
36
+ if res.status_code == 404:
37
+ err(f'Drop not found. If this is a file drop, use: drp rm -f {key}')
38
+ _report_http('rm', 404, f'delete ns={ns} — likely wrong namespace')
39
+ return False
40
+ _handle_error(res, 'Delete failed')
41
+ _report_http('rm', res.status_code, f'delete ns={ns}')
42
+ except Exception as e:
43
+ err(f'Delete error: {e}')
44
+ return False
45
+
46
+
47
+ def rename(host, session, key, new_key, ns='c'):
48
+ """
49
+ Rename a drop key.
50
+
51
+ Returns:
52
+ str — the new key on success
53
+ False — a known error that has already been printed and reported
54
+ (404 wrong-namespace, 409 key taken, 403 locked, 400 bad input)
55
+ None — an unexpected error (network failure, unhandled status code)
56
+ caller should file a SilentFailure report
57
+ """
58
+ csrf = get_csrf(host, session)
59
+ try:
60
+ res = session.post(
61
+ _url(host, ns, key, 'rename'),
62
+ data={'new_key': new_key, 'csrfmiddlewaretoken': csrf},
63
+ timeout=10,
64
+ )
65
+ if res.ok:
66
+ return res.json().get('key')
67
+
68
+ # ── Known errors — print a helpful message and report, then return
69
+ # False so the caller knows not to file a redundant SilentFailure. ──
70
+ if res.status_code == 404:
71
+ ns_flag = '-f ' if ns == 'f' else ''
72
+ other_flag = '' if ns == 'f' else '-f '
73
+ err(
74
+ f'Drop /{ns_flag}{key}/ not found. '
75
+ f'If this is a {"file" if ns == "c" else "clipboard"} drop, '
76
+ f'use: drp mv {other_flag}{key} {new_key}'
77
+ )
78
+ _report_http('mv', 404, f'rename ns={ns} — likely wrong namespace')
79
+ return False
80
+
81
+ if res.status_code == 409:
82
+ err(f'Key "{new_key}" is already taken.')
83
+ _report_http('mv', 409, f'rename ns={ns} key conflict')
84
+ return False
85
+
86
+ if res.status_code == 403:
87
+ try:
88
+ msg = res.json().get('error', 'Permission denied.')
89
+ except Exception:
90
+ msg = 'Permission denied.'
91
+ err(f'Rename blocked: {msg}')
92
+ _report_http('mv', 403, f'rename ns={ns}')
93
+ return False
94
+
95
+ if res.status_code == 400:
96
+ _handle_error(res, 'Rename failed')
97
+ _report_http('mv', 400, f'rename ns={ns}')
98
+ return False
99
+
100
+ # Unexpected status — let caller decide whether to report
101
+ _handle_error(res, 'Rename failed')
102
+ _report_http('mv', res.status_code, f'rename ns={ns}')
103
+
104
+ except Exception as e:
105
+ err(f'Rename error: {e}')
106
+
107
+ return None
108
+
109
+
110
+ def renew(host, session, key, ns='c'):
111
+ csrf = get_csrf(host, session)
112
+ try:
113
+ res = session.post(
114
+ _url(host, ns, key, 'renew'),
115
+ data={'csrfmiddlewaretoken': csrf},
116
+ timeout=10,
117
+ )
118
+ if res.ok:
119
+ data = res.json()
120
+ return data.get('expires_at'), data.get('renewals')
121
+ _handle_error(res, 'Renew failed')
122
+ _report_http('renew', res.status_code, f'renew ns={ns}')
123
+ except Exception as e:
124
+ err(f'Renew error: {e}')
125
+ return None, None
126
+
127
+
128
+ def save_bookmark(host, session, key, ns='c'):
129
+ """
130
+ Bookmark a drop. Returns True if saved, False on failure.
131
+ Requires login — server returns 403 if not authenticated.
132
+ """
133
+ csrf = get_csrf(host, session)
134
+ try:
135
+ res = session.post(
136
+ _url(host, ns, key, 'save'),
137
+ data={'csrfmiddlewaretoken': csrf},
138
+ timeout=10,
139
+ allow_redirects=False,
140
+ )
141
+ if res.status_code in (301, 302, 303):
142
+ err('drp save requires a logged-in account. Run: drp login')
143
+ return False
144
+ if res.ok:
145
+ return True
146
+ if res.status_code == 403:
147
+ err('drp save requires a logged-in account. Run: drp login')
148
+ return False
149
+ if res.status_code == 404:
150
+ err(f'Drop /{key}/ not found.')
151
+ return False
152
+ _handle_error(res, 'Save failed')
153
+ _report_http('save', res.status_code, f'save_bookmark ns={ns}')
154
+ except Exception as e:
155
+ err(f'Save error: {e}')
156
+ return False
157
+
158
+
159
+ def list_drops(host, session):
160
+ try:
161
+ res = session.get(
162
+ f'{host}/auth/account/',
163
+ headers={'Accept': 'application/json'},
164
+ timeout=15,
165
+ )
166
+ if res.ok:
167
+ return res.json().get('drops', [])
168
+ if res.status_code in (302, 403):
169
+ return None
170
+ err(f'Server returned {res.status_code}.')
171
+ _report_http('ls', res.status_code, 'list_drops')
172
+ except Exception as e:
173
+ err(f'List error: {e}')
174
+ return None
175
+
176
+
177
+ def key_exists(host, session, key, ns='c'):
178
+ try:
179
+ res = session.get(
180
+ f'{host}/check-key/',
181
+ params={'key': key, 'ns': ns},
182
+ timeout=10,
183
+ )
184
+ if res.ok:
185
+ return not res.json().get('available', True)
186
+ except Exception:
187
+ pass
188
+ return False
189
+
190
+
191
+ def _handle_error(res, prefix):
192
+ try:
193
+ msg = res.json().get('error', res.text[:200])
194
+ except Exception:
195
+ msg = res.text[:200]
196
+ err(f'{prefix}: {msg}')
197
+
198
+
199
+ def _report_http(command: str, status_code: int, context: str) -> None:
200
+ """Fire-and-forget — never raises."""
201
+ try:
202
+ from cli.crash_reporter import report_http_error
203
+ report_http_error(command, status_code, context)
204
+ except Exception:
205
+ pass
@@ -0,0 +1,40 @@
1
+ """
2
+ Authentication helpers: CSRF token fetching and login.
3
+ """
4
+
5
+ from .helpers import err
6
+
7
+
8
+ def get_csrf(host, session):
9
+ """Return csrftoken, fetching from server only if not already in session."""
10
+ token = _first_csrf(session)
11
+ if token:
12
+ return token
13
+ # Fetch the login page — guaranteed to set the csrftoken cookie
14
+ # (home page may not render {% csrf_token %} and won't set the cookie)
15
+ session.get(f'{host}/auth/login/', timeout=10)
16
+ return _first_csrf(session) or ''
17
+
18
+
19
+ def _first_csrf(session):
20
+ """Safely get csrftoken even if duplicate cookies exist."""
21
+ for cookie in session.cookies:
22
+ if cookie.name == 'csrftoken':
23
+ return cookie.value
24
+ return None
25
+
26
+
27
+ def login(host, session, identifier, password):
28
+ """
29
+ Authenticate with the drp server using username or email.
30
+ Returns True on success, False on bad credentials.
31
+ Raises requests.RequestException on network errors.
32
+ """
33
+ csrf = get_csrf(host, session)
34
+ res = session.post(
35
+ f'{host}/auth/login/',
36
+ data={'email': identifier, 'password': password, 'csrfmiddlewaretoken': csrf},
37
+ timeout=10,
38
+ allow_redirects=False,
39
+ )
40
+ return res.status_code in (301, 302)