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.
- weeb_cli/__init__.py +1 -0
- weeb_cli/__main__.py +4 -0
- weeb_cli/commands/downloads.py +126 -0
- weeb_cli/commands/search.py +428 -0
- weeb_cli/commands/settings.py +254 -0
- weeb_cli/commands/setup.py +26 -0
- weeb_cli/commands/watchlist.py +130 -0
- weeb_cli/config.py +50 -0
- weeb_cli/i18n.py +65 -0
- weeb_cli/locales/en.json +168 -0
- weeb_cli/locales/tr.json +168 -0
- weeb_cli/main.py +85 -0
- weeb_cli/providers/__init__.py +21 -0
- weeb_cli/providers/animecix.py +276 -0
- weeb_cli/providers/anizle.py +450 -0
- weeb_cli/providers/base.py +98 -0
- weeb_cli/providers/registry.py +45 -0
- weeb_cli/providers/turkanime.py +499 -0
- weeb_cli/services/__init__.py +0 -0
- weeb_cli/services/dependency_manager.py +321 -0
- weeb_cli/services/details.py +32 -0
- weeb_cli/services/downloader.py +308 -0
- weeb_cli/services/player.py +47 -0
- weeb_cli/services/progress.py +136 -0
- weeb_cli/services/scraper.py +91 -0
- weeb_cli/services/search.py +16 -0
- weeb_cli/services/updater.py +199 -0
- weeb_cli/services/watch.py +19 -0
- weeb_cli/ui/__init__.py +1 -0
- weeb_cli/ui/header.py +30 -0
- weeb_cli/ui/menu.py +59 -0
- weeb_cli/ui/prompt.py +120 -0
- weeb_cli-1.0.0.dist-info/METADATA +148 -0
- weeb_cli-1.0.0.dist-info/RECORD +38 -0
- weeb_cli-1.0.0.dist-info/WHEEL +5 -0
- weeb_cli-1.0.0.dist-info/entry_points.txt +2 -0
- weeb_cli-1.0.0.dist-info/licenses/LICENSE +390 -0
- weeb_cli-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -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()
|