tempspace-cli 1.2.2__tar.gz → 1.2.4__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.
@@ -0,0 +1,189 @@
1
+ Metadata-Version: 2.4
2
+ Name: tempspace-cli
3
+ Version: 1.2.4
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.32.5
12
+ Requires-Dist: requests-toolbelt==1.0.0
13
+ Requires-Dist: rich==14.2.0
14
+ Requires-Dist: qrcode==8.2
15
+
16
+ # 🚀 Tempspace
17
+
18
+ Tempspace is a self-hostable, terminal-themed file sharing service. It allows you to quickly upload files and share them via a link, with features like password protection, one-time downloads, and automatic expiration.
19
+
20
+ ![Screenshot of Tempspace UI](https://github.com/Bartixxx32/tempspace/blob/main/tempspace.jpeg?raw=true)
21
+
22
+ ## ✨ Core Features
23
+
24
+ - **Seamless Uploading:** Drag-and-drop web interface, cURL, or a dedicated CLI tool.
25
+ - **Chunked Uploads:** Robust support for large files (default 4GB, configurable) via chunking.
26
+ - **Security Focused:**
27
+ - **Password Protection:** Secure individual files with a password.
28
+ - **One-Time Downloads:** Links can be configured to expire after the first download.
29
+ - **Rate Limiting:** Built-in protection against abuse.
30
+ - **Efficient Storage:** Files with identical content are de-duplicated to save space.
31
+ - **Flexible Expiration:** Set custom expiration times from hours to days.
32
+ - **Modern UI:**
33
+ - **Terminal-Inspired Design:** A clean, developer-focused interface.
34
+ - **QR Code Generation:** Instantly generate QR codes for mobile sharing.
35
+ - **Local Upload History:** Your browser remembers your recent uploads.
36
+ - **Automatic Cleanup:** The server periodically cleans up expired files automatically.
37
+
38
+ ## 🚀 Getting Started
39
+
40
+ There are three primary ways to use Tempspace.
41
+
42
+ ### 1. Web Interface (The Easy Way)
43
+
44
+ 1. Open the service URL (e.g., `https://tempspace.fly.dev/` or your self-hosted instance) in your browser.
45
+ 2. Drag and drop your file(s) or click to browse.
46
+ 3. Configure options like expiration time, password, or one-time download.
47
+ 4. Click "Upload" and share the generated link.
48
+
49
+ ### 2. cURL (From the Terminal)
50
+
51
+ Use `cURL` to upload files directly from your command line.
52
+
53
+ ```bash
54
+ # Basic upload (expires in 24 hours)
55
+ curl -F "file=@/path/to/your/file.txt" https://tempspace.fly.dev/upload
56
+
57
+ # Set a custom expiration time (e.g., 7 days) and password
58
+ curl -F "file=@/path/to/secret.txt" \
59
+ -F "hours=168" \
60
+ -F "password=secret123" \
61
+ https://tempspace.fly.dev/upload
62
+
63
+ # Create a one-time download link
64
+ curl -F "file=@/path/to/onetime.zip" \
65
+ -F "one_time=true" \
66
+ https://tempspace.fly.dev/upload
67
+ ```
68
+
69
+ ### 3. Official CLI Tool
70
+
71
+ For the best terminal experience, install the official CLI tool from PyPI.
72
+
73
+ #### Installation
74
+
75
+ ```bash
76
+ pip install tempspace-cli
77
+ ```
78
+
79
+ #### Usage
80
+
81
+ ```bash
82
+ # Interactive mode (prompts for all options)
83
+ tempspace --it
84
+
85
+ # Basic upload (expires in 24 hours)
86
+ tempspace /path/to/document.pdf
87
+
88
+ # Set expiration to 7 days and add a password
89
+ tempspace /path/to/archive.zip -t 7d -p "secure_password"
90
+
91
+ # Display a QR code in the terminal after upload
92
+ tempspace /path/to/image.png --qr
93
+
94
+ # Point to a self-hosted server
95
+ tempspace /path/to/file.txt --url http://localhost:8000
96
+ ```
97
+
98
+ ## 🔧 Self-Hosting
99
+
100
+ You can easily run your own instance of Tempspace using Docker or by running the source code directly.
101
+
102
+ ### Method 1: Using Docker (Recommended)
103
+
104
+ 1. **Clone the repository:**
105
+ ```bash
106
+ git clone https://github.com/Bartixxx32/tempspace.git
107
+ cd tempspace
108
+ ```
109
+
110
+ 2. **Build the Docker image:**
111
+ ```bash
112
+ docker build -t tempspace-app .
113
+ ```
114
+
115
+ 3. **Run the container:**
116
+ Create a `.env` file to configure your admin password.
117
+ ```bash
118
+ cp .env.example .env
119
+ nano .env # Set your ADMIN_PASS
120
+ ```
121
+ Then, run the container, mounting the environment file and a volume for persistent storage.
122
+ ```bash
123
+ docker run -d \
124
+ -p 8000:8000 \
125
+ --name tempspace \
126
+ --env-file .env \
127
+ -v $(pwd)/uploads:/app/uploads \
128
+ tempspace-app
129
+ ```
130
+ Your Tempspace instance will be available at `http://localhost:8000`.
131
+
132
+ ### Method 2: Running from Source
133
+
134
+ 1. **Clone the repository:**
135
+ ```bash
136
+ git clone https://github.com/Bartixxx32/tempspace.git
137
+ cd tempspace
138
+ ```
139
+
140
+ 2. **Install dependencies:**
141
+ It's recommended to use a virtual environment.
142
+ ```bash
143
+ python -m venv venv
144
+ source venv/bin/activate # On Windows, use `venv\Scripts\activate`
145
+ pip install -r requirements.txt
146
+ ```
147
+
148
+ 3. **Configure:**
149
+ Copy the example environment file and set your admin password.
150
+ ```bash
151
+ cp .env.example .env
152
+ nano .env
153
+ ```
154
+
155
+ 4. **Run the server:**
156
+ ```bash
157
+ python -m uvicorn main:app --host 0.0.0.0 --port 8000
158
+ ```
159
+
160
+ ## ⚙️ Configuration (Environment Variables)
161
+
162
+ Configure your Tempspace instance by creating a `.env` file in the project root.
163
+
164
+ | Variable | Description | Default |
165
+ | -------------------- | ------------------------------------------------------------------------ | ------------------------ |
166
+ | `ADMIN_USER` | Username for accessing debug/admin endpoints. | `admin` |
167
+ | `ADMIN_PASS` | **Required.** Password for the admin user. The app will not start without this. | `changeme123` |
168
+ | `MAX_FILE_SIZE` | Maximum file size in bytes. | `4294967296` (4GB) |
169
+ | `RATE_LIMIT_UPLOADS` | Max uploads per IP per hour. | `10` |
170
+ | `RATE_LIMIT_DOWNLOADS` | Max downloads per IP per hour. | `100` |
171
+ | `RATE_LIMIT_WINDOW` | The rate limit time window in seconds. | `3600` (1 hour) |
172
+
173
+ ## 🛠️ API & Admin Endpoints
174
+
175
+ - `/upload`: The primary endpoint for `curl` and simple POST request uploads.
176
+ - `/upload/initiate`, `/upload/chunk`, `/upload/finalize`: Endpoints for the chunked upload flow used by the web UI and CLI.
177
+ - `/health`: A public health check endpoint.
178
+ - `/debug/stats`: (Admin-only) View detailed statistics of all stored files.
179
+ - `/debug/wipe`: (Admin-only) **Destructive.** Deletes all files and metadata.
180
+
181
+ Access admin endpoints by providing the admin credentials via HTTP Basic Authentication.
182
+
183
+ ## 🤝 Contributing
184
+
185
+ Contributions are welcome! Please fork the repository, make your changes, and submit a pull request.
186
+
187
+ ## 📝 License
188
+
189
+ This project is licensed under the MIT License.
@@ -0,0 +1,174 @@
1
+ # 🚀 Tempspace
2
+
3
+ Tempspace is a self-hostable, terminal-themed file sharing service. It allows you to quickly upload files and share them via a link, with features like password protection, one-time downloads, and automatic expiration.
4
+
5
+ ![Screenshot of Tempspace UI](https://github.com/Bartixxx32/tempspace/blob/main/tempspace.jpeg?raw=true)
6
+
7
+ ## ✨ Core Features
8
+
9
+ - **Seamless Uploading:** Drag-and-drop web interface, cURL, or a dedicated CLI tool.
10
+ - **Chunked Uploads:** Robust support for large files (default 4GB, configurable) via chunking.
11
+ - **Security Focused:**
12
+ - **Password Protection:** Secure individual files with a password.
13
+ - **One-Time Downloads:** Links can be configured to expire after the first download.
14
+ - **Rate Limiting:** Built-in protection against abuse.
15
+ - **Efficient Storage:** Files with identical content are de-duplicated to save space.
16
+ - **Flexible Expiration:** Set custom expiration times from hours to days.
17
+ - **Modern UI:**
18
+ - **Terminal-Inspired Design:** A clean, developer-focused interface.
19
+ - **QR Code Generation:** Instantly generate QR codes for mobile sharing.
20
+ - **Local Upload History:** Your browser remembers your recent uploads.
21
+ - **Automatic Cleanup:** The server periodically cleans up expired files automatically.
22
+
23
+ ## 🚀 Getting Started
24
+
25
+ There are three primary ways to use Tempspace.
26
+
27
+ ### 1. Web Interface (The Easy Way)
28
+
29
+ 1. Open the service URL (e.g., `https://tempspace.fly.dev/` or your self-hosted instance) in your browser.
30
+ 2. Drag and drop your file(s) or click to browse.
31
+ 3. Configure options like expiration time, password, or one-time download.
32
+ 4. Click "Upload" and share the generated link.
33
+
34
+ ### 2. cURL (From the Terminal)
35
+
36
+ Use `cURL` to upload files directly from your command line.
37
+
38
+ ```bash
39
+ # Basic upload (expires in 24 hours)
40
+ curl -F "file=@/path/to/your/file.txt" https://tempspace.fly.dev/upload
41
+
42
+ # Set a custom expiration time (e.g., 7 days) and password
43
+ curl -F "file=@/path/to/secret.txt" \
44
+ -F "hours=168" \
45
+ -F "password=secret123" \
46
+ https://tempspace.fly.dev/upload
47
+
48
+ # Create a one-time download link
49
+ curl -F "file=@/path/to/onetime.zip" \
50
+ -F "one_time=true" \
51
+ https://tempspace.fly.dev/upload
52
+ ```
53
+
54
+ ### 3. Official CLI Tool
55
+
56
+ For the best terminal experience, install the official CLI tool from PyPI.
57
+
58
+ #### Installation
59
+
60
+ ```bash
61
+ pip install tempspace-cli
62
+ ```
63
+
64
+ #### Usage
65
+
66
+ ```bash
67
+ # Interactive mode (prompts for all options)
68
+ tempspace --it
69
+
70
+ # Basic upload (expires in 24 hours)
71
+ tempspace /path/to/document.pdf
72
+
73
+ # Set expiration to 7 days and add a password
74
+ tempspace /path/to/archive.zip -t 7d -p "secure_password"
75
+
76
+ # Display a QR code in the terminal after upload
77
+ tempspace /path/to/image.png --qr
78
+
79
+ # Point to a self-hosted server
80
+ tempspace /path/to/file.txt --url http://localhost:8000
81
+ ```
82
+
83
+ ## 🔧 Self-Hosting
84
+
85
+ You can easily run your own instance of Tempspace using Docker or by running the source code directly.
86
+
87
+ ### Method 1: Using Docker (Recommended)
88
+
89
+ 1. **Clone the repository:**
90
+ ```bash
91
+ git clone https://github.com/Bartixxx32/tempspace.git
92
+ cd tempspace
93
+ ```
94
+
95
+ 2. **Build the Docker image:**
96
+ ```bash
97
+ docker build -t tempspace-app .
98
+ ```
99
+
100
+ 3. **Run the container:**
101
+ Create a `.env` file to configure your admin password.
102
+ ```bash
103
+ cp .env.example .env
104
+ nano .env # Set your ADMIN_PASS
105
+ ```
106
+ Then, run the container, mounting the environment file and a volume for persistent storage.
107
+ ```bash
108
+ docker run -d \
109
+ -p 8000:8000 \
110
+ --name tempspace \
111
+ --env-file .env \
112
+ -v $(pwd)/uploads:/app/uploads \
113
+ tempspace-app
114
+ ```
115
+ Your Tempspace instance will be available at `http://localhost:8000`.
116
+
117
+ ### Method 2: Running from Source
118
+
119
+ 1. **Clone the repository:**
120
+ ```bash
121
+ git clone https://github.com/Bartixxx32/tempspace.git
122
+ cd tempspace
123
+ ```
124
+
125
+ 2. **Install dependencies:**
126
+ It's recommended to use a virtual environment.
127
+ ```bash
128
+ python -m venv venv
129
+ source venv/bin/activate # On Windows, use `venv\Scripts\activate`
130
+ pip install -r requirements.txt
131
+ ```
132
+
133
+ 3. **Configure:**
134
+ Copy the example environment file and set your admin password.
135
+ ```bash
136
+ cp .env.example .env
137
+ nano .env
138
+ ```
139
+
140
+ 4. **Run the server:**
141
+ ```bash
142
+ python -m uvicorn main:app --host 0.0.0.0 --port 8000
143
+ ```
144
+
145
+ ## ⚙️ Configuration (Environment Variables)
146
+
147
+ Configure your Tempspace instance by creating a `.env` file in the project root.
148
+
149
+ | Variable | Description | Default |
150
+ | -------------------- | ------------------------------------------------------------------------ | ------------------------ |
151
+ | `ADMIN_USER` | Username for accessing debug/admin endpoints. | `admin` |
152
+ | `ADMIN_PASS` | **Required.** Password for the admin user. The app will not start without this. | `changeme123` |
153
+ | `MAX_FILE_SIZE` | Maximum file size in bytes. | `4294967296` (4GB) |
154
+ | `RATE_LIMIT_UPLOADS` | Max uploads per IP per hour. | `10` |
155
+ | `RATE_LIMIT_DOWNLOADS` | Max downloads per IP per hour. | `100` |
156
+ | `RATE_LIMIT_WINDOW` | The rate limit time window in seconds. | `3600` (1 hour) |
157
+
158
+ ## 🛠️ API & Admin Endpoints
159
+
160
+ - `/upload`: The primary endpoint for `curl` and simple POST request uploads.
161
+ - `/upload/initiate`, `/upload/chunk`, `/upload/finalize`: Endpoints for the chunked upload flow used by the web UI and CLI.
162
+ - `/health`: A public health check endpoint.
163
+ - `/debug/stats`: (Admin-only) View detailed statistics of all stored files.
164
+ - `/debug/wipe`: (Admin-only) **Destructive.** Deletes all files and metadata.
165
+
166
+ Access admin endpoints by providing the admin credentials via HTTP Basic Authentication.
167
+
168
+ ## 🤝 Contributing
169
+
170
+ Contributions are welcome! Please fork the repository, make your changes, and submit a pull request.
171
+
172
+ ## 📝 License
173
+
174
+ This project is licensed under the MIT License.
@@ -21,9 +21,16 @@ DEFAULT_SERVER_URL = "https://tempspace.fly.dev/"
21
21
  CHUNK_SIZE = 1024 * 1024 # 1MB
22
22
 
23
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.
24
+ """Parses a user-provided time string into a total number of hours.
25
+
26
+ Supports strings ending in 'd' for days, 'h' for hours, or a plain
27
+ number for hours.
28
+
29
+ Args:
30
+ time_str: The time string to parse (e.g., '7d', '24h', '360').
31
+
32
+ Returns:
33
+ The total number of hours as an integer, or None if parsing fails.
27
34
  """
28
35
  time_str = time_str.lower().strip()
29
36
  if time_str.endswith('d'):
@@ -44,7 +51,14 @@ def parse_time(time_str: str) -> int:
44
51
  return None
45
52
 
46
53
  def format_size(size_bytes: int) -> str:
47
- """Converts a size in bytes to a human-readable format."""
54
+ """Converts a file size in bytes into a human-readable string.
55
+
56
+ Args:
57
+ size_bytes: The size of the file in bytes.
58
+
59
+ Returns:
60
+ A formatted string representing the size (e.g., "1.23 MB").
61
+ """
48
62
  if size_bytes == 0:
49
63
  return "0B"
50
64
  size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
@@ -53,64 +67,61 @@ def format_size(size_bytes: int) -> str:
53
67
  s = round(size_bytes / p, 2)
54
68
  return f"{s} {size_name[i]}"
55
69
 
56
- def main():
57
- """Main function to handle argument parsing and file upload."""
58
- console = Console()
59
70
 
60
- # --- Header ---
61
- console.print(Panel("[bold cyan]Tempspace File Uploader[/bold cyan]", expand=False, border_style="blue"))
71
+ def calculate_file_hash(filepath: str) -> str:
72
+ """Calculates the SHA256 hash of a file.
62
73
 
74
+ Args:
75
+ filepath: The path to the file.
63
76
 
64
- parser = argparse.ArgumentParser(
65
- description="Upload a file to Tempspace.",
66
- formatter_class=argparse.RawTextHelpFormatter
67
- )
77
+ Returns:
78
+ The hex digest of the SHA256 hash.
79
+ """
80
+ sha256_hash = hashlib.sha256()
81
+ with open(filepath, "rb") as f:
82
+ # Read and update hash in chunks of 4K
83
+ for byte_block in iter(lambda: f.read(4096), b""):
84
+ sha256_hash.update(byte_block)
85
+ return sha256_hash.hexdigest()
68
86
 
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
+ def upload_file(console, filepath, hours, password, one_time, qr, url):
89
+ """Handles the upload of a single file.
87
90
 
91
+ Args:
92
+ console: The Rich console object for output.
93
+ filepath (str): The path to the file to upload.
94
+ hours (int): The number of hours until the file expires.
95
+ password (str): The password for the file.
96
+ one_time (bool): Whether the file should be a one-time download.
97
+ qr (bool): Whether to display a QR code for the download link.
98
+ url (str): The base URL of the Tempspace server.
99
+ """
88
100
  # --- 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)
101
+ if not os.path.isfile(filepath):
102
+ console.print(Panel(f"[bold red]Error:[/] File not found at '{filepath}'", title="[bold red]Error[/bold red]", border_style="red"))
103
+ return # Return instead of exiting to allow other files to be processed
97
104
 
98
105
  # --- Display File Details ---
99
106
  table = Table(title="File Details", show_header=False, box=box.ROUNDED, border_style="cyan")
100
107
  table.add_column("Field", style="bold")
101
108
  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)))
