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.
- drp_dev-1.0.0/LICENSE +31 -0
- drp_dev-1.0.0/PKG-INFO +96 -0
- drp_dev-1.0.0/PYPI_DEV_README.md +53 -0
- drp_dev-1.0.0/README.md +68 -0
- drp_dev-1.0.0/cli/__init__.py +30 -0
- drp_dev-1.0.0/cli/__main__.py +3 -0
- drp_dev-1.0.0/cli/api/__init__.py +18 -0
- drp_dev-1.0.0/cli/api/actions.py +205 -0
- drp_dev-1.0.0/cli/api/auth.py +40 -0
- drp_dev-1.0.0/cli/api/file.py +340 -0
- drp_dev-1.0.0/cli/api/helpers.py +28 -0
- drp_dev-1.0.0/cli/api/text.py +137 -0
- drp_dev-1.0.0/cli/commands/__init__.py +0 -0
- drp_dev-1.0.0/cli/commands/_context.py +44 -0
- drp_dev-1.0.0/cli/commands/ask.py +110 -0
- drp_dev-1.0.0/cli/commands/cache.py +88 -0
- drp_dev-1.0.0/cli/commands/collection.py +219 -0
- drp_dev-1.0.0/cli/commands/cp.py +62 -0
- drp_dev-1.0.0/cli/commands/edit.py +93 -0
- drp_dev-1.0.0/cli/commands/get.py +344 -0
- drp_dev-1.0.0/cli/commands/load.py +58 -0
- drp_dev-1.0.0/cli/commands/lock.py +85 -0
- drp_dev-1.0.0/cli/commands/ls.py +203 -0
- drp_dev-1.0.0/cli/commands/manage.py +90 -0
- drp_dev-1.0.0/cli/commands/save.py +32 -0
- drp_dev-1.0.0/cli/commands/send.py +76 -0
- drp_dev-1.0.0/cli/commands/serve.py +103 -0
- drp_dev-1.0.0/cli/commands/setup.py +306 -0
- drp_dev-1.0.0/cli/commands/shell.py +549 -0
- drp_dev-1.0.0/cli/commands/status.py +181 -0
- drp_dev-1.0.0/cli/commands/token.py +123 -0
- drp_dev-1.0.0/cli/commands/upload.py +327 -0
- drp_dev-1.0.0/cli/completion.py +297 -0
- drp_dev-1.0.0/cli/config.py +81 -0
- drp_dev-1.0.0/cli/crash_reporter.py +168 -0
- drp_dev-1.0.0/cli/drp.py +447 -0
- drp_dev-1.0.0/cli/format.py +105 -0
- drp_dev-1.0.0/cli/path_check.py +57 -0
- drp_dev-1.0.0/cli/progress.py +107 -0
- drp_dev-1.0.0/cli/prompt.py +102 -0
- drp_dev-1.0.0/cli/session.py +154 -0
- drp_dev-1.0.0/cli/smart_parse.py +207 -0
- drp_dev-1.0.0/cli/spinner.py +98 -0
- drp_dev-1.0.0/cli/timing.py +97 -0
- drp_dev-1.0.0/cli/version_check.py +150 -0
- drp_dev-1.0.0/drp_dev.egg-info/PKG-INFO +96 -0
- drp_dev-1.0.0/drp_dev.egg-info/SOURCES.txt +51 -0
- drp_dev-1.0.0/drp_dev.egg-info/dependency_links.txt +1 -0
- drp_dev-1.0.0/drp_dev.egg-info/entry_points.txt +2 -0
- drp_dev-1.0.0/drp_dev.egg-info/requires.txt +21 -0
- drp_dev-1.0.0/drp_dev.egg-info/top_level.txt +1 -0
- drp_dev-1.0.0/pyproject.toml +76 -0
- 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.
|
drp_dev-1.0.0/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# [drp](https://drp.fyi) — instant clipboard & file sharing
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
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
|
+
 ← 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
|
+
[](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,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)
|