mediaflow-proxy 2.4.4__tar.gz → 2.4.6__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 (108) hide show
  1. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/PKG-INFO +13 -4
  2. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/README.md +12 -3
  3. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/configs.py +2 -0
  4. mediaflow_proxy-2.4.6/mediaflow_proxy/extractors/vavoo.py +296 -0
  5. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/media_source.py +2 -0
  6. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/routes/telegram.py +189 -75
  7. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/redis_utils.py +52 -0
  8. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/telegram.py +199 -22
  9. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/pyproject.toml +1 -1
  10. mediaflow_proxy-2.4.4/mediaflow_proxy/extractors/vavoo.py +0 -252
  11. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/.gitignore +0 -0
  12. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/LICENSE +0 -0
  13. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/__init__.py +0 -0
  14. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/const.py +0 -0
  15. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/drm/__init__.py +0 -0
  16. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/drm/decrypter.py +0 -0
  17. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/F16Px.py +0 -0
  18. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/__init__.py +0 -0
  19. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/base.py +0 -0
  20. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/dlhd.py +0 -0
  21. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/doodstream.py +0 -0
  22. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/factory.py +0 -0
  23. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/fastream.py +0 -0
  24. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/filelions.py +0 -0
  25. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/filemoon.py +0 -0
  26. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/gupload.py +0 -0
  27. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/livetv.py +0 -0
  28. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/lulustream.py +0 -0
  29. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/maxstream.py +0 -0
  30. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/mixdrop.py +0 -0
  31. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/okru.py +0 -0
  32. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/sportsonline.py +0 -0
  33. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/streamtape.py +0 -0
  34. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/streamwish.py +0 -0
  35. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/supervideo.py +0 -0
  36. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/turbovidplay.py +0 -0
  37. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/uqload.py +0 -0
  38. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/vidmoly.py +0 -0
  39. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/vidoza.py +0 -0
  40. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/vixcloud.py +0 -0
  41. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/extractors/voe.py +0 -0
  42. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/handlers.py +0 -0
  43. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/main.py +0 -0
  44. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/middleware.py +0 -0
  45. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/mpd_processor.py +0 -0
  46. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/__init__.py +0 -0
  47. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/audio_transcoder.py +0 -0
  48. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/codec_utils.py +0 -0
  49. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/container_probe.py +0 -0
  50. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/ebml_parser.py +0 -0
  51. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/hls_manifest.py +0 -0
  52. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/mkv_demuxer.py +0 -0
  53. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/mp4_muxer.py +0 -0
  54. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/mp4_parser.py +0 -0
  55. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/pyav_demuxer.py +0 -0
  56. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/transcode_handler.py +0 -0
  57. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/transcode_pipeline.py +0 -0
  58. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/ts_muxer.py +0 -0
  59. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/remuxer/video_transcoder.py +0 -0
  60. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/routes/__init__.py +0 -0
  61. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/routes/acestream.py +0 -0
  62. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/routes/extractor.py +0 -0
  63. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/routes/playlist_builder.py +0 -0
  64. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/routes/proxy.py +0 -0
  65. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/routes/speedtest.py +0 -0
  66. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/routes/xtream.py +0 -0
  67. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/schemas.py +0 -0
  68. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/speedtest/__init__.py +0 -0
  69. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/speedtest/models.py +0 -0
  70. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/speedtest/providers/all_debrid.py +0 -0
  71. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/speedtest/providers/base.py +0 -0
  72. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/speedtest/providers/real_debrid.py +0 -0
  73. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/speedtest/service.py +0 -0
  74. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/static/index.html +0 -0
  75. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/static/logo.png +0 -0
  76. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/static/playlist_builder.html +0 -0
  77. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/static/speedtest.html +0 -0
  78. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/static/speedtest.js +0 -0
  79. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/static/url_generator.html +0 -0
  80. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/__init__.py +0 -0
  81. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/acestream.py +0 -0
  82. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/aes.py +0 -0
  83. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/aesgcm.py +0 -0
  84. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/base64_utils.py +0 -0
  85. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/base_prebuffer.py +0 -0
  86. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/cache_utils.py +0 -0
  87. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/codec.py +0 -0
  88. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/compat.py +0 -0
  89. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/constanttime.py +0 -0
  90. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/crypto_utils.py +0 -0
  91. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/cryptomath.py +0 -0
  92. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/dash_prebuffer.py +0 -0
  93. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/deprecations.py +0 -0
  94. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/extractor_helpers.py +0 -0
  95. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/hls_prebuffer.py +0 -0
  96. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/hls_utils.py +0 -0
  97. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/http_client.py +0 -0
  98. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/http_utils.py +0 -0
  99. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/m3u8_processor.py +0 -0
  100. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/mpd_utils.py +0 -0
  101. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/packed.py +0 -0
  102. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/python_aes.py +0 -0
  103. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/python_aesgcm.py +0 -0
  104. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/rate_limit_handlers.py +0 -0
  105. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/rijndael.py +0 -0
  106. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/stream_transformers.py +0 -0
  107. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/tlshashlib.py +0 -0
  108. {mediaflow_proxy-2.4.4 → mediaflow_proxy-2.4.6}/mediaflow_proxy/utils/tlshmac.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mediaflow-proxy
