weeb-cli 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,254 @@
1
+ import questionary
2
+ from rich.console import Console
3
+ from weeb_cli.i18n import i18n
4
+ from weeb_cli.config import config
5
+ import time
6
+ from weeb_cli.ui.header import show_header
7
+ import os
8
+ from weeb_cli.services.dependency_manager import dependency_manager
9
+
10
+ console = Console()
11
+
12
+
13
+ def toggle_config(key, name):
14
+ current = config.get(key)
15
+ new_val = not current
16
+
17
+ if new_val:
18
+ dep_name = name.lower()
19
+ if "aria2" in dep_name: dep_name = "aria2"
20
+ elif "yt-dlp" in dep_name: dep_name = "yt-dlp"
21
+
22
+ path = dependency_manager.check_dependency(dep_name)
23
+ if not path:
24
+ console.print(f"[cyan]{i18n.t('setup.downloading', tool=name)}...[/cyan]")
25
+ if not dependency_manager.install_dependency(dep_name):
26
+ console.print(f"[red]{i18n.t('setup.failed', tool=name)}[/red]")
27
+ time.sleep(1)
28
+ return
29
+
30
+ config.set(key, new_val)
31
+
32
+ msg_key = "settings.toggle_on" if new_val else "settings.toggle_off"
33
+ console.print(f"[green]{i18n.t(msg_key, tool=name)}[/green]")
34
+ time.sleep(0.5)
35
+
36
+ def open_settings():
37
+ while True:
38
+ console.clear()
39
+ show_header(i18n.get("settings.title"))
40
+
41
+ lang = config.get("language")
42
+ source = config.get("scraping_source", "local")
43
+ display_source = "weeb" if source == "local" else source
44
+
45
+ aria2_state = i18n.get("common.enabled") if config.get("aria2_enabled") else i18n.get("common.disabled")
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")
48
+
49
+ opt_lang = i18n.get("settings.language")
50
+ opt_source = f"{i18n.get('settings.source')} [{display_source}]"
51
+ opt_download = i18n.get("settings.download_settings")
52
+ opt_desc = f"{i18n.get('settings.show_description')} [{desc_state}]"
53
+ opt_aria2 = f"{i18n.get('settings.aria2')} [{aria2_state}]"
54
+ opt_ytdlp = f"{i18n.get('settings.ytdlp')} [{ytdlp_state}]"
55
+
56
+ opt_aria2_conf = f" ↳ {i18n.get('settings.aria2_config')}"
57
+ opt_ytdlp_conf = f" ↳ {i18n.get('settings.ytdlp_config')}"
58
+
59
+ choices = [opt_lang, opt_source, opt_download, opt_desc, opt_aria2]
60
+ if config.get("aria2_enabled"):
61
+ choices.append(opt_aria2_conf)
62
+
63
+ choices.append(opt_ytdlp)
64
+ if config.get("ytdlp_enabled"):
65
+ choices.append(opt_ytdlp_conf)
66
+
67
+ try:
68
+ answer = questionary.select(
69
+ i18n.get("settings.title"),
70
+ choices=choices,
71
+ pointer=">",
72
+ use_shortcuts=False,
73
+ style=questionary.Style([
74
+ ('pointer', 'fg:cyan bold'),
75
+ ('highlighted', 'fg:cyan'),
76
+ ('selected', 'fg:cyan bold'),
77
+ ])
78
+ ).ask()
79
+ except KeyboardInterrupt:
80
+ return
81
+
82
+ if answer == opt_lang:
83
+ change_language()
84
+ elif answer == opt_source:
85
+ change_source()
86
+ elif answer == opt_download:
87
+ download_settings_menu()
88
+ elif answer == opt_desc:
89
+ toggle_description()
90
+ elif answer == opt_aria2:
91
+ toggle_config("aria2_enabled", "Aria2")
92
+ elif answer == opt_aria2_conf:
93
+ aria2_settings_menu()
94
+ elif answer == opt_ytdlp:
95
+ toggle_config("ytdlp_enabled", "yt-dlp")
96
+ elif answer == opt_ytdlp_conf:
97
+ ytdlp_settings_menu()
98
+ elif answer is None:
99
+ return
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
+
108
+ def change_language():
109
+ from weeb_cli.services.scraper import scraper
110
+
111
+ langs = {"Türkçe": "tr", "English": "en"}
112
+ try:
113
+ selected = questionary.select(
114
+ "Select Language / Dil Seçiniz:",
115
+ choices=list(langs.keys()),
116
+ pointer=">",
117
+ use_shortcuts=False
118
+ ).ask()
119
+
120
+ if selected:
121
+ lang_code = langs[selected]
122
+ i18n.set_language(lang_code)
123
+
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])
128
+
129
+ console.print(f"[green]{i18n.get('settings.language_changed')}[/green]")
130
+ time.sleep(1)
131
+ except KeyboardInterrupt:
132
+ pass
133
+
134
+ def change_source():
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)
139
+
140
+ if not sources:
141
+ console.print(f"[yellow]{i18n.get('settings.no_sources')}[/yellow]")
142
+ time.sleep(1)
143
+ return
144
+
145
+ try:
146
+ selected = questionary.select(
147
+ i18n.get("settings.source"),
148
+ choices=sources,
149
+ pointer=">",
150
+ use_shortcuts=False
151
+ ).ask()
152
+
153
+ if selected:
154
+ config.set("scraping_source", selected)
155
+ console.print(f"[green]{i18n.t('settings.source_changed', source=selected)}[/green]")
156
+ time.sleep(1)
157
+ except KeyboardInterrupt:
158
+ pass
159
+
160
+
161
+
162
+ def aria2_settings_menu():
163
+ while True:
164
+ console.clear()
165
+ show_header(i18n.get("settings.aria2_config"))
166
+
167
+ curr_conn = config.get("aria2_max_connections", 16)
168
+
169
+ opt_conn = f"{i18n.get('settings.max_conn')} [{curr_conn}]"
170
+
171
+ try:
172
+ sel = questionary.select(
173
+ i18n.get("settings.aria2_config"),
174
+ choices=[opt_conn],
175
+ pointer=">",
176
+ use_shortcuts=False
177
+ ).ask()
178
+
179
+ if sel == opt_conn:
180
+ val = questionary.text(f"{i18n.get('settings.enter_conn')}:", default=str(curr_conn)).ask()
181
+ if val and val.isdigit():
182
+ config.set("aria2_max_connections", int(val))
183
+ elif sel is None:
184
+ return
185
+ except KeyboardInterrupt:
186
+ return
187
+
188
+ def download_settings_menu():
189
+ while True:
190
+ console.clear()
191
+ show_header(i18n.get("settings.download_settings"))
192
+
193
+ curr_dir = config.get("download_dir")
194
+ console.print(f"[dim]Current: {curr_dir}[/dim]\n", justify="left")
195
+
196
+ curr_concurrent = config.get("max_concurrent_downloads", 3)
197
+
198
+ opt_name = i18n.get("settings.change_folder_name")
199
+ opt_path = i18n.get("settings.change_full_path")
200
+ opt_concurrent = f"{i18n.get('settings.concurrent_downloads')} [{curr_concurrent}]"
201
+
202
+ try:
203
+ sel = questionary.select(
204
+ i18n.get("settings.download_settings"),
205
+ choices=[opt_name, opt_path, opt_concurrent],
206
+ pointer=">",
207
+ use_shortcuts=False
208
+ ).ask()
209
+
210
+ if sel == opt_name:
211
+ val = questionary.text("Folder Name:", default="weeb-downloads").ask()
212
+ if val:
213
+ new_path = os.path.join(os.getcwd(), val)
214
+ config.set("download_dir", new_path)
215
+ elif sel == opt_path:
216
+ val = questionary.text("Full Path:", default=curr_dir).ask()
217
+ if val:
218
+ config.set("download_dir", val)
219
+ elif sel == opt_concurrent:
220
+ val = questionary.text(i18n.get("settings.enter_concurrent"), default=str(curr_concurrent)).ask()
221
+ if val and val.isdigit():
222
+ n = int(val)
223
+ if 1 <= n <= 5:
224
+ config.set("max_concurrent_downloads", n)
225
+ elif sel is None:
226
+ return
227
+ except KeyboardInterrupt:
228
+ return
229
+
230
+
231
+ def ytdlp_settings_menu():
232
+ while True:
233
+ console.clear()
234
+ show_header(i18n.get("settings.ytdlp_config"))
235
+
236
+ curr_fmt = config.get("ytdlp_format", "best")
237
+ opt_fmt = f"{i18n.get('settings.format')} [{curr_fmt}]"
238
+
239
+ try:
240
+ sel = questionary.select(
241
+ i18n.get("settings.ytdlp_config"),
242
+ choices=[opt_fmt],
243
+ pointer=">",
244
+ use_shortcuts=False
245
+ ).ask()
246
+
247
+ if sel == opt_fmt:
248
+ val = questionary.text(f"{i18n.get('settings.enter_format')}:", default=curr_fmt).ask()
249
+ if val:
250
+ config.set("ytdlp_format", val)
251
+ elif sel is None:
252
+ return
253
+ except KeyboardInterrupt:
254
+ return
@@ -0,0 +1,26 @@
1
+ from rich.console import Console
2
+ from rich.panel import Panel
3
+ from ..services.dependency_manager import dependency_manager
4
+ from ..i18n import i18n
5
+
6
+ console = Console()
7
+
8
+ def start_setup_wizard(force=False):
9
+ console.print(Panel(f"[bold cyan]{i18n.t('setup.wizard_title')}[/bold cyan]", expand=False))
10
+
11
+ tools = ["yt-dlp", "ffmpeg", "aria2", "mpv"]
12
+
13
+ for tool in tools:
14
+ console.print(f"\n[bold]{tool.upper()}[/bold]")
15
+ console.print(i18n.t('setup.check', tool=tool))
16
+
17
+ path = dependency_manager.check_dependency(tool)
18
+ if path and not force:
19
+ console.print(f"[green]{i18n.t('setup.found', path=path)}[/green]")
20
+ continue
21
+
22
+ console.print(f"[yellow]{i18n.t('setup.not_found', tool=tool)}[/yellow]")
23
+ dependency_manager.install_dependency(tool)
24
+
25
+ console.print(f"\n[bold green]{i18n.t('setup.complete')}[/bold green]")
26
+ console.print(f"[dim]{i18n.t('setup.location', path=dependency_manager.bin_dir)}[/dim]")
@@ -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
weeb_cli/config.py ADDED
@@ -0,0 +1,50 @@
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+
5
+ APP_NAME = "weeb-cli"
6
+ CONFIG_DIR = Path.home() / f".{APP_NAME}"
7
+ CONFIG_FILE = CONFIG_DIR / "config.json"
8
+
9
+ DEFAULT_CONFIG = {
10
+ "language": None,
11
+ "aria2_enabled": True,
12
+ "ytdlp_enabled": True,
13
+ "aria2_max_connections": 16,
14
+ "max_concurrent_downloads": 3,
15
+ "download_dir": os.path.join(os.getcwd(), "weeb-downloads"),
16
+ "ytdlp_format": "bestvideo+bestaudio/best",
17
+ "scraping_source": "animecix",
18
+ "show_description": True
19
+ }
20
+
21
+ class Config:
22
+ def __init__(self):
23
+ self._ensure_config_exists()
24
+ self.data = self._load()
25
+
26
+ def _ensure_config_exists(self):
27
+ if not CONFIG_DIR.exists():
28
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
29
+ if not CONFIG_FILE.exists():
30
+ self._save(DEFAULT_CONFIG)
31
+
32
+ def _load(self):
33
+ try:
34
+ with open(CONFIG_FILE, "r", encoding="utf-8") as f:
35
+ return json.load(f)
36
+ except (json.JSONDecodeError, FileNotFoundError):
37
+ return DEFAULT_CONFIG.copy()
38
+
39
+ def _save(self, data):
40
+ with open(CONFIG_FILE, "w", encoding="utf-8") as f:
41
+ json.dump(data, f, indent=2, ensure_ascii=False)
42
+
43
+ def get(self, key, default=None):
44
+ return self.data.get(key, default)
45
+
46
+ def set(self, key, value):
47
+ self.data[key] = value
48
+ self._save(self.data)
49
+
50
+ config = Config()
weeb_cli/i18n.py ADDED
@@ -0,0 +1,65 @@
1
+ import json
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+ from weeb_cli.config import config
6
+
7
+ def get_locales_dir():
8
+ if getattr(sys, 'frozen', False):
9
+ base_path = Path(sys._MEIPASS)
10
+ possible_path = base_path / "weeb_cli" / "locales"
11
+ if possible_path.exists():
12
+ return possible_path
13
+ return base_path / "locales"
14
+
15
+ return Path(__file__).parent / "locales"
16
+
17
+ LOCALES_DIR = get_locales_dir()
18
+
19
+ class I18n:
20
+ def __init__(self):
21
+ self.language = config.get("language", "en")
22
+ self.translations = {}
23
+ self.load_translations()
24
+
25
+ def set_language(self, language_code):
26
+ self.language = language_code
27
+ config.set("language", language_code)
28
+ self.load_translations()
29
+
30
+ def load_translations(self):
31
+ file_path = LOCALES_DIR / f"{self.language}.json"
32
+ if not file_path.exists():
33
+ file_path = LOCALES_DIR / "en.json"
34
+
35
+ try:
36
+ with open(file_path, "r", encoding="utf-8") as f:
37
+ self.translations = json.load(f)
38
+ except Exception as e:
39
+ print(f"Error loading translations: {e}")
40
+ self.translations = {}
41
+
42
+ def get(self, key_path, **kwargs):
43
+ keys = key_path.split(".")
44
+ value = self.translations
45
+
46
+ for key in keys:
47
+ if isinstance(value, dict):
48
+ value = value.get(key)
49
+ else:
50
+ return key_path
51
+
52
+ if value is None:
53
+ return key_path
54
+
55
+ if isinstance(value, str):
56
+ try:
57
+ return value.format(**kwargs)
58
+ except KeyError:
59
+ return value
60
+
61
+ return value
62
+
63
+ t = get
64
+
65
+ i18n = I18n()