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.
@@ -1,31 +1,31 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zipline-cli
3
- Version: 0.2.2
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: Discord, https://discord.gg/wXy6m2X8wY
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
  [![PyPI Version](https://img.shields.io/pypi/v/zipline-cli?logo=pypi&logoColor=white&label=pypi)](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://raw.githubusercontent.com/smashedr/repo-images/refs/heads/master/zipline/cli/logo128.png"></a>
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
- | `ZIPLINE_EMBED` | Set this enable Embed on your uploads |
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://raw.githubusercontent.com/smashedr/repo-images/refs/heads/master/zipline/cli/logo128.png"></a>
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
- | `ZIPLINE_EMBED` | Set this enable Embed on your uploads |
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
- "Programming Language :: Python",
12
- "Programming Language :: Python :: 3",
13
- "Programming Language :: Python :: 3 :: Only",
14
- "Operating System :: OS Independent",
15
- "License :: OSI Approved :: MIT License",
16
- "Natural Language :: English",
17
- "Typing :: Typed",
18
- "Topic :: Communications :: File Sharing",
19
- "Topic :: Terminals",
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
- "python-decouple",
26
- "python-dotenv",
27
- "requests",
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
- [project.scripts]
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
- Discord = "https://discord.gg/wXy6m2X8wY"
60
-
61
- # Setup Tools
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
- [tool.setuptools]
70
- py-modules = ["zipline"]
71
- include-package-data = true
38
+ [project.scripts]
39
+ zipline = "zipline.cli:app"
72
40
 
73
- [tool.setuptools.packages.find]
74
- where = ["src"]
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,10 @@
1
+ """
2
+ ██████ ▄▄ ▄▄▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄▄▄▄ ▄█████ ██ ██
3
+ ▄▄▀▀ ██ ██▄█▀ ██ ██ ███▄██ ██▄▄ ██ ██ ██
4
+ ██████ ██ ██ ██▄▄▄ ██ ██ ▀██ ██▄▄▄ ▀█████ ██████ ██
5
+ """
6
+
7
+ from .zipline import Zipline
8
+
9
+
10
+ __all__ = ["Zipline"]
@@ -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", "0.0.1")
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.2.2
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: Discord, https://discord.gg/wXy6m2X8wY
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
  [![PyPI Version](https://img.shields.io/pypi/v/zipline-cli?logo=pypi&logoColor=white&label=pypi)](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://raw.githubusercontent.com/smashedr/repo-images/refs/heads/master/zipline/cli/logo128.png"></a>
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
- | `ZIPLINE_EMBED` | Set this enable Embed on your uploads |
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
 
@@ -3,6 +3,9 @@ MANIFEST.in
3
3
  README.md
4
4
  pyproject.toml
5
5
  src/zipline/__init__.py
6
+ src/zipline/_utils.py
7
+ src/zipline/_version.py
8
+ src/zipline/cli.py
6
9
  src/zipline/py.typed
7
10
  src/zipline/zipline.py
8
11
  src/zipline_cli.egg-info/PKG-INFO
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ zipline = zipline.cli:app
@@ -0,0 +1,3 @@
1
+ python-dotenv
2
+ requests<3,>=2
3
+ typer
@@ -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()
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- zipline = zipline.zipline:main
@@ -1,3 +0,0 @@
1
- python-decouple
2
- python-dotenv
3
- requests
File without changes
File without changes
File without changes