3
- Version: 2.4.4
3
+ Version: 2.4.6
4
4
  Summary: A high-performance proxy server for streaming media, supporting HTTP(S), HLS, and MPEG-DASH with real-time DRM decryption.
5
5
  Project-URL: Homepage, https://github.com/mhdzumair/mediaflow-proxy
6
6
  Project-URL: Repository, https://github.com/mhdzumair/mediaflow-proxy
@@ -381,7 +381,7 @@ TELEGRAM_SESSION_STRING=your_session_string_here
381
381
 
382
382
  | Endpoint | Description |
383
383
  |----------|-------------|
384
- | `/proxy/telegram/stream` | Stream media from t.me link or file_id |
384
+ | `/proxy/telegram/stream` | Stream media from t.me link, chat IDs, document_id, or file_id |
385
385
  | `/proxy/telegram/stream/{filename}` | Stream with custom filename |
386
386
  | `/proxy/telegram/transcode/playlist.m3u8` | HLS transcode playlist (recommended for browser playback and smooth seeking) |
387
387
  | `/proxy/telegram/transcode/init.mp4` | fMP4 init segment for Telegram transcode playlist |
@@ -394,8 +394,9 @@ TELEGRAM_SESSION_STRING=your_session_string_here
394
394
  | Parameter | Description |
395
395
  |-----------|-------------|
396
396
  | `d` or `url` | t.me link (e.g., `https://t.me/channel/123`) |
397
- | `chat_id` | Chat/Channel ID (use with `message_id`) - numeric ID or @username |
397
+ | `chat_id` | Chat/Channel ID (use with `message_id` or `document_id`) - numeric ID or @username |
398
398
  | `message_id` | Message ID within the chat (use with `chat_id`) |
399
+ | `document_id` | Telegram document ID (use with `chat_id`, optionally add `file_size` to enforce size match while resolving) |
399
400
  | `file_id` | Bot API file_id (use with `file_size`) |
400
401
  | `file_size` | File size in bytes (required when using `file_id`) |
401
402
  | `transcode` | Set to `true` for direct transcode mode on `/proxy/telegram/stream` (URL Generator defaults to `/proxy/telegram/transcode/playlist.m3u8` when no start time is set) |
@@ -412,8 +413,13 @@ TELEGRAM_SESSION_STRING=your_session_string_here
412
413
  - `chat_id=-1001234567890&message_id=123` - Private channel/supergroup by numeric ID
413
414
  - `chat_id=@channelname&message_id=123` - Public channel by username
414
415
 
415
- **Option 3: Bot API file_id**
416
+ **Option 3: chat_id + document_id**
417
+ - `chat_id=-1001234567890&document_id=6743210987654321` - Resolves by scanning recent chat messages
418
+ - Optional: add `file_size=52428800` to require both document ID and size to match
419
+
420
+ **Option 4: Bot API file_id**
416
421
  - `file_id=BQACAgI...&file_size=1048576` - Direct streaming by file_id
