weeb-cli 0.2.0__tar.gz → 1.0.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 (53) hide show
  1. {weeb_cli-0.2.0/weeb_cli.egg-info → weeb_cli-1.0.0}/PKG-INFO +5 -1
  2. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/pyproject.toml +6 -2
  3. weeb_cli-1.0.0/weeb_cli/__init__.py +1 -0
  4. weeb_cli-1.0.0/weeb_cli/commands/downloads.py +126 -0
  5. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/commands/search.py +111 -28
  6. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/commands/settings.py +27 -11
  7. weeb_cli-1.0.0/weeb_cli/commands/watchlist.py +130 -0
  8. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/config.py +2 -3
  9. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/locales/en.json +62 -3
  10. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/locales/tr.json +62 -3
  11. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/main.py +20 -0
  12. weeb_cli-1.0.0/weeb_cli/providers/__init__.py +21 -0
  13. weeb_cli-1.0.0/weeb_cli/providers/animecix.py +276 -0
  14. weeb_cli-1.0.0/weeb_cli/providers/anizle.py +450 -0
  15. weeb_cli-1.0.0/weeb_cli/providers/base.py +98 -0
  16. weeb_cli-1.0.0/weeb_cli/providers/registry.py +45 -0
  17. weeb_cli-1.0.0/weeb_cli/providers/turkanime.py +499 -0
  18. weeb_cli-1.0.0/weeb_cli/services/details.py +32 -0
  19. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/services/downloader.py +102 -35
  20. weeb_cli-1.0.0/weeb_cli/services/progress.py +136 -0
  21. weeb_cli-1.0.0/weeb_cli/services/scraper.py +91 -0
  22. weeb_cli-1.0.0/weeb_cli/services/search.py +16 -0
  23. weeb_cli-1.0.0/weeb_cli/services/updater.py +199 -0
  24. weeb_cli-1.0.0/weeb_cli/services/watch.py +19 -0
  25. {weeb_cli-0.2.0 → weeb_cli-1.0.0/weeb_cli.egg-info}/PKG-INFO +5 -1
  26. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli.egg-info/SOURCES.txt +7 -1
  27. weeb_cli-1.0.0/weeb_cli.egg-info/requires.txt +8 -0
  28. weeb_cli-0.2.0/weeb_cli/__init__.py +0 -1
  29. weeb_cli-0.2.0/weeb_cli/commands/downloads.py +0 -70
  30. weeb_cli-0.2.0/weeb_cli/commands/watchlist.py +0 -14
  31. weeb_cli-0.2.0/weeb_cli/services/api.py +0 -46
  32. weeb_cli-0.2.0/weeb_cli/services/details.py +0 -4
  33. weeb_cli-0.2.0/weeb_cli/services/progress.py +0 -60
  34. weeb_cli-0.2.0/weeb_cli/services/search.py +0 -4
  35. weeb_cli-0.2.0/weeb_cli/services/updater.py +0 -54
  36. weeb_cli-0.2.0/weeb_cli/services/watch.py +0 -4
  37. weeb_cli-0.2.0/weeb_cli.egg-info/requires.txt +0 -4
  38. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/LICENSE +0 -0
  39. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/README.md +0 -0
  40. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/setup.cfg +0 -0
  41. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/__main__.py +0 -0
  42. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/commands/setup.py +0 -0
  43. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/i18n.py +0 -0
  44. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/services/__init__.py +0 -0
  45. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/services/dependency_manager.py +0 -0
  46. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/services/player.py +0 -0
  47. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/ui/__init__.py +0 -0
  48. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/ui/header.py +0 -0
  49. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/ui/menu.py +0 -0
  50. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/ui/prompt.py +0 -0
  51. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli.egg-info/dependency_links.txt +0 -0
  52. {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli.egg-info/entry_points.txt +0 -0
  53. {weeb_cli-0.2.0 → weeb_cli-1.0.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: 0.2.0
3
+ Version: 1.0.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
@@ -18,6 +18,10 @@ Requires-Dist: typer[all]
18
18
  Requires-Dist: rich
19
19
  Requires-Dist: questionary
20
20
  Requires-Dist: requests
21
+ Requires-Dist: packaging
22
+ Requires-Dist: pycryptodome
23
+ Requires-Dist: curl_cffi
24
+ Requires-Dist: appdirs
21
25
  Dynamic: license-file
22
26
 
23
27
  <div align="center">
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "weeb-cli"
7
- version = "0.2.0"
7
+ version = "1.0.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" }]
@@ -20,7 +20,11 @@ dependencies = [
20
20
  "typer[all]",
21
21
  "rich",
22
22
  "questionary",
23
- "requests"
23
+ "requests",
24
+ "packaging",
25
+ "pycryptodome",
26
+ "curl_cffi",
27
+ "appdirs"
24
28
  ]
