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,47 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import shutil
|
|
3
|
+
import sys
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from weeb_cli.services.dependency_manager import dependency_manager
|
|
7
|
+
from weeb_cli.i18n import i18n
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
class Player:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.mpv_path = dependency_manager.check_dependency("mpv")
|
|
14
|
+
|
|
15
|
+
def is_installed(self):
|
|
16
|
+
return self.mpv_path is not None
|
|
17
|
+
|
|
18
|
+
def play(self, url, title=None, start_time=None, headers=None):
|
|
19
|
+
if not self.mpv_path:
|
|
20
|
+
console.print(f"[yellow]{i18n.get('player.installing_mpv')}[/yellow]")
|
|
21
|
+
if dependency_manager.install_dependency("mpv"):
|
|
22
|
+
self.mpv_path = dependency_manager.check_dependency("mpv")
|
|
23
|
+
|
|
24
|
+
if not self.mpv_path:
|
|
25
|
+
console.print(f"[red]{i18n.get('player.install_failed')}[/red]")
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
cmd = [self.mpv_path, url]
|
|
29
|
+
if title:
|
|
30
|
+
cmd.extend([f"--force-media-title={title}"])
|
|
31
|
+
|
|
32
|
+
if headers:
|
|
33
|
+
header_strs = [f"{k}: {v}" for k, v in headers.items()]
|
|
34
|
+
cmd.append(f"--http-header-fields={','.join(header_strs)}")
|
|
35
|
+
|
|
36
|
+
cmd.append("--fs")
|
|
37
|
+
|
|
38
|
+
cmd.append("--save-position-on-quit")
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
42
|
+
return True
|
|
43
|
+
except Exception as e:
|
|
44
|
+
console.print(f"[red]Error running player: {e}[/red]")
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
player = Player()
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
class ProgressTracker:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self.config_dir = Path.home() / ".weeb-cli"
|
|
8
|
+
self.progress_file = self.config_dir / "progress.json"
|
|
9
|
+
self.history_file = self.config_dir / "search_history.json"
|
|
10
|
+
self._ensure_file()
|
|
11
|
+
|
|
12
|
+
def _ensure_file(self):
|
|
13
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
14
|
+
if not self.progress_file.exists():
|
|
15
|
+
with open(self.progress_file, 'w') as f:
|
|
16
|
+
json.dump({}, f)
|
|
17
|
+
if not self.history_file.exists():
|
|
18
|
+
with open(self.history_file, 'w') as f:
|
|
19
|
+
json.dump([], f)
|
|
20
|
+
|
|
21
|
+
def load_progress(self):
|
|
22
|
+
try:
|
|
23
|
+
with open(self.progress_file, 'r', encoding='utf-8') as f:
|
|
24
|
+
return json.load(f)
|
|
25
|
+
except (json.JSONDecodeError, FileNotFoundError):
|
|
26
|
+
return {}
|
|
27
|
+
|
|
28
|
+
def save_progress(self, data):
|
|
29
|
+
with open(self.progress_file, 'w', encoding='utf-8') as f:
|
|
30
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
31
|
+
|
|
32
|
+
def get_anime_progress(self, slug):
|
|
33
|
+
data = self.load_progress()
|
|
34
|
+
return data.get(slug, {
|
|
35
|
+
"last_watched": 0,
|
|
36
|
+
"completed": [],
|
|
37
|
+
"title": "",
|
|
38
|
+
"total_episodes": 0,
|
|
39
|
+
"last_watched_at": None
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
def mark_watched(self, slug, ep_number, title=None, total_episodes=None):
|
|
43
|
+
data = self.load_progress()
|
|
44
|
+
if slug not in data:
|
|
45
|
+
data[slug] = {
|
|
46
|
+
"last_watched": 0,
|
|
47
|
+
"completed": [],
|
|
48
|
+
"title": title or slug,
|
|
49
|
+
"total_episodes": total_episodes or 0,
|
|
50
|
+
"last_watched_at": None
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
completed_set = set(data[slug].get("completed", []))
|
|
54
|
+
completed_set.add(ep_number)
|
|
55
|
+
data[slug]["completed"] = sorted(list(completed_set))
|
|
56
|
+
|
|
57
|
+
if ep_number > data[slug].get("last_watched", 0):
|
|
58
|
+
data[slug]["last_watched"] = ep_number
|
|
59
|
+
|
|
60
|
+
data[slug]["last_watched_at"] = datetime.now().isoformat()
|
|
61
|
+
|
|
62
|
+
if title:
|
|
63
|
+
data[slug]["title"] = title
|
|
64
|
+
if total_episodes:
|
|
65
|
+
data[slug]["total_episodes"] = total_episodes
|
|
66
|
+
|
|
67
|
+
self.save_progress(data)
|
|
68
|
+
|
|
69
|
+
def get_all_anime(self):
|
|
70
|
+
return self.load_progress()
|
|
71
|
+
|
|
72
|
+
def get_stats(self):
|
|
73
|
+
data = self.load_progress()
|
|
74
|
+
total_anime = len(data)
|
|
75
|
+
total_episodes = sum(len(a.get("completed", [])) for a in data.values())
|
|
76
|
+
total_hours = round(total_episodes * 24 / 60, 1)
|
|
77
|
+
|
|
78
|
+
last_watched = None
|
|
79
|
+
last_time = None
|
|
80
|
+
for slug, info in data.items():
|
|
81
|
+
watched_at = info.get("last_watched_at")
|
|
82
|
+
if watched_at:
|
|
83
|
+
if last_time is None or watched_at > last_time:
|
|
84
|
+
last_time = watched_at
|
|
85
|
+
last_watched = {"slug": slug, **info}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
"total_anime": total_anime,
|
|
89
|
+
"total_episodes": total_episodes,
|
|
90
|
+
"total_hours": total_hours,
|
|
91
|
+
"last_watched": last_watched
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
def get_completed_anime(self):
|
|
95
|
+
data = self.load_progress()
|
|
96
|
+
completed = []
|
|
97
|
+
for slug, info in data.items():
|
|
98
|
+
total = info.get("total_episodes", 0)
|
|
99
|
+
watched = len(info.get("completed", []))
|
|
100
|
+
if total > 0 and watched >= total:
|
|
101
|
+
completed.append({"slug": slug, **info})
|
|
102
|
+
return completed
|
|
103
|
+
|
|
104
|
+
def get_in_progress_anime(self):
|
|
105
|
+
data = self.load_progress()
|
|
106
|
+
in_progress = []
|
|
107
|
+
for slug, info in data.items():
|
|
108
|
+
total = info.get("total_episodes", 0)
|
|
109
|
+
watched = len(info.get("completed", []))
|
|
110
|
+
if watched > 0 and (total == 0 or watched < total):
|
|
111
|
+
in_progress.append({"slug": slug, **info})
|
|
112
|
+
return sorted(in_progress, key=lambda x: x.get("last_watched_at") or "", reverse=True)
|
|
113
|
+
|
|
114
|
+
def add_search_history(self, query):
|
|
115
|
+
try:
|
|
116
|
+
with open(self.history_file, 'r', encoding='utf-8') as f:
|
|
117
|
+
history = json.load(f)
|
|
118
|
+
except:
|
|
119
|
+
history = []
|
|
120
|
+
|
|
121
|
+
if query in history:
|
|
122
|
+
history.remove(query)
|
|
123
|
+
history.insert(0, query)
|
|
124
|
+
history = history[:10]
|
|
125
|
+
|
|
126
|
+
with open(self.history_file, 'w', encoding='utf-8') as f:
|
|
127
|
+
json.dump(history, f, ensure_ascii=False)
|
|
128
|
+
|
|
129
|
+
def get_search_history(self):
|
|
130
|
+
try:
|
|
131
|
+
with open(self.history_file, 'r', encoding='utf-8') as f:
|
|
132
|
+
return json.load(f)
|
|
133
|
+
except:
|
|
134
|
+
return []
|
|
135
|
+
|
|
136
|
+
progress_tracker = ProgressTracker()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from typing import List, Optional, Tuple
|
|
2
|
+
from weeb_cli.config import config
|
|
3
|
+
from weeb_cli.providers import get_provider, get_default_provider, list_providers
|
|
4
|
+
from weeb_cli.providers.base import AnimeResult, AnimeDetails, Episode, StreamLink, ProviderError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Scraper:
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self._provider = None
|
|
11
|
+
self._provider_name = None
|
|
12
|
+
self.last_error = None
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def provider(self):
|
|
16
|
+
current_source = config.get("scraping_source", "")
|
|
17
|
+
|
|
18
|
+
if self._provider_name != current_source:
|
|
19
|
+
self._provider_name = current_source
|
|
20
|
+
self._provider = get_provider(current_source)
|
|
21
|
+
|
|
22
|
+
if not self._provider:
|
|
23
|
+
lang = config.get("language", "tr")
|
|
24
|
+
default = get_default_provider(lang)
|
|
25
|
+
if default:
|
|
26
|
+
self._provider = get_provider(default)
|
|
27
|
+
self._provider_name = default
|
|
28
|
+
|
|
29
|
+
return self._provider
|
|
30
|
+
|
|
31
|
+
def search(self, query: str) -> List[AnimeResult]:
|
|
32
|
+
self.last_error = None
|
|
33
|
+
if not self.provider:
|
|
34
|
+
return []
|
|
35
|
+
try:
|
|
36
|
+
return self.provider.search(query)
|
|
37
|
+
except ProviderError as e:
|
|
38
|
+
self.last_error = e.code
|
|
39
|
+
return []
|
|
40
|
+
except Exception as e:
|
|
41
|
+
self.last_error = str(e)
|
|
42
|
+
return []
|
|
43
|
+
|
|
44
|
+
def get_details(self, anime_id: str) -> Optional[AnimeDetails]:
|
|
45
|
+
self.last_error = None
|
|
46
|
+
if not self.provider:
|
|
47
|
+
return None
|
|
48
|
+
try:
|
|
49
|
+
return self.provider.get_details(anime_id)
|
|
50
|
+
except ProviderError as e:
|
|
51
|
+
self.last_error = e.code
|
|
52
|
+
return None
|
|
53
|
+
except Exception as e:
|
|
54
|
+
self.last_error = str(e)
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def get_episodes(self, anime_id: str) -> List[Episode]:
|
|
58
|
+
self.last_error = None
|
|
59
|
+
if not self.provider:
|
|
60
|
+
return []
|
|
61
|
+
try:
|
|
62
|
+
return self.provider.get_episodes(anime_id)
|
|
63
|
+
except ProviderError as e:
|
|
64
|
+
self.last_error = e.code
|
|
65
|
+
return []
|
|
66
|
+
except Exception as e:
|
|
67
|
+
self.last_error = str(e)
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
def get_streams(self, anime_id: str, episode_id: str) -> List[StreamLink]:
|
|
71
|
+
self.last_error = None
|
|
72
|
+
if not self.provider:
|
|
73
|
+
return []
|
|
74
|
+
try:
|
|
75
|
+
return self.provider.get_streams(anime_id, episode_id)
|
|
76
|
+
except ProviderError as e:
|
|
77
|
+
self.last_error = e.code
|
|
78
|
+
return []
|
|
79
|
+
except Exception as e:
|
|
80
|
+
self.last_error = str(e)
|
|
81
|
+
return []
|
|
82
|
+
|
|
83
|
+
def get_available_sources(self) -> List[dict]:
|
|
84
|
+
return list_providers()
|
|
85
|
+
|
|
86
|
+
def get_sources_for_lang(self, lang: str) -> List[str]:
|
|
87
|
+
from weeb_cli.providers import get_providers_for_lang
|
|
88
|
+
return get_providers_for_lang(lang)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
scraper = Scraper()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from weeb_cli.services.scraper import scraper
|
|
2
|
+
|
|
3
|
+
def search(query):
|
|
4
|
+
results = scraper.search(query)
|
|
5
|
+
return [
|
|
6
|
+
{
|
|
7
|
+
"id": r.id,
|
|
8
|
+
"title": r.title,
|
|
9
|
+
"name": r.title,
|
|
10
|
+
"slug": r.id,
|
|
11
|
+
"type": r.type,
|
|
12
|
+
"cover": r.cover,
|
|
13
|
+
"year": r.year
|
|
14
|
+
}
|
|
15
|
+
for r in results
|
|
16
|
+
]
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from packaging import version
|
|
3
|
+
from weeb_cli import __version__
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
import questionary
|
|
6
|
+
from weeb_cli.i18n import i18n
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
import platform
|
|
10
|
+
import subprocess
|
|
11
|
+
import tempfile
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
def get_install_method():
|
|
16
|
+
if getattr(sys, 'frozen', False):
|
|
17
|
+
return "exe"
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
result = subprocess.run(
|
|
21
|
+
[sys.executable, "-m", "pip", "show", "weeb-cli"],
|
|
22
|
+
capture_output=True, text=True, timeout=5
|
|
23
|
+
)
|
|
24
|
+
if result.returncode == 0:
|
|
25
|
+
return "pip"
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
return "standalone"
|
|
30
|
+
|
|
31
|
+
def get_platform_info():
|
|
32
|
+
system = platform.system().lower()
|
|
33
|
+
machine = platform.machine().lower()
|
|
34
|
+
|
|
35
|
+
if system == "windows":
|
|
36
|
+
return "windows", "exe"
|
|
37
|
+
elif system == "darwin":
|
|
38
|
+
return "macos", "macos" if "arm" in machine else "macos-intel"
|
|
39
|
+
elif system == "linux":
|
|
40
|
+
return "linux", "linux"
|
|
41
|
+
else:
|
|
42
|
+
return system, system
|
|
43
|
+
|
|
44
|
+
def check_for_updates():
|
|
45
|
+
try:
|
|
46
|
+
url = "https://api.github.com/repos/ewgsta/weeb-cli/releases/latest"
|
|
47
|
+
response = requests.get(url, timeout=5)
|
|
48
|
+
if response.status_code == 200:
|
|
49
|
+
data = response.json()
|
|
50
|
+
latest_tag = data.get("tag_name", "").lstrip("v")
|
|
51
|
+
assets = data.get("assets", [])
|
|
52
|
+
|
|
53
|
+
if not latest_tag:
|
|
54
|
+
return False, None, None
|
|
55
|
+
|
|
56
|
+
current_ver = version.parse(__version__)
|
|
57
|
+
latest_ver = version.parse(latest_tag)
|
|
58
|
+
|
|
59
|
+
if latest_ver > current_ver:
|
|
60
|
+
return True, latest_tag, assets
|
|
61
|
+
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
return False, None, None
|
|
66
|
+
|
|
67
|
+
def find_asset_for_platform(assets):
|
|
68
|
+
system, platform_key = get_platform_info()
|
|
69
|
+
|
|
70
|
+
for asset in assets:
|
|
71
|
+
name = asset.get("name", "").lower()
|
|
72
|
+
download_url = asset.get("browser_download_url", "")
|
|
73
|
+
|
|
74
|
+
if system == "windows" and name.endswith(".exe"):
|
|
75
|
+
return download_url, name
|
|
76
|
+
elif system == "darwin" and ("macos" in name or "darwin" in name):
|
|
77
|
+
return download_url, name
|
|
78
|
+
elif system == "linux" and "linux" in name:
|
|
79
|
+
return download_url, name
|
|
80
|
+
|
|
81
|
+
return None, None
|
|
82
|
+
|
|
83
|
+
def download_exe(url, filename):
|
|
84
|
+
try:
|
|
85
|
+
console.print(f"[cyan]{i18n.get('update.downloading')}...[/cyan]")
|
|
86
|
+
|
|
87
|
+
response = requests.get(url, stream=True, timeout=60)
|
|
88
|
+
response.raise_for_status()
|
|
89
|
+
|
|
90
|
+
total_size = int(response.headers.get('content-length', 0))
|
|
91
|
+
|
|
92
|
+
if getattr(sys, 'frozen', False):
|
|
93
|
+
current_exe = sys.executable
|
|
94
|
+
download_dir = os.path.dirname(current_exe)
|
|
95
|
+
new_exe_path = os.path.join(download_dir, f"weeb-cli-new.exe")
|
|
96
|
+
else:
|
|
97
|
+
download_dir = tempfile.gettempdir()
|
|
98
|
+
new_exe_path = os.path.join(download_dir, filename)
|
|
99
|
+
|
|
100
|
+
downloaded = 0
|
|
101
|
+
with open(new_exe_path, 'wb') as f:
|
|
102
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
103
|
+
if chunk:
|
|
104
|
+
f.write(chunk)
|
|
105
|
+
downloaded += len(chunk)
|
|
106
|
+
if total_size > 0:
|
|
107
|
+
percent = int((downloaded / total_size) * 100)
|
|
108
|
+
console.print(f"\r[cyan]İndiriliyor: {percent}%[/cyan]", end="")
|
|
109
|
+
|
|
110
|
+
console.print(f"\n[green]{i18n.get('update.downloaded')}[/green]")
|
|
111
|
+
console.print(f"[dim]Konum: {new_exe_path}[/dim]")
|
|
112
|
+
|
|
113
|
+
if getattr(sys, 'frozen', False):
|
|
114
|
+
batch_content = f'''@echo off
|
|
115
|
+
timeout /t 2 /nobreak >nul
|
|
116
|
+
del "{current_exe}"
|
|
117
|
+
move "{new_exe_path}" "{current_exe}"
|
|
118
|
+
start "" "{current_exe}"
|
|
119
|
+
del "%~f0"
|
|
120
|
+
'''
|
|
121
|
+
batch_path = os.path.join(download_dir, "update.bat")
|
|
122
|
+
with open(batch_path, 'w') as f:
|
|
123
|
+
f.write(batch_content)
|
|
124
|
+
|
|
125
|
+
console.print(f"[green]{i18n.get('update.restarting')}...[/green]")
|
|
126
|
+
subprocess.Popen(['cmd', '/c', batch_path],
|
|
127
|
+
creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, 'CREATE_NO_WINDOW') else 0)
|
|
128
|
+
sys.exit(0)
|
|
129
|
+
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
console.print(f"[red]{i18n.get('update.error')}: {e}[/red]")
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
def update_via_pip():
|
|
137
|
+
try:
|
|
138
|
+
console.print(f"[cyan]{i18n.get('update.updating_pip')}...[/cyan]")
|
|
139
|
+
|
|
140
|
+
result = subprocess.run(
|
|
141
|
+
[sys.executable, "-m", "pip", "install", "--upgrade", "weeb-cli"],
|
|
142
|
+
capture_output=True, text=True, timeout=120
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if result.returncode == 0:
|
|
146
|
+
console.print(f"[green]{i18n.get('update.success')}[/green]")
|
|
147
|
+
console.print(f"[dim]{i18n.get('update.restart_required')}[/dim]")
|
|
148
|
+
return True
|
|
149
|
+
else:
|
|
150
|
+
console.print(f"[red]{i18n.get('update.error')}[/red]")
|
|
151
|
+
console.print(f"[dim]{result.stderr}[/dim]")
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
except Exception as e:
|
|
155
|
+
console.print(f"[red]{i18n.get('update.error')}: {e}[/red]")
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
def update_prompt():
|
|
159
|
+
is_available, latest_ver, assets = check_for_updates()
|
|
160
|
+
|
|
161
|
+
if not is_available:
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
console.clear()
|
|
165
|
+
console.print(f"\n[green bold]{i18n.get('update.available')} (v{latest_ver})[/green bold]")
|
|
166
|
+
console.print(f"[dim]{i18n.get('update.current')}: v{__version__}[/dim]\n")
|
|
167
|
+
|
|
168
|
+
should_update = questionary.confirm(
|
|
169
|
+
i18n.get("update.prompt"),
|
|
170
|
+
default=True
|
|
171
|
+
).ask()
|
|
172
|
+
|
|
173
|
+
if not should_update:
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
install_method = get_install_method()
|
|
177
|
+
|
|
178
|
+
if install_method == "exe":
|
|
179
|
+
asset_url, asset_name = find_asset_for_platform(assets or [])
|
|
180
|
+
if asset_url:
|
|
181
|
+
download_exe(asset_url, asset_name)
|
|
182
|
+
else:
|
|
183
|
+
console.print(f"[red]{i18n.get('update.no_asset')}[/red]")
|
|
184
|
+
|
|
185
|
+
elif install_method == "pip":
|
|
186
|
+
update_via_pip()
|
|
187
|
+
|
|
188
|
+
else:
|
|
189
|
+
asset_url, asset_name = find_asset_for_platform(assets or [])
|
|
190
|
+
if asset_url:
|
|
191
|
+
system, _ = get_platform_info()
|
|
192
|
+
if system == "windows":
|
|
193
|
+
download_exe(asset_url, asset_name)
|
|
194
|
+
else:
|
|
195
|
+
console.print(f"[cyan]{i18n.get('update.download_url')}:[/cyan]")
|
|
196
|
+
console.print(f"[blue]{asset_url}[/blue]")
|
|
197
|
+
else:
|
|
198
|
+
console.print(f"[yellow]{i18n.get('update.manual_update')}[/yellow]")
|
|
199
|
+
console.print("[blue]pip install --upgrade weeb-cli[/blue]")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from weeb_cli.services.scraper import scraper
|
|
2
|
+
|
|
3
|
+
def get_streams(anime_id, episode_id):
|
|
4
|
+
streams = scraper.get_streams(anime_id, episode_id)
|
|
5
|
+
if not streams:
|
|
6
|
+
return None
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
"data": {
|
|
10
|
+
"links": [
|
|
11
|
+
{
|
|
12
|
+
"url": s.url,
|
|
13
|
+
"quality": s.quality,
|
|
14
|
+
"server": s.server
|
|
15
|
+
}
|
|
16
|
+
for s in streams
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
weeb_cli/ui/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Init UI package
|
weeb_cli/ui/header.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from rich import print
|
|
2
|
+
from rich.text import Text
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from weeb_cli.config import config
|
|
5
|
+
from weeb_cli import __version__
|
|
6
|
+
|
|
7
|
+
console = Console()
|
|
8
|
+
|
|
9
|
+
def show_header(title="Weeb API", show_version=False, show_source=False):
|
|
10
|
+
console.clear()
|
|
11
|
+
|
|
12
|
+
text = Text()
|
|
13
|
+
text.append(f" {title} ", style="bold white on blue")
|
|
14
|
+
|
|
15
|
+
parts = []
|
|
16
|
+
|
|
17
|
+
if show_source:
|
|
18
|
+
cfg_source = config.get("scraping_source", "local")
|
|
19
|
+
disp_source = "weeb" if cfg_source == "local" else cfg_source
|
|
20
|
+
parts.append(disp_source)
|
|
21
|
+
|
|
22
|
+
if show_version:
|
|
23
|
+
parts.append(f"v{__version__}")
|
|
24
|
+
|
|
25
|
+
if parts:
|
|
26
|
+
joined = " | ".join(parts)
|
|
27
|
+
text.append(f" | {joined}", style="dim white")
|
|
28
|
+
|
|
29
|
+
console.print(text, justify="left")
|
|
30
|
+
print()
|
weeb_cli/ui/menu.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import questionary
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
import sys
|
|
4
|
+
from .header import show_header
|
|
5
|
+
from weeb_cli.i18n import i18n
|
|
6
|
+
from weeb_cli.commands.search import search_anime
|
|
7
|
+
from weeb_cli.commands.settings import open_settings
|
|
8
|
+
from weeb_cli.commands.watchlist import show_watchlist
|
|
9
|
+
from weeb_cli.commands.downloads import show_downloads
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
def show_main_menu():
|
|
14
|
+
console.clear()
|
|
15
|
+
show_header("Weeb API", show_version=True, show_source=True)
|
|
16
|
+
|
|
17
|
+
opt_search = i18n.get("menu.options.search")
|
|
18
|
+
opt_watchlist = i18n.get("menu.options.watchlist")
|
|
19
|
+
opt_downloads = i18n.get("menu.options.downloads")
|
|
20
|
+
opt_settings = i18n.get("menu.options.settings")
|
|
21
|
+
opt_exit = i18n.get("menu.options.exit")
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
selected = questionary.select(
|
|
25
|
+
i18n.get("menu.prompt"),
|
|
26
|
+
choices=[
|
|
27
|
+
opt_search,
|
|
28
|
+
opt_watchlist,
|
|
29
|
+
opt_downloads,
|
|
30
|
+
opt_settings,
|
|
31
|
+
opt_exit
|
|
32
|
+
],
|
|
33
|
+
pointer=">",
|
|
34
|
+
use_shortcuts=False,
|
|
35
|
+
style=questionary.Style([
|
|
36
|
+
('pointer', 'fg:cyan bold'),
|
|
37
|
+
('highlighted', 'fg:cyan'),
|
|
38
|
+
('selected', 'fg:cyan bold'),
|
|
39
|
+
])
|
|
40
|
+
).ask()
|
|
41
|
+
|
|
42
|
+
console.clear()
|
|
43
|
+
|
|
44
|
+
if selected == opt_search:
|
|
45
|
+
search_anime()
|
|
46
|
+
elif selected == opt_watchlist:
|
|
47
|
+
show_watchlist()
|
|
48
|
+
elif selected == opt_downloads:
|
|
49
|
+
show_downloads()
|
|
50
|
+
elif selected == opt_settings:
|
|
51
|
+
open_settings()
|
|
52
|
+
elif selected == opt_exit or selected is None:
|
|
53
|
+
console.print(f"[yellow] {i18n.get('common.success')}...[/yellow]")
|
|
54
|
+
sys.exit(0)
|
|
55
|
+
|
|
56
|
+
show_main_menu()
|
|
57
|
+
|
|
58
|
+
except KeyboardInterrupt:
|
|
59
|
+
sys.exit(0)
|