422
+ - `chat_id=-1001234567890&file_id=BQACAgI...&file_size=1048576` - If the file reference is stale, server can resolve by scanning this chat
417
423
  - Requires `file_size` parameter for range request support (seeking in video players)
418
424
  - Get file_id and file_size from Telegram Bot API's `getFile` response
419
425
 
@@ -432,6 +438,9 @@ mpv "http://localhost:8888/proxy/telegram/stream?chat_id=@channelname&message_id
432
438
  # Stream using Bot API file_id (requires file_size)
433
439
  mpv "http://localhost:8888/proxy/telegram/stream?file_id=BQACAgIAAxkBAAI...&file_size=52428800&api_password=your_password"
434
440
 
441
+ # Stream using chat_id + document_id (server scans recent messages)
442
+ mpv "http://localhost:8888/proxy/telegram/stream?chat_id=-1001234567890&document_id=6743210987654321&api_password=your_password"
443
+
435
444
  # Stream with custom filename
436
445
  mpv "http://localhost:8888/proxy/telegram/stream/movie.mp4?d=https://t.me/channelname/123&api_password=your_password"
437
446
 
@@ -340,7 +340,7 @@ TELEGRAM_SESSION_STRING=your_session_string_here
340
340
 
341
341
  | Endpoint | Description |
342
342
  |----------|-------------|
343
- | `/proxy/telegram/stream` | Stream media from t.me link or file_id |
343
+ | `/proxy/telegram/stream` | Stream media from t.me link, chat IDs, document_id, or file_id |
344
344
  | `/proxy/telegram/stream/{filename}` | Stream with custom filename |
345
345
  | `/proxy/telegram/transcode/playlist.m3u8` | HLS transcode playlist (recommended for browser playback and smooth seeking) |
346
346
  | `/proxy/telegram/transcode/init.mp4` | fMP4 init segment for Telegram transcode playlist |
@@ -353,8 +353,9 @@ TELEGRAM_SESSION_STRING=your_session_string_here
353
353
  | Parameter | Description |
354
354
  |-----------|-------------|
355
355
  | `d` or `url` | t.me link (e.g., `https://t.me/channel/123`) |
356
- | `chat_id` | Chat/Channel ID (use with `message_id`) - numeric ID or @username |
356
+ | `chat_id` | Chat/Channel ID (use with `message_id` or `document_id`) - numeric ID or @username |
357
357
  | `message_id` | Message ID within the chat (use with `chat_id`) |
358
+ | `document_id` | Telegram document ID (use with `chat_id`, optionally add `file_size` to enforce size match while resolving) |
358
359
  | `file_id` | Bot API file_id (use with `file_size`) |
359
360
  | `file_size` | File size in bytes (required when using `file_id`) |
360
361
  | `transcode` | Set to `true` for direct transcode mode on `/proxy/telegram/stream` (URL Generator defaults to `/proxy/telegram/transcode/playlist.m3u8` when no start time is set) |
@@ -371,8 +372,13 @@ TELEGRAM_SESSION_STRING=your_session_string_here
371
372
  - `chat_id=-1001234567890&message_id=123` - Private channel/supergroup by numeric ID
372
373
  - `chat_id=@channelname&message_id=123` - Public channel by username
373
374
 
374
- **Option 3: Bot API file_id**
375
+ **Option 3: chat_id + document_id**
376
+ - `chat_id=-1001234567890&document_id=6743210987654321` - Resolves by scanning recent chat messages
377
+ - Optional: add `file_size=52428800` to require both document ID and size to match
378
+
379
+ **Option 4: Bot API file_id**
375
380
  - `file_id=BQACAgI...&file_size=1048576` - Direct streaming by file_id
381
+ - `chat_id=-1001234567890&file_id=BQACAgI...&file_size=1048576` - If the file reference is stale, server can resolve by scanning this chat
376
382
  - Requires `file_size` parameter for range request support (seeking in video players)