109
+ table.add_row("File Name", os.path.basename(filepath))
110
+ table.add_row("File Size", format_size(os.path.getsize(filepath)))
104
111
  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
-
112
+ table.add_row("Password", "[green]Yes[/green]" if password else "[red]No[/red]")
113
+ table.add_row("One-Time Download", "[green]Yes[/green]" if one_time else "[red]No[/red]")
109
114
 
110
115
  # --- Prepare Upload ---
111
- upload_url = f"{args.url.rstrip('/')}"
112
- filename = os.path.basename(args.filepath)
113
- file_size = os.path.getsize(args.filepath)
116
+ upload_url = f"{url.rstrip('/')}"
117
+ filename = os.path.basename(filepath)
118
+ file_size = os.path.getsize(filepath)
119
+
120
+ # --- Calculate Hash ---
121
+ client_hash = calculate_file_hash(filepath)
122
+ table.add_row("File Hash", f"[cyan]{client_hash}[/cyan]")
123
+ console.print(table)
124
+
114
125
 
115
126
  # --- Chunked Upload ---
116
127
  response = None
@@ -131,7 +142,7 @@ def main():
131
142
 
132
143
  with Live(Panel(progress, title="[cyan]Uploading[/cyan]", border_style="cyan", title_align="left")) as live:
