tempspace-cli 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tempspace-cli might be problematic. Click here for more details.
cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
cli/tempspace.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import requests
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import hashlib
|
|
6
|
+
import multiprocessing
|
|
7
|
+
from requests_toolbelt.multipart.encoder import MultipartEncoder, MultipartEncoderMonitor
|
|
8
|
+
import math
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
from rich import box
|
|
13
|
+
import qrcode
|
|
14
|
+
from rich.prompt import Prompt, Confirm
|
|
15
|
+
from rich.progress import Progress, BarColumn, TextColumn, TransferSpeedColumn, TimeRemainingColumn
|
|
16
|
+
from rich.console import Group
|
|
17
|
+
from rich.live import Live
|
|
18
|
+
|
|
19
|
+
# Default configuration
|
|
20
|
+
DEFAULT_SERVER_URL = "https://tempspace.fly.dev/"
|
|
21
|
+
CHUNK_SIZE = 1024 * 1024 # 1MB
|
|
22
|
+
|
|
23
|
+
def parse_time(time_str: str) -> int:
|
|
24
|
+
"""
|
|
25
|
+
Parse a time string (e.g., '7d', '24h', '360') into an integer number of hours.
|
|
26
|
+
Returns the number of hours as an integer, or None if parsing fails.
|
|
27
|
+
"""
|
|
28
|
+
time_str = time_str.lower().strip()
|
|
29
|
+
if time_str.endswith('d'):
|
|
30
|
+
try:
|
|
31
|
+
days = int(time_str[:-1])
|
|
32
|
+
return days * 24
|
|
33
|
+
except ValueError:
|
|
34
|
+
return None
|
|
35
|
+
elif time_str.endswith('h'):
|
|
36
|
+
try:
|
|
37
|
+
return int(time_str[:-1])
|
|
38
|
+
except ValueError:
|
|
39
|
+
return None
|
|
40
|
+
else:
|
|
41
|
+
try:
|
|
42
|
+
return int(time_str)
|
|
43
|
+
except ValueError:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
def format_size(size_bytes: int) -> str:
|
|
47
|
+
"""Converts a size in bytes to a human-readable format."""
|
|
48
|
+
if size_bytes == 0:
|
|
49
|
+
return "0B"
|
|
50
|
+
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
|
51
|
+
i = int(math.floor(math.log(size_bytes, 1024)))
|
|
52
|
+
p = math.pow(1024, i)
|
|
53
|
+
s = round(size_bytes / p, 2)
|
|
54
|
+
return f"{s} {size_name[i]}"
|
|
55
|
+
|
|
56
|
+
def main():
|
|
57
|
+
"""Main function to handle argument parsing and file upload."""
|
|
58
|
+
console = Console()
|
|
59
|
+
|
|
60
|
+
# --- Header ---
|
|
61
|
+
console.print(Panel("[bold cyan]Tempspace File Uploader[/bold cyan]", expand=False, border_style="blue"))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
parser = argparse.ArgumentParser(
|
|
65
|
+
description="Upload a file to Tempspace.",
|
|
66
|
+
formatter_class=argparse.RawTextHelpFormatter
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
parser.add_argument("filepath", nargs='?', default=None, help="The path to the file you want to upload.")
|
|
70
|
+
parser.add_argument("-t", "--time", type=str, default='24', help="Set the file's expiration time. Examples: '24h', '7d', '360' (hours).\nDefault: '24' (24 hours).")
|
|
71
|
+
parser.add_argument("-p", "--password", type=str, help="Protect the file with a password.")
|
|
72
|
+
parser.add_argument("--one-time", action="store_true", help="The file will be deleted after the first download.")
|
|
73
|
+
parser.add_argument("--url", type=str, default=os.environ.get("TEMPSPACE_URL", DEFAULT_SERVER_URL), help=f"The URL of the Tempspace server.\nCan also be set with the TEMPSPACE_URL environment variable.\nDefault: {DEFAULT_SERVER_URL}")
|
|
74
|
+
parser.add_argument("--qr", action="store_true", help="Display a QR code of the download link.")
|
|
75
|
+
parser.add_argument("--it", action="store_true", help="Enable interactive mode.")
|
|
76
|
+
|
|
77
|
+
args = parser.parse_args()
|
|
78
|
+
|
|
79
|
+
# --- Interactive Mode ---
|
|
80
|
+
if args.it:
|
|
81
|
+
args.filepath = Prompt.ask("Enter the path to the file you want to upload")
|
|
82
|
+
args.time = Prompt.ask("Set the file's expiration time (e.g., '24h', '7d')", default='24')
|
|
83
|
+
args.password = Prompt.ask("Protect the file with a password?", default=None, password=True)
|
|
84
|
+
args.one_time = Confirm.ask("Delete the file after the first download?", default=False)
|
|
85
|
+
args.qr = Confirm.ask("Display a QR code of the download link?", default=False)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# --- Validate Inputs ---
|
|
89
|
+
if not args.filepath or not os.path.isfile(args.filepath):
|
|
90
|
+
console.print(Panel(f"[bold red]Error:[/] File not found at '{args.filepath}'", title="[bold red]Error[/bold red]", border_style="red"))
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
hours = parse_time(args.time)
|
|
94
|
+
if hours is None:
|
|
95
|
+
console.print(Panel(f"[bold red]Error:[/] Invalid time format '{args.time}'. Use formats like '24h', '7d', or '360'.", title="[bold red]Error[/bold red]", border_style="red"))
|
|
96
|
+
sys.exit(1)
|
|
97
|
+
|
|
98
|
+
# --- Display File Details ---
|
|
99
|
+
table = Table(title="File Details", show_header=False, box=box.ROUNDED, border_style="cyan")
|
|
100
|
+
table.add_column("Field", style="bold")
|
|
101
|
+
table.add_column("Value")
|
|
102
|
+
table.add_row("File Name", os.path.basename(args.filepath))
|
|
103
|
+
table.add_row("File Size", format_size(os.path.getsize(args.filepath)))
|
|
104
|
+
table.add_row("Expiration", f"{hours} hours")
|
|
105
|
+
table.add_row("Password", "[green]Yes[/green]" if args.password else "[red]No[/red]")
|
|
106
|
+
table.add_row("One-Time Download", "[green]Yes[/green]" if args.one_time else "[red]No[/red]")
|
|
107
|
+
console.print(table)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# --- Prepare Upload ---
|
|
111
|
+
upload_url = f"{args.url.rstrip('/')}"
|
|
112
|
+
filename = os.path.basename(args.filepath)
|
|
113
|
+
file_size = os.path.getsize(args.filepath)
|
|
114
|
+
|
|
115
|
+
# --- Chunked Upload ---
|
|
116
|
+
response = None
|
|
117
|
+
try:
|
|
118
|
+
# 1. Initiate Upload
|
|
119
|
+
initiate_response = requests.post(f"{upload_url}/upload/initiate")
|
|
120
|
+
initiate_response.raise_for_status()
|
|
121
|
+
upload_id = initiate_response.json()['upload_id']
|
|
122
|
+
|
|
123
|
+
# 2. Upload Chunks
|
|
124
|
+
progress = Progress(
|
|
125
|
+
TextColumn("[bold blue]{task.description}", justify="right"),
|
|
126
|
+
BarColumn(bar_width=None),
|
|
127
|
+
"[progress.percentage]{task.percentage:>3.1f}%", "•",
|
|
128
|
+
TransferSpeedColumn(), "•",
|
|
129
|
+
TimeRemainingColumn(),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
with Live(Panel(progress, title="[cyan]Uploading[/cyan]", border_style="cyan", title_align="left")) as live:
|
|
133
|
+
task_id = progress.add_task(filename, total=file_size)
|
|
134
|
+
with open(args.filepath, 'rb') as f:
|
|
135
|
+
chunk_number = 0
|
|
136
|
+
while chunk := f.read(CHUNK_SIZE):
|
|
137
|
+
chunk_number += 1
|
|
138
|
+
chunk_data = {
|
|
139
|
+
'upload_id': upload_id,
|
|
140
|
+
'chunk_number': str(chunk_number)
|
|
141
|
+
}
|
|
142
|
+
files = {'file': (f'chunk_{chunk_number}', chunk, 'application/octet-stream')}
|
|
143
|
+
|
|
144
|
+
chunk_response = requests.post(
|
|
145
|
+
f"{upload_url}/upload/chunk",
|
|
146
|
+
data=chunk_data,
|
|
147
|
+
files=files
|
|
148
|
+
)
|
|
149
|
+
chunk_response.raise_for_status()
|
|
150
|
+
progress.update(task_id, advance=len(chunk))
|
|
151
|
+
|
|
152
|
+
# 3. Finalize Upload
|
|
153
|
+
console.print(Panel("[bold green]Finalizing upload...[/bold green]", border_style="green"))
|
|
154
|
+
finalize_data = {
|
|
155
|
+
'upload_id': upload_id,
|
|
156
|
+
'filename': filename,
|
|
157
|
+
'hours': str(hours),
|
|
158
|
+
'one_time': str(args.one_time).lower(),
|
|
159
|
+
}
|
|
160
|
+
if args.password:
|
|
161
|
+
finalize_data['password'] = args.password
|
|
162
|
+
|
|
163
|
+
response = requests.post(f"{upload_url}/upload/finalize", data=finalize_data)
|
|
164
|
+
response.raise_for_status()
|
|
165
|
+
|
|
166
|
+
except FileNotFoundError:
|
|
167
|
+
console.print(Panel(f"[bold red]Error:[/] The file '{args.filepath}' was not found.", title="[bold red]Error[/bold red]", border_style="red"))
|
|
168
|
+
sys.exit(1)
|
|
169
|
+
except requests.exceptions.RequestException as e:
|
|
170
|
+
error_message = str(e)
|
|
171
|
+
if e.response:
|
|
172
|
+
try:
|
|
173
|
+
error_message = e.response.json().get('detail', e.response.text)
|
|
174
|
+
except:
|
|
175
|
+
error_message = e.response.text
|
|
176
|
+
console.print(Panel(f"[bold red]An error occurred:[/] {error_message}", title="[bold red]Error[/bold red]", border_style="red"))
|
|
177
|
+
sys.exit(1)
|
|
178
|
+
except Exception as e:
|
|
179
|
+
console.print(Panel(f"[bold red]An unexpected error occurred:[/] {e}", title="[bold red]Error[/bold red]", border_style="red"))
|
|
180
|
+
sys.exit(1)
|
|
181
|
+
|
|
182
|
+
# --- Handle Response ---
|
|
183
|
+
if response is not None:
|
|
184
|
+
if response.status_code == 200:
|
|
185
|
+
download_link = response.text.strip()
|
|
186
|
+
success_panel = Panel(f"[bold green]Upload successful![/bold green]\n\nDownload Link: {download_link}",
|
|
187
|
+
title="[bold cyan]Success[/bold cyan]", border_style="green")
|
|
188
|
+
console.print(success_panel)
|
|
189
|
+
|
|
190
|
+
if args.qr:
|
|
191
|
+
qr = qrcode.QRCode()
|
|
192
|
+
qr.add_data(download_link)
|
|
193
|
+
qr.make(fit=True)
|
|
194
|
+
qr.print_ascii()
|
|
195
|
+
else:
|
|
196
|
+
try:
|
|
197
|
+
error_details = response.json()
|
|
198
|
+
error_message = error_details.get('detail', 'No details provided.')
|
|
199
|
+
except requests.exceptions.JSONDecodeError:
|
|
200
|
+
error_message = response.text
|
|
201
|
+
console.print(Panel(f"[bold red]Error:[/] Upload failed with status code {response.status_code}\n[red]Server message:[/] {error_message}", title="[bold red]Error[/bold red]", border_style="red"))
|
|
202
|
+
sys.exit(1)
|
|
203
|
+
|
|
204
|
+
if __name__ == "__main__":
|
|
205
|
+
multiprocessing.freeze_support()
|
|
206
|
+
main()
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tempspace-cli
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: A command-line tool for uploading files to Tempspace.
|
|
5
|
+
Author-email: Tempspace <mcbplay1@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: requests==2.31.0
|
|
12
|
+
Requires-Dist: requests-toolbelt==1.0.0
|
|
13
|
+
Requires-Dist: rich==13.7.1
|
|
14
|
+
Requires-Dist: qrcode==8.2
|
|
15
|
+
|
|
16
|
+
# 🚀 Tempspace
|
|
17
|
+
|
|
18
|
+
Tempspace is a terminal-style file sharing service that allows you to upload files and share them via a link, with features like password protection, one-time downloads, and automatic expiration.
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
## ✨ Features
|
|
23
|
+
|
|
24
|
+
- **Multiple Upload Methods:** Use the web interface, cURL, or the official CLI tool.
|
|
25
|
+
- **Security:** Optional password protection, one-time downloads, and rate limiting.
|
|
26
|
+
- **Modern UI:** A terminal-inspired design with QR code generation and upload history.
|
|
27
|
+
- **File Management:** Support for large files, multiple expiry options, and automatic cleanup.
|
|
28
|
+
|
|
29
|
+
## 🚀 Getting Started
|
|
30
|
+
|
|
31
|
+
### Method 1: Using the Web Interface
|
|
32
|
+
|
|
33
|
+
The easiest way to use Tempspace is through the web interface.
|
|
34
|
+
|
|
35
|
+
1. **Open** the service URL (e.g., `http://localhost:8000`) in your browser.
|
|
36
|
+
2. **Drag and drop** a file or click to browse.
|
|
37
|
+
3. **Configure** the expiry time and an optional password.
|
|
38
|
+
4. **Upload** and share the generated link.
|
|
39
|
+
|
|
40
|
+
### Method 2: Using cURL
|
|
41
|
+
|
|
42
|
+
You can use `cURL` to upload files directly from your terminal.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Basic upload
|
|
46
|
+
curl -F "file=@/path/to/file.txt" -F "hours=6" https://tempspace.fly.dev/upload
|
|
47
|
+
|
|
48
|
+
# With a password
|
|
49
|
+
curl -F "file=@/path/to/secret.txt" -F "password=secret123" https://tempspace.fly.dev/upload
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Method 3: Using the Official CLI Tool
|
|
53
|
+
|
|
54
|
+
For a more integrated terminal experience, you can install the official CLI tool from PyPI.
|
|
55
|
+
|
|
56
|
+
#### Installation
|
|
57
|
+
```bash
|
|
58
|
+
pip install tempspace-cli
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### Usage
|
|
62
|
+
```bash
|
|
63
|
+
# Basic upload
|
|
64
|
+
tempspace /path/to/document.pdf -t 6h
|
|
65
|
+
|
|
66
|
+
# With a password
|
|
67
|
+
tempspace /path/to/secret.txt -p "mypassword"
|
|
68
|
+
|
|
69
|
+
# Point to a self-hosted server
|
|
70
|
+
tempspace /path/to/file.txt --url http://localhost:8000
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 🔧 Self-Hosting
|
|
74
|
+
|
|
75
|
+
You can run your own instance of Tempspace.
|
|
76
|
+
|
|
77
|
+
### Installation
|
|
78
|
+
```bash
|
|
79
|
+
# Clone the repository
|
|
80
|
+
git clone https://github.com/your-repo/tempspace.git
|
|
81
|
+
cd tempspace
|
|
82
|
+
|
|
83
|
+
# Install dependencies
|
|
84
|
+
pip install -r requirements.txt
|
|
85
|
+
|
|
86
|
+
# Configure
|
|
87
|
+
cp .env.example .env
|
|
88
|
+
nano .env # Change the admin password
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Running the Server
|
|
92
|
+
```bash
|
|
93
|
+
python main.py
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The server will be available at `http://localhost:8000`.
|
|
97
|
+
|
|
98
|
+
## 🛡️ Admin & Configuration
|
|
99
|
+
|
|
100
|
+
For advanced configuration, such as setting up a reverse proxy, managing admin endpoints, and more, please refer to the project's documentation or open an issue.
|
|
101
|
+
|
|
102
|
+
## 🤝 Contributing
|
|
103
|
+
|
|
104
|
+
Contributions are welcome! Please fork the repository and submit a pull request.
|
|
105
|
+
|
|
106
|
+
## 📝 License
|
|
107
|
+
|
|
108
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
cli/__init__.py,sha256=J-j-u0itpEFT6irdmWmixQqYMadNl1X91TxUmoiLHMI,22
|
|
2
|
+
cli/tempspace.py,sha256=iswFGSxqkYR2qvujJjm0wm8UUxBM89r9jKOgbqbbJnc,8821
|
|
3
|
+
tempspace_cli-1.2.0.dist-info/METADATA,sha256=0HY-QH_JkX8YyCNYicgvtwo9wPPSUlngIDQgqrhyfDE,3002
|
|
4
|
+
tempspace_cli-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
+
tempspace_cli-1.2.0.dist-info/entry_points.txt,sha256=DBBYsRS-fK4MSA3-4lrSPyi6oyHxoJLCII7c793nPtw,49
|
|
6
|
+
tempspace_cli-1.2.0.dist-info/top_level.txt,sha256=2ImG917oaVHlm0nP9oJE-Qrgs-fq_fGWgba2H1f8fpE,4
|
|
7
|
+
tempspace_cli-1.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cli
|