377
383
  - Get file_id and file_size from Telegram Bot API's `getFile` response
378
384
 
@@ -391,6 +397,9 @@ mpv "http://localhost:8888/proxy/telegram/stream?chat_id=@channelname&message_id
391
397
  # Stream using Bot API file_id (requires file_size)
392
398
  mpv "http://localhost:8888/proxy/telegram/stream?file_id=BQACAgIAAxkBAAI...&file_size=52428800&api_password=your_password"
393
399
 
400
+ # Stream using chat_id + document_id (server scans recent messages)
401
+ mpv "http://localhost:8888/proxy/telegram/stream?chat_id=-1001234567890&document_id=6743210987654321&api_password=your_password"
402
+
394
403
  # Stream with custom filename
395
404
  mpv "http://localhost:8888/proxy/telegram/stream/movie.mp4?d=https://t.me/channelname/123&api_password=your_password"
396
405
 
@@ -95,6 +95,8 @@ class Settings(BaseSettings):
95
95
  telegram_session_string: SecretStr | None = None # Persistent session string (avoids re-authentication).
96
96
  telegram_max_connections: int = 8 # Max parallel DC connections for downloads (max 20, careful of floods).
97
97
  telegram_request_timeout: int = 30 # Request timeout in seconds.
98
+ telegram_document_scan_limit: int = 500 # Max recent messages to scan when resolving chat_id+document_id.
99
+ telegram_document_cache_ttl: int = 3600 # TTL (seconds) for cached document_id->message_id mappings.
98
100
 
99
101
  # Transcode settings
100
102
  enable_transcode: bool = True # Whether to enable on-the-fly transcoding endpoints (MKV→fMP4, HLS VOD).