25
29
 
26
30
  [tool.setuptools.packages.find]
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
@@ -0,0 +1,126 @@
1
+ import questionary
2
+ from rich.console import Console
3
+ from rich.live import Live
4
+ from rich.table import Table
5
+ from weeb_cli.services.downloader import queue_manager
6
+ from weeb_cli.i18n import i18n
7
+ from weeb_cli.ui.header import show_header
8
+ import time
9
+
10
+ console = Console()
11
+
12
+ def show_downloads():
13
+ while True:
14
+ console.clear()
15
+ show_header(i18n.get("downloads.title"))
16
+
17
+ pending = queue_manager.get_pending_count()
18
+ is_running = queue_manager.is_running()
19
+
20
+ if pending > 0:
21
+ status = i18n.get("downloads.queue_running") if is_running else i18n.get("downloads.queue_stopped")
22
+ console.print(f"[cyan]{i18n.t('downloads.pending_count', count=pending)}[/cyan] - {status}\n")
23
+
24
+ queue = queue_manager.queue
25
+
26
+ if not queue:
27
+ console.print(f"[dim]{i18n.get('downloads.empty')}[/dim]")
28
+ try:
29
+ input(i18n.get("common.continue_key"))
30
+ except KeyboardInterrupt:
31
+ pass
32
+ return
33
+
34
+ opt_view = i18n.get("downloads.view_queue")
35
+ opt_start = i18n.get("downloads.start_queue")
36
+ opt_stop = i18n.get("downloads.stop_queue")
37
+ opt_clear = i18n.get("downloads.clear_completed")
38
+
39
+ choices = [opt_view]
40
+ if pending > 0:
41
+ if is_running:
42
+ choices.append(opt_stop)
43
+ else:
44
+ choices.append(opt_start)
45
+ choices.append(opt_clear)
46
+
47
+ try:
48
+ action = questionary.select(
49
+ i18n.get("downloads.action_prompt"),
50
+ choices=choices,
51
+ pointer=">",
52
+ use_shortcuts=False
53
+ ).ask()
54
+
55
+ if action is None:
56
+ return
57
+
58
+ if action == opt_view:
59
+ show_queue_live()
60
+ elif action == opt_start:
61
+ queue_manager.start_queue()
62
+ console.print(f"[green]{i18n.get('downloads.queue_started')}[/green]")
63
+ time.sleep(0.5)
64
+ elif action == opt_stop:
65
+ queue_manager.stop_queue()
66
+ console.print(f"[yellow]{i18n.get('downloads.queue_stopped')}[/yellow]")
67
+ time.sleep(0.5)
68
+ elif action == opt_clear:
69
+ queue_manager.queue = [i for i in queue_manager.queue if i["status"] in ["pending", "processing"]]
70
+ queue_manager._save_queue()
71
+ console.print(f"[green]{i18n.get('downloads.cleared')}[/green]")
72
+ time.sleep(0.5)
73
+
74
+ except KeyboardInterrupt:
75
+ return
76
+
77
+ def show_queue_live():
78
+ def generate_table():
79
+ table = Table(show_header=True, header_style="bold cyan")
80
+ table.add_column(i18n.get("watchlist.anime_title"), width=30)
81
+ table.add_column(i18n.get("details.episode"), justify="right", width=5)
82
+ table.add_column(i18n.get("downloads.status"), width=15)
83
+ table.add_column(i18n.get("downloads.progress"), width=20)
84
+
85
+ active = [i for i in queue_manager.queue if i["status"] == "processing"]
86
+ pending = [i for i in queue_manager.queue if i["status"] == "pending"]
87
+ finished = [i for i in queue_manager.queue if i["status"] in ["completed", "failed"]]
88
+ finished = finished[-10:]
89
+
90
+ display_list = active + pending + finished
91
+
92
+ for item in display_list:
93
+ status = item["status"]
94
+ style = "white"
95
+ if status == "processing":
96
+ style = "cyan"
97
+ elif status == "completed":
98
+ style = "green"
99
+ elif status == "failed":
100
+ style = "red"
101
+ elif status == "pending":
102
+ style = "dim"
103
+
104
+ progress = item.get("progress", 0)
105
+ bars = int(progress / 5)
106
+ bar_str = "█" * bars + "░" * (20 - bars)
107
+
108
+ status_text = i18n.get(f"downloads.status_{status}", status.upper())
109
+ p_text = f"{progress}%" if status == "processing" else ""
110
+
111
+ table.add_row(
112
+ f"[{style}]{item['anime_title'][:28]}[/{style}]",
113
+ f"{item['episode_number']}",
114
+ f"[{style}]{status_text}[/{style}]",
115
+ f"[{style}]{bar_str} {p_text}[/{style}]"
116
+ )
117
+
118
+ return table
119
+
120
+ try:
121
+ with Live(generate_table(), refresh_per_second=1) as live:
122
+ while True:
123
+ live.update(generate_table())
124
+ time.sleep(1)
125
+ except KeyboardInterrupt:
126
+ return
@@ -8,15 +8,36 @@ from weeb_cli.services.watch import get_streams
8
8
  from weeb_cli.services.player import player