133
144
  task_id = progress.add_task(filename, total=file_size)
134
- with open(args.filepath, 'rb') as f:
145
+ with open(filepath, 'rb') as f:
135
146
  chunk_number = 0
136
147
  while chunk := f.read(CHUNK_SIZE):
137
148
  chunk_number += 1
@@ -155,17 +166,18 @@ def main():
155
166
  'upload_id': upload_id,
156
167
  'filename': filename,
157
168
  'hours': str(hours),
158
- 'one_time': str(args.one_time).lower(),
169
+ 'one_time': str(one_time).lower(),
170
+ 'client_hash': client_hash, # Send the client-side hash for verification
159
171
  }
160
- if args.password:
161
- finalize_data['password'] = args.password
172
+ if password:
173
+ finalize_data['password'] = password
162
174
 
163
175
  response = requests.post(f"{upload_url}/upload/finalize", data=finalize_data)
164
176
  response.raise_for_status()
165
177
 
166
178
  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)
179
+ console.print(Panel(f"[bold red]Error:[/] The file '{filepath}' was not found.", title="[bold red]Error[/bold red]", border_style="red"))
180
+ return
169
181
  except requests.exceptions.RequestException as e:
170
182
  error_message = str(e)
171
183
  if e.response:
@@ -174,24 +186,47 @@ def main():
174
186
  except:
