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.
- {weeb_cli-0.2.0/weeb_cli.egg-info → weeb_cli-1.0.0}/PKG-INFO +5 -1
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/pyproject.toml +6 -2
- weeb_cli-1.0.0/weeb_cli/__init__.py +1 -0
- weeb_cli-1.0.0/weeb_cli/commands/downloads.py +126 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/commands/search.py +111 -28
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/commands/settings.py +27 -11
- weeb_cli-1.0.0/weeb_cli/commands/watchlist.py +130 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/config.py +2 -3
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/locales/en.json +62 -3
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/locales/tr.json +62 -3
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/main.py +20 -0
- weeb_cli-1.0.0/weeb_cli/providers/__init__.py +21 -0
- weeb_cli-1.0.0/weeb_cli/providers/animecix.py +276 -0
- weeb_cli-1.0.0/weeb_cli/providers/anizle.py +450 -0
- weeb_cli-1.0.0/weeb_cli/providers/base.py +98 -0
- weeb_cli-1.0.0/weeb_cli/providers/registry.py +45 -0
- weeb_cli-1.0.0/weeb_cli/providers/turkanime.py +499 -0
- weeb_cli-1.0.0/weeb_cli/services/details.py +32 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/services/downloader.py +102 -35
- weeb_cli-1.0.0/weeb_cli/services/progress.py +136 -0
- weeb_cli-1.0.0/weeb_cli/services/scraper.py +91 -0
- weeb_cli-1.0.0/weeb_cli/services/search.py +16 -0
- weeb_cli-1.0.0/weeb_cli/services/updater.py +199 -0
- weeb_cli-1.0.0/weeb_cli/services/watch.py +19 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0/weeb_cli.egg-info}/PKG-INFO +5 -1
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli.egg-info/SOURCES.txt +7 -1
- weeb_cli-1.0.0/weeb_cli.egg-info/requires.txt +8 -0
- weeb_cli-0.2.0/weeb_cli/__init__.py +0 -1
- weeb_cli-0.2.0/weeb_cli/commands/downloads.py +0 -70
- weeb_cli-0.2.0/weeb_cli/commands/watchlist.py +0 -14
- weeb_cli-0.2.0/weeb_cli/services/api.py +0 -46
- weeb_cli-0.2.0/weeb_cli/services/details.py +0 -4
- weeb_cli-0.2.0/weeb_cli/services/progress.py +0 -60
- weeb_cli-0.2.0/weeb_cli/services/search.py +0 -4
- weeb_cli-0.2.0/weeb_cli/services/updater.py +0 -54
- weeb_cli-0.2.0/weeb_cli/services/watch.py +0 -4
- weeb_cli-0.2.0/weeb_cli.egg-info/requires.txt +0 -4
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/LICENSE +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/README.md +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/setup.cfg +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/__main__.py +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/commands/setup.py +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/i18n.py +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/services/__init__.py +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/services/dependency_manager.py +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/services/player.py +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/ui/__init__.py +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/ui/header.py +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/ui/menu.py +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli/ui/prompt.py +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli.egg-info/dependency_links.txt +0 -0
- {weeb_cli-0.2.0 → weeb_cli-1.0.0}/weeb_cli.egg-info/entry_points.txt +0 -0
- {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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
252
|
+
|
|
253
|
+
streams_list = []
|
|
224
254
|
if stream_resp and isinstance(stream_resp, dict):
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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:
|