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.
Files changed (54) hide show
  1. {weeb_cli-2.1.5/weeb_cli.egg-info → weeb_cli-2.2.0}/PKG-INFO +5 -1
  2. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/pyproject.toml +6 -2
  3. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/commands/downloads.py +12 -6
  4. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/i18n.py +3 -3
  5. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/dependency_manager.py +3 -3
  6. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/downloader.py +31 -6
  7. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/local_library.py +16 -3
  8. weeb_cli-2.2.0/weeb_cli/services/notifier.py +63 -0
  9. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/updater.py +8 -0
  10. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/ui/header.py +1 -1
  11. weeb_cli-2.2.0/weeb_cli/ui/menu.py +127 -0
  12. {weeb_cli-2.1.5 → weeb_cli-2.2.0/weeb_cli.egg-info}/PKG-INFO +5 -1
  13. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli.egg-info/requires.txt +6 -0
  14. weeb_cli-2.1.5/weeb_cli/services/notifier.py +0 -41
  15. weeb_cli-2.1.5/weeb_cli/ui/menu.py +0 -59
  16. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/LICENSE +0 -0
  17. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/README.md +0 -0
  18. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/setup.cfg +0 -0
  19. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/__init__.py +0 -0
  20. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/__main__.py +0 -0
  21. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/commands/search.py +0 -0
  22. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/commands/settings.py +0 -0
  23. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/commands/setup.py +0 -0
  24. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/commands/watchlist.py +0 -0
  25. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/config.py +0 -0
  26. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/locales/en.json +0 -0
  27. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/locales/tr.json +0 -0
  28. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/main.py +0 -0
  29. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/__init__.py +0 -0
  30. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/allanime.py +0 -0
  31. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/animecix.py +0 -0
  32. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/anizle.py +0 -0
  33. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/base.py +0 -0
  34. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/extractors/__init__.py +0 -0
  35. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/extractors/megacloud.py +0 -0
  36. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/hianime.py +0 -0
  37. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/registry.py +0 -0
  38. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/providers/turkanime.py +0 -0
  39. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/__init__.py +0 -0
  40. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/database.py +0 -0
  41. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/details.py +0 -0
  42. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/logger.py +0 -0
  43. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/player.py +0 -0
  44. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/progress.py +0 -0
  45. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/scraper.py +0 -0
  46. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/search.py +0 -0
  47. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/tracker.py +0 -0
  48. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/services/watch.py +0 -0
  49. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/ui/__init__.py +0 -0
  50. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli/ui/prompt.py +0 -0
  51. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli.egg-info/SOURCES.txt +0 -0
  52. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli.egg-info/dependency_links.txt +0 -0
  53. {weeb_cli-2.1.5 → weeb_cli-2.2.0}/weeb_cli.egg-info/entry_points.txt +0 -0
  54. {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.1.5
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.1.5"
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=30)
424
- table.add_column(i18n.get("details.episode"), justify="right", width=5)
425
- table.add_column(i18n.get("downloads.status"), width=15)
426
- table.add_column(i18n.get("downloads.progress"), width=20)
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'][:28]}[/{style}]",
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
- self._update_progress(item, progress=progress, eta=eta_part.strip())
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
- self._update_progress(item, progress=progress, eta=eta)
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
- speed = downloaded / elapsed
323
+ speed_bytes = downloaded / elapsed
308
324
  remaining = total - downloaded
309
- eta_s = remaining / speed
310
- self._update_progress(item, progress=progress, eta=f"{int(eta_s)}s")
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
- for source in self.get_all_sources():
241
- if source["available"]:
242
- total += self.smart_index_source(source["path"], source["name"])
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
@@ -6,7 +6,7 @@ from weeb_cli import __version__
6
6
 
7
7
  console = Console()
8
8
 
9
- def show_header(title="Weeb API", show_version=False, show_source=False):
9
+ def show_header(title="Weeb CLI", show_version=False, show_source=False):
10
10
  console.clear()
11
11
 
12
12
  text = Text()
@@ -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.1.5
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">
@@ -7,3 +7,9 @@ pycryptodome
7
7
  curl_cffi
8
8
  appdirs
9
9
  prompt_toolkit<3.0.50,>=3.0.36
10
+ beautifulsoup4
11
+ lxml
12
+ py7zr
13
+
14
+ [:sys_platform == "win32"]
15
+ winotify
@@ -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