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.
Files changed (114) hide show
  1. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/PKG-INFO +38 -5
  2. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/README.md +31 -1
  3. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/pyproject.toml +5 -4
  4. weeb_cli-2.14.0/tests/test_restful_api.py +227 -0
  5. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/__init__.py +1 -1
  6. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/serve.py +34 -4
  7. weeb_cli-2.14.0/weeb_cli/commands/serve_restful.py +384 -0
  8. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/en/hianime.py +1 -1
  9. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/registry.py +17 -5
  10. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli.egg-info/PKG-INFO +38 -5
  11. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli.egg-info/SOURCES.txt +2 -0
  12. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli.egg-info/requires.txt +5 -1
  13. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/LICENSE +0 -0
  14. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/setup.cfg +0 -0
  15. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_anilist_tracker.py +0 -0
  16. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_aniworld_provider.py +0 -0
  17. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_api.py +0 -0
  18. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_cache.py +0 -0
  19. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_exceptions.py +0 -0
  20. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_kitsu_tracker.py +0 -0
  21. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_mal_tracker.py +0 -0
  22. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_providers.py +0 -0
  23. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_sanitizer.py +0 -0
  24. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/tests/test_sanitizer_security.py +0 -0
  25. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/__main__.py +0 -0
  26. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/__init__.py +0 -0
  27. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/api.py +0 -0
  28. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/downloads.py +0 -0
  29. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/library.py +0 -0
  30. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/__init__.py +0 -0
  31. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/anime_details.py +0 -0
  32. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/download_flow.py +0 -0
  33. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/episode_utils.py +0 -0
  34. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/search_handlers.py +0 -0
  35. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/stream_utils.py +0 -0
  36. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search/watch_flow.py +0 -0
  37. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/search.py +0 -0
  38. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/__init__.py +0 -0
  39. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_backup.py +0 -0
  40. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_cache.py +0 -0
  41. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_config.py +0 -0
  42. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_download.py +0 -0
  43. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_drives.py +0 -0
  44. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_menu.py +0 -0
  45. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_shortcuts.py +0 -0
  46. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings/settings_trackers.py +0 -0
  47. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/settings.py +0 -0
  48. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/setup.py +0 -0
  49. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/commands/watchlist.py +0 -0
  50. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/config.py +0 -0
  51. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/exceptions.py +0 -0
  52. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/i18n.py +0 -0
  53. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/locales/de.json +0 -0
  54. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/locales/en.json +0 -0
  55. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/locales/pl.json +0 -0
  56. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/locales/tr.json +0 -0
  57. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/main.py +0 -0
  58. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/__init__.py +0 -0
  59. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/base.py +0 -0
  60. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/de/__init__.py +0 -0
  61. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/de/aniworld.py +0 -0
  62. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/en/__init__.py +0 -0
  63. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/en/allanime.py +0 -0
  64. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/__init__.py +0 -0
  65. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/doodstream.py +0 -0
  66. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/filemoon.py +0 -0
  67. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/megacloud.py +0 -0
  68. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/streamtape.py +0 -0
  69. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/vidoza.py +0 -0
  70. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/extractors/voe.py +0 -0
  71. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/pl/__init__.py +0 -0
  72. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/pl/docchi.py +0 -0
  73. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/tr/__init__.py +0 -0
  74. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/tr/animecix.py +0 -0
  75. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/tr/anizle.py +0 -0
  76. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/tr/turkanime.py +0 -0
  77. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/providers/tr/weeb.py +0 -0
  78. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/__init__.py +0 -0
  79. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/_base.py +0 -0
  80. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/_tracker_base.py +0 -0
  81. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/aniskip.py +0 -0
  82. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/cache.py +0 -0
  83. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/database.py +0 -0
  84. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/dependency_manager.py +0 -0
  85. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/details.py +0 -0
  86. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/discord_rpc.py +0 -0
  87. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/downloader.py +0 -0
  88. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/error_handler.py +0 -0
  89. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/headless_downloader.py +0 -0
  90. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/local_library.py +0 -0
  91. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/logger.py +0 -0
  92. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/notifier.py +0 -0
  93. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/player.py +0 -0
  94. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/progress.py +0 -0
  95. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/scraper.py +0 -0
  96. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/search.py +0 -0
  97. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/shortcuts.py +0 -0
  98. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/stream_validator.py +0 -0
  99. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/tracker.py +0 -0
  100. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/updater.py +0 -0
  101. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/services/watch.py +0 -0
  102. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/templates/anilist_error.html +0 -0
  103. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/templates/anilist_success.html +0 -0
  104. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/templates/mal_error.html +0 -0
  105. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/templates/mal_success.html +0 -0
  106. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/ui/__init__.py +0 -0
  107. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/ui/header.py +0 -0
  108. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/ui/menu.py +0 -0
  109. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/ui/prompt.py +0 -0
  110. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/utils/__init__.py +0 -0
  111. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli/utils/sanitizer.py +0 -0
  112. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli.egg-info/dependency_links.txt +0 -0
  113. {weeb_cli-2.13.0 → weeb_cli-2.14.0}/weeb_cli.egg-info/entry_points.txt +0 -0
  114. {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.13.0
4
- Summary: Tarayıcı yok, reklam yok, dikkat dağıtıcı unsur yok. Sadece siz ve eşsiz bir anime izleme deneyimi.
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,weeb,anime-download,anizm,anime-watch,anime-watching,anime-downloading,anime-cli,allanime,animecix,anime-indir,anime-izle,weeb-cli,anime-izleme,anime-indirme,weebanime,tranime
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.50,>=3.0.36
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
- #### Docker
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
- #### Docker
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.13.0"
8
- description = "Tarayıcı yok, reklam yok, dikkat dağıtıcı unsur yok. Sadece siz ve eşsiz bir anime izleme deneyimi."
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", "weeb", "anime-download", "anizm", "anime-watch", "anime-watching", "anime-downloading", "anime-cli", "allanime", "animecix", "anime-indir", "anime-izle", "weeb-cli", "anime-izleme", "anime-indirme", "weebanime", "tranime"]
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.50",
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
@@ -22,4 +22,4 @@ Attributes:
22
22
  __version__ (str): Current version of Weeb CLI.
23
23
  """
24
24
 
25
- __version__ = "2.13.0"
25
+ __version__ = "2.14.0"
@@ -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 a Torznab-compatible server for Sonarr/*arr integration.",
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
- # -- Main serve command -------------------------------------------------------
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
- @serve_app.callback(invoke_without_command=True)
212
- def serve(
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")