175
187
  error_message = e.response.text
176
188
  console.print(Panel(f"[bold red]An error occurred:[/] {error_message}", title="[bold red]Error[/bold red]", border_style="red"))
177
- sys.exit(1)
189
+ return
178
190
  except Exception as e:
179
191
  console.print(Panel(f"[bold red]An unexpected error occurred:[/] {e}", title="[bold red]Error[/bold red]", border_style="red"))
180
- sys.exit(1)
192
+ return
181
193
 
182
194
  # --- Handle Response ---
183
195
  if response is not None:
184
196
  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()
197
+ try:
198
+ data = response.json()
199
+ download_link = data.get('url')
200
+ file_hash = data.get('hash')
201
+ hash_verified = data.get('hash_verified', False)
202
+
203
+ success_message = f"[bold green]Upload successful![/bold green]\n\nDownload Link: {download_link}"
204
+ if file_hash:
205
+ verified_status = "[bold green]Yes[/bold green]" if hash_verified else "[bold red]No[/bold red]"
206
+ success_message += f"\nFile Hash (SHA256): {file_hash}"
207
+ success_message += f"\nHash Verified: {verified_status}"
208
+
209
+ success_panel = Panel(success_message, title="[bold cyan]Success[/bold cyan]", border_style="green")
210
+ console.print(success_panel)
211
+
212
+ if qr:
213
+ qr_code = qrcode.QRCode()
214
+ qr_code.add_data(download_link)
215
+ qr_code.make(fit=True)
216
+ qr_code.print_ascii()
217
+
218
+ except requests.exceptions.JSONDecodeError:
219
+ # Fallback for older servers or unexpected plain text responses
220
+ download_link = response.text.strip()
221
+ success_panel = Panel(f"[bold green]Upload successful![/bold green]\n\nDownload Link: {download_link}",
222
+ title="[bold cyan]Success[/bold cyan]", border_style="green")
223
+ console.print(success_panel)
224
+
225
+ if qr:
226
+ qr_code = qrcode.QRCode()
227
+ qr_code.add_data(download_link)
228
+ qr_code.make(fit=True)
229
+ qr_code.print_ascii()
195
230
  else:
