televault 0.1.0__py3-none-any.whl → 2.0.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.
- televault/__init__.py +1 -1
- televault/chunker.py +29 -27
- televault/cli.py +237 -90
- televault/compress.py +59 -23
- televault/config.py +16 -17
- televault/core.py +140 -203
- televault/crypto.py +26 -33
- televault/models.py +29 -30
- televault/telegram.py +136 -107
- televault/tui.py +632 -0
- televault-2.0.0.dist-info/METADATA +310 -0
- televault-2.0.0.dist-info/RECORD +14 -0
- {televault-0.1.0.dist-info → televault-2.0.0.dist-info}/entry_points.txt +1 -0
- televault-0.1.0.dist-info/METADATA +0 -242
- televault-0.1.0.dist-info/RECORD +0 -13
- {televault-0.1.0.dist-info → televault-2.0.0.dist-info}/WHEEL +0 -0
televault/cli.py
CHANGED
|
@@ -3,17 +3,14 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import sys
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Optional
|
|
7
6
|
|
|
8
7
|
import click
|
|
9
8
|
from rich.console import Console
|
|
9
|
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeRemainingColumn
|
|
10
10
|
from rich.table import Table
|
|
11
|
-
from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, TimeRemainingColumn
|
|
12
|
-
from rich import print as rprint
|
|
13
11
|
|
|
14
|
-
from .core import TeleVault, UploadProgress, DownloadProgress
|
|
15
12
|
from .config import Config, get_config_dir
|
|
16
|
-
|
|
13
|
+
from .core import DownloadProgress, TeleVault, UploadProgress
|
|
17
14
|
|
|
18
15
|
console = Console()
|
|
19
16
|
|
|
@@ -32,6 +29,22 @@ def run_async(coro):
|
|
|
32
29
|
return asyncio.get_event_loop().run_until_complete(coro)
|
|
33
30
|
|
|
34
31
|
|
|
32
|
+
async def check_auth(vault: TeleVault) -> bool:
|
|
33
|
+
"""Check if user is authenticated. Returns True if authenticated, False otherwise."""
|
|
34
|
+
if not await vault.is_authenticated():
|
|
35
|
+
console.print("[red]Not logged in. Run 'televault login' first.[/red]")
|
|
36
|
+
return False
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def check_channel(vault: TeleVault) -> bool:
|
|
41
|
+
"""Check if channel is set. Returns True if set, False otherwise."""
|
|
42
|
+
if vault.config.channel_id is None:
|
|
43
|
+
console.print("[red]No storage channel configured. Run 'televault setup' first.[/red]")
|
|
44
|
+
return False
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
|
|
35
48
|
@click.group(invoke_without_command=True)
|
|
36
49
|
@click.option("-h", "--help", is_flag=True, help="Show this message and exit.")
|
|
37
50
|
@click.pass_context
|
|
@@ -43,27 +56,28 @@ def main(ctx, help):
|
|
|
43
56
|
|
|
44
57
|
@main.command()
|
|
45
58
|
@click.option("--phone", "-p", help="Phone number for login")
|
|
46
|
-
def login(phone:
|
|
59
|
+
def login(phone: str | None):
|
|
47
60
|
"""Login to Telegram."""
|
|
61
|
+
|
|
48
62
|
async def _login():
|
|
49
63
|
vault = TeleVault()
|
|
50
64
|
await vault.connect(skip_channel=True) # Don't try to access channel yet
|
|
51
|
-
|
|
65
|
+
|
|
52
66
|
console.print("[bold blue]TeleVault Login[/bold blue]")
|
|
53
67
|
console.print("You'll receive a code on Telegram.\n")
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
|
|
69
|
+
await vault.login(phone)
|
|
70
|
+
|
|
57
71
|
console.print("\n[bold green]✓ Login successful![/bold green]")
|
|
58
72
|
console.print(f"Session saved to: {get_config_dir() / 'telegram.json'}")
|
|
59
|
-
|
|
73
|
+
|
|
60
74
|
# Now set up channel if configured
|
|
61
75
|
if vault.config.channel_id:
|
|
62
76
|
await vault.telegram.set_channel(vault.config.channel_id)
|
|
63
77
|
console.print(f"Channel configured: {vault.config.channel_id}")
|
|
64
|
-
|
|
78
|
+
|
|
65
79
|
await vault.disconnect()
|
|
66
|
-
|
|
80
|
+
|
|
67
81
|
run_async(_login())
|
|
68
82
|
|
|
69
83
|
|
|
@@ -72,7 +86,7 @@ def logout():
|
|
|
72
86
|
"""Logout and clear session."""
|
|
73
87
|
config_dir = get_config_dir()
|
|
74
88
|
telegram_config = config_dir / "telegram.json"
|
|
75
|
-
|
|
89
|
+
|
|
76
90
|
if telegram_config.exists():
|
|
77
91
|
telegram_config.unlink()
|
|
78
92
|
console.print("[green]✓ Logged out successfully[/green]")
|
|
@@ -82,22 +96,69 @@ def logout():
|
|
|
82
96
|
|
|
83
97
|
@main.command()
|
|
84
98
|
@click.option("--channel-id", "-c", type=int, help="Existing channel ID to use")
|
|
85
|
-
|
|
99
|
+
@click.option("--auto-create", is_flag=True, help="Auto-create a new channel without prompting")
|
|
100
|
+
def setup(channel_id: int | None, auto_create: bool):
|
|
86
101
|
"""Set up storage channel."""
|
|
102
|
+
|
|
87
103
|
async def _setup():
|
|
88
104
|
vault = TeleVault()
|
|
89
105
|
await vault.connect()
|
|
90
|
-
|
|
106
|
+
|
|
107
|
+
# Check authentication first
|
|
108
|
+
if not await check_auth(vault):
|
|
109
|
+
await vault.disconnect()
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# If channel_id is provided via CLI, use it
|
|
91
113
|
if channel_id:
|
|
92
114
|
cid = await vault.setup_channel(channel_id)
|
|
93
|
-
console.print(f"[green]✓ Using channel: {cid}[/green]")
|
|
94
|
-
|
|
115
|
+
console.print(f"[green]✓ Using existing channel: {cid}[/green]")
|
|
116
|
+
console.print("[dim]Note: Make sure the bot is a member of this channel.[/dim]")
|
|
117
|
+
elif auto_create:
|
|
118
|
+
# Auto-create without prompting
|
|
95
119
|
console.print("[bold]Creating new storage channel...[/bold]")
|
|
96
120
|
cid = await vault.setup_channel()
|
|
97
|
-
console.print(f"[green]✓ Created channel: {cid}[/green]")
|
|
98
|
-
|
|
121
|
+
console.print(f"[green]✓ Created new channel: {cid}[/green]")
|
|
122
|
+
else:
|
|
123
|
+
# Interactive mode - ask user what they want to do
|
|
124
|
+
console.print("[bold blue]TeleVault Storage Channel Setup[/bold blue]\n")
|
|
125
|
+
console.print("How would you like to set up your storage?")
|
|
126
|
+
console.print(" 1. Create a new private channel (recommended)")
|
|
127
|
+
console.print(" 2. Use an existing channel by ID")
|
|
128
|
+
console.print("")
|
|
129
|
+
|
|
130
|
+
choice = input("Enter your choice (1 or 2): ").strip()
|
|
131
|
+
|
|
132
|
+
if choice == "1":
|
|
133
|
+
console.print("\n[bold]Creating new storage channel...[/bold]")
|
|
134
|
+
cid = await vault.setup_channel()
|
|
135
|
+
console.print(f"[green]✓ Created new channel: {cid}[/green]")
|
|
136
|
+
elif choice == "2":
|
|
137
|
+
console.print("\n[bold]Using existing channel[/bold]")
|
|
138
|
+
console.print(
|
|
139
|
+
"[dim]Note: The channel ID should start with -100 (e.g., -1001234567890)[/dim]"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
existing_id = input("Enter channel ID: ").strip()
|
|
144
|
+
existing_id_int = int(existing_id)
|
|
145
|
+
cid = await vault.setup_channel(existing_id_int)
|
|
146
|
+
console.print(f"[green]✓ Using existing channel: {cid}[/green]")
|
|
147
|
+
except ValueError:
|
|
148
|
+
console.print("[red]✗ Invalid channel ID. Please provide a valid number.[/red]")
|
|
149
|
+
await vault.disconnect()
|
|
150
|
+
return
|
|
151
|
+
except Exception as e:
|
|
152
|
+
console.print(f"[red]✗ Error setting up channel: {e}[/red]")
|
|
153
|
+
await vault.disconnect()
|
|
154
|
+
return
|
|
155
|
+
else:
|
|
156
|
+
console.print("[red]✗ Invalid choice. Please enter 1 or 2.[/red]")
|
|
157
|
+
await vault.disconnect()
|
|
158
|
+
return
|
|
159
|
+
|
|
99
160
|
await vault.disconnect()
|
|
100
|
-
|
|
161
|
+
|
|
101
162
|
run_async(_setup())
|
|
102
163
|
|
|
103
164
|
|
|
@@ -107,43 +168,56 @@ def setup(channel_id: Optional[int]):
|
|
|
107
168
|
@click.option("--no-compress", is_flag=True, help="Disable compression")
|
|
108
169
|
@click.option("--no-encrypt", is_flag=True, help="Disable encryption")
|
|
109
170
|
@click.option("--recursive", "-r", is_flag=True, help="Upload directory recursively")
|
|
110
|
-
def push(
|
|
171
|
+
def push(
|
|
172
|
+
file_path: str, password: str | None, no_compress: bool, no_encrypt: bool, recursive: bool
|
|
173
|
+
):
|
|
111
174
|
"""Upload a file or directory to TeleVault."""
|
|
175
|
+
|
|
112
176
|
async def _push():
|
|
113
177
|
config = Config.load_or_create()
|
|
114
|
-
|
|
178
|
+
|
|
115
179
|
if no_compress:
|
|
116
180
|
config.compression = False
|
|
117
181
|
if no_encrypt:
|
|
118
182
|
config.encryption = False
|
|
119
|
-
|
|
183
|
+
|
|
120
184
|
if config.encryption and not password:
|
|
121
185
|
console.print("[yellow]Warning: Encryption enabled but no password provided.[/yellow]")
|
|
122
186
|
console.print("Set password with --password or TELEVAULT_PASSWORD env var.")
|
|
123
187
|
console.print("Use --no-encrypt to disable encryption.\n")
|
|
124
|
-
|
|
188
|
+
|
|
125
189
|
vault = TeleVault(config=config, password=password)
|
|
126
190
|
await vault.connect()
|
|
127
|
-
|
|
191
|
+
|
|
192
|
+
if not await check_auth(vault):
|
|
193
|
+
await vault.disconnect()
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
if not await check_channel(vault):
|
|
197
|
+
await vault.disconnect()
|
|
198
|
+
return
|
|
199
|
+
|
|
128
200
|
file_path_obj = Path(file_path)
|
|
129
|
-
|
|
201
|
+
|
|
130
202
|
# Handle directory upload
|
|
131
203
|
if file_path_obj.is_dir():
|
|
132
204
|
if not recursive:
|
|
133
|
-
console.print(
|
|
205
|
+
console.print(
|
|
206
|
+
f"[red]'{file_path}' is a directory. Use --recursive (-r) to upload.[/red]"
|
|
207
|
+
)
|
|
134
208
|
await vault.disconnect()
|
|
135
209
|
return
|
|
136
|
-
|
|
210
|
+
|
|
137
211
|
files = list(file_path_obj.rglob("*"))
|
|
138
212
|
files = [f for f in files if f.is_file()]
|
|
139
|
-
|
|
213
|
+
|
|
140
214
|
if not files:
|
|
141
215
|
console.print("[yellow]No files found in directory.[/yellow]")
|
|
142
216
|
await vault.disconnect()
|
|
143
217
|
return
|
|
144
|
-
|
|
218
|
+
|
|
145
219
|
console.print(f"[bold]Uploading {len(files)} files from {file_path_obj.name}/[/bold]\n")
|
|
146
|
-
|
|
220
|
+
|
|
147
221
|
for i, f in enumerate(files, 1):
|
|
148
222
|
rel_path = f.relative_to(file_path_obj)
|
|
149
223
|
console.print(f"[{i}/{len(files)}] {rel_path}...", end=" ")
|
|
@@ -152,12 +226,12 @@ def push(file_path: str, password: Optional[str], no_compress: bool, no_encrypt:
|
|
|
152
226
|
console.print(f"[green]✓[/green] ({format_size(metadata.size)})")
|
|
153
227
|
except Exception as e:
|
|
154
228
|
console.print(f"[red]✗ {e}[/red]")
|
|
155
|
-
|
|
229
|
+
|
|
156
230
|
console.print(f"\n[bold green]✓ Uploaded {len(files)} files[/bold green]")
|
|
157
231
|
else:
|
|
158
232
|
# Single file upload with progress
|
|
159
233
|
file_size = file_path_obj.stat().st_size
|
|
160
|
-
|
|
234
|
+
|
|
161
235
|
with Progress(
|
|
162
236
|
SpinnerColumn(),
|
|
163
237
|
TextColumn("[progress.description]{task.description}"),
|
|
@@ -169,26 +243,24 @@ def push(file_path: str, password: Optional[str], no_compress: bool, no_encrypt:
|
|
|
169
243
|
refresh_per_second=10,
|
|
170
244
|
) as progress:
|
|
171
245
|
task = progress.add_task(
|
|
172
|
-
f"Uploading {file_path_obj.name}",
|
|
173
|
-
total=100,
|
|
174
|
-
size=format_size(file_size)
|
|
246
|
+
f"Uploading {file_path_obj.name}", total=100, size=format_size(file_size)
|
|
175
247
|
)
|
|
176
|
-
|
|
248
|
+
|
|
177
249
|
def on_progress(p: UploadProgress):
|
|
178
250
|
progress.update(task, completed=p.percent)
|
|
179
|
-
|
|
251
|
+
|
|
180
252
|
metadata = await vault.upload(file_path, progress_callback=on_progress)
|
|
181
253
|
progress.update(task, completed=100) # Ensure 100% at end
|
|
182
|
-
|
|
183
|
-
console.print(
|
|
254
|
+
|
|
255
|
+
console.print("\n[bold green]✓ Uploaded successfully![/bold green]")
|
|
184
256
|
console.print(f" File ID: {metadata.id}")
|
|
185
257
|
console.print(f" Size: {format_size(metadata.size)}")
|
|
186
258
|
console.print(f" Chunks: {metadata.chunk_count}")
|
|
187
259
|
console.print(f" Encrypted: {'Yes' if metadata.encrypted else 'No'}")
|
|
188
260
|
console.print(f" Compressed: {'Yes' if metadata.compressed else 'No'}")
|
|
189
|
-
|
|
261
|
+
|
|
190
262
|
await vault.disconnect()
|
|
191
|
-
|
|
263
|
+
|
|
192
264
|
run_async(_push())
|
|
193
265
|
|
|
194
266
|
|
|
@@ -196,12 +268,21 @@ def push(file_path: str, password: Optional[str], no_compress: bool, no_encrypt:
|
|
|
196
268
|
@click.argument("file_id_or_name")
|
|
197
269
|
@click.option("--output", "-o", type=click.Path(), help="Output path")
|
|
198
270
|
@click.option("--password", "-p", help="Decryption password", envvar="TELEVAULT_PASSWORD")
|
|
199
|
-
def pull(file_id_or_name: str, output:
|
|
271
|
+
def pull(file_id_or_name: str, output: str | None, password: str | None):
|
|
200
272
|
"""Download a file from TeleVault."""
|
|
273
|
+
|
|
201
274
|
async def _pull():
|
|
202
275
|
vault = TeleVault(password=password)
|
|
203
276
|
await vault.connect()
|
|
204
|
-
|
|
277
|
+
|
|
278
|
+
if not await check_auth(vault):
|
|
279
|
+
await vault.disconnect()
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
if not await check_channel(vault):
|
|
283
|
+
await vault.disconnect()
|
|
284
|
+
return
|
|
285
|
+
|
|
205
286
|
with Progress(
|
|
206
287
|
SpinnerColumn(),
|
|
207
288
|
TextColumn("[progress.description]{task.description}"),
|
|
@@ -212,10 +293,10 @@ def pull(file_id_or_name: str, output: Optional[str], password: Optional[str]):
|
|
|
212
293
|
refresh_per_second=10,
|
|
213
294
|
) as progress:
|
|
214
295
|
task = progress.add_task(f"Downloading {file_id_or_name}", total=100)
|
|
215
|
-
|
|
296
|
+
|
|
216
297
|
def on_progress(p: DownloadProgress):
|
|
217
298
|
progress.update(task, completed=p.percent)
|
|
218
|
-
|
|
299
|
+
|
|
219
300
|
try:
|
|
220
301
|
output_path = await vault.download(
|
|
221
302
|
file_id_or_name,
|
|
@@ -231,11 +312,11 @@ def pull(file_id_or_name: str, output: Optional[str], password: Optional[str]):
|
|
|
231
312
|
console.print(f"[red]✗ Error: {e}[/red]")
|
|
232
313
|
await vault.disconnect()
|
|
233
314
|
sys.exit(1)
|
|
234
|
-
|
|
315
|
+
|
|
235
316
|
console.print(f"\n[bold green]✓ Downloaded to: {output_path}[/bold green]")
|
|
236
|
-
|
|
317
|
+
|
|
237
318
|
await vault.disconnect()
|
|
238
|
-
|
|
319
|
+
|
|
239
320
|
run_async(_pull())
|
|
240
321
|
|
|
241
322
|
|
|
@@ -244,12 +325,21 @@ def pull(file_id_or_name: str, output: Optional[str], password: Optional[str]):
|
|
|
244
325
|
@click.option("--sort", type=click.Choice(["name", "size", "date"]), default="name")
|
|
245
326
|
def list_files(as_json: bool, sort: str):
|
|
246
327
|
"""List all files in the vault."""
|
|
328
|
+
|
|
247
329
|
async def _list():
|
|
248
330
|
vault = TeleVault()
|
|
249
331
|
await vault.connect()
|
|
250
|
-
|
|
332
|
+
|
|
333
|
+
if not await check_auth(vault):
|
|
334
|
+
await vault.disconnect()
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
if not await check_channel(vault):
|
|
338
|
+
await vault.disconnect()
|
|
339
|
+
return
|
|
340
|
+
|
|
251
341
|
files = await vault.list_files()
|
|
252
|
-
|
|
342
|
+
|
|
253
343
|
# Sort
|
|
254
344
|
if sort == "name":
|
|
255
345
|
files.sort(key=lambda f: f.name.lower())
|
|
@@ -257,9 +347,10 @@ def list_files(as_json: bool, sort: str):
|
|
|
257
347
|
files.sort(key=lambda f: f.size, reverse=True)
|
|
258
348
|
elif sort == "date":
|
|
259
349
|
files.sort(key=lambda f: f.created_at, reverse=True)
|
|
260
|
-
|
|
350
|
+
|
|
261
351
|
if as_json:
|
|
262
352
|
import json
|
|
353
|
+
|
|
263
354
|
output = [{"id": f.id, "name": f.name, "size": f.size} for f in files]
|
|
264
355
|
click.echo(json.dumps(output, indent=2))
|
|
265
356
|
else:
|
|
@@ -272,7 +363,7 @@ def list_files(as_json: bool, sort: str):
|
|
|
272
363
|
table.add_column("Size", justify="right")
|
|
273
364
|
table.add_column("Chunks", justify="right")
|
|
274
365
|
table.add_column("Encrypted")
|
|
275
|
-
|
|
366
|
+
|
|
276
367
|
for f in files:
|
|
277
368
|
table.add_row(
|
|
278
369
|
f.id[:8],
|
|
@@ -281,12 +372,13 @@ def list_files(as_json: bool, sort: str):
|
|
|
281
372
|
str(f.chunk_count),
|
|
282
373
|
"🔒" if f.encrypted else "📄",
|
|
283
374
|
)
|
|
284
|
-
|
|
375
|
+
|
|
285
376
|
console.print(table)
|
|
286
|
-
|
|
287
|
-
|
|
377
|
+
total_size = format_size(sum(f.size for f in files))
|
|
378
|
+
console.print(f"\n[dim]{len(files)} file(s), {total_size} total[/dim]")
|
|
379
|
+
|
|
288
380
|
await vault.disconnect()
|
|
289
|
-
|
|
381
|
+
|
|
290
382
|
run_async(_list())
|
|
291
383
|
|
|
292
384
|
|
|
@@ -294,20 +386,29 @@ def list_files(as_json: bool, sort: str):
|
|
|
294
386
|
@click.argument("query")
|
|
295
387
|
def search(query: str):
|
|
296
388
|
"""Search files by name."""
|
|
389
|
+
|
|
297
390
|
async def _search():
|
|
298
391
|
vault = TeleVault()
|
|
299
392
|
await vault.connect()
|
|
300
|
-
|
|
393
|
+
|
|
394
|
+
if not await check_auth(vault):
|
|
395
|
+
await vault.disconnect()
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
if not await check_channel(vault):
|
|
399
|
+
await vault.disconnect()
|
|
400
|
+
return
|
|
401
|
+
|
|
301
402
|
files = await vault.search(query)
|
|
302
|
-
|
|
403
|
+
|
|
303
404
|
if not files:
|
|
304
405
|
console.print(f"[dim]No files matching '{query}'[/dim]")
|
|
305
406
|
else:
|
|
306
407
|
for f in files:
|
|
307
408
|
console.print(f"[cyan]{f.id[:8]}[/cyan] {f.name} ({format_size(f.size)})")
|
|
308
|
-
|
|
409
|
+
|
|
309
410
|
await vault.disconnect()
|
|
310
|
-
|
|
411
|
+
|
|
311
412
|
run_async(_search())
|
|
312
413
|
|
|
313
414
|
|
|
@@ -315,10 +416,19 @@ def search(query: str):
|
|
|
315
416
|
@click.argument("file_id_or_name")
|
|
316
417
|
def info(file_id_or_name: str):
|
|
317
418
|
"""Show detailed file information."""
|
|
419
|
+
|
|
318
420
|
async def _info():
|
|
319
421
|
vault = TeleVault()
|
|
320
422
|
await vault.connect()
|
|
321
|
-
|
|
423
|
+
|
|
424
|
+
if not await check_auth(vault):
|
|
425
|
+
await vault.disconnect()
|
|
426
|
+
return
|
|
427
|
+
|
|
428
|
+
if not await check_channel(vault):
|
|
429
|
+
await vault.disconnect()
|
|
430
|
+
return
|
|
431
|
+
|
|
322
432
|
try:
|
|
323
433
|
# Find file
|
|
324
434
|
files = await vault.search(file_id_or_name)
|
|
@@ -330,14 +440,14 @@ def info(file_id_or_name: str):
|
|
|
330
440
|
metadata = await vault.telegram.get_metadata(msg_id)
|
|
331
441
|
files = [metadata]
|
|
332
442
|
break
|
|
333
|
-
|
|
443
|
+
|
|
334
444
|
if not files:
|
|
335
445
|
console.print(f"[red]File not found: {file_id_or_name}[/red]")
|
|
336
446
|
await vault.disconnect()
|
|
337
447
|
return
|
|
338
|
-
|
|
448
|
+
|
|
339
449
|
f = files[0]
|
|
340
|
-
|
|
450
|
+
|
|
341
451
|
console.print(f"[bold]{f.name}[/bold]\n")
|
|
342
452
|
console.print(f" ID: {f.id}")
|
|
343
453
|
console.print(f" Size: {format_size(f.size)}")
|
|
@@ -349,20 +459,21 @@ def info(file_id_or_name: str):
|
|
|
349
459
|
console.print(f" Comp. ratio: {f.compression_ratio:.1%}")
|
|
350
460
|
if f.mime_type:
|
|
351
461
|
console.print(f" MIME type: {f.mime_type}")
|
|
352
|
-
|
|
462
|
+
|
|
353
463
|
from datetime import datetime
|
|
464
|
+
|
|
354
465
|
created = datetime.fromtimestamp(f.created_at)
|
|
355
466
|
console.print(f" Created: {created.strftime('%Y-%m-%d %H:%M')}")
|
|
356
|
-
|
|
467
|
+
|
|
357
468
|
if f.chunks:
|
|
358
469
|
stored = sum(c.size for c in f.chunks)
|
|
359
470
|
console.print(f" Stored size: {format_size(stored)}")
|
|
360
|
-
|
|
471
|
+
|
|
361
472
|
except Exception as e:
|
|
362
473
|
console.print(f"[red]Error: {e}[/red]")
|
|
363
|
-
|
|
474
|
+
|
|
364
475
|
await vault.disconnect()
|
|
365
|
-
|
|
476
|
+
|
|
366
477
|
run_async(_info())
|
|
367
478
|
|
|
368
479
|
|
|
@@ -371,38 +482,55 @@ def info(file_id_or_name: str):
|
|
|
371
482
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
|
|
372
483
|
def rm(file_id_or_name: str, yes: bool):
|
|
373
484
|
"""Delete a file from the vault."""
|
|
485
|
+
|
|
374
486
|
async def _rm():
|
|
375
487
|
vault = TeleVault()
|
|
376
488
|
await vault.connect()
|
|
377
|
-
|
|
378
|
-
if not
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
489
|
+
|
|
490
|
+
if not await check_auth(vault):
|
|
491
|
+
await vault.disconnect()
|
|
492
|
+
return
|
|
493
|
+
|
|
494
|
+
if not await check_channel(vault):
|
|
495
|
+
await vault.disconnect()
|
|
496
|
+
return
|
|
497
|
+
|
|
498
|
+
if not yes and not click.confirm(f"Delete '{file_id_or_name}'?"):
|
|
499
|
+
console.print("[dim]Cancelled[/dim]")
|
|
500
|
+
await vault.disconnect()
|
|
501
|
+
return
|
|
502
|
+
|
|
384
503
|
deleted = await vault.delete(file_id_or_name)
|
|
385
|
-
|
|
504
|
+
|
|
386
505
|
if deleted:
|
|
387
506
|
console.print(f"[green]✓ Deleted: {file_id_or_name}[/green]")
|
|
388
507
|
else:
|
|
389
508
|
console.print(f"[red]✗ File not found: {file_id_or_name}[/red]")
|
|
390
|
-
|
|
509
|
+
|
|
391
510
|
await vault.disconnect()
|
|
392
|
-
|
|
511
|
+
|
|
393
512
|
run_async(_rm())
|
|
394
513
|
|
|
395
514
|
|
|
396
515
|
@main.command()
|
|
397
516
|
def status():
|
|
398
517
|
"""Show vault status."""
|
|
518
|
+
|
|
399
519
|
async def _status():
|
|
400
520
|
vault = TeleVault()
|
|
401
521
|
await vault.connect()
|
|
402
|
-
|
|
522
|
+
|
|
523
|
+
if not await check_auth(vault):
|
|
524
|
+
await vault.disconnect()
|
|
525
|
+
return
|
|
526
|
+
|
|
527
|
+
if not await check_channel(vault):
|
|
528
|
+
await vault.disconnect()
|
|
529
|
+
return
|
|
530
|
+
|
|
403
531
|
try:
|
|
404
532
|
status = await vault.get_status()
|
|
405
|
-
|
|
533
|
+
|
|
406
534
|
console.print("[bold]TeleVault Status[/bold]\n")
|
|
407
535
|
console.print(f" Channel: {status['channel_id']}")
|
|
408
536
|
console.print(f" Files: {status['file_count']}")
|
|
@@ -412,34 +540,53 @@ def status():
|
|
|
412
540
|
except Exception as e:
|
|
413
541
|
console.print(f"[red]Error: {e}[/red]")
|
|
414
542
|
console.print("\n[dim]Have you run 'televault login' and 'televault setup'?[/dim]")
|
|
415
|
-
|
|
543
|
+
|
|
416
544
|
await vault.disconnect()
|
|
417
|
-
|
|
545
|
+
|
|
418
546
|
run_async(_status())
|
|
419
547
|
|
|
420
548
|
|
|
421
549
|
@main.command()
|
|
422
550
|
def whoami():
|
|
423
551
|
"""Show current Telegram account."""
|
|
552
|
+
|
|
424
553
|
async def _whoami():
|
|
425
554
|
vault = TeleVault()
|
|
426
555
|
await vault.connect()
|
|
427
|
-
|
|
556
|
+
|
|
557
|
+
if not await vault.telegram._client.is_user_authorized():
|
|
558
|
+
console.print("[red]Not logged in. Run 'televault login' first.[/red]")
|
|
559
|
+
await vault.disconnect()
|
|
560
|
+
return
|
|
561
|
+
|
|
428
562
|
me = await vault.telegram._client.get_me()
|
|
429
|
-
|
|
563
|
+
|
|
564
|
+
if me is None:
|
|
565
|
+
console.print("[red]Not logged in. Run 'televault login' first.[/red]")
|
|
566
|
+
await vault.disconnect()
|
|
567
|
+
return
|
|
568
|
+
|
|
430
569
|
console.print(f"[bold]{me.first_name}[/bold]", end="")
|
|
431
570
|
if me.last_name:
|
|
432
571
|
console.print(f" {me.last_name}", end="")
|
|
433
572
|
console.print()
|
|
434
|
-
|
|
573
|
+
|
|
435
574
|
if me.username:
|
|
436
575
|
console.print(f" @{me.username}")
|
|
437
576
|
console.print(f" ID: {me.id}")
|
|
438
|
-
|
|
577
|
+
|
|
439
578
|
await vault.disconnect()
|
|
440
|
-
|
|
579
|
+
|
|
441
580
|
run_async(_whoami())
|
|
442
581
|
|
|
443
582
|
|
|
583
|
+
@main.command()
|
|
584
|
+
def tui():
|
|
585
|
+
"""Launch the interactive TUI."""
|
|
586
|
+
from .tui import run_tui
|
|
587
|
+
|
|
588
|
+
run_tui()
|
|
589
|
+
|
|
590
|
+
|
|
444
591
|
if __name__ == "__main__":
|
|
445
592
|
main()
|