@@ -0,0 +1,296 @@
1
+ import logging
2
+ import time
3
+ import re
4
+ import uuid
5
+ from typing import Any, Dict, Optional
6
+ from urllib.parse import quote
7
+
8
+ from mediaflow_proxy.extractors.base import BaseExtractor, ExtractorError
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class VavooExtractor(BaseExtractor):
14
+ """Vavoo URL extractor per risolvere link vavoo.to"""
15
+
16
+ API_UA = "electron-fetch/1.0 electron (+https://github.com/arantes555/electron-fetch)"
17
+ TS_UA = "VAVOO/2.6"
18
+
19
+ def __init__(self, request_headers: dict):
20
+ super().__init__(request_headers)
21
+ self.mediaflow_endpoint = "proxy_stream_endpoint"
22
+
23
+ async def _get_auth_signature(self) -> Optional[str]:
24
+ """Get authentication signature via /api/app/ping (electron flow)."""
25
+ unique_id = uuid.uuid4().hex[:16]
26
+ headers = {
27
+ "user-agent": self.API_UA,
28
+ "accept": "*/*",
29
+ "Accept-Language": "de",
30
+ "Accept-Encoding": "gzip, deflate",
31
+ "content-type": "application/json; charset=utf-8",
32
+ "Connection": "close",
33
+ }
34
+ body = {
35
+ "token": "8Us2TfjeOFrzqFFTEjL3E5KfdAWGa5PV3wQe60uK4BmzlkJRMYFu0ufaM_eeDXKS2U04XUuhbDTgGRJrJARUwzDyCcRToXhW5AcDekfFMfwNUjuieeQ1uzeDB9YWyBL2cn5Al3L3gTnF8Vk1t7rPwkBob0swvxA",
36
+ "reason": "player.enter",
37
+ "locale": "de",
38
+ "theme": "dark",
39
+ "metadata": {
40
+ "device": {
41
+ "type": "Desktop",
42
+ "brand": "Unknown",
43
+ "model": "Unknown",
44
+ "name": "Unknown",
45
+ "uniqueId": unique_id,
46
+ },
47
+ "os": {"name": "windows", "version": "10.0.22631", "abis": [], "host": "electron"},
48
+ "app": {
49
+ "platform": "electron",
50
+ "version": "3.1.4",
51
+ "buildId": "288045000",
52
+ "engine": "jsc",
53
+ "signatures": [],
54
+ "installer": "unknown",
55
+ },
56
+ "version": {"package": "tv.vavoo.app", "binary": "3.1.4", "js": "3.1.4"},
57
+ },
58
+ "appFocusTime": 27229,
59
+ "playerActive": True,
60
+ "playDuration": 0,
61
+ "devMode": False,
62
+ "hasAddon": False,
63
+ "castConnected": False,
64
+ "package": "tv.vavoo.app",
65
+ "version": "3.1.4",
66
+ "process": "app",
67
+ "firstAppStart": int(time.time() * 1000) - 86400000,
68
+ "lastAppStart": int(time.time() * 1000),
69
+ "ipLocation": "",
70
+ "adblockEnabled": False,
71
+ "proxy": {"supported": ["ss"], "engine": "ss", "enabled": False, "autoServer": True, "id": "ca-bhs"},
72
+ "iap": {"supported": True},
73
+ }
74
+ try:
75
+ resp = await self._make_request(
76
+ "https://www.vavoo.tv/api/app/ping",
77
+ method="POST",
78
+ json=body,
79
+ headers=headers,
80
+ timeout=15,
81
+ retries=2,
82
+ )
83
+ try:
84
+ result = resp.json()
85
+ except Exception:
86
+ logger.warning("Vavoo ping returned non-json response (status=%s).", resp.status)
87
+ return None
88
+ addon_sig = result.get("addonSig") if isinstance(result, dict) else None
89
+ if addon_sig:
90
+ logger.info("Successfully obtained Vavoo auth signature (electron mode)")
91
+ return addon_sig
92
+ logger.warning("No addonSig in Vavoo API response: %s", result)
93
+ return None
94
+ except Exception as e:
95
+ logger.debug("_get_auth_signature error: %s", e)
96
+ return None
97
+
98
+ async def _get_ts_signature(self) -> Optional[str]:
99
+ """Get TS-based signature via /api/box/ping2 (fallback)."""
100
+ vec = "9frjpxPjxSNilxJPCJ0XGYs6scej3dW/h/VWlnKUiLSG8IP7mfyDU7NirOlld+VtCKGj03XjetfliDMhIev7wcARo+YTU8KPFuVQP9E2DVXzY2BFo1NhE6qEmPfNDnm74eyl/7iFJ0EETm6XbYyz8IKBkAqPN/Spp3PZ2ulKg3QBSDxcVN4R5zRn7OsgLJ2CNTuWkd/h451lDCp+TtTuvnAEhcQckdsydFhTZCK5IiWrrTIC/d4qDXEd+GtOP4hPdoIuCaNzYfX3lLCwFENC6RZoTBYLrcKVVgbqyQZ7DnLqfLqvf3z0FVUWx9H21liGFpByzdnoxyFkue3NzrFtkRL37xkx9ITucepSYKzUVEfyBh+/3mtzKY26VIRkJFkpf8KVcCRNrTRQn47Wuq4gC7sSwT7eHCAydKSACcUMMdpPSvbvfOmIqeBNA83osX8FPFYUMZsjvYNEE3arbFiGsQlggBKgg1V3oN+5ni3Vjc5InHg/xv476LHDFnNdAJx448ph3DoAiJjr2g4ZTNynfSxdzA68qSuJY8UjyzgDjG0RIMv2h7DlQNjkAXv4k1BrPpfOiOqH67yIarNmkPIwrIV+W9TTV/yRyE1LEgOr4DK8uW2AUtHOPA2gn6P5sgFyi68w55MZBPepddfYTQ+E1N6R/hWnMYPt/i0xSUeMPekX47iucfpFBEv9Uh9zdGiEB+0P3LVMP+q+pbBU4o1NkKyY1V8wH1Wilr0a+q87kEnQ1LWYMMBhaP9yFseGSbYwdeLsX9uR1uPaN+u4woO2g8sw9Y5ze5XMgOVpFCZaut02I5k0U4WPyN5adQjG8sAzxsI3KsV04DEVymj224iqg2Lzz53Xz9yEy+7/85ILQpJ6llCyqpHLFyHq/kJxYPhDUF755WaHJEaFRPxUqbparNX+mCE9Xzy7Q/KTgAPiRS41FHXXv+7XSPp4cy9jli0BVnYf13Xsp28OGs/D8Nl3NgEn3/eUcMN80JRdsOrV62fnBVMBNf36+LbISdvsFAFr0xyuPGmlIETcFyxJkrGZnhHAxwzsvZ+Uwf8lffBfZFPRrNv+tgeeLpatVcHLHZGeTgWWml6tIHwWUqv2TVJeMkAEL5PPS4Gtbscau5HM+FEjtGS+KClfX1CNKvgYJl7mLDEf5ZYQv5kHaoQ6RcPaR6vUNn02zpq5/X3EPIgUKF0r/0ctmoT84B2J1BKfCbctdFY9br7JSJ6DvUxyde68jB+Il6qNcQwTFj4cNErk4x719Y42NoAnnQYC2/qfL/gAhJl8TKMvBt3Bno+va8ve8E0z8yEuMLUqe8OXLce6nCa+L5LYK1aBdb60BYbMeWk1qmG6Nk9OnYLhzDyrd9iHDd7X95OM6X5wiMVZRn5ebw4askTTc50xmrg4eic2U1w1JpSEjdH/u/hXrWKSMWAxaj34uQnMuWxPZEXoVxzGyuUbroXRfkhzpqmqqqOcypjsWPdq5BOUGL/Riwjm6yMI0x9kbO8+VoQ6RYfjAbxNriZ1cQ+AW1fqEgnRWXmjt4Z1M0ygUBi8w71bDML1YG6UHeC2cJ2CCCxSrfycKQhpSdI1QIuwd2eyIpd4LgwrMiY3xNWreAF+qobNxvE7ypKTISNrz0iYIhU0aKNlcGwYd0FXIRfKVBzSBe4MRK2pGLDNO6ytoHxvJweZ8h1XG8RWc4aB5gTnB7Tjiqym4b64lRdj1DPHJnzD4aqRixpXhzYzWVDN2kONCR5i2quYbnVFN4sSfLiKeOwKX4JdmzpYixNZXjLkG14seS6KR0Wl8Itp5IMIWFpnNokjRH76RYRZAcx0jP0V5/GfNNTi5QsEU98en0SiXHQGXnROiHpRUDXTl8FmJORjwXc0AjrEMuQ2FDJDmAIlKUSLhjbIiKw3iaqp5TVyXuz0ZMYBhnqhcwqULqtFSuIKpaW8FgF8QJfP2frADf4kKZG1bQ99MrRrb2A="
101
+ try:
102
+ resp = await self._make_request(
103
+ "https://www.vavoo.tv/api/box/ping2",
104
+ method="POST",
105
+ data={"vec": vec},
106
+ timeout=15,
107
+ retries=2,
108
+ )
109
+ try:
110
+ result = resp.json()
111
+ except Exception:
112
+ return None
113
+ return (result.get("response") or {}).get("signed")
114
+ except Exception as e:
115
+ logger.debug("_get_ts_signature error: %s", e)
116
+ return None
117
+
118
+ async def _resolve_with_auth(self, url: str, signature: str) -> Optional[str]:
119
+ """Resolve a Vavoo link using the MediaHubMX API with electron-mode signature."""
120
+ headers = {
121
+ "user-agent": self.API_UA,
122
+ "accept": "*/*",
123
+ "Accept-Language": "de",
124
+ "Accept-Encoding": "gzip, deflate",
125
+ "content-type": "application/json; charset=utf-8",
126
+ "Connection": "close",
127
+ "mediahubmx-signature": signature,
128
+ }
129
+ payload = {"language": "de", "region": "AT", "url": url, "clientVersion": "3.1.4"}
130
+ try:
131
+ resp = await self._make_request(
132
+ "https://vavoo.to/mediahubmx-resolve.json",
133
+ method="POST",
134
+ json=payload,
135
+ headers=headers,
136
+ timeout=15,
137
+ retries=3,
138
+ backoff_factor=0.6,
139
+ )
140
+ try:
141
+ result = resp.json()
142
+ except Exception:
143
+ logger.warning("Vavoo resolve returned non-json (status=%s)", resp.status)
144
+ return None
145
+ logger.debug("Vavoo API response: %s", result)
146
+ if isinstance(result, list) and result and isinstance(result[0], dict) and result[0].get("url"):
147
+ return str(result[0]["url"])
148
+ if isinstance(result, dict):
149
+ if result.get("url"):
150
+ return str(result["url"])
151
+ if isinstance(result.get("data"), dict) and result["data"].get("url"):
152
+ return str(result["data"]["url"])
153
+ logger.warning("No URL found in Vavoo API response: %s", result)
154
+ return None
155
+ except Exception as e:
156
+ logger.debug("_resolve_with_auth error: %s", e)
157
+ return None
158
+
159
+ async def _follow_stream_url(self, url: str) -> str:
160
+ """Follow redirects and extract final stream URL."""
161
+ stream_headers = {
162
+ "User-Agent": self.API_UA,
163
+ "Accept": "*/*",
164
+ "Accept-Encoding": "gzip, deflate",
165
+ "Connection": "close",
166
+ }
167
+ try:
168
+ resp = await self._make_request(url, method="HEAD", headers=stream_headers, timeout=15, retries=1)
169
+ final_url = str(getattr(resp, "url", url))
170
+ ctype = (getattr(resp, "headers", {}).get("Content-Type") or "").lower()
171
+ if "text/html" in ctype:
172
+ resp2 = await self._make_request(url, method="GET", headers=stream_headers, timeout=15, retries=1)
173
+ text = getattr(resp2, "text", "") or ""
174
+ m3u8 = re.findall(r'(https?://[^\s"\'<>]+\.m3u8[^\s"\'<>]*)', text)
175
+ if m3u8:
176
+ return m3u8[0]
177
+ generic = re.findall(
178
+ r'(https?://[^\s"\'<>]+(?:\.ts|/live/|/stream/|/playlist|/index)[^\s"\'<>]*)', text
179
+ )
180
+ if generic:
181
+ return generic[0]
182
+ return final_url
183
+ except Exception:
184
+ return url
185
+
186
+ async def _build_ts_fallback(self, url: str) -> Optional[str]:
187
+ """Build a .ts fallback URL for vavoo-iptv streams using ping2 signature."""
188
+ if "vavoo-iptv" not in url:
189
+ return None
190
+ ts_sig = await self._get_ts_signature()
191
+ if not ts_sig:
192
+ return None
193
+ base = re.sub(r"/index\.m3u8(?:\?.*)?$", "", url.replace("vavoo-iptv", "live2")).rstrip("/")
194
+ ts_url = f"{base}.ts?n=1&b=5&vavoo_auth={quote(ts_sig, safe='')}"
195
+ try:
196
+ resp = await self._make_request(
197
+ ts_url, method="GET", headers={"User-Agent": self.TS_UA}, timeout=15, retries=1
198
+ )
199
+ if getattr(resp, "status", 400) < 400:
200
+ return ts_url
201
+ except Exception:
202
+ pass
203
+ return None
204
+
205
+ async def _resolve_web_vod_link(self, url: str) -> str:
206
+ """Resolve a web-vod API link by getting the redirect Location header."""
207
+ try:
208
+ resp = await self._make_request(
209
+ url,
210
+ method="GET",
211
+ headers={"Accept": "application/json"},
212
+ timeout=10,
213
+ retries=2,
214
+ allow_redirects=False,
215
+ )
216
+ status = getattr(resp, "status", 0)
217
+ if status in (301, 302, 303, 307, 308):
218
+ location = getattr(resp, "headers", {}).get("Location") or getattr(resp, "headers", {}).get("location")
219
+ if location:
220
+ logger.info("Vavoo web-vod redirected to: %s", location)
221
+ return location
222
+ if status == 200:
223
+ text = getattr(resp, "text", "") or ""
224
+ if text and text.startswith("http"):
225
+ logger.info("Vavoo web-vod resolved to: %s", text.strip())
226
+ return text.strip()
227
+ raise ExtractorError(f"Vavoo web-vod API returned unexpected status {status}")
228
+ except ExtractorError:
229
+ raise
230
+ except Exception as e:
231
+ raise ExtractorError(f"Failed to resolve Vavoo web-vod link: {e}")
232
+
233
+ async def extract(self, url: str, **kwargs) -> Dict[str, Any]:
234
+ """Extract Vavoo stream URL.
235
+
236
+ Flow:
237
+ 1. Auth Resolve Mode: electron-mode signature → mediahubmx-resolve
238
+ 2. TS Fallback Mode: ping2 signature → live2 .ts URL
239
+ 3. Direct Fallback: raw URL with VAVOO UA
240
+ """
241
+ if "vavoo.to" not in url:
242
+ raise ExtractorError("Not a valid Vavoo URL")
243
+
244
+ # Web-VOD links (new format)
245
+ if "/web-vod/api/get" in url:
246
+ resolved_url = await self._resolve_web_vod_link(url)
247
+ stream_headers = {
248
+ "user-agent": self.API_UA,
249
+ "referer": "https://vavoo.to/",
250
+ }
251
+ return {
252
+ "destination_url": resolved_url,
253
+ "request_headers": stream_headers,
254
+ "mediaflow_endpoint": self.mediaflow_endpoint,
255
+ }
256
+
257
+ resolved_url = None
258
+ stream_headers = None
259
+
260
+ # Mode 1: Auth Resolve (electron signature + mediahubmx)
261
+ sig = await self._get_auth_signature()
262
+ if sig:
263
+ candidate = await self._resolve_with_auth(url, sig)
264
+ if candidate:
265
+ candidate = await self._follow_stream_url(candidate)
266
+ resolved_url = candidate
267
+ stream_headers = {
268
+ "user-agent": self.API_UA,
269
+ "referer": "https://vavoo.to/",
270
+ "origin": "https://vavoo.to",
271
+ "mediahubmx-signature": sig,
272
+ }
273
+ logger.info("Using Auth Resolve Mode: %s", resolved_url)
274
+
275
+ # Mode 2: TS Fallback (ping2 + live2 .ts)
276
+ if not resolved_url:
277
+ ts_url = await self._build_ts_fallback(url)
278
+ if ts_url:
279
+ resolved_url = ts_url
280
+ stream_headers = {"user-agent": self.TS_UA}
281
+ logger.info("Using TS Fallback Mode: %s", resolved_url)
282
+
283
+ # Mode 3: Direct Fallback
284
+ if not resolved_url:
285
+ resolved_url = url
286
+ stream_headers = {
287
+ "user-agent": self.TS_UA,
288
+ "referer": "https://vavoo.to/",
289
+ }
290
+ logger.info("Using Direct Fallback Mode: %s", resolved_url)
291
+
292
+ return {
293
+ "destination_url": resolved_url,
294
+ "request_headers": stream_headers,
295
+ "mediaflow_endpoint": self.mediaflow_endpoint,
296
+ }
@@ -132,6 +132,8 @@ class TelegramMediaSource:
132
132
  raw = f"file_id:{ref.file_id}"
133
133
  elif ref.chat_id is not None and ref.message_id is not None:
134
134
  raw = f"chat:{ref.chat_id}:msg:{ref.message_id}"
135
+ elif ref.chat_id is not None and ref.document_id is not None:
136
+ raw = f"chat:{ref.chat_id}:doc:{ref.document_id}"
135
137
  else:
136
138
  return ""
137
139
  return hashlib.sha256(raw.encode()).hexdigest()[:16]