196
231
  try:
197
232
  error_details = response.json()
@@ -199,7 +234,63 @@ def main():
199
234
  except requests.exceptions.JSONDecodeError:
200
235
  error_message = response.text
201
236
  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)
237
+
238
+
239
+ def main():
240
+ """The main entry point for the Tempspace CLI tool.
241
+
242
+ Handles argument parsing, interactive mode, file validation,
243
+ and orchestrates the chunked file upload process for one or more files.
244
+ """
245
+ console = Console()
246
+
247
+ # --- Header ---
248
+ console.print(Panel("[bold cyan]Tempspace File Uploader[/bold cyan]", expand=False, border_style="blue"))
249
+
250
+ parser = argparse.ArgumentParser(
251
+ description="Upload one or more files to Tempspace.",
252
+ formatter_class=argparse.RawTextHelpFormatter
253
+ )
254
+
255
+ parser.add_argument("filepaths", nargs='*', default=[], help="The path(s) to the file(s) you want to upload.")
256
+ parser.add_argument("-t", "--time", type=str, default='24', help="Set the file's expiration time for all files. Examples: '24h', '7d', '360' (hours).\nDefault: '24' (24 hours).")
257
+ parser.add_argument("-p", "--password", type=str, help="Protect all files with a password.")
258
+ parser.add_argument("--one-time", action="store_true", help="All files will be deleted after the first download.")
259
+ 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}")
260
+ parser.add_argument("--qr", action="store_true", help="Display a QR code of the download link for each file.")
261
+ parser.add_argument("--it", action="store_true", help="Enable interactive mode for a single file upload.")
262
+
263
+ args = parser.parse_args()
264
+
265
+ filepaths = args.filepaths
266
+
267
+ # --- Interactive Mode ---
268
+ if args.it:
269
+ # Interactive mode only supports a single file, so we overwrite the filepaths list
270
+ filepath = Prompt.ask("Enter the path to the file you want to upload")
271
+ filepaths = [filepath]
272
+ args.time = Prompt.ask("Set the file's expiration time (e.g., '24h', '7d')", default='24')
273
+ args.password = Prompt.ask("Protect the file with a password?", default=None, password=True)
274
+ args.one_time = Confirm.ask("Delete the file after the first download?", default=False)
275
+ args.qr = Confirm.ask("Display a QR code of the download link?", default=False)
276
+
277
+ # --- Validate Inputs ---
278
+ if not filepaths:
279
+ console.print(Panel("[bold red]Error:[/] No file path(s) provided.", title="[bold red]Error[/bold red]", border_style="red"))
280
+ parser.print_help()
281
+ sys.exit(1)
282
+
283
+ hours = parse_time(args.time)
284
+ if hours is None:
285
+ 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"))
286
+ sys.exit(1)
287
+
288
+ # --- Process each file ---
289
+ for i, filepath in enumerate(filepaths):
290
+ if len(filepaths) > 1:
291
+ console.print(f"\n[bold yellow]Uploading file {i+1} of {len(filepaths)}: {os.path.basename(filepath)}[/bold yellow]\n")
292
+ upload_file(console, filepath, hours, args.password, args.one_time, args.qr, args.url)
293
+
203
294
 
204
295
  if __name__ == "__main__":
205
296
  multiprocessing.freeze_support()
@@ -7,7 +7,7 @@ packages = ["cli"]
7
7
 
8
8
  [project]
9
9
  name = "tempspace-cli"
10
- version = "1.2.2"
10
+ version = "1.2.4"
11
11
  authors = [
12
12
  { name="Tempspace", email="mcbplay1@gmail.com" },
13
13
  ]
@@ -20,9 +20,9 @@ classifiers = [
20
20
  "Operating System :: OS Independent",
21
21
  ]
22
22
  dependencies = [
23
- "requests==2.31.0",
23
+ "requests==2.32.5",
24
24
  "requests-toolbelt==1.0.0",
25
- "rich==13.7.1",
25
+ "rich==14.2.0",
26
26
  "qrcode==8.2",
27
27
  ]
28
28