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.
Files changed (39) hide show
  1. {weeb_cli-0.1.2/weeb_cli.egg-info → weeb_cli-0.1.4}/PKG-INFO +3 -3
  2. {weeb_cli-0.1.2 → weeb_cli-0.1.4}/README.md +1 -1
  3. {weeb_cli-0.1.2 → weeb_cli-0.1.4}/pyproject.toml +8 -4
  4. weeb_cli-0.1.4/weeb_cli/__init__.py +1 -0
  5. weeb_cli-0.1.4/weeb_cli/commands/downloads.py +70 -0
  6. weeb_cli-0.1.4/weeb_cli/commands/search.py +345 -0
  7. weeb_cli-0.1.4/weeb_cli/commands/settings.py +238 -0
  8. weeb_cli-0.1.4/weeb_cli/commands/setup.py +26 -0
  9. weeb_cli-0.1.4/weeb_cli/commands/watchlist.py +14 -0
  10. weeb_cli-0.1.4/weeb_cli/locales/en.json +109 -0
  11. weeb_cli-0.1.4/weeb_cli/locales/tr.json +109 -0
  12. {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli/main.py +14 -12
  13. weeb_cli-0.1.4/weeb_cli/services/__init__.py +0 -0
  14. weeb_cli-0.1.4/weeb_cli/services/api.py +46 -0
  15. weeb_cli-0.1.4/weeb_cli/services/dependency_manager.py +321 -0
  16. weeb_cli-0.1.4/weeb_cli/services/details.py +4 -0
  17. weeb_cli-0.1.4/weeb_cli/services/downloader.py +241 -0
  18. weeb_cli-0.1.4/weeb_cli/services/player.py +47 -0
  19. weeb_cli-0.1.4/weeb_cli/services/progress.py +60 -0
  20. weeb_cli-0.1.4/weeb_cli/services/search.py +4 -0
  21. weeb_cli-0.1.4/weeb_cli/services/updater.py +54 -0
  22. weeb_cli-0.1.4/weeb_cli/services/watch.py +4 -0
  23. weeb_cli-0.1.4/weeb_cli/ui/__init__.py +1 -0
  24. weeb_cli-0.1.4/weeb_cli/ui/header.py +30 -0
  25. weeb_cli-0.1.4/weeb_cli/ui/menu.py +59 -0
  26. weeb_cli-0.1.4/weeb_cli/ui/prompt.py +120 -0
  27. {weeb_cli-0.1.2 → weeb_cli-0.1.4/weeb_cli.egg-info}/PKG-INFO +3 -3
  28. weeb_cli-0.1.4/weeb_cli.egg-info/SOURCES.txt +35 -0
  29. weeb_cli-0.1.2/weeb_cli/__init__.py +0 -1
  30. weeb_cli-0.1.2/weeb_cli.egg-info/SOURCES.txt +0 -14
  31. {weeb_cli-0.1.2 → weeb_cli-0.1.4}/LICENSE +0 -0
  32. {weeb_cli-0.1.2 → weeb_cli-0.1.4}/setup.cfg +0 -0
  33. {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli/__main__.py +0 -0
  34. {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli/config.py +0 -0
  35. {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli/i18n.py +0 -0
  36. {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli.egg-info/dependency_links.txt +0 -0
  37. {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli.egg-info/entry_points.txt +0 -0
  38. {weeb_cli-0.1.2 → weeb_cli-0.1.4}/weeb_cli.egg-info/requires.txt +0 -0
  39. {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.2
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://github.com/ewgsta/weeb-cli
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
@@ -76,7 +76,7 @@ choco install weeb-cli
76
76
  Aracı terminalden başlatmak için:
77
77
 
78
78
  ```bash
79
- weeb
79
+ weeb-cli
80
80
  ```
81
81
 
82
82
  ### Kontroller
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "weeb-cli"
7
- version = "0.1.2"
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
- packages = ["weeb_cli"]
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://github.com/ewgsta/weeb-cli"
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