9
9
  from weeb_cli.services.progress import progress_tracker
10
10
  from weeb_cli.services.downloader import queue_manager
11
+ from weeb_cli.services.scraper import scraper
11
12
  import time
12
13
 
13
14
  console = Console()
14
15
 
16
+ PLAYER_PRIORITY = [
17
+ "ALUCARD", "AMATERASU", "SIBNET", "MP4UPLOAD", "UQLOAD",
18
+ "MAIL", "DAILYMOTION", "SENDVID", "ODNOKLASSNIKI", "VK",
19
+ "VIDMOLY", "YOURUPLOAD", "MYVI", "GDRIVE", "PIXELDRAIN", "HDVID", "YADISK"
20
+ ]
21
+
22
+ def _get_player_priority(server_name: str) -> int:
23
+ server_upper = server_name.upper()
24
+ for i, p in enumerate(PLAYER_PRIORITY):
25
+ if p in server_upper:
26
+ return i
27
+ return 999
28
+
29
+ def _sort_streams(streams: list) -> list:
30
+ return sorted(streams, key=lambda s: _get_player_priority(s.get("server", "")))
31
+
15
32
  def search_anime():
16
33
  while True:
17
34
  console.clear()
18
35
  show_header(i18n.get("menu.options.search"), show_source=True)
19
36
 
37
+ history = progress_tracker.get_search_history()
38
+ if history:
39
+ console.print(f"[dim]{i18n.get('search.recent')}: {', '.join(history[:5])}[/dim]\n", justify="left")
40
+
20
41
  try:
