weeb-cli 2.13.0__tar.gz → 2.14.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.13.0 → weeb_cli-2.14.0}/PKG-INFO +38 -5
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/README.md +31 -1
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/pyproject.toml +5 -4
- weeb_cli-2.14.0/tests/test_restful_api.py +227 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/__init__.py +1 -1
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/serve.py +34 -4
- weeb_cli-2.14.0/weeb_cli/commands/serve_restful.py +384 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/en/hianime.py +1 -1
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/registry.py +17 -5
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli.egg-info/PKG-INFO +38 -5
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli.egg-info/SOURCES.txt +2 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli.egg-info/requires.txt +5 -1
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/LICENSE +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/setup.cfg +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_anilist_tracker.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_aniworld_provider.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_api.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_cache.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_exceptions.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_kitsu_tracker.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_mal_tracker.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_providers.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_sanitizer.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_sanitizer_security.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/__main__.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/__init__.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/api.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/downloads.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/library.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/__init__.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/anime_details.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/download_flow.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/episode_utils.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/search_handlers.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/stream_utils.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/watch_flow.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/__init__.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_backup.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_cache.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_config.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_download.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_drives.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_menu.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_shortcuts.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_trackers.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/setup.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/watchlist.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/config.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/exceptions.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/i18n.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/locales/de.json +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/locales/en.json +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/locales/pl.json +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/locales/tr.json +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/main.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/__init__.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/base.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/de/__init__.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/de/aniworld.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/en/__init__.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/en/allanime.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/__init__.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/doodstream.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/filemoon.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/megacloud.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/streamtape.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/vidoza.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/voe.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/pl/__init__.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/pl/docchi.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/tr/__init__.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/tr/animecix.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/tr/anizle.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/tr/turkanime.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/tr/weeb.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/__init__.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/_base.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/_tracker_base.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/aniskip.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/cache.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/database.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/dependency_manager.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/details.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/discord_rpc.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/downloader.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/error_handler.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/headless_downloader.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/local_library.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/logger.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/notifier.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/player.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/progress.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/scraper.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/search.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/shortcuts.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/stream_validator.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/tracker.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/updater.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/watch.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/templates/anilist_error.html +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/templates/anilist_success.html +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/templates/mal_error.html +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/templates/mal_success.html +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/ui/__init__.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/ui/header.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/ui/menu.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/ui/prompt.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/utils/__init__.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/utils/sanitizer.py +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli.egg-info/dependency_links.txt +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli.egg-info/entry_points.txt +0 -0
- {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli.egg-info/top_level.txt +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weeb-cli
|
|
3
|
-
Version: 2.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 2.14.0
|
|
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
|
|
7
7
|
Project-URL: Homepage, https://weeb-cli.ewgsta.me
|
|
8
8
|
Project-URL: Repository, https://github.com/ewgsta/weeb-cli
|
|
9
9
|
Project-URL: Issues, https://github.com/ewgsta/weeb-cli/issues
|
|
10
|
-
Keywords: anime,
|
|
10
|
+
Keywords: terminal,anime,tui,crossplatform,anime-download,turkanime,anizm,anime-watch,aniwatch,anime-cli,ani-cli,openanime,allanime,hianime,anitr-cli,animecix,anime-indir,anime-izle,weeb-cli,katsuanime
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Environment :: Console
|
|
@@ -24,7 +24,7 @@ Requires-Dist: lxml
|
|
|
24
24
|
Requires-Dist: pycryptodome
|
|
25
25
|
Requires-Dist: curl_cffi
|
|
26
26
|
Requires-Dist: appdirs
|
|
27
|
-
Requires-Dist: prompt_toolkit<3.0.
|
|
27
|
+
Requires-Dist: prompt_toolkit<3.0.53,>=3.0.36
|
|
28
28
|
Requires-Dist: pyfiglet
|
|
29
29
|
Requires-Dist: py7zr
|
|
30
30
|
Requires-Dist: pypresence
|
|
@@ -32,6 +32,9 @@ Requires-Dist: winotify; sys_platform == "win32"
|
|
|
32
32
|
Provides-Extra: serve
|
|
33
33
|
Requires-Dist: flask>=3.0; extra == "serve"
|
|
34
34
|
Requires-Dist: pyyaml>=6.0; extra == "serve"
|
|
35
|
+
Provides-Extra: serve-restful
|
|
36
|
+
Requires-Dist: flask>=3.0; extra == "serve-restful"
|
|
37
|
+
Requires-Dist: flask-cors>=4.0; extra == "serve-restful"
|
|
35
38
|
Provides-Extra: dev
|
|
36
39
|
Requires-Dist: build; extra == "dev"
|
|
37
40
|
Requires-Dist: pyinstaller; extra == "dev"
|
|
@@ -118,6 +121,7 @@ Dynamic: license-file
|
|
|
118
121
|
- Automatic update checks
|
|
119
122
|
- Non-interactive JSON API for scripts and AI agents
|
|
120
123
|
- Torznab server mode for Sonarr/*arr integration
|
|
124
|
+
- RESTful API server for web/mobile applications
|
|
121
125
|
|
|
122
126
|
---
|
|
123
127
|
|
|
@@ -195,7 +199,34 @@ weeb-cli serve --port 9876 \
|
|
|
195
199
|
|
|
196
200
|
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.
|
|
197
201
|
|
|
198
|
-
|
|
202
|
+
### RESTful API Server
|
|
203
|
+
|
|
204
|
+
For web/mobile applications and custom integrations, weeb-cli provides a RESTful API server:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
pip install weeb-cli[serve-restful]
|
|
208
|
+
|
|
209
|
+
weeb-cli serve restful --port 8080 --cors
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**API Endpoints:**
|
|
213
|
+
- `GET /health` - Health check
|
|
214
|
+
- `GET /api/providers` - List available providers
|
|
215
|
+
- `GET /api/search?q=naruto&provider=animecix` - Search anime
|
|
216
|
+
- `GET /api/anime/{id}?provider=animecix` - Get anime details
|
|
217
|
+
- `GET /api/anime/{id}/episodes?season=1` - List episodes
|
|
218
|
+
- `GET /api/anime/{id}/episodes/{ep_id}/streams` - Get stream URLs
|
|
219
|
+
|
|
220
|
+
All available providers are loaded automatically. Select which provider to use via the `provider` query parameter.
|
|
221
|
+
|
|
222
|
+
**Docker Support:**
|
|
223
|
+
```bash
|
|
224
|
+
docker-compose -f docker-compose.restful.yml up -d
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
See [RESTful API Documentation](https://ewgsta.github.io/weeb-cli/cli/restful-api/) for full details.
|
|
228
|
+
|
|
229
|
+
#### Docker (Torznab)
|
|
199
230
|
|
|
200
231
|
```dockerfile
|
|
201
232
|
FROM python:3.13-slim
|
|
@@ -292,6 +323,7 @@ All settings can be modified through the interactive Settings menu.
|
|
|
292
323
|
- [x] Keyboard shortcuts
|
|
293
324
|
- [x] Non-interactive API mode (JSON output)
|
|
294
325
|
- [x] Torznab server for Sonarr/*arr integration
|
|
326
|
+
- [x] RESTful API server for web/mobile apps
|
|
295
327
|
|
|
296
328
|
|
|
297
329
|
### Planned
|
|
@@ -315,6 +347,7 @@ weeb-cli/
|
|
|
315
347
|
│ │ ├── downloads.py # Download management commands
|
|
316
348
|
│ │ ├── search.py # Anime search functionality
|
|
317
349
|
│ │ ├── serve.py # Torznab server for *arr integration
|
|
350
|
+
│ │ ├── serve_restful.py # RESTful API server
|
|
318
351
|
│ │ ├── settings.py # Settings menu and configuration
|
|
319
352
|
│ │ ├── setup.py # Initial setup wizard
|
|
320
353
|
│ │ └── watchlist.py # Watch history and progress
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
- Automatic update checks
|
|
76
76
|
- Non-interactive JSON API for scripts and AI agents
|
|
77
77
|
- Torznab server mode for Sonarr/*arr integration
|
|
78
|
+
- RESTful API server for web/mobile applications
|
|
78
79
|
|
|
79
80
|
---
|
|
80
81
|
|
|
@@ -152,7 +153,34 @@ weeb-cli serve --port 9876 \
|
|
|
152
153
|
|
|
153
154
|
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.
|
|
154
155
|
|
|
155
|
-
|
|
156
|
+
### RESTful API Server
|
|
157
|
+
|
|
158
|
+
For web/mobile applications and custom integrations, weeb-cli provides a RESTful API server:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
pip install weeb-cli[serve-restful]
|
|
162
|
+
|
|
163
|
+
weeb-cli serve restful --port 8080 --cors
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**API Endpoints:**
|
|
167
|
+
- `GET /health` - Health check
|
|
168
|
+
- `GET /api/providers` - List available providers
|
|
169
|
+
- `GET /api/search?q=naruto&provider=animecix` - Search anime
|
|
170
|
+
- `GET /api/anime/{id}?provider=animecix` - Get anime details
|
|
171
|
+
- `GET /api/anime/{id}/episodes?season=1` - List episodes
|
|
172
|
+
- `GET /api/anime/{id}/episodes/{ep_id}/streams` - Get stream URLs
|
|
173
|
+
|
|
174
|
+
All available providers are loaded automatically. Select which provider to use via the `provider` query parameter.
|
|
175
|
+
|
|
176
|
+
**Docker Support:**
|
|
177
|
+
```bash
|
|
178
|
+
docker-compose -f docker-compose.restful.yml up -d
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
See [RESTful API Documentation](https://ewgsta.github.io/weeb-cli/cli/restful-api/) for full details.
|
|
182
|
+
|
|
183
|
+
#### Docker (Torznab)
|
|
156
184
|
|
|
157
185
|
```dockerfile
|
|
158
186
|
FROM python:3.13-slim
|
|
@@ -249,6 +277,7 @@ All settings can be modified through the interactive Settings menu.
|
|
|
249
277
|
- [x] Keyboard shortcuts
|
|
250
278
|
- [x] Non-interactive API mode (JSON output)
|
|
251
279
|
- [x] Torznab server for Sonarr/*arr integration
|
|
280
|
+
- [x] RESTful API server for web/mobile apps
|
|
252
281
|
|
|
253
282
|
|
|
254
283
|
### Planned
|
|
@@ -272,6 +301,7 @@ weeb-cli/
|
|
|
272
301
|
│ │ ├── downloads.py # Download management commands
|
|
273
302
|
│ │ ├── search.py # Anime search functionality
|
|
274
303
|
│ │ ├── serve.py # Torznab server for *arr integration
|
|
304
|
+
│ │ ├── serve_restful.py # RESTful API server
|
|
275
305
|
│ │ ├── settings.py # Settings menu and configuration
|
|
276
306
|
│ │ ├── setup.py # Initial setup wizard
|
|
277
307
|
│ │ └── watchlist.py # Watch history and progress
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "weeb-cli"
|
|
7
|
-
version = "2.
|
|
8
|
-
description = "
|
|
7
|
+
version = "2.14.0"
|
|
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" }]
|
|
11
11
|
license = "GPL-3.0-only"
|
|
@@ -14,7 +14,7 @@ classifiers = [
|
|
|
14
14
|
"Operating System :: OS Independent",
|
|
15
15
|
"Environment :: Console",
|
|
16
16
|
]
|
|
17
|
-
keywords = ["anime", "
|
|
17
|
+
keywords = ["terminal", "anime", "tui", "crossplatform", "anime-download", "turkanime", "anizm", "anime-watch", "aniwatch", "anime-cli", "ani-cli", "openanime", "allanime", "hianime", "anitr-cli", "animecix", "anime-indir", "anime-izle", "weeb-cli", "katsuanime"]
|
|
18
18
|
requires-python = ">=3.12"
|
|
19
19
|
dependencies = [
|
|
20
20
|
"typer[all]",
|
|
@@ -27,7 +27,7 @@ dependencies = [
|
|
|
27
27
|
"pycryptodome",
|
|
28
28
|
"curl_cffi",
|
|
29
29
|
"appdirs",
|
|
30
|
-
"prompt_toolkit>=3.0.36,<3.0.
|
|
30
|
+
"prompt_toolkit>=3.0.36,<3.0.53",
|
|
31
31
|
"pyfiglet",
|
|
32
32
|
"py7zr",
|
|
33
33
|
"pypresence",
|
|
@@ -36,6 +36,7 @@ dependencies = [
|
|
|
36
36
|
|
|
37
37
|
[project.optional-dependencies]
|
|
38
38
|
serve = ["flask>=3.0", "pyyaml>=6.0"]
|
|
39
|
+
serve-restful = ["flask>=3.0", "flask-cors>=4.0"]
|
|
39
40
|
dev = ["build", "pyinstaller", "pytest>=7.0.0", "pytest-cov", "pytest-asyncio", "pytest-mock"]
|
|
40
41
|
|
|
41
42
|
[tool.setuptools.packages.find]
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Tests for RESTful API server."""
|
|
2
|
+
import pytest
|
|
3
|
+
from unittest.mock import Mock, patch, MagicMock
|
|
4
|
+
from weeb_cli.providers.base import AnimeResult, Episode, StreamLink
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.fixture
|
|
8
|
+
def mock_provider():
|
|
9
|
+
"""Create a mock provider."""
|
|
10
|
+
provider = Mock()
|
|
11
|
+
provider.name = "test_provider"
|
|
12
|
+
provider.search.return_value = [
|
|
13
|
+
AnimeResult(
|
|
14
|
+
id="test123",
|
|
15
|
+
title="Test Anime",
|
|
16
|
+
type="series",
|
|
17
|
+
cover="https://example.com/cover.jpg",
|
|
18
|
+
year=2024,
|
|
19
|
+
)
|
|
20
|
+
]
|
|
21
|
+
provider.get_episodes.return_value = [
|
|
22
|
+
Episode(id="ep1", number=1, title="Episode 1", season=1),
|
|
23
|
+
Episode(id="ep2", number=2, title="Episode 2", season=1),
|
|
24
|
+
]
|
|
25
|
+
provider.get_streams.return_value = [
|
|
26
|
+
StreamLink(
|
|
27
|
+
url="https://example.com/stream.m3u8",
|
|
28
|
+
quality="1080p",
|
|
29
|
+
server="default",
|
|
30
|
+
headers={"Referer": "https://example.com"},
|
|
31
|
+
)
|
|
32
|
+
]
|
|
33
|
+
return provider
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
def mock_flask_app(mock_provider):
|
|
38
|
+
"""Create a mock Flask app with routes."""
|
|
39
|
+
with patch("weeb_cli.commands.serve_restful.get_provider") as mock_get_provider:
|
|
40
|
+
mock_get_provider.return_value = mock_provider
|
|
41
|
+
|
|
42
|
+
with patch("weeb_cli.commands.serve_restful.list_all_providers") as mock_list:
|
|
43
|
+
mock_list.return_value = [
|
|
44
|
+
{"name": "test_provider", "lang": "en", "region": "US"}
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Import after patching
|
|
48
|
+
from flask import Flask
|
|
49
|
+
app = Flask(__name__)
|
|
50
|
+
|
|
51
|
+
# Mock the routes (simplified for testing)
|
|
52
|
+
@app.route("/health")
|
|
53
|
+
def health():
|
|
54
|
+
from flask import jsonify
|
|
55
|
+
return jsonify({
|
|
56
|
+
"status": "ok",
|
|
57
|
+
"service": "weeb-cli-restful",
|
|
58
|
+
"providers": ["test_provider"],
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
yield app
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_health_endpoint(mock_flask_app):
|
|
65
|
+
"""Test health check endpoint."""
|
|
66
|
+
client = mock_flask_app.test_client()
|
|
67
|
+
response = client.get("/health")
|
|
68
|
+
|
|
69
|
+
assert response.status_code == 200
|
|
70
|
+
data = response.get_json()
|
|
71
|
+
assert data["status"] == "ok"
|
|
72
|
+
assert data["service"] == "weeb-cli-restful"
|
|
73
|
+
assert "test_provider" in data["providers"]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_serialize_anime_result():
|
|
77
|
+
"""Test AnimeResult serialization."""
|
|
78
|
+
from weeb_cli.commands.serve_restful import _serialize_anime_result
|
|
79
|
+
|
|
80
|
+
result = AnimeResult(
|
|
81
|
+
id="test123",
|
|
82
|
+
title="Test Anime",
|
|
83
|
+
type="series",
|
|
84
|
+
cover="https://example.com/cover.jpg",
|
|
85
|
+
year=2024,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
serialized = _serialize_anime_result(result)
|
|
89
|
+
|
|
90
|
+
assert serialized["id"] == "test123"
|
|
91
|
+
assert serialized["title"] == "Test Anime"
|
|
92
|
+
assert serialized["type"] == "series"
|
|
93
|
+
assert serialized["cover"] == "https://example.com/cover.jpg"
|
|
94
|
+
assert serialized["year"] == 2024
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_serialize_episode():
|
|
98
|
+
"""Test Episode serialization."""
|
|
99
|
+
from weeb_cli.commands.serve_restful import _serialize_episode
|
|
100
|
+
|
|
101
|
+
episode = Episode(
|
|
102
|
+
id="ep1",
|
|
103
|
+
number=1,
|
|
104
|
+
title="Episode 1",
|
|
105
|
+
season=1,
|
|
106
|
+
url="https://example.com/ep1",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
serialized = _serialize_episode(episode)
|
|
110
|
+
|
|
111
|
+
assert serialized["id"] == "ep1"
|
|
112
|
+
assert serialized["number"] == 1
|
|
113
|
+
assert serialized["title"] == "Episode 1"
|
|
114
|
+
assert serialized["season"] == 1
|
|
115
|
+
assert serialized["url"] == "https://example.com/ep1"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_serialize_stream():
|
|
119
|
+
"""Test StreamLink serialization."""
|
|
120
|
+
from weeb_cli.commands.serve_restful import _serialize_stream
|
|
121
|
+
|
|
122
|
+
stream = StreamLink(
|
|
123
|
+
url="https://example.com/stream.m3u8",
|
|
124
|
+
quality="1080p",
|
|
125
|
+
server="default",
|
|
126
|
+
headers={"Referer": "https://example.com"},
|
|
127
|
+
subtitles="https://example.com/subs.vtt",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
serialized = _serialize_stream(stream)
|
|
131
|
+
|
|
132
|
+
assert serialized["url"] == "https://example.com/stream.m3u8"
|
|
133
|
+
assert serialized["quality"] == "1080p"
|
|
134
|
+
assert serialized["server"] == "default"
|
|
135
|
+
assert serialized["headers"]["Referer"] == "https://example.com"
|
|
136
|
+
assert serialized["subtitles"] == "https://example.com/subs.vtt"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_quality_score():
|
|
140
|
+
"""Test quality scoring function."""
|
|
141
|
+
from weeb_cli.commands.serve_restful import _quality_score
|
|
142
|
+
|
|
143
|
+
assert _quality_score("4k") == 5
|
|
144
|
+
assert _quality_score("2160p") == 5
|
|
145
|
+
assert _quality_score("1080p") == 4
|
|
146
|
+
assert _quality_score("720p") == 3
|
|
147
|
+
assert _quality_score("480p") == 2
|
|
148
|
+
assert _quality_score("360p") == 1
|
|
149
|
+
assert _quality_score("unknown") == 0
|
|
150
|
+
assert _quality_score(None) == 0
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@patch("weeb_cli.commands.serve_restful.get_provider")
|
|
154
|
+
def test_search_with_provider(mock_get_provider, mock_provider):
|
|
155
|
+
"""Test search functionality with provider selection."""
|
|
156
|
+
mock_get_provider.return_value = mock_provider
|
|
157
|
+
|
|
158
|
+
# Simulate search
|
|
159
|
+
results = mock_provider.search("test query")
|
|
160
|
+
|
|
161
|
+
assert len(results) == 1
|
|
162
|
+
assert results[0].title == "Test Anime"
|
|
163
|
+
assert results[0].id == "test123"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@patch("weeb_cli.commands.serve_restful.get_provider")
|
|
167
|
+
def test_get_episodes_with_season_filter(mock_get_provider, mock_provider):
|
|
168
|
+
"""Test episode listing with season filter."""
|
|
169
|
+
mock_get_provider.return_value = mock_provider
|
|
170
|
+
|
|
171
|
+
# Add episodes from different seasons
|
|
172
|
+
mock_provider.get_episodes.return_value = [
|
|
173
|
+
Episode(id="ep1", number=1, season=1),
|
|
174
|
+
Episode(id="ep2", number=2, season=1),
|
|
175
|
+
Episode(id="ep3", number=1, season=2),
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
episodes = mock_provider.get_episodes("test123")
|
|
179
|
+
season_1_episodes = [ep for ep in episodes if ep.season == 1]
|
|
180
|
+
|
|
181
|
+
assert len(season_1_episodes) == 2
|
|
182
|
+
assert all(ep.season == 1 for ep in season_1_episodes)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@patch("weeb_cli.commands.serve_restful.get_provider")
|
|
186
|
+
def test_get_streams_sorted(mock_get_provider, mock_provider):
|
|
187
|
+
"""Test stream retrieval with quality sorting."""
|
|
188
|
+
from weeb_cli.commands.serve_restful import _quality_score
|
|
189
|
+
|
|
190
|
+
mock_get_provider.return_value = mock_provider
|
|
191
|
+
|
|
192
|
+
# Add streams with different qualities
|
|
193
|
+
mock_provider.get_streams.return_value = [
|
|
194
|
+
StreamLink(url="url1", quality="480p"),
|
|
195
|
+
StreamLink(url="url2", quality="1080p"),
|
|
196
|
+
StreamLink(url="url3", quality="720p"),
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
streams = mock_provider.get_streams("test123", "ep1")
|
|
200
|
+
sorted_streams = sorted(streams, key=lambda s: _quality_score(s.quality), reverse=True)
|
|
201
|
+
|
|
202
|
+
assert sorted_streams[0].quality == "1080p"
|
|
203
|
+
assert sorted_streams[1].quality == "720p"
|
|
204
|
+
assert sorted_streams[2].quality == "480p"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_missing_flask_import():
|
|
208
|
+
"""Test graceful handling when Flask is not installed."""
|
|
209
|
+
with patch("builtins.__import__", side_effect=ImportError("No module named 'flask'")):
|
|
210
|
+
with pytest.raises(ImportError):
|
|
211
|
+
import flask
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@pytest.mark.parametrize("quality,expected_score", [
|
|
215
|
+
("4K", 5),
|
|
216
|
+
("2160p", 5),
|
|
217
|
+
("1080p", 4),
|
|
218
|
+
("720p", 3),
|
|
219
|
+
("480p", 2),
|
|
220
|
+
("360p", 1),
|
|
221
|
+
("auto", 0),
|
|
222
|
+
("", 0),
|
|
223
|
+
])
|
|
224
|
+
def test_quality_score_parametrized(quality, expected_score):
|
|
225
|
+
"""Test quality scoring with various inputs."""
|
|
226
|
+
from weeb_cli.commands.serve_restful import _quality_score
|
|
227
|
+
assert _quality_score(quality) == expected_score
|
|
@@ -32,10 +32,30 @@ def _sanitize_for_release(name: str) -> str:
|
|
|
32
32
|
|
|
33
33
|
serve_app = typer.Typer(
|
|
34
34
|
name="serve",
|
|
35
|
-
help="Start
|
|
35
|
+
help="Start server modes: Torznab for Sonarr/*arr integration or RESTful API server.",
|
|
36
36
|
add_completion=False,
|
|
37
|
+
invoke_without_command=True,
|
|
37
38
|
)
|
|
38
39
|
|
|
40
|
+
@serve_app.callback()
|
|
41
|
+
def serve_callback(ctx: typer.Context):
|
|
42
|
+
"""Server modes for weeb-cli.
|
|
43
|
+
|
|
44
|
+
Available modes:
|
|
45
|
+
- torznab: Torznab-compatible server for Sonarr/*arr integration
|
|
46
|
+
- restful: RESTful API server for web/mobile applications
|
|
47
|
+
"""
|
|
48
|
+
if ctx.invoked_subcommand is None:
|
|
49
|
+
typer.echo(ctx.get_help())
|
|
50
|
+
raise typer.Exit(0)
|
|
51
|
+
|
|
52
|
+
# Import restful subcommand
|
|
53
|
+
try:
|
|
54
|
+
from weeb_cli.commands.serve_restful import restful_app
|
|
55
|
+
serve_app.add_typer(restful_app, name="restful")
|
|
56
|
+
except ImportError:
|
|
57
|
+
pass # Flask not installed, restful mode unavailable
|
|
58
|
+
|
|
39
59
|
|
|
40
60
|
# -- Helpers ------------------------------------------------------------------
|
|
41
61
|
|
|
@@ -206,10 +226,16 @@ def _get_fallback_streams(fallback_providers, sonarr_title, season, episode_num)
|
|
|
206
226
|
return []
|
|
207
227
|
|
|
208
228
|
|
|
209
|
-
# --
|
|
229
|
+
# -- Torznab serve command ----------------------------------------------------
|
|
230
|
+
|
|
231
|
+
torznab_app = typer.Typer(
|
|
232
|
+
name="torznab",
|
|
233
|
+
help="Start Torznab-compatible server for Sonarr/*arr integration.",
|
|
234
|
+
add_completion=False,
|
|
235
|
+
)
|
|
210
236
|
|
|
211
|
-
@
|
|
212
|
-
def
|
|
237
|
+
@torznab_app.callback(invoke_without_command=True)
|
|
238
|
+
def serve_torznab(
|
|
213
239
|
ctx: typer.Context,
|
|
214
240
|
port: int = typer.Option(9876, "--port", envvar="FLASK_PORT", help="HTTP port"),
|
|
215
241
|
watch_dir: str = typer.Option("/downloads/weeb-cli/watch", "--watch-dir", envvar="WATCH_DIR", help="Blackhole watch directory"),
|
|
@@ -470,3 +496,7 @@ def serve(
|
|
|
470
496
|
worker.start()
|
|
471
497
|
|
|
472
498
|
flask_app.run(host="0.0.0.0", port=port, debug=False)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
# Register torznab subcommand
|
|
502
|
+
serve_app.add_typer(torznab_app, name="torznab")
|