zipline-cli 0.2.2__tar.gz → 0.3.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.
- {zipline_cli-0.2.2/src/zipline_cli.egg-info → zipline_cli-0.3.0}/PKG-INFO +22 -19
- {zipline_cli-0.2.2 → zipline_cli-0.3.0}/README.md +11 -8
- {zipline_cli-0.2.2 → zipline_cli-0.3.0}/pyproject.toml +60 -58
- zipline_cli-0.3.0/src/zipline/__init__.py +10 -0
- zipline_cli-0.3.0/src/zipline/_utils.py +31 -0
- zipline_cli-0.2.2/src/zipline/__init__.py → zipline_cli-0.3.0/src/zipline/_version.py +1 -9
- zipline_cli-0.3.0/src/zipline/cli.py +165 -0
- zipline_cli-0.3.0/src/zipline/zipline.py +184 -0
- {zipline_cli-0.2.2 → zipline_cli-0.3.0/src/zipline_cli.egg-info}/PKG-INFO +22 -19
- {zipline_cli-0.2.2 → zipline_cli-0.3.0}/src/zipline_cli.egg-info/SOURCES.txt +3 -0
- zipline_cli-0.3.0/src/zipline_cli.egg-info/entry_points.txt +2 -0
- zipline_cli-0.3.0/src/zipline_cli.egg-info/requires.txt +3 -0
- zipline_cli-0.2.2/src/zipline/zipline.py +0 -349
- zipline_cli-0.2.2/src/zipline_cli.egg-info/entry_points.txt +0 -2
- zipline_cli-0.2.2/src/zipline_cli.egg-info/requires.txt +0 -3
- {zipline_cli-0.2.2 → zipline_cli-0.3.0}/LICENSE +0 -0
- {zipline_cli-0.2.2 → zipline_cli-0.3.0}/MANIFEST.in +0 -0
- {zipline_cli-0.2.2 → zipline_cli-0.3.0}/setup.cfg +0 -0
- {zipline_cli-0.2.2 → zipline_cli-0.3.0}/src/zipline/py.typed +0 -0
- {zipline_cli-0.2.2 → zipline_cli-0.3.0}/src/zipline_cli.egg-info/dependency_links.txt +0 -0
- {zipline_cli-0.2.2 → zipline_cli-0.3.0}/src/zipline_cli.egg-info/top_level.txt +0 -0
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zipline-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python 3 CLI File Uploader for Diced/Zipline v3/v4 Upload Server and Django Files.
|
|
5
5
|
Author: Shane
|
|
6
6
|
License: MIT License
|
|
7
|
-
Project-URL: GitHub, https://github.com/cssnr/zipline-cli
|
|
8
|
-
Project-URL: Issues, https://github.com/cssnr/zipline-cli/issues
|
|
9
7
|
Project-URL: Changelog, https://github.com/cssnr/zipline-cli/releases
|
|
8
|
+
Project-URL: Discord, https://discord.gg/wXy6m2X8wY
|
|
10
9
|
Project-URL: Documentation, https://zipline-cli.cssnr.com/
|
|
11
|
-
Project-URL: Homepage, https://cssnr.com/
|
|
12
10
|
Project-URL: Funding, https://ko-fi.com/cssnr
|
|
13
|
-
Project-URL:
|
|
11
|
+
Project-URL: GitHub, https://github.com/cssnr/zipline-cli
|
|
12
|
+
Project-URL: Homepage, https://cssnr.com/
|
|
13
|
+
Project-URL: Issues, https://github.com/cssnr/zipline-cli/issues
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
14
17
|
Classifier: Programming Language :: Python
|
|
15
18
|
Classifier: Programming Language :: Python :: 3
|
|
16
19
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
-
Classifier: Operating System :: OS Independent
|
|
18
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
-
Classifier: Natural Language :: English
|
|
20
|
-
Classifier: Typing :: Typed
|
|
21
20
|
Classifier: Topic :: Communications :: File Sharing
|
|
22
21
|
Classifier: Topic :: Terminals
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.9
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
|
-
Requires-Dist: python-decouple
|
|
27
26
|
Requires-Dist: python-dotenv
|
|
28
|
-
Requires-Dist: requests
|
|
27
|
+
Requires-Dist: requests<3,>=2
|
|
28
|
+
Requires-Dist: typer
|
|
29
29
|
Dynamic: license-file
|
|
30
30
|
|
|
31
31
|
[](https://pypi.org/project/zipline-cli/)
|
|
@@ -55,7 +55,7 @@ Dynamic: license-file
|
|
|
55
55
|
# Zipline CLI
|
|
56
56
|
|
|
57
57
|
<a title="Zipline CLI" href="https://zipline-cli.cssnr.com/" target="_blank">
|
|
58
|
-
<img alt="Zipline CLI" align="right" width="128" height="auto" src="https://
|
|
58
|
+
<img alt="Zipline CLI" align="right" width="128" height="auto" src="https://zipline-cli.cssnr.com/assets/images/logo.png"></a>
|
|
59
59
|
|
|
60
60
|
- [Quick Start](#quick-start)
|
|
61
61
|
- [Install](#install)
|
|
@@ -75,6 +75,11 @@ Python 3 CLI File Uploader for [Diced/Zipline](https://github.com/diced/zipline)
|
|
|
75
75
|
> You can also [request new features](https://github.com/cssnr/zipline-cli/discussions/new?category=feature-requests)
|
|
76
76
|
> or report any [issues](https://github.com/cssnr/zipline-cli/issues/new).
|
|
77
77
|
|
|
78
|
+
> [!IMPORTANT]
|
|
79
|
+
> A new app is in progress which supports ShareX Configurations.
|
|
80
|
+
> All new features will be added to this app.
|
|
81
|
+
> Please [let us know](https://github.com/cssnr/zipline-cli/discussions/new?category=feature-requests) what you want to see...
|
|
82
|
+
|
|
78
83
|
## Quick Start<a id="quick-start"></a>
|
|
79
84
|
|
|
80
85
|
```shell
|
|
@@ -153,13 +158,11 @@ Environment Variables are stored in the `.zipline` file in your home directory.
|
|
|
153
158
|
|
|
154
159
|
- Location: `~/.zipline` or `$HOME/.zipline`
|
|
155
160
|
|
|
156
|
-
| Variable | Description
|
|
157
|
-
| ---------------- |
|
|
158
|
-
| `ZIPLINE_URL` | URL to your Zipline Instance
|
|
159
|
-
| `ZIPLINE_TOKEN` | Authorization Token from Zipline
|
|
160
|
-
| `
|
|
161
|
-
| `ZIPLINE_FORMAT` | Output Format. Variables: `{filename}`, `{url}` and `{raw_url}` |
|
|
162
|
-
| `ZIPLINE_EXPIRE` | Reference: [Zipline](https://zipline.diced.sh/docs/guides/upload-options#image-expiration) / [Django Files](https://github.com/onegreyonewhite/pytimeparse2#pytimeparse2-time-expression-parser) |
|
|
161
|
+
| Variable | Description |
|
|
162
|
+
| ---------------- | --------------------------------------------------------------- |
|
|
163
|
+
| `ZIPLINE_URL` | URL to your Zipline Instance |
|
|
164
|
+
| `ZIPLINE_TOKEN` | Authorization Token from Zipline |
|
|
165
|
+
| `ZIPLINE_FORMAT` | Output Format. Variables: `{filename}`, `{url}` and `{raw_url}` |
|
|
163
166
|
|
|
164
167
|
See [.zipline.example](.zipline.example) for an example `.zipline` file.
|
|
165
168
|
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
# Zipline CLI
|
|
26
26
|
|
|
27
27
|
<a title="Zipline CLI" href="https://zipline-cli.cssnr.com/" target="_blank">
|
|
28
|
-
<img alt="Zipline CLI" align="right" width="128" height="auto" src="https://
|
|
28
|
+
<img alt="Zipline CLI" align="right" width="128" height="auto" src="https://zipline-cli.cssnr.com/assets/images/logo.png"></a>
|
|
29
29
|
|
|
30
30
|
- [Quick Start](#quick-start)
|
|
31
31
|
- [Install](#install)
|
|
@@ -45,6 +45,11 @@ Python 3 CLI File Uploader for [Diced/Zipline](https://github.com/diced/zipline)
|
|
|
45
45
|
> You can also [request new features](https://github.com/cssnr/zipline-cli/discussions/new?category=feature-requests)
|
|
46
46
|
> or report any [issues](https://github.com/cssnr/zipline-cli/issues/new).
|
|
47
47
|
|
|
48
|
+
> [!IMPORTANT]
|
|
49
|
+
> A new app is in progress which supports ShareX Configurations.
|
|
50
|
+
> All new features will be added to this app.
|
|
51
|
+
> Please [let us know](https://github.com/cssnr/zipline-cli/discussions/new?category=feature-requests) what you want to see...
|
|
52
|
+
|
|
48
53
|
## Quick Start<a id="quick-start"></a>
|
|
49
54
|
|
|
50
55
|
```shell
|
|
@@ -123,13 +128,11 @@ Environment Variables are stored in the `.zipline` file in your home directory.
|
|
|
123
128
|
|
|
124
129
|
- Location: `~/.zipline` or `$HOME/.zipline`
|
|
125
130
|
|
|
126
|
-
| Variable | Description
|
|
127
|
-
| ---------------- |
|
|
128
|
-
| `ZIPLINE_URL` | URL to your Zipline Instance
|
|
129
|
-
| `ZIPLINE_TOKEN` | Authorization Token from Zipline
|
|
130
|
-
| `
|
|
131
|
-
| `ZIPLINE_FORMAT` | Output Format. Variables: `{filename}`, `{url}` and `{raw_url}` |
|
|
132
|
-
| `ZIPLINE_EXPIRE` | Reference: [Zipline](https://zipline.diced.sh/docs/guides/upload-options#image-expiration) / [Django Files](https://github.com/onegreyonewhite/pytimeparse2#pytimeparse2-time-expression-parser) |
|
|
131
|
+
| Variable | Description |
|
|
132
|
+
| ---------------- | --------------------------------------------------------------- |
|
|
133
|
+
| `ZIPLINE_URL` | URL to your Zipline Instance |
|
|
134
|
+
| `ZIPLINE_TOKEN` | Authorization Token from Zipline |
|
|
135
|
+
| `ZIPLINE_FORMAT` | Output Format. Variables: `{filename}`, `{url}` and `{raw_url}` |
|
|
133
136
|
|
|
134
137
|
See [.zipline.example](.zipline.example) for an example `.zipline` file.
|
|
135
138
|
|
|
@@ -3,80 +3,66 @@
|
|
|
3
3
|
[project]
|
|
4
4
|
name = "zipline-cli"
|
|
5
5
|
description = "Python 3 CLI File Uploader for Diced/Zipline v3/v4 Upload Server and Django Files."
|
|
6
|
-
authors = [{ name="Shane" }]
|
|
7
6
|
readme = "README.md"
|
|
8
|
-
dynamic = ["version"]
|
|
9
7
|
requires-python = ">=3.9"
|
|
8
|
+
license = { text = "MIT License" }
|
|
9
|
+
authors = [{ name = "Shane" }]
|
|
10
10
|
classifiers = [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
"License :: OSI Approved :: MIT License",
|
|
12
|
+
"Natural Language :: English",
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
"Programming Language :: Python",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Topic :: Communications :: File Sharing",
|
|
18
|
+
"Topic :: Terminals",
|
|
19
|
+
"Typing :: Typed",
|
|
20
20
|
]
|
|
21
|
-
license = { text = "MIT License" }
|
|
22
|
-
#license = { file = "LICENSE" }
|
|
23
|
-
|
|
24
21
|
dependencies = [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
[dependency-groups]
|
|
31
|
-
dev = [
|
|
32
|
-
"bandit",
|
|
33
|
-
"black",
|
|
34
|
-
"build",
|
|
35
|
-
"isort",
|
|
36
|
-
"mypy",
|
|
37
|
-
"python-decouple-typed",
|
|
38
|
-
"psycopg2",
|
|
39
|
-
"ruff",
|
|
40
|
-
"setuptools",
|
|
41
|
-
"ty",
|
|
42
|
-
"types-psycopg2",
|
|
43
|
-
"types-requests",
|
|
44
|
-
"validate-pyproject[all]",
|
|
45
|
-
"wheel",
|
|
22
|
+
"python-dotenv",
|
|
23
|
+
"requests>=2,<3",
|
|
24
|
+
"typer",
|
|
46
25
|
]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
zipline = "zipline.zipline:main"
|
|
26
|
+
dynamic = ["version"]
|
|
27
|
+
# license = { file = "LICENSE" }
|
|
50
28
|
|
|
51
29
|
[project.urls]
|
|
52
|
-
GitHub = "https://github.com/cssnr/zipline-cli"
|
|
53
|
-
Issues = "https://github.com/cssnr/zipline-cli/issues"
|
|
54
30
|
Changelog = "https://github.com/cssnr/zipline-cli/releases"
|
|
31
|
+
Discord = "https://discord.gg/wXy6m2X8wY"
|
|
55
32
|
Documentation = "https://zipline-cli.cssnr.com/"
|
|
56
|
-
|
|
57
|
-
Homepage = "https://cssnr.com/"
|
|
58
33
|
Funding = "https://ko-fi.com/cssnr"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# https://setuptools.pypa.io/en/latest/
|
|
63
|
-
#[tool.setuptools.package-data]
|
|
64
|
-
#actions = ["py.typed"]
|
|
65
|
-
[tool.setuptools.dynamic]
|
|
66
|
-
version = { attr = "zipline.__version__" }
|
|
67
|
-
#version = {file = "version.txt"}
|
|
34
|
+
GitHub = "https://github.com/cssnr/zipline-cli"
|
|
35
|
+
Homepage = "https://cssnr.com/"
|
|
36
|
+
Issues = "https://github.com/cssnr/zipline-cli/issues"
|
|
68
37
|
|
|
69
|
-
[
|
|
70
|
-
|
|
71
|
-
include-package-data = true
|
|
38
|
+
[project.scripts]
|
|
39
|
+
zipline = "zipline.cli:app"
|
|
72
40
|
|
|
73
|
-
[
|
|
74
|
-
|
|
41
|
+
[dependency-groups]
|
|
42
|
+
dev = [
|
|
43
|
+
"bandit",
|
|
44
|
+
"black",
|
|
45
|
+
"build",
|
|
46
|
+
"isort",
|
|
47
|
+
"mypy",
|
|
48
|
+
"psycopg2",
|
|
49
|
+
"python-dotenv",
|
|
50
|
+
"requests",
|
|
51
|
+
"ruff",
|
|
52
|
+
"setuptools",
|
|
53
|
+
"tombi",
|
|
54
|
+
"ty",
|
|
55
|
+
"typer",
|
|
56
|
+
"types-psycopg2",
|
|
57
|
+
"types-requests",
|
|
58
|
+
"validate-pyproject[all]",
|
|
59
|
+
"wheel",
|
|
60
|
+
]
|
|
75
61
|
|
|
76
62
|
# Build System
|
|
77
63
|
[build-system]
|
|
78
|
-
build-backend = "setuptools.build_meta"
|
|
79
64
|
requires = ["setuptools>=61.0", "wheel"]
|
|
65
|
+
build-backend = "setuptools.build_meta"
|
|
80
66
|
|
|
81
67
|
# Bandit
|
|
82
68
|
# https://bandit.readthedocs.io/en/latest/config.html
|
|
@@ -107,7 +93,7 @@ skip = ["venv", ".github"]
|
|
|
107
93
|
# Mypy
|
|
108
94
|
# https://mypy.readthedocs.io/en/stable/config_file.html
|
|
109
95
|
[tool.mypy]
|
|
110
|
-
#ignore_missing_imports = true
|
|
96
|
+
# ignore_missing_imports = true
|
|
111
97
|
exclude = ["venv"]
|
|
112
98
|
|
|
113
99
|
# Ruff
|
|
@@ -119,3 +105,19 @@ extend-exclude = [".github"]
|
|
|
119
105
|
|
|
120
106
|
[tool.ruff.lint]
|
|
121
107
|
select = ["E4", "E7", "E9", "F", "B", "Q"]
|
|
108
|
+
|
|
109
|
+
# Setup Tools
|
|
110
|
+
# https://setuptools.pypa.io/en/latest/
|
|
111
|
+
[tool.setuptools]
|
|
112
|
+
py-modules = ["zipline"]
|
|
113
|
+
include-package-data = true
|
|
114
|
+
|
|
115
|
+
[tool.setuptools.package-data]
|
|
116
|
+
zipline = ["py.typed"]
|
|
117
|
+
|
|
118
|
+
[tool.setuptools.dynamic]
|
|
119
|
+
version = { attr = "zipline._version.__version__" }
|
|
120
|
+
# version = {file = "version.txt"}
|
|
121
|
+
|
|
122
|
+
[tool.setuptools.packages.find]
|
|
123
|
+
where = ["src"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import secrets
|
|
3
|
+
import string
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from dotenv import find_dotenv
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_env(file_name: str = ".zipline") -> Path:
|
|
10
|
+
# NOTE: Remove `find_dotenv` and use central config file...
|
|
11
|
+
env_file = Path(os.path.expanduser("~")) / file_name
|
|
12
|
+
if os.path.isfile(env_file):
|
|
13
|
+
return env_file
|
|
14
|
+
dotenv_file = find_dotenv(filename=file_name)
|
|
15
|
+
if dotenv_file:
|
|
16
|
+
dotenv_path = Path(dotenv_file)
|
|
17
|
+
return dotenv_path
|
|
18
|
+
return env_file
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def gen_rand(length: int = 4) -> str:
|
|
22
|
+
r = "".join(secrets.choice(string.ascii_letters) for _ in range(length))
|
|
23
|
+
return "".join(r)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def bytes_to_human(num: float, suffix: str = "B") -> str:
|
|
27
|
+
for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
|
|
28
|
+
if abs(num) < 1024.0:
|
|
29
|
+
return f"{num:3.1f} {unit}{suffix}"
|
|
30
|
+
num /= 1024.0
|
|
31
|
+
return f"{num:.1f} Yi{suffix}"
|
|
@@ -2,19 +2,11 @@ import os
|
|
|
2
2
|
import re
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
try:
|
|
6
|
-
from .zipline import Zipline
|
|
7
|
-
except ImportError:
|
|
8
|
-
Zipline = None # type: ignore
|
|
9
|
-
|
|
10
|
-
|
|
11
5
|
def get_version() -> str:
|
|
12
|
-
version = os.environ.get("GITHUB_REF_NAME", "
|
|
6
|
+
version = os.environ.get("GITHUB_REF_NAME", "")
|
|
13
7
|
pattern = r"^\d+\.\d+\.\d+(?:[abc]\d*)?$"
|
|
14
8
|
match = re.match(pattern, version)
|
|
15
9
|
return version if match else "0.0.1"
|
|
16
10
|
|
|
17
11
|
|
|
18
12
|
__version__ = get_version()
|
|
19
|
-
|
|
20
|
-
__all__ = ["Zipline"]
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, List, Optional
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import typer
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
from rich import print
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
from typing_extensions import Annotated
|
|
15
|
+
|
|
16
|
+
from . import __doc__ as package_doc
|
|
17
|
+
from . import _utils as utils
|
|
18
|
+
from .zipline import Zipline, ZipURL
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
env_file = utils.get_env()
|
|
22
|
+
dotenv_loaded = load_dotenv(dotenv_path=env_file)
|
|
23
|
+
|
|
24
|
+
app = typer.Typer(
|
|
25
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
26
|
+
pretty_exceptions_show_locals=False,
|
|
27
|
+
rich_markup_mode="rich",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
state = {"verbose": False}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def vprint(*objects: Any, **kwargs):
|
|
34
|
+
if state.get("verbose"):
|
|
35
|
+
print(*objects, file=sys.stderr, **kwargs)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def format_output(filename: str, url: ZipURL) -> str:
|
|
39
|
+
zipline_format = os.environ.get("ZIPLINE_FORMAT", "{filename}\n{url}\n{raw_url}")
|
|
40
|
+
return zipline_format.format(filename=filename, url=url, raw_url=url.raw)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def run_setup(_url: str) -> None:
|
|
44
|
+
# NOTE: Overhaul and update to typer/click/rich methods...
|
|
45
|
+
env_file.touch()
|
|
46
|
+
print(f"Setting up Environment File: [bold cyan]{env_file.absolute()}")
|
|
47
|
+
url = typer.prompt("Zipline URL", default=_url).strip()
|
|
48
|
+
token = typer.prompt("Zipline Authorization Token").strip()
|
|
49
|
+
if not url or not token:
|
|
50
|
+
raise click.ClickException("Missing URL or Token.")
|
|
51
|
+
output = f"ZIPLINE_URL={url}\nZIPLINE_TOKEN={token}\n"
|
|
52
|
+
embed = typer.confirm("Enabled Embed?", default=True)
|
|
53
|
+
output += f"ZIPLINE_EMBED={embed}\n"
|
|
54
|
+
expire = typer.prompt("Default Expire?", default="").strip().lower()
|
|
55
|
+
if expire:
|
|
56
|
+
output += f"ZIPLINE_EXPIRE={expire}\n"
|
|
57
|
+
with open(env_file, "w") as f:
|
|
58
|
+
f.write(output)
|
|
59
|
+
print(f"Setup Complete. Variables Saved to: [bold green]{env_file.absolute()}")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def info_callback(value: bool):
|
|
63
|
+
if value:
|
|
64
|
+
table = Table(title="App Information")
|
|
65
|
+
# Head
|
|
66
|
+
table.add_column("Item", style="bold magenta", no_wrap=True)
|
|
67
|
+
table.add_column("Value", style="bold cyan")
|
|
68
|
+
# Body
|
|
69
|
+
table.add_row("Zipline Version", version("zipline-cli"))
|
|
70
|
+
table.add_row("Config File", str(env_file.absolute()))
|
|
71
|
+
table.add_row("Config Loaded", str(dotenv_loaded))
|
|
72
|
+
table.add_row("Server URL", os.environ.get("ZIPLINE_URL", ""))
|
|
73
|
+
table.add_row("Token (ends in)", os.environ.get("ZIPLINE_TOKEN", "")[-10:])
|
|
74
|
+
table.add_row("Expire", os.environ.get("ZIPLINE_EXPIRE", ""))
|
|
75
|
+
table.add_row("Embed", os.environ.get("ZIPLINE_EMBED", ""))
|
|
76
|
+
table.add_row("Results Format", repr(os.environ.get("ZIPLINE_FORMAT", "{url}\n{raw_url}"))[1:-1])
|
|
77
|
+
console = Console()
|
|
78
|
+
console.print(table)
|
|
79
|
+
raise typer.Exit()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def version_callback(value: bool):
|
|
83
|
+
if value:
|
|
84
|
+
print(f"[magenta bold]{package_doc}", file=sys.stderr)
|
|
85
|
+
version_string = version("zipline-cli")
|
|
86
|
+
print(f"[white bold]{version_string}")
|
|
87
|
+
raise typer.Exit()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@app.command(epilog="Docs: https://zipline-cli.cssnr.com/")
|
|
91
|
+
def main(
|
|
92
|
+
files: Annotated[Optional[List[Path]], typer.Argument(help="Files...", exists=True, dir_okay=False)] = None,
|
|
93
|
+
_name: Annotated[
|
|
94
|
+
Optional[str], typer.Option("-n", "--name", metavar="", help="File Name (sent with upload).")
|
|
95
|
+
] = None,
|
|
96
|
+
# _embed: Annotated[
|
|
97
|
+
# Optional[bool], typer.Option("-E", "--embed", help="Enable Embed.", envvar="ZIPLINE_EMBED")
|
|
98
|
+
# ] = False,
|
|
99
|
+
# _expire: Annotated[
|
|
100
|
+
# str,
|
|
101
|
+
# typer.Option(
|
|
102
|
+
# "-e", "-x", "--expire", "--expires_at", metavar="", help="File Expiration.", envvar="ZIPLINE_EXPIRE"
|
|
103
|
+
# ),
|
|
104
|
+
# ] = "",
|
|
105
|
+
_url: Annotated[str, typer.Option("-u", "--url", metavar="", help="Zipline URL.", envvar="ZIPLINE_URL")] = "",
|
|
106
|
+
_token: Annotated[
|
|
107
|
+
str,
|
|
108
|
+
typer.Option(
|
|
109
|
+
"-t",
|
|
110
|
+
"-a",
|
|
111
|
+
"--token",
|
|
112
|
+
"--authorization",
|
|
113
|
+
metavar="",
|
|
114
|
+
help="Zipline token.",
|
|
115
|
+
envvar=["ZIPLINE_TOKEN", "ZIPLINE_AUTHORIZATION"],
|
|
116
|
+
),
|
|
117
|
+
] = "",
|
|
118
|
+
_verbose: Annotated[
|
|
119
|
+
bool, typer.Option("-v", "--verbose", help="Verbose Output (jq safe).", envvar="ZIPLINE_VERBOSE")
|
|
120
|
+
] = False,
|
|
121
|
+
_setup: Annotated[bool, typer.Option("-S", "--setup", help="Run interactive setup.")] = False,
|
|
122
|
+
_info: Annotated[
|
|
123
|
+
Optional[bool], typer.Option("-I", "--info", callback=info_callback, help="Show saved information.")
|
|
124
|
+
] = None,
|
|
125
|
+
_version: Annotated[
|
|
126
|
+
Optional[bool], typer.Option("-V", "--version", callback=version_callback, help="Show installed version.")
|
|
127
|
+
] = None,
|
|
128
|
+
):
|
|
129
|
+
"""Zipline CLI"""
|
|
130
|
+
if _verbose:
|
|
131
|
+
state["verbose"] = _verbose
|
|
132
|
+
|
|
133
|
+
vprint(f"{_url=}", f"{_token[-12:]=}", sep="\n")
|
|
134
|
+
# vprint(f"{_expire=}", f"{_embed=}", sep="\n")
|
|
135
|
+
|
|
136
|
+
if _setup or not _url and not _token:
|
|
137
|
+
print("[bold red]Error![/bold red] Missing --url or --token, Entering Setup.")
|
|
138
|
+
run_setup(_url)
|
|
139
|
+
raise typer.Exit()
|
|
140
|
+
|
|
141
|
+
zipline = Zipline(_url, authorization=_token)
|
|
142
|
+
|
|
143
|
+
if not files:
|
|
144
|
+
file_name: str = _name or f"{utils.gen_rand(8)}.txt"
|
|
145
|
+
zip_url: ZipURL = zipline.send_file(file_name, click.get_text_stream("stdin"))
|
|
146
|
+
print(format_output(file_name, zip_url))
|
|
147
|
+
raise typer.Exit()
|
|
148
|
+
|
|
149
|
+
exit_code = 1
|
|
150
|
+
for i, file in enumerate(files, 1):
|
|
151
|
+
size = utils.bytes_to_human(file.stat().st_size)
|
|
152
|
+
text_format = f"Uploading {i}/{len(files)}: " + "[progress.description]{task.description}"
|
|
153
|
+
with Progress(SpinnerColumn(), TextColumn(text_format), transient=True) as progress:
|
|
154
|
+
description = f"[green bold]{click.format_filename(file.name)}[/green bold] - [yellow bold]{size}"
|
|
155
|
+
progress.add_task(description=description, total=None)
|
|
156
|
+
file_name = _name if _name and len(files) == 1 else file.name
|
|
157
|
+
with open(file, "rb") as f:
|
|
158
|
+
zip_url_: ZipURL = zipline.send_file(file_name, f)
|
|
159
|
+
print(format_output(file.name, zip_url_))
|
|
160
|
+
exit_code = 0
|
|
161
|
+
raise typer.Exit(exit_code)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
app()
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import mimetypes
|
|
2
|
+
import warnings
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import IO, Dict, Optional
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ZipURL(object):
|
|
10
|
+
"""
|
|
11
|
+
Zipline URL Object
|
|
12
|
+
:param file_url: Zipline File Display URL
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__slots__ = ["url", "raw"]
|
|
16
|
+
|
|
17
|
+
def __init__(self, file_url: str):
|
|
18
|
+
self.url: str = file_url
|
|
19
|
+
self.raw: str = self._get_raw(file_url)
|
|
20
|
+
|
|
21
|
+
def __repr__(self):
|
|
22
|
+
return f"<url={self.url} raw={self.raw}>"
|
|
23
|
+
|
|
24
|
+
def __str__(self):
|
|
25
|
+
return self.url
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def _get_raw(url: str) -> str:
|
|
29
|
+
try:
|
|
30
|
+
s = url.split("/", 4)
|
|
31
|
+
return f"{s[0]}//{s[2]}/r/{s[4]}"
|
|
32
|
+
except Exception: # noqa
|
|
33
|
+
return ""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Zipline(object):
|
|
37
|
+
"""
|
|
38
|
+
Zipline Python API
|
|
39
|
+
:param base_url: Zipline URL
|
|
40
|
+
:param kwargs: Zipline Headers
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# noinspection SpellCheckingInspection
|
|
44
|
+
allowed_headers = [
|
|
45
|
+
# zipline v3
|
|
46
|
+
"format",
|
|
47
|
+
"image_compression_percent",
|
|
48
|
+
"expires_at",
|
|
49
|
+
"password",
|
|
50
|
+
"zws",
|
|
51
|
+
"embed",
|
|
52
|
+
"max_views",
|
|
53
|
+
"uploadtext",
|
|
54
|
+
"authorization",
|
|
55
|
+
"no_json",
|
|
56
|
+
"x_zipline_filename",
|
|
57
|
+
"original_name",
|
|
58
|
+
"override_domain",
|
|
59
|
+
# zipline v4
|
|
60
|
+
"x-zipline-deletes-at",
|
|
61
|
+
"x-zipline-format",
|
|
62
|
+
"x-zipline-image-compression-percent",
|
|
63
|
+
"x-zipline-password",
|
|
64
|
+
"x-zipline-max-views",
|
|
65
|
+
"x-zipline-no-json",
|
|
66
|
+
"x-zipline-original-name",
|
|
67
|
+
"x-zipline-folder",
|
|
68
|
+
"x-zipline-filename",
|
|
69
|
+
"x-zipline-domain",
|
|
70
|
+
"x-zipline-file-extension",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
def __init__(self, url: str, **kwargs):
|
|
74
|
+
self.base_url: str = url.rstrip("/")
|
|
75
|
+
self._headers: Dict[str, str] = {}
|
|
76
|
+
for header, value in kwargs.items():
|
|
77
|
+
if header.lower() not in self.allowed_headers:
|
|
78
|
+
continue
|
|
79
|
+
if value is None:
|
|
80
|
+
continue
|
|
81
|
+
key = header.replace("_", "-").title()
|
|
82
|
+
self._headers[key] = str(value)
|
|
83
|
+
|
|
84
|
+
def send_file(self, file_name: str, file_object: IO, overrides: Optional[dict] = None) -> ZipURL:
|
|
85
|
+
"""
|
|
86
|
+
Upload File to Zipline
|
|
87
|
+
:param file_name: Name of File for files tuple
|
|
88
|
+
:param file_object: File to Upload
|
|
89
|
+
:param overrides: Header Overrides
|
|
90
|
+
:return: File URL
|
|
91
|
+
"""
|
|
92
|
+
url = self.base_url + "/api/upload"
|
|
93
|
+
path = Path(file_name)
|
|
94
|
+
mime_type = get_type(path)
|
|
95
|
+
# print(f"mime_type: {mime_type}")
|
|
96
|
+
files = {"file": (file_name, file_object, mime_type)}
|
|
97
|
+
headers = self._headers | overrides if overrides else self._headers
|
|
98
|
+
r = requests.post(url, headers=headers, files=files) # nosec
|
|
99
|
+
r.raise_for_status()
|
|
100
|
+
data = r.json()["files"][0]
|
|
101
|
+
if isinstance(data, dict):
|
|
102
|
+
return ZipURL(data["url"])
|
|
103
|
+
elif isinstance(data, list):
|
|
104
|
+
return ZipURL(data[0])
|
|
105
|
+
else:
|
|
106
|
+
return ZipURL(data)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_type(file_path: Path) -> str: # NOSONAR
|
|
110
|
+
"""
|
|
111
|
+
Get MIME type from guess_type or by reading magic headers
|
|
112
|
+
https://en.wikipedia.org/wiki/List_of_file_signatures
|
|
113
|
+
|
|
114
|
+
Deprecated since version 3.13: Passing a file path instead of URL is soft deprecated. Use guess_file_type() for this.
|
|
115
|
+
https://docs.python.org/3/library/mimetypes.html#mimetypes.guess_type
|
|
116
|
+
"""
|
|
117
|
+
mime_type, _ = mimetypes.guess_type(file_path, strict=False)
|
|
118
|
+
if mime_type:
|
|
119
|
+
return mime_type
|
|
120
|
+
|
|
121
|
+
if not file_path.is_file():
|
|
122
|
+
return "text/plain"
|
|
123
|
+
|
|
124
|
+
with open(file_path, "rb") as file:
|
|
125
|
+
chunk = file.read(512)
|
|
126
|
+
|
|
127
|
+
# print(f"chunk: {type(chunk)} - {chunk[:20]}")
|
|
128
|
+
# print(f"test chunk: {chunk[8:11]}")
|
|
129
|
+
|
|
130
|
+
if isinstance(chunk, str): # pragma: no cover
|
|
131
|
+
warnings.warn("This condition should not be True...", stacklevel=2)
|
|
132
|
+
return "text/plain"
|
|
133
|
+
|
|
134
|
+
# Images
|
|
135
|
+
if chunk[0:3] == b"\xff\xd8\xff" or chunk[6:10] in (b"JFIF", b"Exif"):
|
|
136
|
+
return "image/jpeg"
|
|
137
|
+
elif chunk.startswith(b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"):
|
|
138
|
+
return "image/png"
|
|
139
|
+
elif chunk.startswith(b"RIFF") and chunk[8:12] == b"WEBP":
|
|
140
|
+
return "image/webp"
|
|
141
|
+
elif chunk.startswith((b"\x47\x49\x46\x38\x37\x61", b"\x47\x49\x46\x38\x39\x61")):
|
|
142
|
+
return "image/gif"
|
|
143
|
+
elif chunk.startswith(b"\x66\x74\x79\x70\x68\x65\x69\x63\x66\x74\79\70\6d") or chunk[4:12] == b"ftypheic":
|
|
144
|
+
return "image/heic"
|
|
145
|
+
elif chunk.startswith(b"\x00\x00\x01\x00"):
|
|
146
|
+
return "image/ico"
|
|
147
|
+
elif chunk.startswith(b"II*") or chunk.startswith(b"II+") or chunk.startswith(b"MM"):
|
|
148
|
+
return "image/tiff"
|
|
149
|
+
elif chunk.startswith(b"BM"):
|
|
150
|
+
return "image/bmp"
|
|
151
|
+
elif chunk[4:12] == b"ftypavif":
|
|
152
|
+
return "image/avif"
|
|
153
|
+
|
|
154
|
+
# Video
|
|
155
|
+
elif chunk[4:12] == b"ftypisom" or chunk[4:12] == b"ftypMSNV" or chunk[4:12] == b"ftypmp42":
|
|
156
|
+
return "video/mp4"
|
|
157
|
+
elif chunk[3:11] in (b"\x66\x74\x79\x70\x4d\x53\x4e\x56", b"\x66\x74\x79\x70\x69\x73\x6f\x6d"):
|
|
158
|
+
return "video/mp4"
|
|
159
|
+
elif chunk.startswith(b"\x1a\x45\xdf\xa3"):
|
|
160
|
+
# https://www.loc.gov/preservation/digital/formats//fdd/fdd000342.shtml
|
|
161
|
+
return "video/x-matroska"
|
|
162
|
+
elif chunk.startswith(b"RIFF") and chunk[8:11] == b"AVI":
|
|
163
|
+
return "video/x-msvideo"
|
|
164
|
+
elif chunk.startswith(b"\x30\x26\xb2\x75\x8e\x66\xcf\x11\xa6\xd9\x00\xaa\x00\x62\xce\x6c"):
|
|
165
|
+
# https://www.loc.gov/preservation/digital/formats/fdd/fdd000091.shtml
|
|
166
|
+
return "video/x-ms-asf"
|
|
167
|
+
elif chunk.startswith(b"\x6d\x6f\x6f\x76") or chunk[4:10] == b"ftypqt":
|
|
168
|
+
# https://www.file-recovery.com/mov-signature-format.htm
|
|
169
|
+
return "video/quicktime"
|
|
170
|
+
|
|
171
|
+
# Audio
|
|
172
|
+
elif chunk.startswith((b"\xff\xfb", b"\xff\xfb", b"\xff\xfb", b"\x49\x44\x33")):
|
|
173
|
+
return "audio/mp3"
|
|
174
|
+
elif chunk.startswith(b"RIFF") and chunk[8:12] == b"WAVE":
|
|
175
|
+
return "audio/wav"
|
|
176
|
+
elif chunk.startswith(b"OggS"):
|
|
177
|
+
return "application/ogg"
|
|
178
|
+
|
|
179
|
+
# Fallback
|
|
180
|
+
try:
|
|
181
|
+
chunk.decode("utf-8")
|
|
182
|
+
return "text/plain"
|
|
183
|
+
except UnicodeDecodeError:
|
|
184
|
+
return "application/octet-stream"
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zipline-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python 3 CLI File Uploader for Diced/Zipline v3/v4 Upload Server and Django Files.
|
|
5
5
|
Author: Shane
|
|
6
6
|
License: MIT License
|
|
7
|
-
Project-URL: GitHub, https://github.com/cssnr/zipline-cli
|
|
8
|
-
Project-URL: Issues, https://github.com/cssnr/zipline-cli/issues
|
|
9
7
|
Project-URL: Changelog, https://github.com/cssnr/zipline-cli/releases
|
|
8
|
+
Project-URL: Discord, https://discord.gg/wXy6m2X8wY
|
|
10
9
|
Project-URL: Documentation, https://zipline-cli.cssnr.com/
|
|
11
|
-
Project-URL: Homepage, https://cssnr.com/
|
|
12
10
|
Project-URL: Funding, https://ko-fi.com/cssnr
|
|
13
|
-
Project-URL:
|
|
11
|
+
Project-URL: GitHub, https://github.com/cssnr/zipline-cli
|
|
12
|
+
Project-URL: Homepage, https://cssnr.com/
|
|
13
|
+
Project-URL: Issues, https://github.com/cssnr/zipline-cli/issues
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
14
17
|
Classifier: Programming Language :: Python
|
|
15
18
|
Classifier: Programming Language :: Python :: 3
|
|
16
19
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
-
Classifier: Operating System :: OS Independent
|
|
18
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
-
Classifier: Natural Language :: English
|
|
20
|
-
Classifier: Typing :: Typed
|
|
21
20
|
Classifier: Topic :: Communications :: File Sharing
|
|
22
21
|
Classifier: Topic :: Terminals
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.9
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
|
-
Requires-Dist: python-decouple
|
|
27
26
|
Requires-Dist: python-dotenv
|
|
28
|
-
Requires-Dist: requests
|
|
27
|
+
Requires-Dist: requests<3,>=2
|
|
28
|
+
Requires-Dist: typer
|
|
29
29
|
Dynamic: license-file
|
|
30
30
|
|
|
31
31
|
[](https://pypi.org/project/zipline-cli/)
|
|
@@ -55,7 +55,7 @@ Dynamic: license-file
|
|
|
55
55
|
# Zipline CLI
|
|
56
56
|
|
|
57
57
|
<a title="Zipline CLI" href="https://zipline-cli.cssnr.com/" target="_blank">
|
|
58
|
-
<img alt="Zipline CLI" align="right" width="128" height="auto" src="https://
|
|
58
|
+
<img alt="Zipline CLI" align="right" width="128" height="auto" src="https://zipline-cli.cssnr.com/assets/images/logo.png"></a>
|
|
59
59
|
|
|
60
60
|
- [Quick Start](#quick-start)
|
|
61
61
|
- [Install](#install)
|
|
@@ -75,6 +75,11 @@ Python 3 CLI File Uploader for [Diced/Zipline](https://github.com/diced/zipline)
|
|
|
75
75
|
> You can also [request new features](https://github.com/cssnr/zipline-cli/discussions/new?category=feature-requests)
|
|
76
76
|
> or report any [issues](https://github.com/cssnr/zipline-cli/issues/new).
|
|
77
77
|
|
|
78
|
+
> [!IMPORTANT]
|
|
79
|
+
> A new app is in progress which supports ShareX Configurations.
|
|
80
|
+
> All new features will be added to this app.
|
|
81
|
+
> Please [let us know](https://github.com/cssnr/zipline-cli/discussions/new?category=feature-requests) what you want to see...
|
|
82
|
+
|
|
78
83
|
## Quick Start<a id="quick-start"></a>
|
|
79
84
|
|
|
80
85
|
```shell
|
|
@@ -153,13 +158,11 @@ Environment Variables are stored in the `.zipline` file in your home directory.
|
|
|
153
158
|
|
|
154
159
|
- Location: `~/.zipline` or `$HOME/.zipline`
|
|
155
160
|
|
|
156
|
-
| Variable | Description
|
|
157
|
-
| ---------------- |
|
|
158
|
-
| `ZIPLINE_URL` | URL to your Zipline Instance
|
|
159
|
-
| `ZIPLINE_TOKEN` | Authorization Token from Zipline
|
|
160
|
-
| `
|
|
161
|
-
| `ZIPLINE_FORMAT` | Output Format. Variables: `{filename}`, `{url}` and `{raw_url}` |
|
|
162
|
-
| `ZIPLINE_EXPIRE` | Reference: [Zipline](https://zipline.diced.sh/docs/guides/upload-options#image-expiration) / [Django Files](https://github.com/onegreyonewhite/pytimeparse2#pytimeparse2-time-expression-parser) |
|
|
161
|
+
| Variable | Description |
|
|
162
|
+
| ---------------- | --------------------------------------------------------------- |
|
|
163
|
+
| `ZIPLINE_URL` | URL to your Zipline Instance |
|
|
164
|
+
| `ZIPLINE_TOKEN` | Authorization Token from Zipline |
|
|
165
|
+
| `ZIPLINE_FORMAT` | Output Format. Variables: `{filename}`, `{url}` and `{raw_url}` |
|
|
163
166
|
|
|
164
167
|
See [.zipline.example](.zipline.example) for an example `.zipline` file.
|
|
165
168
|
|
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
import io
|
|
3
|
-
import mimetypes
|
|
4
|
-
import os
|
|
5
|
-
import re
|
|
6
|
-
import secrets
|
|
7
|
-
import string
|
|
8
|
-
import sys
|
|
9
|
-
from importlib.metadata import version
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import IO, Any, Dict, List, Optional, TextIO
|
|
12
|
-
|
|
13
|
-
import requests
|
|
14
|
-
from decouple import config
|
|
15
|
-
from dotenv import find_dotenv, load_dotenv
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class ZipURL(object):
|
|
19
|
-
"""
|
|
20
|
-
Zipline URL Object
|
|
21
|
-
:param file_url: Zipline File Display URL
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
__slots__ = ["url", "raw"]
|
|
25
|
-
|
|
26
|
-
def __init__(self, file_url: str):
|
|
27
|
-
self.url: str = file_url
|
|
28
|
-
self.raw: str = self._get_raw(file_url)
|
|
29
|
-
|
|
30
|
-
def __repr__(self):
|
|
31
|
-
return f"<url={self.url} raw={self.raw}>"
|
|
32
|
-
|
|
33
|
-
def __str__(self):
|
|
34
|
-
return self.url
|
|
35
|
-
|
|
36
|
-
@staticmethod
|
|
37
|
-
def _get_raw(url: str) -> str:
|
|
38
|
-
try:
|
|
39
|
-
s = url.split("/", 4)
|
|
40
|
-
return f"{s[0]}//{s[2]}/r/{s[4]}"
|
|
41
|
-
except Exception: # noqa
|
|
42
|
-
return ""
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class Zipline(object):
|
|
46
|
-
"""
|
|
47
|
-
Zipline Python API
|
|
48
|
-
:param base_url: Zipline URL
|
|
49
|
-
:param kwargs: Zipline Headers
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
# noinspection SpellCheckingInspection
|
|
53
|
-
allowed_headers = [
|
|
54
|
-
# zipline v3
|
|
55
|
-
"format",
|
|
56
|
-
"image_compression_percent",
|
|
57
|
-
"expires_at",
|
|
58
|
-
"password",
|
|
59
|
-
"zws",
|
|
60
|
-
"embed",
|
|
61
|
-
"max_views",
|
|
62
|
-
"uploadtext",
|
|
63
|
-
"authorization",
|
|
64
|
-
"no_json",
|
|
65
|
-
"x_zipline_filename",
|
|
66
|
-
"original_name",
|
|
67
|
-
"override_domain",
|
|
68
|
-
# zipline v4
|
|
69
|
-
"x-zipline-deletes-at",
|
|
70
|
-
"x-zipline-format",
|
|
71
|
-
"x-zipline-image-compression-percent",
|
|
72
|
-
"x-zipline-password",
|
|
73
|
-
"x-zipline-max-views",
|
|
74
|
-
"x-zipline-no-json",
|
|
75
|
-
"x-zipline-original-name",
|
|
76
|
-
"x-zipline-folder",
|
|
77
|
-
"x-zipline-filename",
|
|
78
|
-
"x-zipline-domain",
|
|
79
|
-
"x-zipline-file-extension",
|
|
80
|
-
]
|
|
81
|
-
|
|
82
|
-
def __init__(self, url: str, **kwargs):
|
|
83
|
-
self.base_url: str = url.rstrip("/")
|
|
84
|
-
self._headers: Dict[str, str] = {}
|
|
85
|
-
for header, value in kwargs.items():
|
|
86
|
-
if header.lower() not in self.allowed_headers:
|
|
87
|
-
continue
|
|
88
|
-
if value is None:
|
|
89
|
-
continue
|
|
90
|
-
key = header.replace("_", "-").title()
|
|
91
|
-
self._headers[key] = str(value)
|
|
92
|
-
|
|
93
|
-
def send_file(self, file_name: str, file_object: IO, overrides: Optional[dict] = None) -> ZipURL:
|
|
94
|
-
"""
|
|
95
|
-
Upload File to Zipline
|
|
96
|
-
:param file_name: Name of File for files tuple
|
|
97
|
-
:param file_object: File to Upload
|
|
98
|
-
:param overrides: Header Overrides
|
|
99
|
-
:return: File URL
|
|
100
|
-
"""
|
|
101
|
-
url = self.base_url + "/api/upload"
|
|
102
|
-
|
|
103
|
-
path = Path(file_name)
|
|
104
|
-
if not path.is_file():
|
|
105
|
-
raise ValueError(f"Not a File: {path.resolve()}")
|
|
106
|
-
|
|
107
|
-
mime_type = get_type(path)
|
|
108
|
-
# print(f"mime_type: {mime_type}")
|
|
109
|
-
files = {"file": (file_name, file_object, mime_type)}
|
|
110
|
-
headers = self._headers | overrides if overrides else self._headers
|
|
111
|
-
r = requests.post(url, headers=headers, files=files) # nosec
|
|
112
|
-
r.raise_for_status()
|
|
113
|
-
data = r.json()["files"][0]
|
|
114
|
-
if isinstance(data, dict):
|
|
115
|
-
return ZipURL(data["url"])
|
|
116
|
-
elif isinstance(data, list):
|
|
117
|
-
return ZipURL(data[0])
|
|
118
|
-
else:
|
|
119
|
-
return ZipURL(data)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def get_type(file_path: Path) -> str:
|
|
123
|
-
# Deprecated since version 3.13: Passing a file path instead of URL is soft deprecated. Use guess_file_type() for this.
|
|
124
|
-
# https://docs.python.org/3/library/mimetypes.html#mimetypes.guess_type
|
|
125
|
-
mime_type, _ = mimetypes.guess_type(file_path, strict=False)
|
|
126
|
-
if mime_type:
|
|
127
|
-
return mime_type
|
|
128
|
-
return magic_type(file_path)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def magic_type(file_path: Path) -> str: # NOSONAR
|
|
132
|
-
try:
|
|
133
|
-
with open(file_path, "rb") as file:
|
|
134
|
-
chunk = file.read(512)
|
|
135
|
-
# print(chunk)
|
|
136
|
-
if chunk[0:3] == b"\xff\xd8\xff" or chunk[6:10] in (b"JFIF", b"Exif"):
|
|
137
|
-
return "image/jpeg"
|
|
138
|
-
elif chunk.startswith(b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"):
|
|
139
|
-
return "image/png"
|
|
140
|
-
elif chunk.startswith(b"RIFF") and chunk[8:12] == b"WEBP":
|
|
141
|
-
return "image/webp"
|
|
142
|
-
elif chunk.startswith((b"\x47\x49\x46\x38\x37\x61", b"\x47\x49\x46\x38\x39\x61")):
|
|
143
|
-
return "image/gif"
|
|
144
|
-
elif chunk.startswith(b"\x66\x74\x79\x70\x68\x65\x69\x63\x66\x74\79\70\6d"):
|
|
145
|
-
return "image/heic"
|
|
146
|
-
elif chunk.startswith(b"\x00\x00\x01\x00"):
|
|
147
|
-
return "image/ico"
|
|
148
|
-
|
|
149
|
-
elif chunk[3:11] in (b"\x66\x74\x79\x70\x4d\x53\x4e\x56", b"\x66\x74\x79\x70\x69\x73\x6f\x6d"):
|
|
150
|
-
return "video/mp4"
|
|
151
|
-
elif chunk[4:12] == b"ftypisom" or chunk[4:12] == b"ftypMSNV" or chunk[4:12] == b"ftypmp42":
|
|
152
|
-
return "video/mp4"
|
|
153
|
-
elif chunk.startswith(b"\x1a\x45\xdf\xa3"):
|
|
154
|
-
return "video/x-matroska"
|
|
155
|
-
elif chunk.startswith(b"\x6d\x6f\x6f\x76"):
|
|
156
|
-
return "video/quicktime"
|
|
157
|
-
|
|
158
|
-
elif chunk.startswith((b"\xff\xfb", b"\xff\xfb", b"\xff\xfb", b"\x49\x44\x33")):
|
|
159
|
-
return "audio/mp3"
|
|
160
|
-
elif chunk.startswith(b"RIFF") and chunk[8:12] == b"WAVE":
|
|
161
|
-
return "audio/wav"
|
|
162
|
-
elif chunk.startswith(b"OggS"):
|
|
163
|
-
return "application/ogg"
|
|
164
|
-
|
|
165
|
-
chunk.decode("utf-8")
|
|
166
|
-
return "text/plain"
|
|
167
|
-
except UnicodeDecodeError:
|
|
168
|
-
return "application/octet-stream"
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def format_output(filename: str, url: ZipURL) -> str:
|
|
172
|
-
"""
|
|
173
|
-
Format URL Output
|
|
174
|
-
:param filename: Original or File Name
|
|
175
|
-
:param url: ZipURL to Format
|
|
176
|
-
:return: Formatted Output
|
|
177
|
-
"""
|
|
178
|
-
zipline_format = config("ZIPLINE_FORMAT", "{filename}\n{url}\n{raw_url}")
|
|
179
|
-
return zipline_format.format(filename=filename, url=url, raw_url=url.raw)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def gen_rand(length: int = 4) -> str:
|
|
183
|
-
"""
|
|
184
|
-
Generate Random Streng
|
|
185
|
-
:param length: Length of String
|
|
186
|
-
:return: Random String
|
|
187
|
-
"""
|
|
188
|
-
r = "".join(secrets.choice(string.ascii_letters) for _ in range(length))
|
|
189
|
-
return "".join(r)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def get_default(
|
|
193
|
-
values: List[str],
|
|
194
|
-
default: Optional[Any] = None,
|
|
195
|
-
cast: type = str,
|
|
196
|
-
pre: str = "ZIPLINE_",
|
|
197
|
-
suf: str = "",
|
|
198
|
-
) -> Optional[str]:
|
|
199
|
-
"""
|
|
200
|
-
Get Default Environment Variable from List of values
|
|
201
|
-
:param values: List of Values to Check
|
|
202
|
-
:param default: Default Value if None
|
|
203
|
-
:param cast: Type to Cast Value
|
|
204
|
-
:param pre: Environment Variable Prefix
|
|
205
|
-
:param suf: Environment Variable Suffix
|
|
206
|
-
:return: Environment Variable or None
|
|
207
|
-
"""
|
|
208
|
-
for value in values:
|
|
209
|
-
result = config(f"{pre}{value.upper()}{suf}", "", cast)
|
|
210
|
-
if result:
|
|
211
|
-
return result
|
|
212
|
-
return default
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def setup(env_file: Path) -> None:
|
|
216
|
-
print("Setting up Environment File...")
|
|
217
|
-
url = input("Zipline URL: ").strip()
|
|
218
|
-
token = input("Zipline Authorization Token: ").strip()
|
|
219
|
-
if not url or not token:
|
|
220
|
-
raise ValueError("Missing URL or Token.")
|
|
221
|
-
output = f"ZIPLINE_URL={url}\nZIPLINE_TOKEN={token}\n"
|
|
222
|
-
embed = input("Enabled Embed? [Yes]/No: ").strip()
|
|
223
|
-
if not embed or embed.lower() not in ["n", "o", "no", "noo"]:
|
|
224
|
-
output += "ZIPLINE_EMBED=true\n"
|
|
225
|
-
expire = input("Default Expire? [Blank for None]: ").strip().lower()
|
|
226
|
-
if expire:
|
|
227
|
-
match = re.search(r"^(\d+)(?:ms|s|m|h|d|w|y)$", expire)
|
|
228
|
-
if not match:
|
|
229
|
-
print(f"Warning: invalid expire format: {expire}. See --help")
|
|
230
|
-
else:
|
|
231
|
-
output += f"ZIPLINE_EXPIRE={expire}\n"
|
|
232
|
-
with open(env_file, "w") as f:
|
|
233
|
-
f.write(output)
|
|
234
|
-
print(f"Setup Complete. Variables Saved to: {env_file}")
|
|
235
|
-
sys.exit(0)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
def run() -> None:
|
|
239
|
-
zipline_file = ".zipline"
|
|
240
|
-
env_file = Path(os.path.expanduser("~")) / zipline_file
|
|
241
|
-
dotenv_path = env_file if os.path.isfile(env_file) else find_dotenv(filename=zipline_file)
|
|
242
|
-
env = load_dotenv(dotenv_path=dotenv_path)
|
|
243
|
-
|
|
244
|
-
parser = argparse.ArgumentParser(description="Zipline CLI.")
|
|
245
|
-
parser.add_argument("files", metavar="Files", type=str, nargs="*", help="Files to Upload.")
|
|
246
|
-
parser.add_argument("-s", "--setup", action="store_true", default=False, help="run the interactive setup")
|
|
247
|
-
parser.add_argument("-i", "--info", action="store_true", help="show application information")
|
|
248
|
-
parser.add_argument("-V", "--version", action="store_true", help="show the installed version")
|
|
249
|
-
parser.add_argument("-u", "--url", type=str, default=get_default(["url"]), help="Zipline URL")
|
|
250
|
-
parser.add_argument(
|
|
251
|
-
"-a",
|
|
252
|
-
"-t",
|
|
253
|
-
"--authorization",
|
|
254
|
-
"--token",
|
|
255
|
-
type=str,
|
|
256
|
-
default=get_default(["token", "authorization"]),
|
|
257
|
-
help="Zipline Access Token for Authorization or ZIPLINE_TOKEN",
|
|
258
|
-
)
|
|
259
|
-
parser.add_argument(
|
|
260
|
-
"-e",
|
|
261
|
-
"-x",
|
|
262
|
-
"--expires_at",
|
|
263
|
-
"--expire",
|
|
264
|
-
type=str,
|
|
265
|
-
default=get_default(["expire", "expire_at"]),
|
|
266
|
-
help="Ex: 1d, 2w. See: https://zipline.diced.sh/docs/guides/upload-options#image-expiration",
|
|
267
|
-
)
|
|
268
|
-
parser.add_argument(
|
|
269
|
-
"--embed", action="store_true", default=get_default(["embed"], False, bool), help="Enable Embeds on Uploads."
|
|
270
|
-
)
|
|
271
|
-
args = parser.parse_args()
|
|
272
|
-
|
|
273
|
-
if args.version:
|
|
274
|
-
print(version("zipline-cli"))
|
|
275
|
-
return
|
|
276
|
-
|
|
277
|
-
if args.info:
|
|
278
|
-
print(f"Zipline Version: {version('zipline-cli')}")
|
|
279
|
-
print(f"Config File: {env_file.absolute()}")
|
|
280
|
-
print(f"Server URL: {config('ZIPLINE_URL', '')}")
|
|
281
|
-
print(f"Token (ends in): {config('ZIPLINE_TOKEN', '')[-10:]}")
|
|
282
|
-
print(f"Expire: {config('ZIPLINE_EXPIRE', '')}")
|
|
283
|
-
print(f"Embed: {config('ZIPLINE_EMBED', '')}")
|
|
284
|
-
zipline_format = config("ZIPLINE_FORMAT", "{url}\n{raw_url}")
|
|
285
|
-
print(f"URL Format::\n{zipline_format}")
|
|
286
|
-
return
|
|
287
|
-
|
|
288
|
-
if args.setup:
|
|
289
|
-
setup(env_file)
|
|
290
|
-
|
|
291
|
-
if not env and not args.url and not args.authorization and not os.path.isfile(env_file):
|
|
292
|
-
env_file.touch()
|
|
293
|
-
print("First Run Detected, Entering Setup.")
|
|
294
|
-
setup(env_file)
|
|
295
|
-
|
|
296
|
-
if not args.url:
|
|
297
|
-
parser.print_help()
|
|
298
|
-
raise ValueError("Missing URL. Use --setup or specify --url")
|
|
299
|
-
|
|
300
|
-
if not args.authorization:
|
|
301
|
-
parser.print_help()
|
|
302
|
-
raise ValueError("Missing Token. Use --setup or specify --token")
|
|
303
|
-
|
|
304
|
-
if args.expires_at:
|
|
305
|
-
args.expires_at = args.expires_at.strip().lower()
|
|
306
|
-
match = re.search(r"^(\d+)(?:ms|s|m|h|d|w|y)$", args.expires_at)
|
|
307
|
-
if not match:
|
|
308
|
-
parser.print_help()
|
|
309
|
-
raise ValueError(f"Invalid Expire Format: {args.expires_at}.")
|
|
310
|
-
|
|
311
|
-
zipline = Zipline(**vars(args))
|
|
312
|
-
|
|
313
|
-
if not args.files:
|
|
314
|
-
content: str = sys.stdin.read().rstrip("\n") + "\n"
|
|
315
|
-
text_f: TextIO = io.StringIO(content)
|
|
316
|
-
name = f"{gen_rand(8)}.txt"
|
|
317
|
-
url: ZipURL = zipline.send_file(name, text_f)
|
|
318
|
-
print(format_output(name, url))
|
|
319
|
-
sys.exit(0)
|
|
320
|
-
|
|
321
|
-
exit_code = 1
|
|
322
|
-
for name in args.files:
|
|
323
|
-
if not os.path.isfile(name):
|
|
324
|
-
print(f"Warning: File Not Found: {name}")
|
|
325
|
-
continue
|
|
326
|
-
# mode: Literal["r", "rb"] = get_mode(name)
|
|
327
|
-
with open(name, "rb") as f:
|
|
328
|
-
# name, ext = os.path.splitext(os.path.basename(filename))
|
|
329
|
-
# ext = f'.{ext}' if ext else ''
|
|
330
|
-
# name = f'{name}-{gen_rand(8)}{ext}'
|
|
331
|
-
# url: str = zipline.send_file(name, f)
|
|
332
|
-
zip_url: ZipURL = zipline.send_file(name, f)
|
|
333
|
-
print(format_output(name, zip_url))
|
|
334
|
-
exit_code = 0
|
|
335
|
-
sys.exit(exit_code)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
def main() -> None:
|
|
339
|
-
try:
|
|
340
|
-
run()
|
|
341
|
-
except KeyboardInterrupt:
|
|
342
|
-
sys.exit(1)
|
|
343
|
-
except Exception as error:
|
|
344
|
-
print("\nError: {}".format(str(error)))
|
|
345
|
-
sys.exit(1)
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if __name__ == "__main__":
|
|
349
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|