21
42
  query = questionary.text(
22
43
  i18n.get("search.prompt") + ":",
@@ -34,6 +55,8 @@ def search_anime():
34
55
  if not query.strip():
35
56
  continue
36
57
 
58
+ progress_tracker.add_search_history(query.strip())
59
+
37
60
  with console.status(i18n.get("search.searching"), spinner="dots"):
38
61
  data = search(query)
39
62
 
@@ -129,13 +152,19 @@ def show_anime_details(anime):
129
152
  time.sleep(1)
130
153
  return
131
154
 
155
+ from weeb_cli.config import config
132
156
  desc = details.get("description") or details.get("synopsis") or details.get("desc")
133
- if desc:
134
- console.print(f"\n[dim]{desc[:300]}...[/dim]\n", justify="left")
157
+ show_desc = config.get("show_description", True)
135
158
 
136
159
  opt_watch = i18n.get("details.watch")
137
160
  opt_dl = i18n.get("details.download")
138
161
 
162
+ console.clear()
163
+ show_header(details.get("title", ""))
164
+
165
+ if show_desc and desc:
166
+ console.print(f"\n[dim]{desc}[/dim]\n", justify="left")
167
+
139
168
  try:
140
169
  action = questionary.select(
141
170
  i18n.get("details.action_prompt"),
@@ -220,27 +249,56 @@ def handle_watch_flow(slug, details):
220
249
 
221
250
  with console.status(i18n.get("common.processing"), spinner="dots"):
222
251
  stream_resp = get_streams(slug, ep_id)
223
- stream_url = None
252
+
253
+ streams_list = []
224
254
  if stream_resp and isinstance(stream_resp, dict):
225
- data_node = stream_resp
226
- for _ in range(3):
227
- if "data" in data_node and isinstance(data_node["data"], (dict, list)):
228
- data_node = data_node["data"]
229
- else:
230
- break
231
-
232
- sources = None
233
- if isinstance(data_node, list):
234
- sources = data_node
235
- elif isinstance(data_node, dict):
236
- sources = data_node.get("links") or data_node.get("sources")
237
-
238
- if sources and isinstance(sources, list):
239
- first = sources[0]
240
- if isinstance(first, dict):
241
- stream_url = first.get("url")
242
- elif isinstance(data_node, dict) and "url" in data_node:
243
- stream_url = data_node["url"]
255
+ data_node = stream_resp
256
+ for _ in range(3):
257
+ if "data" in data_node and isinstance(data_node["data"], (dict, list)):
258
+ data_node = data_node["data"]
259
+ else:
260
+ break
261
+
262
+ sources = None
263
+ if isinstance(data_node, list):
264
+ sources = data_node
265
+ elif isinstance(data_node, dict):
266
+ sources = data_node.get("links") or data_node.get("sources")
267
+
268
+ if sources and isinstance(sources, list):
269
+ streams_list = sources
270
+
271
+ if not streams_list:
272
+ error_msg = i18n.get('details.stream_not_found')
273
+ if scraper.last_error:
274
+ error_msg += f" [{scraper.last_error}]"
275
+ console.print(f"[red]{error_msg}[/red]")
276
+ time.sleep(1.5)
277
+ continue
278
+
279
+ streams_list = _sort_streams(streams_list)
280
+
281
+ stream_choices = []
282
+ for idx, s in enumerate(streams_list):
283
+ server = s.get("server", "Unknown")
284
+ quality = s.get("quality", "auto")
285
+ label = f"{server} ({quality})"
286
+ stream_choices.append(questionary.Choice(label, value=s))
287
+
288
+ if len(streams_list) == 1:
289
+ selected_stream = streams_list[0]
290
+ else:
291
+ selected_stream = questionary.select(
292
+ i18n.get("details.select_source"),
293
+ choices=stream_choices,
294
+ pointer=">",
295
+ use_shortcuts=False
296
+ ).ask()
297
+
298
+ if selected_stream is None:
299
+ continue
300
+
301
+ stream_url = selected_stream.get("url")
244
302
 
245
303
  if not stream_url:
246
304
  console.print(f"[red]{i18n.get('details.stream_not_found')}[/red]")
@@ -261,7 +319,13 @@ def handle_watch_flow(slug, details):
261
319
  ans = questionary.confirm(i18n.get("details.mark_watched")).ask()
262
320
  if ans:
263
321
  n = int(ep_num)
264
- progress_tracker.mark_watched(slug, n)
322
+ total_eps = details.get("total_episodes") or len(episodes)
323
+ progress_tracker.mark_watched(
324
+ slug,
325
+ n,
326
+ title=details.get("title"),
327
+ total_episodes=total_eps
328
+ )
265
329
 
266
330
  completed_ids.add(n)
267
331
  if n >= next_ep_num:
@@ -332,14 +396,33 @@ def handle_download_flow(slug, details):
332
396
 
333
397
  if not selected_eps:
334
398
  return
335
-
336
- console.print(f"[green]{i18n.get('details.download_options.started')}[/green]")
337
- console.print(f"[dim]{i18n.t('details.queueing', count=len(selected_eps))}[/dim]")
338
399
 
339
400
  anime_title = details.get("title") or "Unknown Anime"
340
- queue_manager.add_to_queue(anime_title, selected_eps, slug)
341
401
 
342
- time.sleep(1.5)
402
+ opt_now = i18n.get("downloads.start_now")
403
+ opt_queue = i18n.get("downloads.add_to_queue")
404
+
405
+ action = questionary.select(
406
+ i18n.get("downloads.action_prompt"),
407
+ choices=[opt_now, opt_queue],
408
+ pointer=">",
409
+ use_shortcuts=False
410
+ ).ask()
411
+
412
+ if action is None:
413
+ return
414
+
415
+ added = queue_manager.add_to_queue(anime_title, selected_eps, slug)
416
+
417
+ if added > 0:
418
+ console.print(f"[green]{i18n.t('downloads.queued', count=added)}[/green]")
419
+
420
+ if action == opt_now:
421
+ queue_manager.start_queue()
422
+ else:
423
+ console.print(f"[yellow]{i18n.get('downloads.already_in_queue')}[/yellow]")
424
+
425
+ time.sleep(1)
343
426
 
344
427
  except KeyboardInterrupt:
345
428
  return
@@ -44,17 +44,19 @@ def open_settings():
44
44
 
45
45
  aria2_state = i18n.get("common.enabled") if config.get("aria2_enabled") else i18n.get("common.disabled")
46
46
  ytdlp_state = i18n.get("common.enabled") if config.get("ytdlp_enabled") else i18n.get("common.disabled")
47
+ desc_state = i18n.get("common.enabled") if config.get("show_description", True) else i18n.get("common.disabled")
47
48
 
48
49
  opt_lang = i18n.get("settings.language")
49
50
  opt_source = f"{i18n.get('settings.source')} [{display_source}]"
50
51
  opt_download = i18n.get("settings.download_settings")
52
+ opt_desc = f"{i18n.get('settings.show_description')} [{desc_state}]"
51
53
  opt_aria2 = f"{i18n.get('settings.aria2')} [{aria2_state}]"
52
54
  opt_ytdlp = f"{i18n.get('settings.ytdlp')} [{ytdlp_state}]"
53
55
 
54
56
  opt_aria2_conf = f" ↳ {i18n.get('settings.aria2_config')}"
55
57
  opt_ytdlp_conf = f" ↳ {i18n.get('settings.ytdlp_config')}"
56
58
 
57
- choices = [opt_lang, opt_source, opt_download, opt_aria2]
59
+ choices = [opt_lang, opt_source, opt_download, opt_desc, opt_aria2]
58
60
  if config.get("aria2_enabled"):
59
61
  choices.append(opt_aria2_conf)
60
62
 
@@ -83,6 +85,8 @@ def open_settings():
83
85
  change_source()
84
86
  elif answer == opt_download:
85
87
  download_settings_menu()
88
+ elif answer == opt_desc:
89
+ toggle_description()
86
90
  elif answer == opt_aria2:
87
91
  toggle_config("aria2_enabled", "Aria2")
88
92
  elif answer == opt_aria2_conf:
@@ -94,7 +98,16 @@ def open_settings():
94
98
  elif answer is None:
95
99
  return
96
100
 
101
+ def toggle_description():
102
+ current = config.get("show_description", True)
103
+ config.set("show_description", not current)
104
+ msg_key = "settings.toggle_on" if not current else "settings.toggle_off"
105
+ console.print(f"[green]{i18n.t(msg_key, tool=i18n.get('settings.show_description'))}[/green]")
106
+ time.sleep(0.5)
107
+
97
108
  def change_language():
109
+ from weeb_cli.services.scraper import scraper
110
+
98
111
  langs = {"Türkçe": "tr", "English": "en"}
99
112
  try:
100
113
  selected = questionary.select(
@@ -108,8 +121,10 @@ def change_language():
108
121
  lang_code = langs[selected]
109
122
  i18n.set_language(lang_code)
110
123
 
111
- new_source = "local" if lang_code == "tr" else "hianime"
112
- config.set("scraping_source", new_source)
124
+ # Dil için varsayılan kaynağı ayarla
125
+ sources = scraper.get_sources_for_lang(lang_code)
126
+ if sources:
127
+ config.set("scraping_source", sources[0])
113
128
 
114
129
  console.print(f"[green]{i18n.get('settings.language_changed')}[/green]")
115
130
  time.sleep(1)
@@ -117,13 +132,15 @@ def change_language():
117
132
  pass
118
133
 
119
134
  def change_source():
120
- current_lang = config.get("language")
135
+ from weeb_cli.services.scraper import scraper
136
+
137
+ current_lang = config.get("language", "tr")
138
+ sources = scraper.get_sources_for_lang(current_lang)
121
139
 
122
- sources = []
123
- if current_lang == "tr":
124
- sources = ["weeb", "animecix", "turkanime", "anizle"]
125
- else:
126
- sources = ["hianime", "allanime"]
140
+ if not sources:
141
+ console.print(f"[yellow]{i18n.get('settings.no_sources')}[/yellow]")
142
+ time.sleep(1)
143
+ return
127
144
 
128
145
  try:
129
146
  selected = questionary.select(
@@ -134,8 +151,7 @@ def change_source():
134
151
  ).ask()
135
152
 
136
153
  if selected:
137
- save_val = "local" if selected == "Weeb" else selected
138
- config.set("scraping_source", save_val)
154
+ config.set("scraping_source", selected)
139
155
  console.print(f"[green]{i18n.t('settings.source_changed', source=selected)}[/green]")
140
156
  time.sleep(1)
141
157
  except KeyboardInterrupt:
@@ -0,0 +1,130 @@
1
+ import questionary
2
+ from rich.console import Console
3
+ from rich.table import Table
4
+ from weeb_cli.i18n import i18n
5
+ from weeb_cli.ui.header import show_header
6
+ from weeb_cli.services.progress import progress_tracker
7
+
8
+ console = Console()
9
+
10
+ def show_watchlist():
11
+ while True:
12
+ console.clear()
13
+ show_header(i18n.get("menu.options.watchlist"))
14
+
15
+ stats = progress_tracker.get_stats()
16
+
17
+ console.print(f"[cyan]{i18n.get('watchlist.total_anime')}:[/cyan] {stats['total_anime']}", justify="left")
18
+ console.print(f"[cyan]{i18n.get('watchlist.total_episodes')}:[/cyan] {stats['total_episodes']}", justify="left")
19
+ console.print(f"[cyan]{i18n.get('watchlist.total_hours')}:[/cyan] {stats['total_hours']}h", justify="left")
20
+
21
+ if stats['last_watched']:
22
+ last = stats['last_watched']
23
+ console.print(f"\n[dim]{i18n.get('watchlist.last_watched')}:[/dim] {last['title']} - {i18n.get('details.episode')} {last['last_watched']}", justify="left")
24
+
25
+ console.print("")
26
+
27
+ opt_completed = i18n.get("watchlist.completed")
28
+ opt_in_progress = i18n.get("watchlist.in_progress")
29
+
30
+ try:
31
+ choice = questionary.select(
32
+ i18n.get("watchlist.select_category"),
33
+ choices=[opt_in_progress, opt_completed],
34
+ pointer=">",
35
+ use_shortcuts=False
36
+ ).ask()
37
+
38
+ if choice is None:
39
+ return
40
+
41
+ if choice == opt_completed:
42
+ show_completed_list()
43
+ elif choice == opt_in_progress:
44
+ show_in_progress_list()
45
+
46
+ except KeyboardInterrupt:
47
+ return
48
+
49
+ def show_completed_list():
50
+ console.clear()
51
+ show_header(i18n.get("watchlist.completed"))
52
+
53
+ completed = progress_tracker.get_completed_anime()
54
+
55
+ if not completed:
56
+ console.print(f"[dim]{i18n.get('watchlist.no_completed')}[/dim]")
57
+ try:
58
+ input(i18n.get("common.continue_key"))
59
+ except KeyboardInterrupt:
60
+ pass
61
+ return
62
+
63
+ table = Table(show_header=True, header_style="bold cyan")
64
+ table.add_column("#", width=4)
65
+ table.add_column(i18n.get("watchlist.anime_title"), width=40)
66
+ table.add_column(i18n.get("watchlist.episodes_watched"), width=15, justify="center")
67
+
68
+ for i, anime in enumerate(completed, 1):
69
+ watched = len(anime.get("completed", []))
70
+ total = anime.get("total_episodes", watched)
71
+ table.add_row(
72
+ str(i),
73
+ anime.get("title", anime["slug"]),
74
+ f"[green]{watched}/{total}[/green]"
75
+ )
76
+
77
+ console.print(table)
78
+
79
+ try:
80
+ input(i18n.get("common.continue_key"))
81
+ except KeyboardInterrupt:
82
+ pass
83
+
84
+ def show_in_progress_list():
85
+ from weeb_cli.commands.search import show_anime_details
86
+
87
+ console.clear()
88
+ show_header(i18n.get("watchlist.in_progress"))
89
+
90
+ in_progress = progress_tracker.get_in_progress_anime()
91
+
92
+ if not in_progress:
93
+ console.print(f"[dim]{i18n.get('watchlist.no_in_progress')}[/dim]")
94
+ try:
95
+ input(i18n.get("common.continue_key"))
96
+ except KeyboardInterrupt:
97
+ pass
98
+ return
99
+
100
+ choices = []
101
+ for anime in in_progress:
102
+ watched = len(anime.get("completed", []))
103
+ total = anime.get("total_episodes", 0)
104
+ total_str = str(total) if total > 0 else "?"
105
+ title = anime.get("title", anime["slug"])
106
+ next_ep = anime.get("last_watched", 0) + 1
107
+ choices.append(questionary.Choice(
108
+ title=f"{title} [{watched}/{total_str}] - {i18n.get('watchlist.next')}: {next_ep}",
109
+ value=anime
110
+ ))
111
+
112
+ try:
113
+ selected = questionary.select(
114
+ i18n.get("watchlist.select_anime"),
115
+ choices=choices,
116
+ pointer=">",
117
+ use_shortcuts=False
118
+ ).ask()
119
+
120
+ if selected:
121
+ anime_data = {
122
+ "id": selected["slug"],
123
+ "slug": selected["slug"],
124
+ "title": selected.get("title", selected["slug"]),
125
+ "name": selected.get("title", selected["slug"])
126
+ }
127
+ show_anime_details(anime_data)
128
+
129
+ except KeyboardInterrupt:
130
+ pass
@@ -14,9 +14,8 @@ DEFAULT_CONFIG = {
14
14
  "max_concurrent_downloads": 3,
15
15
  "download_dir": os.path.join(os.getcwd(), "weeb-downloads"),
16
16
  "ytdlp_format": "bestvideo+bestaudio/best",
17
- "scraping_source": "",
18
- "dev_mode": False,
19
- "api_url": "http://127.0.0.1:8000"
17
+ "scraping_source": "animecix",
18
+ "show_description": True
20
19
  }
21
20
 
22
21
  class Config: