weeb-cli 2.1.5__tar.gz → 2.2.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.
- {weeb_cli-2.1.5/weeb_cli.egg-info → weeb_cli-2.2.0}/PKG-INFO +5 -1
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/pyproject.toml +6 -2
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/commands/downloads.py +12 -6
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/i18n.py +3 -3
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/dependency_manager.py +3 -3
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/downloader.py +31 -6
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/local_library.py +16 -3
- weeb_cli-2.2.0/weeb_cli/services/notifier.py +63 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/updater.py +8 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/ui/header.py +1 -1
- weeb_cli-2.2.0/weeb_cli/ui/menu.py +127 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0/weeb_cli.egg-info}/PKG-INFO +5 -1
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli.egg-info/requires.txt +6 -0
- weeb_cli-2.1.5/weeb_cli/services/notifier.py +0 -41
- weeb_cli-2.1.5/weeb_cli/ui/menu.py +0 -59
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/LICENSE +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/README.md +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/setup.cfg +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/__init__.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/__main__.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/commands/search.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/commands/settings.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/commands/setup.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/commands/watchlist.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/config.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/locales/en.json +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/locales/tr.json +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/main.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/__init__.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/allanime.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/animecix.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/anizle.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/base.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/extractors/__init__.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/extractors/megacloud.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/hianime.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/registry.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/turkanime.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/__init__.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/database.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/details.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/logger.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/player.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/progress.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/scraper.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/search.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/tracker.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/watch.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/ui/__init__.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/ui/prompt.py +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli.egg-info/SOURCES.txt +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli.egg-info/dependency_links.txt +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli.egg-info/entry_points.txt +0 -0
- {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weeb-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Tarayıcı yok, reklam yok, dikkat dağıtıcı unsur yok. Sadece siz ve eşsiz bir anime izleme deneyimi.
|
|
5
5
|
Author-email: ewgsta <ewgst@proton.me>
|
|
6
6
|
License-Expression: CC-BY-NC-ND-4.0
|
|
@@ -23,6 +23,10 @@ Requires-Dist: pycryptodome
|
|
|
23
23
|
Requires-Dist: curl_cffi
|
|
24
24
|
Requires-Dist: appdirs
|
|
25
25
|
Requires-Dist: prompt_toolkit<3.0.50,>=3.0.36
|
|
26
|
+
Requires-Dist: beautifulsoup4
|
|
27
|
+
Requires-Dist: lxml
|
|
28
|
+
Requires-Dist: py7zr
|
|
29
|
+
Requires-Dist: winotify; sys_platform == "win32"
|
|
26
30
|
Dynamic: license-file
|
|
27
31
|
|
|
28
32
|
<p align="center">
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "weeb-cli"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.2.0"
|
|
8
8
|
description = "Tarayıcı yok, reklam yok, dikkat dağıtıcı unsur yok. Sadece siz ve eşsiz bir anime izleme deneyimi."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "ewgsta", email = "ewgst@proton.me" }]
|
|
@@ -25,7 +25,11 @@ dependencies = [
|
|
|
25
25
|
"pycryptodome",
|
|
26
26
|
"curl_cffi",
|
|
27
27
|
"appdirs",
|
|
28
|
-
"prompt_toolkit>=3.0.36,<3.0.50"
|
|
28
|
+
"prompt_toolkit>=3.0.36,<3.0.50",
|
|
29
|
+
"beautifulsoup4",
|
|
30
|
+
"lxml",
|
|
31
|
+
"py7zr",
|
|
32
|
+
"winotify; sys_platform == 'win32'"
|
|
29
33
|
]
|
|
30
34
|
|
|
31
35
|
[tool.setuptools.packages.find]
|
|
@@ -420,10 +420,12 @@ def manage_queue():
|
|
|
420
420
|
def show_queue_live():
|
|
421
421
|
def generate_table():
|
|
422
422
|
table = Table(show_header=True, header_style="bold cyan")
|
|
423
|
-
table.add_column(i18n.get("watchlist.anime_title"), width=
|
|
424
|
-
table.add_column(i18n.get("details.episode"), justify="right", width=
|
|
425
|
-
table.add_column(i18n.get("downloads.status"), width=
|
|
426
|
-
table.add_column(i18n.get("downloads.progress"), width=
|
|
423
|
+
table.add_column(i18n.get("watchlist.anime_title"), width=28)
|
|
424
|
+
table.add_column(i18n.get("details.episode"), justify="right", width=4)
|
|
425
|
+
table.add_column(i18n.get("downloads.status"), width=12)
|
|
426
|
+
table.add_column(i18n.get("downloads.progress"), width=24)
|
|
427
|
+
table.add_column("Hız", width=10)
|
|
428
|
+
table.add_column("ETA", width=8)
|
|
427
429
|
|
|
428
430
|
active = [i for i in queue_manager.queue if i["status"] == "processing"]
|
|
429
431
|
pending = [i for i in queue_manager.queue if i["status"] == "pending"]
|
|
@@ -450,12 +452,16 @@ def show_queue_live():
|
|
|
450
452
|
|
|
451
453
|
status_text = i18n.get(f"downloads.status_{status}", status.upper())
|
|
452
454
|
p_text = f"{progress}%" if status == "processing" else ""
|
|
455
|
+
speed = item.get("speed", "") if status == "processing" else ""
|
|
456
|
+
eta = item.get("eta", "") if status == "processing" else ""
|
|
453
457
|
|
|
454
458
|
table.add_row(
|
|
455
|
-
f"[{style}]{item['anime_title'][:
|
|
459
|
+
f"[{style}]{item['anime_title'][:26]}[/{style}]",
|
|
456
460
|
f"{item['episode_number']}",
|
|
457
461
|
f"[{style}]{status_text}[/{style}]",
|
|
458
|
-
f"[{style}]{bar_str} {p_text}[/{style}]"
|
|
462
|
+
f"[{style}]{bar_str} {p_text}[/{style}]",
|
|
463
|
+
f"[{style}]{speed}[/{style}]",
|
|
464
|
+
f"[{style}]{eta}[/{style}]"
|
|
459
465
|
)
|
|
460
466
|
|
|
461
467
|
return table
|
|
@@ -39,7 +39,7 @@ class I18n:
|
|
|
39
39
|
print(f"Error loading translations: {e}")
|
|
40
40
|
self.translations = {}
|
|
41
41
|
|
|
42
|
-
def get(self, key_path, **kwargs):
|
|
42
|
+
def get(self, key_path, default=None, **kwargs):
|
|
43
43
|
keys = key_path.split(".")
|
|
44
44
|
value = self.translations
|
|
45
45
|
|
|
@@ -47,10 +47,10 @@ class I18n:
|
|
|
47
47
|
if isinstance(value, dict):
|
|
48
48
|
value = value.get(key)
|
|
49
49
|
else:
|
|
50
|
-
return key_path
|
|
50
|
+
return default if default is not None else key_path
|
|
51
51
|
|
|
52
52
|
if value is None:
|
|
53
|
-
return key_path
|
|
53
|
+
return default if default is not None else key_path
|
|
54
54
|
|
|
55
55
|
if isinstance(value, str):
|
|
56
56
|
try:
|
|
@@ -143,13 +143,13 @@ class DependencyManager:
|
|
|
143
143
|
pkg_map = info.get("pkg", {})
|
|
144
144
|
|
|
145
145
|
managers = {
|
|
146
|
-
"winget": ["winget", "install", "-e", "--id"],
|
|
147
|
-
"choco": ["choco", "install", "-y"],
|
|
148
146
|
"scoop": ["scoop", "install"],
|
|
147
|
+
"choco": ["choco", "install", "-y"],
|
|
149
148
|
"brew": ["brew", "install"],
|
|
150
149
|
"yay": ["yay", "-S", "--noconfirm"],
|
|
151
150
|
"pacman": ["sudo", "pacman", "-S", "--noconfirm"],
|
|
152
151
|
"apt": ["sudo", "apt", "install", "-y"],
|
|
152
|
+
"winget": ["winget", "install", "-e", "--id"],
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
for mgr, cmd_prefix in managers.items():
|
|
@@ -159,7 +159,7 @@ class DependencyManager:
|
|
|
159
159
|
|
|
160
160
|
full_cmd = cmd_prefix + [pkg_name]
|
|
161
161
|
try:
|
|
162
|
-
subprocess.run(full_cmd, check=True)
|
|
162
|
+
subprocess.run(full_cmd, check=True, timeout=60)
|
|
163
163
|
console.print(f"[green]{i18n.t('setup.success', tool=name)}[/green]")
|
|
164
164
|
return True
|
|
165
165
|
except subprocess.CalledProcessError:
|
|
@@ -200,12 +200,14 @@ class QueueManager:
|
|
|
200
200
|
return node["url"]
|
|
201
201
|
return None
|
|
202
202
|
|
|
203
|
-
def _update_progress(self, item, progress=None, eta=None):
|
|
203
|
+
def _update_progress(self, item, progress=None, eta=None, speed=None):
|
|
204
204
|
updates = {}
|
|
205
205
|
if progress is not None:
|
|
206
206
|
updates["progress"] = progress
|
|
207
207
|
if eta is not None:
|
|
208
208
|
updates["eta"] = eta
|
|
209
|
+
if speed is not None:
|
|
210
|
+
updates["speed"] = speed
|
|
209
211
|
if updates:
|
|
210
212
|
self.db.update_queue_item(item["episode_id"], **updates)
|
|
211
213
|
|
|
@@ -239,7 +241,14 @@ class QueueManager:
|
|
|
239
241
|
|
|
240
242
|
match = re.search(r'\((\d+)%\)', line)
|
|
241
243
|
progress = int(match.group(1)) if match else None
|
|
242
|
-
|
|
244
|
+
|
|
245
|
+
# Parse speed (e.g., "DL:1.2MiB")
|
|
246
|
+
speed = None
|
|
247
|
+
speed_match = re.search(r'DL:([\d.]+[KMG]?i?B)', line)
|
|
248
|
+
if speed_match:
|
|
249
|
+
speed = speed_match.group(1) + "/s"
|
|
250
|
+
|
|
251
|
+
self._update_progress(item, progress=progress, eta=eta_part.strip(), speed=speed)
|
|
243
252
|
except:
|
|
244
253
|
pass
|
|
245
254
|
|
|
@@ -268,7 +277,14 @@ class QueueManager:
|
|
|
268
277
|
p_str = line.split("%")[0].split()[-1]
|
|
269
278
|
progress = float(p_str)
|
|
270
279
|
eta = line.split("ETA")[-1].strip() if "ETA" in line else None
|
|
271
|
-
|
|
280
|
+
|
|
281
|
+
# Parse speed (e.g., "at 1.5MiB/s")
|
|
282
|
+
speed = None
|
|
283
|
+
speed_match = re.search(r'at\s+([\d.]+[KMG]?i?B/s)', line)
|
|
284
|
+
if speed_match:
|
|
285
|
+
speed = speed_match.group(1)
|
|
286
|
+
|
|
287
|
+
self._update_progress(item, progress=progress, eta=eta, speed=speed)
|
|
272
288
|
except:
|
|
273
289
|
pass
|
|
274
290
|
if process.returncode != 0:
|
|
@@ -304,10 +320,19 @@ class QueueManager:
|
|
|
304
320
|
|
|
305
321
|
elapsed = time.time() - start_time
|
|
306
322
|
if elapsed > 0:
|
|
307
|
-
|
|
323
|
+
speed_bytes = downloaded / elapsed
|
|
308
324
|
remaining = total - downloaded
|
|
309
|
-
eta_s = remaining /
|
|
310
|
-
|
|
325
|
+
eta_s = remaining / speed_bytes if speed_bytes > 0 else 0
|
|
326
|
+
|
|
327
|
+
# Format speed
|
|
328
|
+
if speed_bytes >= 1024 * 1024:
|
|
329
|
+
speed_str = f"{speed_bytes / (1024*1024):.1f}MB/s"
|
|
330
|
+
elif speed_bytes >= 1024:
|
|
331
|
+
speed_str = f"{speed_bytes / 1024:.1f}KB/s"
|
|
332
|
+
else:
|
|
333
|
+
speed_str = f"{speed_bytes:.0f}B/s"
|
|
334
|
+
|
|
335
|
+
self._update_progress(item, progress=progress, eta=f"{int(eta_s)}s", speed=speed_str)
|
|
311
336
|
else:
|
|
312
337
|
with open(path, 'wb') as f:
|
|
313
338
|
for chunk in r.iter_content(chunk_size=8192):
|
|
@@ -236,10 +236,23 @@ class LocalLibrary:
|
|
|
236
236
|
return added
|
|
237
237
|
|
|
238
238
|
def smart_index_all(self):
|
|
239
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
240
|
+
|
|
241
|
+
sources = [s for s in self.get_all_sources() if s["available"]]
|
|
242
|
+
if not sources:
|
|
243
|
+
return 0
|
|
244
|
+
|
|
239
245
|
total = 0
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
246
|
+
with ThreadPoolExecutor(max_workers=min(4, len(sources))) as executor:
|
|
247
|
+
futures = {
|
|
248
|
+
executor.submit(self.smart_index_source, s["path"], s["name"]): s
|
|
249
|
+
for s in sources
|
|
250
|
+
}
|
|
251
|
+
for future in as_completed(futures):
|
|
252
|
+
try:
|
|
253
|
+
total += future.result()
|
|
254
|
+
except:
|
|
255
|
+
pass
|
|
243
256
|
return total
|
|
244
257
|
|
|
245
258
|
def index_all_sources(self):
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import subprocess
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
def send_notification(title: str, message: str):
|
|
6
|
+
threading.Thread(target=_send_notification_sync, args=(title, message), daemon=True).start()
|
|
7
|
+
|
|
8
|
+
def _send_notification_sync(title: str, message: str):
|
|
9
|
+
system = platform.system()
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
if system == "Windows":
|
|
13
|
+
_notify_windows(title, message)
|
|
14
|
+
elif system == "Darwin":
|
|
15
|
+
_notify_macos(title, message)
|
|
16
|
+
else:
|
|
17
|
+
_notify_linux(title, message)
|
|
18
|
+
except:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def _notify_windows(title: str, message: str):
|
|
22
|
+
try:
|
|
23
|
+
from winotify import Notification, audio
|
|
24
|
+
toast = Notification(
|
|
25
|
+
app_id="Weeb CLI",
|
|
26
|
+
title=title,
|
|
27
|
+
msg=message,
|
|
28
|
+
duration="short"
|
|
29
|
+
)
|
|
30
|
+
toast.set_audio(audio.Default, loop=False)
|
|
31
|
+
toast.show()
|
|
32
|
+
return
|
|
33
|
+
except ImportError:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from win10toast import ToastNotifier
|
|
38
|
+
toaster = ToastNotifier()
|
|
39
|
+
toaster.show_toast(title, message, duration=3, threaded=True)
|
|
40
|
+
return
|
|
41
|
+
except ImportError:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
ps_script = f'''
|
|
46
|
+
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
|
|
47
|
+
$template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)
|
|
48
|
+
$textNodes = $template.GetElementsByTagName("text")
|
|
49
|
+
$textNodes.Item(0).AppendChild($template.CreateTextNode("{title}")) | Out-Null
|
|
50
|
+
$textNodes.Item(1).AppendChild($template.CreateTextNode("{message}")) | Out-Null
|
|
51
|
+
$toast = [Windows.UI.Notifications.ToastNotification]::new($template)
|
|
52
|
+
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Weeb CLI").Show($toast)
|
|
53
|
+
'''
|
|
54
|
+
subprocess.run(["powershell", "-Command", ps_script], capture_output=True, timeout=5)
|
|
55
|
+
except:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def _notify_macos(title: str, message: str):
|
|
59
|
+
script = f'display notification "{message}" with title "{title}"'
|
|
60
|
+
subprocess.run(["osascript", "-e", script], capture_output=True)
|
|
61
|
+
|
|
62
|
+
def _notify_linux(title: str, message: str):
|
|
63
|
+
subprocess.run(["notify-send", title, message], capture_output=True)
|
|
@@ -155,8 +155,16 @@ def update_via_pip():
|
|
|
155
155
|
console.print(f"[red]{i18n.get('update.error')}: {e}[/red]")
|
|
156
156
|
return False
|
|
157
157
|
|
|
158
|
+
from weeb_cli.config import config
|
|
159
|
+
import time
|
|
160
|
+
|
|
158
161
|
def update_prompt():
|
|
162
|
+
last_check = float(config.get("last_update_check") or 0)
|
|
163
|
+
if time.time() - last_check < 86400: # 24 hours
|
|
164
|
+
return
|
|
165
|
+
|
|
159
166
|
is_available, latest_ver, assets = check_for_updates()
|
|
167
|
+
config.set("last_update_check", str(time.time()))
|
|
160
168
|
|
|
161
169
|
if not is_available:
|
|
162
170
|
return
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import questionary
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
import sys
|
|
4
|
+
from .header import show_header
|
|
5
|
+
from weeb_cli.i18n import i18n
|
|
6
|
+
from weeb_cli.commands.search import search_anime
|
|
7
|
+
from weeb_cli.commands.settings import open_settings
|
|
8
|
+
from weeb_cli.commands.watchlist import show_watchlist
|
|
9
|
+
from weeb_cli.commands.downloads import show_downloads, show_queue_live, manage_queue
|
|
10
|
+
from weeb_cli.services.downloader import queue_manager
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
def show_main_menu():
|
|
15
|
+
console.clear()
|
|
16
|
+
show_header("Weeb CLI", show_version=True, show_source=True)
|
|
17
|
+
|
|
18
|
+
opt_search = i18n.get("menu.options.search")
|
|
19
|
+
opt_downloads = i18n.get("menu.options.downloads")
|
|
20
|
+
opt_watchlist = i18n.get("menu.options.watchlist")
|
|
21
|
+
opt_settings = i18n.get("menu.options.settings")
|
|
22
|
+
opt_exit = i18n.get("menu.options.exit")
|
|
23
|
+
|
|
24
|
+
choices = [
|
|
25
|
+
opt_search,
|
|
26
|
+
opt_downloads,
|
|
27
|
+
opt_watchlist,
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
active_queue = [i for i in queue_manager.queue if i["status"] in ["pending", "processing"]]
|
|
31
|
+
opt_active = None
|
|
32
|
+
if active_queue or queue_manager.queue:
|
|
33
|
+
is_running = queue_manager.is_running()
|
|
34
|
+
status = i18n.get("downloads.queue_running") if is_running else i18n.get("downloads.queue_stopped")
|
|
35
|
+
opt_active = f"{i18n.get('downloads.active_downloads')} ({len(active_queue)}) - {status}"
|
|
36
|
+
choices.append(opt_active)
|
|
37
|
+
|
|
38
|
+
choices.extend([opt_settings, opt_exit])
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
selected = questionary.select(
|
|
42
|
+
i18n.get("menu.prompt"),
|
|
43
|
+
choices=choices,
|
|
44
|
+
pointer=">",
|
|
45
|
+
use_shortcuts=False,
|
|
46
|
+
style=questionary.Style([
|
|
47
|
+
('pointer', 'fg:cyan bold'),
|
|
48
|
+
('highlighted', 'fg:cyan'),
|
|
49
|
+
('selected', 'fg:cyan bold'),
|
|
50
|
+
])
|
|
51
|
+
).ask()
|
|
52
|
+
|
|
53
|
+
console.clear()
|
|
54
|
+
|
|
55
|
+
if selected == opt_search:
|
|
56
|
+
search_anime()
|
|
57
|
+
elif selected == opt_watchlist:
|
|
58
|
+
show_watchlist()
|
|
59
|
+
elif selected == opt_downloads:
|
|
60
|
+
show_downloads()
|
|
61
|
+
elif selected == opt_active:
|
|
62
|
+
show_active_downloads_menu()
|
|
63
|
+
elif selected == opt_settings:
|
|
64
|
+
open_settings()
|
|
65
|
+
elif selected == opt_exit or selected is None:
|
|
66
|
+
console.print(f"[yellow] {i18n.get('common.success')}...[/yellow]")
|
|
67
|
+
sys.exit(0)
|
|
68
|
+
|
|
69
|
+
show_main_menu()
|
|
70
|
+
|
|
71
|
+
except KeyboardInterrupt:
|
|
72
|
+
sys.exit(0)
|
|
73
|
+
|
|
74
|
+
def show_active_downloads_menu():
|
|
75
|
+
while True:
|
|
76
|
+
console.clear()
|
|
77
|
+
show_header(i18n.get("downloads.active_downloads"))
|
|
78
|
+
|
|
79
|
+
pending = queue_manager.get_pending_count()
|
|
80
|
+
is_running = queue_manager.is_running()
|
|
81
|
+
|
|
82
|
+
if pending > 0:
|
|
83
|
+
status = i18n.get("downloads.queue_running") if is_running else i18n.get("downloads.queue_stopped")
|
|
84
|
+
console.print(f"[cyan]{i18n.t('downloads.pending_count', count=pending)}[/cyan] - {status}\n")
|
|
85
|
+
|
|
86
|
+
opt_view = i18n.get("downloads.view_queue")
|
|
87
|
+
opt_start = i18n.get("downloads.start_queue")
|
|
88
|
+
opt_stop = i18n.get("downloads.stop_queue")
|
|
89
|
+
opt_clear = i18n.get("downloads.clear_completed")
|
|
90
|
+
|
|
91
|
+
choices = [opt_view]
|
|
92
|
+
if pending > 0:
|
|
93
|
+
if is_running:
|
|
94
|
+
choices.append(opt_stop)
|
|
95
|
+
else:
|
|
96
|
+
choices.append(opt_start)
|
|
97
|
+
choices.append(opt_clear)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
import time
|
|
101
|
+
action = questionary.select(
|
|
102
|
+
i18n.get("downloads.action_prompt"),
|
|
103
|
+
choices=choices,
|
|
104
|
+
pointer=">",
|
|
105
|
+
use_shortcuts=False
|
|
106
|
+
).ask()
|
|
107
|
+
|
|
108
|
+
if action is None:
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
if action == opt_view:
|
|
112
|
+
show_queue_live()
|
|
113
|
+
elif action == opt_start:
|
|
114
|
+
queue_manager.start_queue()
|
|
115
|
+
console.print(f"[green]{i18n.get('downloads.queue_started')}[/green]")
|
|
116
|
+
time.sleep(0.5)
|
|
117
|
+
elif action == opt_stop:
|
|
118
|
+
queue_manager.stop_queue()
|
|
119
|
+
console.print(f"[yellow]{i18n.get('downloads.queue_stopped')}[/yellow]")
|
|
120
|
+
time.sleep(0.5)
|
|
121
|
+
elif action == opt_clear:
|
|
122
|
+
queue_manager.clear_completed()
|
|
123
|
+
console.print(f"[green]{i18n.get('downloads.cleared')}[/green]")
|
|
124
|
+
time.sleep(0.5)
|
|
125
|
+
|
|
126
|
+
except KeyboardInterrupt:
|
|
127
|
+
return
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weeb-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Tarayıcı yok, reklam yok, dikkat dağıtıcı unsur yok. Sadece siz ve eşsiz bir anime izleme deneyimi.
|
|
5
5
|
Author-email: ewgsta <ewgst@proton.me>
|
|
6
6
|
License-Expression: CC-BY-NC-ND-4.0
|
|
@@ -23,6 +23,10 @@ Requires-Dist: pycryptodome
|
|
|
23
23
|
Requires-Dist: curl_cffi
|
|
24
24
|
Requires-Dist: appdirs
|
|
25
25
|
Requires-Dist: prompt_toolkit<3.0.50,>=3.0.36
|
|
26
|
+
Requires-Dist: beautifulsoup4
|
|
27
|
+
Requires-Dist: lxml
|
|
28
|
+
Requires-Dist: py7zr
|
|
29
|
+
Requires-Dist: winotify; sys_platform == "win32"
|
|
26
30
|
Dynamic: license-file
|
|
27
31
|
|
|
28
32
|
<p align="center">
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import platform
|
|
2
|
-
import subprocess
|
|
3
|
-
import threading
|
|
4
|
-
|
|
5
|
-
def send_notification(title: str, message: str):
|
|
6
|
-
threading.Thread(target=_send_notification_sync, args=(title, message), daemon=True).start()
|
|
7
|
-
|
|
8
|
-
def _send_notification_sync(title: str, message: str):
|
|
9
|
-
system = platform.system()
|
|
10
|
-
|
|
11
|
-
try:
|
|
12
|
-
if system == "Windows":
|
|
13
|
-
_notify_windows(title, message)
|
|
14
|
-
elif system == "Darwin":
|
|
15
|
-
_notify_macos(title, message)
|
|
16
|
-
else:
|
|
17
|
-
_notify_linux(title, message)
|
|
18
|
-
except:
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
def _notify_windows(title: str, message: str):
|
|
22
|
-
try:
|
|
23
|
-
from win10toast import ToastNotifier
|
|
24
|
-
toaster = ToastNotifier()
|
|
25
|
-
toaster.show_toast(title, message, duration=5, threaded=True)
|
|
26
|
-
return
|
|
27
|
-
except ImportError:
|
|
28
|
-
pass
|
|
29
|
-
|
|
30
|
-
try:
|
|
31
|
-
import ctypes
|
|
32
|
-
ctypes.windll.user32.MessageBoxW(0, message, title, 0x40)
|
|
33
|
-
except:
|
|
34
|
-
pass
|
|
35
|
-
|
|
36
|
-
def _notify_macos(title: str, message: str):
|
|
37
|
-
script = f'display notification "{message}" with title "{title}"'
|
|
38
|
-
subprocess.run(["osascript", "-e", script], capture_output=True)
|
|
39
|
-
|
|
40
|
-
def _notify_linux(title: str, message: str):
|
|
41
|
-
subprocess.run(["notify-send", title, message], capture_output=True)
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import questionary
|
|
2
|
-
from rich.console import Console
|
|
3
|
-
import sys
|
|
4
|
-
from .header import show_header
|
|
5
|
-
from weeb_cli.i18n import i18n
|
|
6
|
-
from weeb_cli.commands.search import search_anime
|
|
7
|
-
from weeb_cli.commands.settings import open_settings
|
|
8
|
-
from weeb_cli.commands.watchlist import show_watchlist
|
|
9
|
-
from weeb_cli.commands.downloads import show_downloads
|
|
10
|
-
|
|
11
|
-
console = Console()
|
|
12
|
-
|
|
13
|
-
def show_main_menu():
|
|
14
|
-
console.clear()
|
|
15
|
-
show_header("Weeb API", show_version=True, show_source=True)
|
|
16
|
-
|
|
17
|
-
opt_search = i18n.get("menu.options.search")
|
|
18
|
-
opt_downloads = i18n.get("menu.options.downloads")
|
|
19
|
-
opt_watchlist = i18n.get("menu.options.watchlist")
|
|
20
|
-
opt_settings = i18n.get("menu.options.settings")
|
|
21
|
-
opt_exit = i18n.get("menu.options.exit")
|
|
22
|
-
|
|
23
|
-
try:
|
|
24
|
-
selected = questionary.select(
|
|
25
|
-
i18n.get("menu.prompt"),
|
|
26
|
-
choices=[
|
|
27
|
-
opt_search,
|
|
28
|
-
opt_downloads,
|
|
29
|
-
opt_watchlist,
|
|
30
|
-
opt_settings,
|
|
31
|
-
opt_exit
|
|
32
|
-
],
|
|
33
|
-
pointer=">",
|
|
34
|
-
use_shortcuts=False,
|
|
35
|
-
style=questionary.Style([
|
|
36
|
-
('pointer', 'fg:cyan bold'),
|
|
37
|
-
('highlighted', 'fg:cyan'),
|
|
38
|
-
('selected', 'fg:cyan bold'),
|
|
39
|
-
])
|
|
40
|
-
).ask()
|
|
41
|
-
|
|
42
|
-
console.clear()
|
|
43
|
-
|
|
44
|
-
if selected == opt_search:
|
|
45
|
-
search_anime()
|
|
46
|
-
elif selected == opt_watchlist:
|
|
47
|
-
show_watchlist()
|
|
48
|
-
elif selected == opt_downloads:
|
|
49
|
-
show_downloads()
|
|
50
|
-
elif selected == opt_settings:
|
|
51
|
-
open_settings()
|
|
52
|
-
elif selected == opt_exit or selected is None:
|
|
53
|
-
console.print(f"[yellow] {i18n.get('common.success')}...[/yellow]")
|
|
54
|
-
sys.exit(0)
|
|
55
|
-
|
|
56
|
-
show_main_menu()
|
|
57
|
-
|
|
58
|
-
except KeyboardInterrupt:
|
|
59
|
-
sys.exit(0)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|