weeb-cli 0.1.2__tar.gz → 0.1.4__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.1.2/weeb_cli.egg-info → weeb_cli-0.1.4}/PKG-INFO +3 -3
- {weeb_cli-0.1.2 → weeb_cli-0.1.4}/README.md +1 -1
- {weeb_cli-0.1.2 → weeb_cli-0.1.4}/pyproject.toml +8 -4
- weeb_cli-0.1.4/weeb_cli/__init__.py +1 -0
- weeb_cli-0.1.4/weeb_cli/commands/downloads.py +70 -0
- weeb_cli-0.1.4/weeb_cli/commands/search.py +345 -0
- weeb_cli-0.1.4/weeb_cli/commands/settings.py +238 -0
- weeb_cli-0.1.4/weeb_cli/commands/setup.py +26 -0
- weeb_cli-0.1.4/weeb_cli/commands/watchlist.py +14 -0
- weeb_cli-0.1.4/weeb_cli/locales/en.json +109 -0
- weeb_cli-0.1.4/weeb_cli/locales/tr.json +109 -0
- {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli/main.py +14 -12
- weeb_cli-0.1.4/weeb_cli/services/__init__.py +0 -0
- weeb_cli-0.1.4/weeb_cli/services/api.py +46 -0
- weeb_cli-0.1.4/weeb_cli/services/dependency_manager.py +321 -0
- weeb_cli-0.1.4/weeb_cli/services/details.py +4 -0
- weeb_cli-0.1.4/weeb_cli/services/downloader.py +241 -0
- weeb_cli-0.1.4/weeb_cli/services/player.py +47 -0
- weeb_cli-0.1.4/weeb_cli/services/progress.py +60 -0
- weeb_cli-0.1.4/weeb_cli/services/search.py +4 -0
- weeb_cli-0.1.4/weeb_cli/services/updater.py +54 -0
- weeb_cli-0.1.4/weeb_cli/services/watch.py +4 -0
- weeb_cli-0.1.4/weeb_cli/ui/__init__.py +1 -0
- weeb_cli-0.1.4/weeb_cli/ui/header.py +30 -0
- weeb_cli-0.1.4/weeb_cli/ui/menu.py +59 -0
- weeb_cli-0.1.4/weeb_cli/ui/prompt.py +120 -0
- {weeb_cli-0.1.2 → weeb_cli-0.1.4/weeb_cli.egg-info}/PKG-INFO +3 -3
- weeb_cli-0.1.4/weeb_cli.egg-info/SOURCES.txt +35 -0
- weeb_cli-0.1.2/weeb_cli/__init__.py +0 -1
- weeb_cli-0.1.2/weeb_cli.egg-info/SOURCES.txt +0 -14
- {weeb_cli-0.1.2 → weeb_cli-0.1.4}/LICENSE +0 -0
- {weeb_cli-0.1.2 → weeb_cli-0.1.4}/setup.cfg +0 -0
- {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli/__main__.py +0 -0
- {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli/config.py +0 -0
- {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli/i18n.py +0 -0
- {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli.egg-info/dependency_links.txt +0 -0
- {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli.egg-info/entry_points.txt +0 -0
- {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli.egg-info/requires.txt +0 -0
- {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli.egg-info/top_level.txt +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weeb-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
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
|
|
7
|
-
Project-URL: Homepage, https://
|
|
7
|
+
Project-URL: Homepage, https://weeb-cli.ewgsta.me
|
|
8
8
|
Project-URL: Repository, https://github.com/ewgsta/weeb-cli
|
|
9
9
|
Project-URL: Issues, https://github.com/ewgsta/weeb-cli/issues
|
|
10
10
|
Keywords: anime,weeb,anime-download,anizm,anime-watch,anime-watching,anime-downloading,anime-cli,allanime,animecix,anime-indir,anime-izle,weeb-cli,anime-izleme,anime-indirme,weebanime,tranime
|
|
@@ -98,7 +98,7 @@ choco install weeb-cli
|
|
|
98
98
|
Aracı terminalden başlatmak için:
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
|
-
weeb
|
|
101
|
+
weeb-cli
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
### Kontroller
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "weeb-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
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" }]
|
|
@@ -23,11 +23,15 @@ dependencies = [
|
|
|
23
23
|
"requests"
|
|
24
24
|
]
|
|
25
25
|
|
|
26
|
-
[tool.setuptools]
|
|
27
|
-
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["."]
|
|
28
|
+
include = ["weeb_cli*"]
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.package-data]
|
|
31
|
+
weeb_cli = ["locales/*.json"]
|
|
28
32
|
|
|
29
33
|
[project.urls]
|
|
30
|
-
Homepage = "https://
|
|
34
|
+
Homepage = "https://weeb-cli.ewgsta.me"
|
|
31
35
|
Repository = "https://github.com/ewgsta/weeb-cli"
|
|
32
36
|
Issues = "https://github.com/ewgsta/weeb-cli/issues"
|
|
33
37
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.4"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
from rich.live import Live
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
from rich.progress import ProgressBar, BarColumn
|
|
5
|
+
from weeb_cli.services.downloader import queue_manager
|
|
6
|
+
from weeb_cli.i18n import i18n
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
def show_downloads():
|
|
12
|
+
console.clear()
|
|
13
|
+
console.print(f"[bold cyan]{i18n.get('details.download')}[/bold cyan] ([dim]Ctrl+C to exit[/dim])\n")
|
|
14
|
+
|
|
15
|
+
def generate_table():
|
|
16
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
17
|
+
table.add_column("Anime", width=30)
|
|
18
|
+
table.add_column("Ep", justify="right", width=5)
|
|
19
|
+
table.add_column("Status", width=15)
|
|
20
|
+
table.add_column("Progress", width=20)
|
|
21
|
+
table.add_column("ETA", width=10)
|
|
22
|
+
|
|
23
|
+
queue = queue_manager.queue
|
|
24
|
+
|
|
25
|
+
active = [i for i in queue if i["status"] == "processing"]
|
|
26
|
+
pending = [i for i in queue if i["status"] == "pending"]
|
|
27
|
+
finished = [i for i in queue if i["status"] in ["completed", "failed"]]
|
|
28
|
+
finished = finished[-5:]
|
|
29
|
+
|
|
30
|
+
display_list = active + pending + finished
|
|
31
|
+
|
|
32
|
+
if not display_list:
|
|
33
|
+
table.add_row("-", "-", "Empty", "-", "-")
|
|
34
|
+
return table
|
|
35
|
+
|
|
36
|
+
for item in display_list:
|
|
37
|
+
status = item["status"]
|
|
38
|
+
style = "white"
|
|
39
|
+
if status == "processing":
|
|
40
|
+
style = "cyan"
|
|
41
|
+
elif status == "completed":
|
|
42
|
+
style = "green"
|
|
43
|
+
elif status == "failed":
|
|
44
|
+
style = "red"
|
|
45
|
+
elif status == "pending":
|
|
46
|
+
style = "dim"
|
|
47
|
+
|
|
48
|
+
progress = item.get("progress", 0)
|
|
49
|
+
bars = int(progress / 5)
|
|
50
|
+
bar_str = "█" * bars + "░" * (20 - bars)
|
|
51
|
+
|
|
52
|
+
p_text = f"{progress}%" if status == "processing" else ""
|
|
53
|
+
|
|
54
|
+
table.add_row(
|
|
55
|
+
f"[{style}]{item['anime_title'][:28]}[/{style}]",
|
|
56
|
+
f"{item['episode_number']}",
|
|
57
|
+
f"[{style}]{status.upper()}[/{style}]",
|
|
58
|
+
f"[{style}]{bar_str} {p_text}[/{style}]",
|
|
59
|
+
f"{item.get('eta', '-')}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return table
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
with Live(generate_table(), refresh_per_second=1) as live:
|
|
66
|
+
while True:
|
|
67
|
+
live.update(generate_table())
|
|
68
|
+
time.sleep(1)
|
|
69
|
+
except KeyboardInterrupt:
|
|
70
|
+
return
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import questionary
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from weeb_cli.i18n import i18n
|
|
4
|
+
from weeb_cli.ui.header import show_header
|
|
5
|
+
from weeb_cli.services.search import search
|
|
6
|
+
from weeb_cli.services.details import get_details
|
|
7
|
+
from weeb_cli.services.watch import get_streams
|
|
8
|
+
from weeb_cli.services.player import player
|
|
9
|
+
from weeb_cli.services.progress import progress_tracker
|
|
10
|
+
from weeb_cli.services.downloader import queue_manager
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
def search_anime():
|
|
16
|
+
while True:
|
|
17
|
+
console.clear()
|
|
18
|
+
show_header(i18n.get("menu.options.search"), show_source=True)
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
query = questionary.text(
|
|
22
|
+
i18n.get("search.prompt") + ":",
|
|
23
|
+
qmark=">",
|
|
24
|
+
style=questionary.Style([
|
|
25
|
+
('qmark', 'fg:cyan bold'),
|
|
26
|
+
('question', 'fg:white'),
|
|
27
|
+
('answer', 'fg:cyan bold'),
|
|
28
|
+
])
|
|
29
|
+
).ask()
|
|
30
|
+
|
|
31
|
+
if query is None:
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
if not query.strip():
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
with console.status(i18n.get("search.searching"), spinner="dots"):
|
|
38
|
+
data = search(query)
|
|
39
|
+
|
|
40
|
+
if data is None:
|
|
41
|
+
time.sleep(1)
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
if isinstance(data, dict):
|
|
45
|
+
if "results" in data and isinstance(data["results"], list):
|
|
46
|
+
data = data["results"]
|
|
47
|
+
elif "data" in data:
|
|
48
|
+
inner = data["data"]
|
|
49
|
+
if isinstance(inner, list):
|
|
50
|
+
data = inner
|
|
51
|
+
elif isinstance(inner, dict):
|
|
52
|
+
if "results" in inner and isinstance(inner["results"], list):
|
|
53
|
+
data = inner["results"]
|
|
54
|
+
elif "animes" in inner and isinstance(inner["animes"], list):
|
|
55
|
+
data = inner["animes"]
|
|
56
|
+
elif "items" in inner and isinstance(inner["items"], list):
|
|
57
|
+
data = inner["items"]
|
|
58
|
+
|
|
59
|
+
if not data or not isinstance(data, list):
|
|
60
|
+
console.print(f"[red]{i18n.get('search.no_results')}[/red]")
|
|
61
|
+
time.sleep(1.5)
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
choices = []
|
|
65
|
+
for item in data:
|
|
66
|
+
title = item.get("title") or item.get("name")
|
|
67
|
+
if title:
|
|
68
|
+
choices.append(questionary.Choice(title, value=item))
|
|
69
|
+
|
|
70
|
+
if not choices:
|
|
71
|
+
console.print(f"[red]{i18n.get('search.no_results')}[/red]")
|
|
72
|
+
time.sleep(1.5)
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
selected = questionary.select(
|
|
78
|
+
i18n.get("search.results"),
|
|
79
|
+
choices=choices,
|
|
80
|
+
pointer=">",
|
|
81
|
+
use_shortcuts=False,
|
|
82
|
+
style=questionary.Style([
|
|
83
|
+
('pointer', 'fg:cyan bold'),
|
|
84
|
+
('highlighted', 'fg:cyan'),
|
|
85
|
+
('selected', 'fg:cyan bold'),
|
|
86
|
+
])
|
|
87
|
+
).ask()
|
|
88
|
+
|
|
89
|
+
if selected == "cancel" or selected is None:
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
show_anime_details(selected)
|
|
93
|
+
|
|
94
|
+
except KeyboardInterrupt:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
from weeb_cli.services.details import get_details
|
|
98
|
+
|
|
99
|
+
def show_anime_details(anime):
|
|
100
|
+
slug = anime.get("slug") or anime.get("id")
|
|
101
|
+
if not slug:
|
|
102
|
+
console.print(f"[red]{i18n.get('details.error_slug')}[/red]")
|
|
103
|
+
time.sleep(1)
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
while True:
|
|
107
|
+
console.clear()
|
|
108
|
+
show_header(anime.get("title") or anime.get("name") or "Anime")
|
|
109
|
+
|
|
110
|
+
with console.status(i18n.get("common.processing"), spinner="dots"):
|
|
111
|
+
details = get_details(slug)
|
|
112
|
+
|
|
113
|
+
# Parse response structure: { data: { details: { ... } } }
|
|
114
|
+
if isinstance(details, dict):
|
|
115
|
+
if "data" in details and isinstance(details["data"], dict):
|
|
116
|
+
details = details["data"]
|
|
117
|
+
|
|
118
|
+
# Capture source
|
|
119
|
+
source = details.get("source")
|
|
120
|
+
|
|
121
|
+
if "details" in details and isinstance(details["details"], dict):
|
|
122
|
+
details = details["details"]
|
|
123
|
+
# Restore source if captured
|
|
124
|
+
if source:
|
|
125
|
+
details["source"] = source
|
|
126
|
+
|
|
127
|
+
if not details:
|
|
128
|
+
console.print(f"[red]{i18n.get('details.not_found')}[/red]")
|
|
129
|
+
time.sleep(1)
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
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")
|
|
135
|
+
|
|
136
|
+
opt_watch = i18n.get("details.watch")
|
|
137
|
+
opt_dl = i18n.get("details.download")
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
action = questionary.select(
|
|
141
|
+
i18n.get("details.action_prompt"),
|
|
142
|
+
choices=[opt_watch, opt_dl],
|
|
143
|
+
pointer=">",
|
|
144
|
+
use_shortcuts=False
|
|
145
|
+
).ask()
|
|
146
|
+
|
|
147
|
+
if action is None:
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
if action == opt_dl:
|
|
151
|
+
handle_download_flow(slug, details)
|
|
152
|
+
elif action == opt_watch:
|
|
153
|
+
handle_watch_flow(slug, details)
|
|
154
|
+
|
|
155
|
+
except KeyboardInterrupt:
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
def get_episodes_safe(details):
|
|
159
|
+
episodes = None
|
|
160
|
+
for k in ["episodes", "episodes_list", "episode_list", "results", "chapters"]:
|
|
161
|
+
if k in details and isinstance(details[k], list):
|
|
162
|
+
episodes = details[k]
|
|
163
|
+
break
|
|
164
|
+
|
|
165
|
+
if not episodes:
|
|
166
|
+
for v in details.values():
|
|
167
|
+
if isinstance(v, list) and v and isinstance(v[0], dict) and ("number" in v[0] or "ep_num" in v[0] or "url" in v[0]):
|
|
168
|
+
episodes = v
|
|
169
|
+
break
|
|
170
|
+
return episodes
|
|
171
|
+
|
|
172
|
+
def handle_watch_flow(slug, details):
|
|
173
|
+
episodes = get_episodes_safe(details)
|
|
174
|
+
if not episodes:
|
|
175
|
+
console.print(f"[yellow]{i18n.get('details.no_episodes')}[/yellow]")
|
|
176
|
+
time.sleep(1.5)
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
prog_data = progress_tracker.get_anime_progress(slug)
|
|
180
|
+
completed_ids = set(prog_data.get("completed", []))
|
|
181
|
+
last_watched = prog_data.get("last_watched", 0)
|
|
182
|
+
next_ep_num = last_watched + 1
|
|
183
|
+
|
|
184
|
+
while True:
|
|
185
|
+
ep_choices = []
|
|
186
|
+
for ep in episodes:
|
|
187
|
+
num_val = ep.get('number') or ep.get('ep_num')
|
|
188
|
+
try:
|
|
189
|
+
num = int(num_val)
|
|
190
|
+
except:
|
|
191
|
+
num = -1
|
|
192
|
+
|
|
193
|
+
prefix = " "
|
|
194
|
+
if num in completed_ids:
|
|
195
|
+
prefix = "✓ "
|
|
196
|
+
elif num == next_ep_num:
|
|
197
|
+
prefix = "● "
|
|
198
|
+
|
|
199
|
+
name = f"{prefix}{i18n.get('details.episode')} {num_val}"
|
|
200
|
+
ep_choices.append(questionary.Choice(name, value=ep))
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
selected_ep = questionary.select(
|
|
204
|
+
i18n.get("details.select_episode") + ":",
|
|
205
|
+
choices=ep_choices,
|
|
206
|
+
pointer=">",
|
|
207
|
+
use_shortcuts=False
|
|
208
|
+
).ask()
|
|
209
|
+
|
|
210
|
+
if selected_ep is None:
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
ep_id = selected_ep.get("id")
|
|
214
|
+
ep_num = selected_ep.get("number")
|
|
215
|
+
|
|
216
|
+
if not ep_id:
|
|
217
|
+
console.print(f"[red]{i18n.get('details.invalid_ep_id')}[/red]")
|
|
218
|
+
time.sleep(1)
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
with console.status(i18n.get("common.processing"), spinner="dots"):
|
|
222
|
+
stream_resp = get_streams(slug, ep_id)
|
|
223
|
+
stream_url = None
|
|
224
|
+
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"]
|
|
244
|
+
|
|
245
|
+
if not stream_url:
|
|
246
|
+
console.print(f"[red]{i18n.get('details.stream_not_found')}[/red]")
|
|
247
|
+
time.sleep(1.5)
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
console.print(f"[green]{i18n.get('details.player_starting')}[/green]")
|
|
251
|
+
title = f"{details.get('title', 'Anime')} - Ep {ep_num}"
|
|
252
|
+
|
|
253
|
+
headers = {}
|
|
254
|
+
if details.get("source") == "hianime":
|
|
255
|
+
headers["Referer"] = "https://hianime.to"
|
|
256
|
+
|
|
257
|
+
success = player.play(stream_url, title=title, headers=headers)
|
|
258
|
+
|
|
259
|
+
if success:
|
|
260
|
+
try:
|
|
261
|
+
ans = questionary.confirm(i18n.get("details.mark_watched")).ask()
|
|
262
|
+
if ans:
|
|
263
|
+
n = int(ep_num)
|
|
264
|
+
progress_tracker.mark_watched(slug, n)
|
|
265
|
+
|
|
266
|
+
completed_ids.add(n)
|
|
267
|
+
if n >= next_ep_num:
|
|
268
|
+
next_ep_num = n + 1
|
|
269
|
+
except:
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
except KeyboardInterrupt:
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
def handle_download_flow(slug, details):
|
|
276
|
+
episodes = get_episodes_safe(details)
|
|
277
|
+
if not episodes:
|
|
278
|
+
console.print(f"[yellow]{i18n.get('details.no_episodes')}[/yellow]")
|
|
279
|
+
time.sleep(1.5)
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
opt_all = i18n.get("details.download_options.all")
|
|
283
|
+
opt_manual = i18n.get("details.download_options.manual")
|
|
284
|
+
opt_range = i18n.get("details.download_options.range")
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
mode = questionary.select(
|
|
288
|
+
i18n.get("details.download_options.prompt"),
|
|
289
|
+
choices=[opt_all, opt_manual, opt_range],
|
|
290
|
+
pointer=">",
|
|
291
|
+
use_shortcuts=False
|
|
292
|
+
).ask()
|
|
293
|
+
|
|
294
|
+
if mode is None:
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
selected_eps = []
|
|
298
|
+
|
|
299
|
+
if mode == opt_all:
|
|
300
|
+
selected_eps = episodes
|
|
301
|
+
|
|
302
|
+
elif mode == opt_manual:
|
|
303
|
+
choices = []
|
|
304
|
+
for ep in episodes:
|
|
305
|
+
name = f"{i18n.get('details.episode')} {ep.get('number')}"
|
|
306
|
+
choices.append(questionary.Choice(name, value=ep))
|
|
307
|
+
|
|
308
|
+
selected_eps = questionary.checkbox(
|
|
309
|
+
"Select Episodes:",
|
|
310
|
+
choices=choices
|
|
311
|
+
).ask()
|
|
312
|
+
|
|
313
|
+
elif mode == opt_range:
|
|
314
|
+
r_str = questionary.text(i18n.get("details.download_options.range_input")).ask()
|
|
315
|
+
if not r_str: return
|
|
316
|
+
nums = set()
|
|
317
|
+
try:
|
|
318
|
+
parts = r_str.split(',')
|
|
319
|
+
for p in parts:
|
|
320
|
+
p = p.strip()
|
|
321
|
+
if '-' in p:
|
|
322
|
+
s, e = p.split('-')
|
|
323
|
+
for x in range(int(s), int(e)+1): nums.add(x)
|
|
324
|
+
elif p.isdigit():
|
|
325
|
+
nums.add(int(p))
|
|
326
|
+
except:
|
|
327
|
+
console.print(f"[red]{i18n.get('details.download_options.range_error')}[/red]")
|
|
328
|
+
time.sleep(1)
|
|
329
|
+
return
|
|
330
|
+
|
|
331
|
+
selected_eps = [ep for ep in episodes if int(ep.get('number', -1)) in nums]
|
|
332
|
+
|
|
333
|
+
if not selected_eps:
|
|
334
|
+
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
|
+
|
|
339
|
+
anime_title = details.get("title") or "Unknown Anime"
|
|
340
|
+
queue_manager.add_to_queue(anime_title, selected_eps, slug)
|
|
341
|
+
|
|
342
|
+
time.sleep(1.5)
|
|
343
|
+
|
|
344
|
+
except KeyboardInterrupt:
|
|
345
|
+
return
|