weeb-cli 2.14.2__tar.gz → 2.15.0__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-2.14.2/weeb_cli.egg-info → weeb_cli-2.15.0}/PKG-INFO +67 -8
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/README.md +66 -7
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/pyproject.toml +1 -1
- weeb_cli-2.15.0/tests/test_sdk.py +291 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/__init__.py +21 -2
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/downloads.py +3 -2
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/config.py +4 -3
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/i18n.py +6 -2
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/locales/de.json +1 -14
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/locales/en.json +1 -14
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/locales/pl.json +3 -14
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/locales/tr.json +4 -15
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/main.py +19 -19
- weeb_cli-2.15.0/weeb_cli/sdk.py +480 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/cache.py +0 -9
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/database.py +29 -19
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/downloader.py +2 -29
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/local_library.py +3 -2
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/logger.py +2 -2
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/tracker.py +110 -272
- {weeb_cli-2.14.2 → weeb_cli-2.15.0/weeb_cli.egg-info}/PKG-INFO +67 -8
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli.egg-info/SOURCES.txt +2 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/LICENSE +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/setup.cfg +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/tests/test_anilist_tracker.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/tests/test_aniworld_provider.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/tests/test_api.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/tests/test_cache.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/tests/test_exceptions.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/tests/test_kitsu_tracker.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/tests/test_mal_tracker.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/tests/test_providers.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/tests/test_restful_api.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/tests/test_sanitizer.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/tests/test_sanitizer_security.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/__main__.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/__init__.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/api.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/library.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/search/__init__.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/search/anime_details.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/search/download_flow.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/search/episode_utils.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/search/search_handlers.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/search/stream_utils.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/search/watch_flow.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/search.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/serve.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/serve_restful.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/settings/__init__.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/settings/settings_backup.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/settings/settings_cache.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/settings/settings_config.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/settings/settings_download.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/settings/settings_drives.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/settings/settings_menu.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/settings/settings_shortcuts.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/settings/settings_trackers.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/settings.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/setup.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/commands/watchlist.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/exceptions.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/__init__.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/base.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/de/__init__.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/de/aniworld.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/en/__init__.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/en/allanime.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/en/hianime.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/extractors/__init__.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/extractors/doodstream.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/extractors/filemoon.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/extractors/megacloud.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/extractors/streamtape.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/extractors/vidoza.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/extractors/voe.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/pl/__init__.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/pl/docchi.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/registry.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/tr/__init__.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/tr/animecix.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/tr/anizle.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/tr/turkanime.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/providers/tr/weeb.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/__init__.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/_base.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/_tracker_base.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/aniskip.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/dependency_manager.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/details.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/discord_rpc.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/error_handler.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/headless_downloader.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/notifier.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/player.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/progress.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/scraper.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/search.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/shortcuts.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/stream_validator.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/updater.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/services/watch.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/templates/anilist_error.html +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/templates/anilist_success.html +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/templates/mal_error.html +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/templates/mal_success.html +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/ui/__init__.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/ui/header.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/ui/menu.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/ui/prompt.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/utils/__init__.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli/utils/sanitizer.py +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli.egg-info/dependency_links.txt +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli.egg-info/entry_points.txt +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli.egg-info/requires.txt +0 -0
- {weeb_cli-2.14.2 → weeb_cli-2.15.0}/weeb_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weeb-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.15.0
|
|
4
4
|
Summary: No browser, no ads, no distractions. Just you and an unparalleled anime viewing experience.
|
|
5
5
|
Author-email: ewgsta <ewgst@proton.me>
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -164,24 +164,77 @@ For scripts, automation, and AI agents, weeb-cli provides JSON API commands that
|
|
|
164
164
|
weeb-cli api providers
|
|
165
165
|
|
|
166
166
|
# Search for anime (returns IDs)
|
|
167
|
-
weeb-cli api search "Angel Beats"
|
|
167
|
+
weeb-cli api search "Angel Beats" --provider hianime
|
|
168
168
|
# Returns: [{"id": "12345", "title": "Angel Beats!", ...}]
|
|
169
169
|
|
|
170
170
|
# List episodes (use ID from search)
|
|
171
|
-
weeb-cli api episodes 12345 --season 1
|
|
171
|
+
weeb-cli api episodes 12345 --season 1 --provider hianime
|
|
172
172
|
|
|
173
173
|
# Get stream URLs for an episode
|
|
174
|
-
weeb-cli api streams 12345 --season 1 --episode 1
|
|
174
|
+
weeb-cli api streams 12345 --season 1 --episode 1 --provider hianime
|
|
175
175
|
|
|
176
176
|
# Get anime details
|
|
177
|
-
weeb-cli api details 12345
|
|
177
|
+
weeb-cli api details 12345 --provider hianime
|
|
178
178
|
|
|
179
179
|
# Download an episode
|
|
180
|
-
weeb-cli api download 12345 --season 1 --episode 1 --output ./downloads
|
|
180
|
+
weeb-cli api download 12345 --season 1 --episode 1 --provider hianime --output ./downloads
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
+
**Provider Selection:**
|
|
184
|
+
All API commands support the `--provider` (or `-p`) option to select which anime source to use:
|
|
185
|
+
- Turkish: `animecix`, `turkanime`, `anizle`, `weeb`
|
|
186
|
+
- English: `hianime`, `allanime`
|
|
187
|
+
- German: `aniworld`
|
|
188
|
+
- Polish: `docchi`
|
|
189
|
+
|
|
190
|
+
Default provider is `animecix` if not specified. Use `weeb-cli api providers` to see all available providers with their language and region information.
|
|
191
|
+
|
|
183
192
|
All API commands output JSON to stdout.
|
|
184
193
|
|
|
194
|
+
### Python SDK (Programmatic Access)
|
|
195
|
+
|
|
196
|
+
For Python applications, weeb-cli provides a native SDK that doesn't require spawning processes:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from weeb_cli import WeebSDK
|
|
200
|
+
|
|
201
|
+
# Initialize SDK
|
|
202
|
+
sdk = WeebSDK(default_provider="hianime")
|
|
203
|
+
|
|
204
|
+
# Search for anime
|
|
205
|
+
results = sdk.search("One Piece")
|
|
206
|
+
for anime in results:
|
|
207
|
+
print(f"{anime.title} ({anime.year})")
|
|
208
|
+
|
|
209
|
+
# Get episodes
|
|
210
|
+
episodes = sdk.get_episodes(results[0].id, season=1)
|
|
211
|
+
|
|
212
|
+
# Get stream URLs
|
|
213
|
+
streams = sdk.get_streams(
|
|
214
|
+
anime_id=results[0].id,
|
|
215
|
+
episode_id=episodes[0].id
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Download episode
|
|
219
|
+
path = sdk.download_episode(
|
|
220
|
+
anime_id=results[0].id,
|
|
221
|
+
season=1,
|
|
222
|
+
episode=1,
|
|
223
|
+
output_dir="./downloads"
|
|
224
|
+
)
|
|
225
|
+
print(f"Downloaded to: {path}")
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**SDK Features:**
|
|
229
|
+
- No subprocess overhead - direct Python API
|
|
230
|
+
- Type-safe with full type hints
|
|
231
|
+
- Automatic provider discovery
|
|
232
|
+
- Same caching as CLI mode
|
|
233
|
+
- Thread-safe operations
|
|
234
|
+
- Headless mode (no database/TUI dependencies)
|
|
235
|
+
|
|
236
|
+
See [SDK Documentation](https://ewgsta.github.io/weeb-cli/api/sdk/) for complete API reference.
|
|
237
|
+
|
|
185
238
|
### Sonarr/*arr Integration (Serve Mode)
|
|
186
239
|
|
|
187
240
|
weeb-cli can run as a Torznab-compatible server for Sonarr and other *arr applications:
|
|
@@ -193,12 +246,18 @@ weeb-cli serve --port 9876 \
|
|
|
193
246
|
--watch-dir /downloads/watch \
|
|
194
247
|
--completed-dir /downloads/completed \
|
|
195
248
|
--sonarr-url http://sonarr:8989 \
|
|
196
|
-
--sonarr-api-key YOUR_KEY
|
|
197
|
-
--providers animecix,anizle,turkanime
|
|
249
|
+
--sonarr-api-key YOUR_KEY
|
|
198
250
|
```
|
|
199
251
|
|
|
200
252
|
Then add `http://weeb-cli-host:9876` as a Torznab indexer in Sonarr with category 5070 (TV/Anime). The server includes a blackhole download worker that automatically processes grabbed episodes.
|
|
201
253
|
|
|
254
|
+
**Docker Support:**
|
|
255
|
+
```bash
|
|
256
|
+
docker-compose -f docs/docker-compose.torznab.yml up -d
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
See [Torznab Server Documentation](https://ewgsta.github.io/weeb-cli/cli/serve-mode/) for full details.
|
|
260
|
+
|
|
202
261
|
### RESTful API Server
|
|
203
262
|
|
|
204
263
|
For web/mobile applications and custom integrations, weeb-cli provides a RESTful API server:
|
|
@@ -118,24 +118,77 @@ For scripts, automation, and AI agents, weeb-cli provides JSON API commands that
|
|
|
118
118
|
weeb-cli api providers
|
|
119
119
|
|
|
120
120
|
# Search for anime (returns IDs)
|
|
121
|
-
weeb-cli api search "Angel Beats"
|
|
121
|
+
weeb-cli api search "Angel Beats" --provider hianime
|
|
122
122
|
# Returns: [{"id": "12345", "title": "Angel Beats!", ...}]
|
|
123
123
|
|
|
124
124
|
# List episodes (use ID from search)
|
|
125
|
-
weeb-cli api episodes 12345 --season 1
|
|
125
|
+
weeb-cli api episodes 12345 --season 1 --provider hianime
|
|
126
126
|
|
|
127
127
|
# Get stream URLs for an episode
|
|
128
|
-
weeb-cli api streams 12345 --season 1 --episode 1
|
|
128
|
+
weeb-cli api streams 12345 --season 1 --episode 1 --provider hianime
|
|
129
129
|
|
|
130
130
|
# Get anime details
|
|
131
|
-
weeb-cli api details 12345
|
|
131
|
+
weeb-cli api details 12345 --provider hianime
|
|
132
132
|
|
|
133
133
|
# Download an episode
|
|
134
|
-
weeb-cli api download 12345 --season 1 --episode 1 --output ./downloads
|
|
134
|
+
weeb-cli api download 12345 --season 1 --episode 1 --provider hianime --output ./downloads
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
+
**Provider Selection:**
|
|
138
|
+
All API commands support the `--provider` (or `-p`) option to select which anime source to use:
|
|
139
|
+
- Turkish: `animecix`, `turkanime`, `anizle`, `weeb`
|
|
140
|
+
- English: `hianime`, `allanime`
|
|
141
|
+
- German: `aniworld`
|
|
142
|
+
- Polish: `docchi`
|
|
143
|
+
|
|
144
|
+
Default provider is `animecix` if not specified. Use `weeb-cli api providers` to see all available providers with their language and region information.
|
|
145
|
+
|
|
137
146
|
All API commands output JSON to stdout.
|
|
138
147
|
|
|
148
|
+
### Python SDK (Programmatic Access)
|
|
149
|
+
|
|
150
|
+
For Python applications, weeb-cli provides a native SDK that doesn't require spawning processes:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from weeb_cli import WeebSDK
|
|
154
|
+
|
|
155
|
+
# Initialize SDK
|
|
156
|
+
sdk = WeebSDK(default_provider="hianime")
|
|
157
|
+
|
|
158
|
+
# Search for anime
|
|
159
|
+
results = sdk.search("One Piece")
|
|
160
|
+
for anime in results:
|
|
161
|
+
print(f"{anime.title} ({anime.year})")
|
|
162
|
+
|
|
163
|
+
# Get episodes
|
|
164
|
+
episodes = sdk.get_episodes(results[0].id, season=1)
|
|
165
|
+
|
|
166
|
+
# Get stream URLs
|
|
167
|
+
streams = sdk.get_streams(
|
|
168
|
+
anime_id=results[0].id,
|
|
169
|
+
episode_id=episodes[0].id
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Download episode
|
|
173
|
+
path = sdk.download_episode(
|
|
174
|
+
anime_id=results[0].id,
|
|
175
|
+
season=1,
|
|
176
|
+
episode=1,
|
|
177
|
+
output_dir="./downloads"
|
|
178
|
+
)
|
|
179
|
+
print(f"Downloaded to: {path}")
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**SDK Features:**
|
|
183
|
+
- No subprocess overhead - direct Python API
|
|
184
|
+
- Type-safe with full type hints
|
|
185
|
+
- Automatic provider discovery
|
|
186
|
+
- Same caching as CLI mode
|
|
187
|
+
- Thread-safe operations
|
|
188
|
+
- Headless mode (no database/TUI dependencies)
|
|
189
|
+
|
|
190
|
+
See [SDK Documentation](https://ewgsta.github.io/weeb-cli/api/sdk/) for complete API reference.
|
|
191
|
+
|
|
139
192
|
### Sonarr/*arr Integration (Serve Mode)
|
|
140
193
|
|
|
141
194
|
weeb-cli can run as a Torznab-compatible server for Sonarr and other *arr applications:
|
|
@@ -147,12 +200,18 @@ weeb-cli serve --port 9876 \
|
|
|
147
200
|
--watch-dir /downloads/watch \
|
|
148
201
|
--completed-dir /downloads/completed \
|
|
149
202
|
--sonarr-url http://sonarr:8989 \
|
|
150
|
-
--sonarr-api-key YOUR_KEY
|
|
151
|
-
--providers animecix,anizle,turkanime
|
|
203
|
+
--sonarr-api-key YOUR_KEY
|
|
152
204
|
```
|
|
153
205
|
|
|
154
206
|
Then add `http://weeb-cli-host:9876` as a Torznab indexer in Sonarr with category 5070 (TV/Anime). The server includes a blackhole download worker that automatically processes grabbed episodes.
|
|
155
207
|
|
|
208
|
+
**Docker Support:**
|
|
209
|
+
```bash
|
|
210
|
+
docker-compose -f docs/docker-compose.torznab.yml up -d
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
See [Torznab Server Documentation](https://ewgsta.github.io/weeb-cli/cli/serve-mode/) for full details.
|
|
214
|
+
|
|
156
215
|
### RESTful API Server
|
|
157
216
|
|
|
158
217
|
For web/mobile applications and custom integrations, weeb-cli provides a RESTful API server:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "weeb-cli"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.15.0"
|
|
8
8
|
description = "No browser, no ads, no distractions. Just you and an unparalleled anime viewing experience."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "ewgsta", email = "ewgst@proton.me" }]
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Tests for Weeb CLI SDK."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from unittest.mock import Mock, patch, MagicMock
|
|
5
|
+
|
|
6
|
+
from weeb_cli.sdk import WeebSDK, list_providers, get_provider_info
|
|
7
|
+
from weeb_cli.providers.base import AnimeResult, Episode, StreamLink, AnimeDetails
|
|
8
|
+
from weeb_cli.exceptions import ProviderError, WeebCLIError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestWeebSDK:
|
|
12
|
+
"""Test suite for WeebSDK class."""
|
|
13
|
+
|
|
14
|
+
def test_init_default(self):
|
|
15
|
+
"""Test SDK initialization with defaults."""
|
|
16
|
+
sdk = WeebSDK()
|
|
17
|
+
assert sdk.headless is True
|
|
18
|
+
assert sdk.default_provider == "animecix"
|
|
19
|
+
|
|
20
|
+
def test_init_custom_provider(self):
|
|
21
|
+
"""Test SDK initialization with custom provider."""
|
|
22
|
+
sdk = WeebSDK(default_provider="hianime")
|
|
23
|
+
assert sdk.default_provider == "hianime"
|
|
24
|
+
|
|
25
|
+
def test_init_non_headless(self):
|
|
26
|
+
"""Test SDK initialization in non-headless mode."""
|
|
27
|
+
sdk = WeebSDK(headless=False)
|
|
28
|
+
assert sdk.headless is False
|
|
29
|
+
|
|
30
|
+
@patch('weeb_cli.sdk._list_providers')
|
|
31
|
+
def test_list_providers(self, mock_list):
|
|
32
|
+
"""Test listing providers."""
|
|
33
|
+
mock_list.return_value = [
|
|
34
|
+
{"name": "animecix", "lang": "tr", "region": "TR"},
|
|
35
|
+
{"name": "hianime", "lang": "en", "region": "US"}
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
sdk = WeebSDK()
|
|
39
|
+
providers = sdk.list_providers()
|
|
40
|
+
|
|
41
|
+
assert len(providers) == 2
|
|
42
|
+
assert providers[0]["name"] == "animecix"
|
|
43
|
+
assert providers[1]["name"] == "hianime"
|
|
44
|
+
|
|
45
|
+
@patch('weeb_cli.sdk._list_providers')
|
|
46
|
+
def test_get_provider_info(self, mock_list):
|
|
47
|
+
"""Test getting provider info."""
|
|
48
|
+
mock_list.return_value = [
|
|
49
|
+
{"name": "animecix", "lang": "tr", "region": "TR"},
|
|
50
|
+
{"name": "hianime", "lang": "en", "region": "US"}
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
sdk = WeebSDK()
|
|
54
|
+
info = sdk.get_provider_info("hianime")
|
|
55
|
+
|
|
56
|
+
assert info is not None
|
|
57
|
+
assert info["name"] == "hianime"
|
|
58
|
+
assert info["lang"] == "en"
|
|
59
|
+
|
|
60
|
+
@patch('weeb_cli.sdk._list_providers')
|
|
61
|
+
def test_get_provider_info_not_found(self, mock_list):
|
|
62
|
+
"""Test getting info for non-existent provider."""
|
|
63
|
+
mock_list.return_value = [
|
|
64
|
+
{"name": "animecix", "lang": "tr", "region": "TR"}
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
sdk = WeebSDK()
|
|
68
|
+
info = sdk.get_provider_info("invalid")
|
|
69
|
+
|
|
70
|
+
assert info is None
|
|
71
|
+
|
|
72
|
+
@patch('weeb_cli.sdk.get_provider')
|
|
73
|
+
def test_search_success(self, mock_get_provider):
|
|
74
|
+
"""Test successful anime search."""
|
|
75
|
+
mock_provider = Mock()
|
|
76
|
+
mock_provider.search.return_value = [
|
|
77
|
+
AnimeResult(id="1", title="Anime 1", year=2020),
|
|
78
|
+
AnimeResult(id="2", title="Anime 2", year=2021)
|
|
79
|
+
]
|
|
80
|
+
mock_get_provider.return_value = mock_provider
|
|
81
|
+
|
|
82
|
+
sdk = WeebSDK()
|
|
83
|
+
results = sdk.search("test", provider="hianime")
|
|
84
|
+
|
|
85
|
+
assert len(results) == 2
|
|
86
|
+
assert results[0].title == "Anime 1"
|
|
87
|
+
mock_provider.search.assert_called_once_with("test")
|
|
88
|
+
|
|
89
|
+
@patch('weeb_cli.sdk.get_provider')
|
|
90
|
+
def test_search_provider_not_found(self, mock_get_provider):
|
|
91
|
+
"""Test search with invalid provider."""
|
|
92
|
+
mock_get_provider.return_value = None
|
|
93
|
+
|
|
94
|
+
sdk = WeebSDK()
|
|
95
|
+
|
|
96
|
+
with pytest.raises(ProviderError) as exc_info:
|
|
97
|
+
sdk.search("test", provider="invalid")
|
|
98
|
+
|
|
99
|
+
assert "Provider not found or disabled" in str(exc_info.value)
|
|
100
|
+
|
|
101
|
+
@patch('weeb_cli.sdk.get_provider')
|
|
102
|
+
def test_search_uses_default_provider(self, mock_get_provider):
|
|
103
|
+
"""Test search uses default provider when not specified."""
|
|
104
|
+
mock_provider = Mock()
|
|
105
|
+
mock_provider.search.return_value = []
|
|
106
|
+
mock_get_provider.return_value = mock_provider
|
|
107
|
+
|
|
108
|
+
sdk = WeebSDK(default_provider="hianime")
|
|
109
|
+
sdk.search("test")
|
|
110
|
+
|
|
111
|
+
mock_get_provider.assert_called_with("hianime")
|
|
112
|
+
|
|
113
|
+
@patch('weeb_cli.sdk.get_provider')
|
|
114
|
+
def test_get_details_success(self, mock_get_provider):
|
|
115
|
+
"""Test getting anime details."""
|
|
116
|
+
mock_provider = Mock()
|
|
117
|
+
mock_provider.get_details.return_value = AnimeDetails(
|
|
118
|
+
id="1",
|
|
119
|
+
title="Test Anime",
|
|
120
|
+
description="Test description",
|
|
121
|
+
genres=["Action", "Adventure"]
|
|
122
|
+
)
|
|
123
|
+
mock_get_provider.return_value = mock_provider
|
|
124
|
+
|
|
125
|
+
sdk = WeebSDK()
|
|
126
|
+
details = sdk.get_details("1", provider="hianime")
|
|
127
|
+
|
|
128
|
+
assert details is not None
|
|
129
|
+
assert details.title == "Test Anime"
|
|
130
|
+
assert len(details.genres) == 2
|
|
131
|
+
|
|
132
|
+
@patch('weeb_cli.sdk.get_provider')
|
|
133
|
+
def test_get_episodes_success(self, mock_get_provider):
|
|
134
|
+
"""Test getting episode list."""
|
|
135
|
+
mock_provider = Mock()
|
|
136
|
+
mock_provider.get_episodes.return_value = [
|
|
137
|
+
Episode(id="e1", number=1, season=1),
|
|
138
|
+
Episode(id="e2", number=2, season=1),
|
|
139
|
+
Episode(id="e3", number=1, season=2)
|
|
140
|
+
]
|
|
141
|
+
mock_get_provider.return_value = mock_provider
|
|
142
|
+
|
|
143
|
+
sdk = WeebSDK()
|
|
144
|
+
episodes = sdk.get_episodes("anime-id", provider="hianime")
|
|
145
|
+
|
|
146
|
+
assert len(episodes) == 3
|
|
147
|
+
|
|
148
|
+
@patch('weeb_cli.sdk.get_provider')
|
|
149
|
+
def test_get_episodes_filter_by_season(self, mock_get_provider):
|
|
150
|
+
"""Test filtering episodes by season."""
|
|
151
|
+
mock_provider = Mock()
|
|
152
|
+
mock_provider.get_episodes.return_value = [
|
|
153
|
+
Episode(id="e1", number=1, season=1),
|
|
154
|
+
Episode(id="e2", number=2, season=1),
|
|
155
|
+
Episode(id="e3", number=1, season=2)
|
|
156
|
+
]
|
|
157
|
+
mock_get_provider.return_value = mock_provider
|
|
158
|
+
|
|
159
|
+
sdk = WeebSDK()
|
|
160
|
+
episodes = sdk.get_episodes("anime-id", season=1, provider="hianime")
|
|
161
|
+
|
|
162
|
+
assert len(episodes) == 2
|
|
163
|
+
assert all(ep.season == 1 for ep in episodes)
|
|
164
|
+
|
|
165
|
+
@patch('weeb_cli.sdk.get_provider')
|
|
166
|
+
def test_get_streams_success(self, mock_get_provider):
|
|
167
|
+
"""Test getting stream URLs."""
|
|
168
|
+
mock_provider = Mock()
|
|
169
|
+
mock_provider.get_streams.return_value = [
|
|
170
|
+
StreamLink(url="http://stream1.com", quality="1080p"),
|
|
171
|
+
StreamLink(url="http://stream2.com", quality="720p")
|
|
172
|
+
]
|
|
173
|
+
mock_get_provider.return_value = mock_provider
|
|
174
|
+
|
|
175
|
+
sdk = WeebSDK()
|
|
176
|
+
streams = sdk.get_streams("anime-id", "ep-id", provider="hianime")
|
|
177
|
+
|
|
178
|
+
assert len(streams) == 2
|
|
179
|
+
assert streams[0].quality == "1080p"
|
|
180
|
+
|
|
181
|
+
@patch('weeb_cli.services.headless_downloader.download_episode')
|
|
182
|
+
@patch('weeb_cli.sdk.get_provider')
|
|
183
|
+
def test_download_episode_success(self, mock_get_provider, mock_download):
|
|
184
|
+
"""Test downloading an episode."""
|
|
185
|
+
mock_provider = Mock()
|
|
186
|
+
mock_provider.get_episodes.return_value = [
|
|
187
|
+
Episode(id="e1", number=1, season=1)
|
|
188
|
+
]
|
|
189
|
+
mock_provider.get_streams.return_value = [
|
|
190
|
+
StreamLink(url="http://stream.com", quality="1080p")
|
|
191
|
+
]
|
|
192
|
+
mock_provider.get_details.return_value = AnimeDetails(
|
|
193
|
+
id="1", title="Test Anime"
|
|
194
|
+
)
|
|
195
|
+
mock_get_provider.return_value = mock_provider
|
|
196
|
+
mock_download.return_value = "/path/to/file.mp4"
|
|
197
|
+
|
|
198
|
+
sdk = WeebSDK()
|
|
199
|
+
path = sdk.download_episode(
|
|
200
|
+
anime_id="1",
|
|
201
|
+
season=1,
|
|
202
|
+
episode=1,
|
|
203
|
+
provider="hianime"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
assert path == "/path/to/file.mp4"
|
|
207
|
+
mock_download.assert_called_once()
|
|
208
|
+
|
|
209
|
+
@patch('weeb_cli.sdk.get_provider')
|
|
210
|
+
def test_download_episode_not_found(self, mock_get_provider):
|
|
211
|
+
"""Test downloading non-existent episode."""
|
|
212
|
+
mock_provider = Mock()
|
|
213
|
+
mock_provider.get_episodes.return_value = [
|
|
214
|
+
Episode(id="e1", number=1, season=1)
|
|
215
|
+
]
|
|
216
|
+
mock_get_provider.return_value = mock_provider
|
|
217
|
+
|
|
218
|
+
sdk = WeebSDK()
|
|
219
|
+
|
|
220
|
+
with pytest.raises(WeebCLIError) as exc_info:
|
|
221
|
+
sdk.download_episode(
|
|
222
|
+
anime_id="1",
|
|
223
|
+
season=2,
|
|
224
|
+
episode=1,
|
|
225
|
+
provider="hianime"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
assert "not found" in str(exc_info.value)
|
|
229
|
+
|
|
230
|
+
@patch('weeb_cli.sdk.get_provider')
|
|
231
|
+
def test_download_episode_no_streams(self, mock_get_provider):
|
|
232
|
+
"""Test downloading when no streams available."""
|
|
233
|
+
mock_provider = Mock()
|
|
234
|
+
mock_provider.get_episodes.return_value = [
|
|
235
|
+
Episode(id="e1", number=1, season=1)
|
|
236
|
+
]
|
|
237
|
+
mock_provider.get_streams.return_value = []
|
|
238
|
+
mock_get_provider.return_value = mock_provider
|
|
239
|
+
|
|
240
|
+
sdk = WeebSDK()
|
|
241
|
+
|
|
242
|
+
with pytest.raises(WeebCLIError) as exc_info:
|
|
243
|
+
sdk.download_episode(
|
|
244
|
+
anime_id="1",
|
|
245
|
+
season=1,
|
|
246
|
+
episode=1,
|
|
247
|
+
provider="hianime"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
assert "No streams available" in str(exc_info.value)
|
|
251
|
+
|
|
252
|
+
@patch('weeb_cli.services.headless_downloader.download_episode')
|
|
253
|
+
def test_download_url_success(self, mock_download):
|
|
254
|
+
"""Test downloading from direct URL."""
|
|
255
|
+
mock_download.return_value = "/path/to/file.mp4"
|
|
256
|
+
|
|
257
|
+
sdk = WeebSDK()
|
|
258
|
+
path = sdk.download_url(
|
|
259
|
+
stream_url="http://stream.com/video.m3u8",
|
|
260
|
+
title="Test Anime",
|
|
261
|
+
season=1,
|
|
262
|
+
episode=1
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
assert path == "/path/to/file.mp4"
|
|
266
|
+
mock_download.assert_called_once()
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class TestConvenienceFunctions:
|
|
270
|
+
"""Test suite for convenience functions."""
|
|
271
|
+
|
|
272
|
+
@patch('weeb_cli.sdk._list_providers')
|
|
273
|
+
def test_list_providers_function(self, mock_list):
|
|
274
|
+
"""Test list_providers convenience function."""
|
|
275
|
+
mock_list.return_value = [
|
|
276
|
+
{"name": "animecix", "lang": "tr"}
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
providers = list_providers()
|
|
280
|
+
assert len(providers) == 1
|
|
281
|
+
|
|
282
|
+
@patch('weeb_cli.sdk.list_providers')
|
|
283
|
+
def test_get_provider_info_function(self, mock_list):
|
|
284
|
+
"""Test get_provider_info convenience function."""
|
|
285
|
+
mock_list.return_value = [
|
|
286
|
+
{"name": "animecix", "lang": "tr"},
|
|
287
|
+
{"name": "hianime", "lang": "en"}
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
info = get_provider_info("hianime")
|
|
291
|
+
assert info["name"] == "hianime"
|
|
@@ -10,16 +10,35 @@ Features:
|
|
|
10
10
|
- Advanced download queue with Aria2 and yt-dlp
|
|
11
11
|
- Discord Rich Presence integration
|
|
12
12
|
- Multi-language support (TR, EN, DE, PL)
|
|
13
|
+
- Python SDK for programmatic access
|
|
13
14
|
|
|
14
15
|
Example:
|
|
15
|
-
|
|
16
|
+
CLI usage::
|
|
16
17
|
|
|
17
18
|
$ weeb-cli start
|
|
18
19
|
$ weeb-cli api search "anime name"
|
|
19
20
|
$ weeb-cli serve --port 8080
|
|
21
|
+
|
|
22
|
+
SDK usage::
|
|
23
|
+
|
|
24
|
+
from weeb_cli import WeebSDK
|
|
25
|
+
|
|
26
|
+
sdk = WeebSDK(default_provider="hianime")
|
|
27
|
+
results = sdk.search("One Piece")
|
|
28
|
+
episodes = sdk.get_episodes(results[0].id)
|
|
20
29
|
|
|
21
30
|
Attributes:
|
|
22
31
|
__version__ (str): Current version of Weeb CLI.
|
|
23
32
|
"""
|
|
24
33
|
|
|
25
|
-
__version__ = "2.
|
|
34
|
+
__version__ = "2.15.0"
|
|
35
|
+
|
|
36
|
+
# SDK exports for easy import
|
|
37
|
+
from weeb_cli.sdk import WeebSDK, list_providers, get_provider_info
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"__version__",
|
|
41
|
+
"WeebSDK",
|
|
42
|
+
"list_providers",
|
|
43
|
+
"get_provider_info",
|
|
44
|
+
]
|
|
@@ -416,8 +416,9 @@ def play_local_episode(anime, episode):
|
|
|
416
416
|
|
|
417
417
|
except KeyboardInterrupt:
|
|
418
418
|
pass
|
|
419
|
-
except Exception:
|
|
420
|
-
|
|
419
|
+
except Exception as e:
|
|
420
|
+
from weeb_cli.services.logger import debug
|
|
421
|
+
debug(f"[Downloads] Tracker sync display error: {e}")
|
|
421
422
|
|
|
422
423
|
def manage_queue():
|
|
423
424
|
while True:
|
|
@@ -127,8 +127,9 @@ class Config:
|
|
|
127
127
|
val = self.db.get_config(key)
|
|
128
128
|
if val is not None:
|
|
129
129
|
return val
|
|
130
|
-
except Exception:
|
|
131
|
-
|
|
130
|
+
except Exception as e:
|
|
131
|
+
from weeb_cli.services.logger import debug
|
|
132
|
+
debug(f"[Config] DB read failed for '{key}': {e}")
|
|
132
133
|
|
|
133
134
|
# Special handling for download_dir
|
|
134
135
|
if key == "download_dir":
|
|
@@ -136,7 +137,7 @@ class Config:
|
|
|
136
137
|
|
|
137
138
|
# Use provided default, fallback to DEFAULT_CONFIG, then None
|
|
138
139
|
if default is not None:
|
|
139
|
-
return
|
|
140
|
+
return default
|
|
140
141
|
return DEFAULT_CONFIG.get(key)
|
|
141
142
|
|
|
142
143
|
def set(self, key: str, value: Any) -> None:
|
|
@@ -70,7 +70,9 @@ class I18n:
|
|
|
70
70
|
"""Initialize i18n with language from config or default to English."""
|
|
71
71
|
try:
|
|
72
72
|
self.language: str = config.get("language", "en")
|
|
73
|
-
except Exception:
|
|
73
|
+
except Exception as e:
|
|
74
|
+
from weeb_cli.services.logger import debug
|
|
75
|
+
debug(f"[I18n] Config read failed, defaulting to English: {e}")
|
|
74
76
|
self.language = "en"
|
|
75
77
|
self.translations: Dict[str, Any] = {}
|
|
76
78
|
self.load_translations()
|
|
@@ -98,7 +100,9 @@ class I18n:
|
|
|
98
100
|
try:
|
|
99
101
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
100
102
|
self.translations = json.load(f)
|
|
101
|
-
except
|
|
103
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
104
|
+
from weeb_cli.services.logger import debug
|
|
105
|
+
debug(f"[I18n] Failed to load translations for '{self.language}': {e}")
|
|
102
106
|
self.translations = {}
|
|
103
107
|
|
|
104
108
|
def get(self, key_path: str, default: Optional[str] = None, **kwargs: Any) -> str:
|
|
@@ -82,20 +82,7 @@
|
|
|
82
82
|
"drive_removed": "Laufwerk entfernt.",
|
|
83
83
|
"current_dir": "Aktuell: {dir}",
|
|
84
84
|
"retry_delay_error": "Wiederholungsverzögerung muss zwischen 0 und 300 Sekunden liegen.",
|
|
85
|
-
"cache":
|
|
86
|
-
"title": "Cache-Verwaltung",
|
|
87
|
-
"memory_entries": "Speichereinträge",
|
|
88
|
-
"file_entries": "Dateieinträge",
|
|
89
|
-
"total_size": "Gesamtgröße",
|
|
90
|
-
"clear_all": "Gesamten Cache leeren",
|
|
91
|
-
"clear_provider": "Aktuellen Provider-Cache leeren",
|
|
92
|
-
"cleanup_old": "Alten Cache bereinigen (>24h)",
|
|
93
|
-
"cleared": "Cache geleert",
|
|
94
|
-
"provider_cleared": "Provider-Cache geleert",
|
|
95
|
-
"cleaned": "Bereinigt",
|
|
96
|
-
"prompt": "Aktion auswählen",
|
|
97
|
-
"confirm_clear_all": "Gesamten Cache leeren?"
|
|
98
|
-
},
|
|
85
|
+
"cache": "Cache-Verwaltung",
|
|
99
86
|
"anilist": "AniList",
|
|
100
87
|
"anilist_connected": "Verbunden: {user}",
|
|
101
88
|
"anilist_not_connected": "Verbinden Sie Ihr AniList-Konto, um Ihren Verlauf zu synchronisieren.",
|
|
@@ -82,20 +82,7 @@
|
|
|
82
82
|
"drive_removed": "Drive removed.",
|
|
83
83
|
"current_dir": "Current: {dir}",
|
|
84
84
|
"retry_delay_error": "Retry delay must be between 0 and 300 seconds.",
|
|
85
|
-
"cache":
|
|
86
|
-
"title": "Cache Management",
|
|
87
|
-
"memory_entries": "Memory entries",
|
|
88
|
-
"file_entries": "File entries",
|
|
89
|
-
"total_size": "Total size",
|
|
90
|
-
"clear_all": "Clear all cache",
|
|
91
|
-
"clear_provider": "Clear current provider cache",
|
|
92
|
-
"cleanup_old": "Cleanup old cache (>24h)",
|
|
93
|
-
"cleared": "Cache cleared",
|
|
94
|
-
"provider_cleared": "Provider cache cleared",
|
|
95
|
-
"cleaned": "Cleaned",
|
|
96
|
-
"prompt": "Select action",
|
|
97
|
-
"confirm_clear_all": "Clear all cache?"
|
|
98
|
-
},
|
|
85
|
+
"cache": "Cache Management",
|
|
99
86
|
"anilist": "AniList",
|
|
100
87
|
"anilist_connected": "Connected: {user}",
|
|
101
88
|
"anilist_not_connected": "Connect your AniList account to sync your watch history.",
|