sanch1tx commited on
Commit
d24ae96
·
verified ·
1 Parent(s): cdbf565

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +159 -113
app.py CHANGED
@@ -1,5 +1,4 @@
1
  import httpx
2
- import json
3
  import logging
4
  from fastapi import FastAPI, HTTPException, Query
5
  from fastapi.middleware.cors import CORSMiddleware
@@ -7,171 +6,218 @@ from typing import List, Optional, Dict, Any
7
  from pydantic import BaseModel
8
 
9
  # --- Configuration ---
10
- # The base URL of your existing TypeScript/Node API (Vega Providers)
11
  VEGA_API_BASE = "https://sanch1tx-vega-providers.hf.space"
12
- # The URL for the provider list
13
  MODFLIX_JSON_URL = "https://raw.githubusercontent.com/himanshu8443/providers/main/modflix.json"
14
 
15
- # Fallback providers in case GitHub is down or inaccessible
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  FALLBACK_PROVIDERS = [
17
- {"name": "vega", "baseUrl": VEGA_API_BASE, "isActive": True},
18
- {"name": "uhd", "baseUrl": VEGA_API_BASE, "isActive": True},
19
- {"name": "movies4u", "baseUrl": VEGA_API_BASE, "isActive": True},
20
- {"name": "flimmo", "baseUrl": VEGA_API_BASE, "isActive": True},
21
- {"name": "tobias", "baseUrl": VEGA_API_BASE, "isActive": True},
22
- {"name": "goku", "baseUrl": VEGA_API_BASE, "isActive": True},
23
- {"name": "kissKh", "baseUrl": VEGA_API_BASE, "isActive": True},
24
- {"name": "superVideoExtractor", "baseUrl": VEGA_API_BASE, "isActive": True},
25
  ]
26
 
27
- # --- FastAPI App Setup ---
28
- app = FastAPI(title="Vega Providers Gateway", description="A Python bridge for Vega Providers")
29
 
30
- # Enable CORS for your frontend
31
  app.add_middleware(
32
  CORSMiddleware,
33
- allow_origins=["*"], # Allow all origins for now (adjust for production)
34
  allow_credentials=True,
35
  allow_methods=["*"],
36
  allow_headers=["*"],
37
  )
38
 
39
- # Logger setup
40
  logging.basicConfig(level=logging.INFO)
41
  logger = logging.getLogger("vega_gateway")
42
 
43
- # --- Data Models ---
44
- # (Optional, but good for documentation)
45
- class Provider(BaseModel):
46
  name: str
 
47
  baseUrl: str
48
  isActive: bool
49
 
50
- # --- Helper Functions ---
 
 
51
 
52
- async def fetch_providers_list():
53
- """Fetches the provider list from GitHub with fallback."""
54
  async with httpx.AsyncClient() as client:
55
  try:
56
- logger.info(f"Fetching providers from {MODFLIX_JSON_URL}")
57
- response = await client.get(MODFLIX_JSON_URL, timeout=5.0)
58
- response.raise_for_status()
59
- data = response.json()
60
-
61
- # Normalize data structure
62
- providers = []
63
- if isinstance(data, list):
64
- providers = data
65
- elif isinstance(data, dict) and "providers" in data:
66
- providers = data["providers"]
67
- elif isinstance(data, dict):
68
- providers = list(data.values())
69
-
70
- # Normalize fields
71
- normalized = []
72
- for p in providers:
73
- if not isinstance(p, dict): continue
74
- name = p.get("name") or p.get("value") or p.get("id") or "Unknown"
75
- base_url = p.get("baseUrl") or VEGA_API_BASE
76
- is_active = p.get("isActive", not p.get("disabled", False))
77
- normalized.append({"name": name, "baseUrl": base_url, "isActive": is_active})
78
-
79
- if not normalized:
80
- logger.warning("Fetched data empty, using fallback.")
81
- return FALLBACK_PROVIDERS
82
 
83
- return normalized
84
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  except Exception as e:
86
- logger.error(f"Failed to fetch providers: {e}. Using fallback.")
87
- return FALLBACK_PROVIDERS
 
88
 
89
- # --- Endpoints ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
- @app.get("/", tags=["Health"])
92
- async def root():
93
- return {"status": "ok", "message": "Vega Python Gateway is running"}
94
 
95
- @app.get("/providers", response_model=List[Provider], tags=["Core"])
96
- async def get_providers():
97
- """Returns a clean list of active providers."""
98
- return await fetch_providers_list()
99
 
100
- @app.get("/{provider}/catalog", tags=["Provider"])
101
- async def get_catalog(provider: str):
102
- """Fetches the catalog for a specific provider."""
103
- url = f"{VEGA_API_BASE}/{provider}/catalog"
104
- async with httpx.AsyncClient() as client:
105
- try:
106
- resp = await client.get(url, timeout=10.0)
107
- if resp.status_code != 200:
108
- # Return empty catalog instead of erroring out
109
- logger.warning(f"Catalog fetch failed for {provider}: {resp.status_code}")
110
- return {"catalog": []}
111
- return resp.json()
112
- except Exception as e:
113
- logger.error(f"Error fetching catalog for {provider}: {e}")
114
- return {"catalog": []}
115
-
116
- @app.get("/{provider}/posts", tags=["Provider"])
117
- async def get_posts(
118
- provider: str,
119
- filter: str = Query(..., description="The filter path from catalog"),
120
- page: int = 1
121
- ):
122
- """Fetches posts (movies/series) for a provider and filter."""
123
  url = f"{VEGA_API_BASE}/{provider}/posts"
 
124
  params = {"filter": filter, "page": page}
 
125
  async with httpx.AsyncClient() as client:
126
  try:
127
  resp = await client.get(url, params=params, timeout=15.0)
128
- data = resp.json()
129
- # Ensure it's always a list
130
- if isinstance(data, list): return data
131
- return []
132
  except Exception as e:
133
- logger.error(f"Error fetching posts for {provider}: {e}")
134
- return []
 
135
 
136
- @app.get("/{provider}/meta", tags=["Provider"])
137
  async def get_meta(provider: str, link: str):
138
- """Fetches metadata for a specific item."""
139
  url = f"{VEGA_API_BASE}/{provider}/meta"
140
  params = {"link": link}
141
  async with httpx.AsyncClient() as client:
142
  try:
143
  resp = await client.get(url, params=params, timeout=15.0)
144
- resp.raise_for_status()
145
- return resp.json()
146
- except Exception as e:
147
- raise HTTPException(status_code=500, detail=str(e))
 
148
 
149
- @app.get("/{provider}/episodes", tags=["Provider"])
150
  async def get_episodes(provider: str, url: str):
151
- """Fetches episodes if they require a separate call."""
152
  api_url = f"{VEGA_API_BASE}/{provider}/episodes"
153
  params = {"url": url}
154
  async with httpx.AsyncClient() as client:
155
  try:
156
  resp = await client.get(api_url, params=params, timeout=15.0)
157
- data = resp.json()
158
- if isinstance(data, list): return data
159
- return []
160
- except Exception as e:
161
- logger.error(f"Error fetching episodes: {e}")
162
- return []
163
 
164
- @app.get("/{provider}/stream", tags=["Provider"])
165
  async def get_stream(provider: str, link: str, type: str = "movie"):
166
- """Fetches stream links (runs extractors on the upstream server)."""
167
  api_url = f"{VEGA_API_BASE}/{provider}/stream"
168
  params = {"link": link, "type": type}
169
  async with httpx.AsyncClient() as client:
170
  try:
171
- resp = await client.get(api_url, params=params, timeout=30.0) # Longer timeout for extraction
172
- data = resp.json()
173
- if isinstance(data, list): return data
174
- return []
175
- except Exception as e:
176
- logger.error(f"Error fetching stream: {e}")
177
- return []
 
1
  import httpx
 
2
  import logging
3
  from fastapi import FastAPI, HTTPException, Query
4
  from fastapi.middleware.cors import CORSMiddleware
 
6
  from pydantic import BaseModel
7
 
8
  # --- Configuration ---
9
+ # Upstream API where the provider logic (JS/TS) lives
10
  VEGA_API_BASE = "https://sanch1tx-vega-providers.hf.space"
11
+ # List of providers
12
  MODFLIX_JSON_URL = "https://raw.githubusercontent.com/himanshu8443/providers/main/modflix.json"
13
 
14
+ # --- Hardcoded Fallback Catalogs ---
15
+ # Keys MUST match the 'value' (folder name) of the provider.
16
+ DEFAULT_CATALOGS = {
17
+ "drive": [
18
+ {"title": "Latest", "filter": ""},
19
+ {"title": "Anime", "filter": "category/anime/"},
20
+ {"title": "Netflix", "filter": "category/netflix/"},
21
+ {"title": "4K", "filter": "category/2160p-4k/"},
22
+ ],
23
+ "movies4u": [
24
+ {"title": "Featured", "filter": "featured"},
25
+ {"title": "Movies", "filter": "movies"},
26
+ {"title": "TV Shows", "filter": "tv-shows"},
27
+ ],
28
+ "uhd": [
29
+ {"title": "Latest", "filter": ""},
30
+ {"title": "Hindi", "filter": "category/hindi-movies/"},
31
+ {"title": "Dual Audio", "filter": "category/dual-audio/"},
32
+ ],
33
+ "vega": [
34
+ {"title": "New", "filter": ""},
35
+ {"title": "Netflix", "filter": "web-series/netflix"},
36
+ {"title": "Amazon Prime", "filter": "web-series/amazon-prime-video"},
37
+ ],
38
+ "flimmo": [
39
+ {"title": "Home", "filter": "home"},
40
+ {"title": "Movies", "filter": "movies"},
41
+ {"title": "TV Series", "filter": "tv-series"},
42
+ ]
43
+ }
44
+
45
+ # Generic default if provider specific catalog is missing
46
+ GENERIC_CATALOG = [
47
+ {"title": "Latest", "filter": ""},
48
+ {"title": "Trending", "filter": "trending"},
49
+ {"title": "Movies", "filter": "movies"},
50
+ ]
51
+
52
+ # Fallback providers with correct IDs (value) matching folder names
53
  FALLBACK_PROVIDERS = [
54
+ {"name": "Vega", "value": "vega", "baseUrl": VEGA_API_BASE, "isActive": True},
55
+ {"name": "UHD Movies", "value": "uhd", "baseUrl": VEGA_API_BASE, "isActive": True},
56
+ {"name": "Movies4U", "value": "movies4u", "baseUrl": VEGA_API_BASE, "isActive": True},
57
+ {"name": "Drive", "value": "drive", "baseUrl": VEGA_API_BASE, "isActive": True},
58
+ {"name": "Flimmo", "value": "flimmo", "baseUrl": VEGA_API_BASE, "isActive": True},
59
+ {"name": "Tobias", "value": "tobias", "baseUrl": VEGA_API_BASE, "isActive": True},
 
 
60
  ]
61
 
62
+ app = FastAPI(title="Vega Providers Gateway")
 
63
 
 
64
  app.add_middleware(
65
  CORSMiddleware,
66
+ allow_origins=["*"],
67
  allow_credentials=True,
68
  allow_methods=["*"],
69
  allow_headers=["*"],
70
  )
71
 
 
72
  logging.basicConfig(level=logging.INFO)
73
  logger = logging.getLogger("vega_gateway")
74
 
75
+ class ProviderModel(BaseModel):
 
 
76
  name: str
77
+ value: str # Critical: The unique ID used for URL routing
78
  baseUrl: str
79
  isActive: bool
80
 
81
+ @app.get("/")
82
+ async def root():
83
+ return {"status": "ok", "message": "Vega Python Gateway Active"}
84
 
85
+ @app.get("/providers", response_model=List[ProviderModel])
86
+ async def get_providers():
87
  async with httpx.AsyncClient() as client:
88
  try:
89
+ response = await client.get(MODFLIX_JSON_URL, timeout=4.0)
90
+ if response.status_code == 200:
91
+ data = response.json()
92
+ providers = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
+ # Handle different JSON structures (list or object)
95
+ raw_list = []
96
+ if isinstance(data, list):
97
+ raw_list = data
98
+ elif isinstance(data, dict):
99
+ raw_list = data.get("providers", list(data.values()))
100
+
101
+ for p in raw_list:
102
+ if isinstance(p, dict):
103
+ name = p.get("name") or "Unknown"
104
+
105
+ # CRITICAL FIX:
106
+ # We must capture the 'value' or 'id' field if it exists.
107
+ # This 'value' typically matches the folder name on the server (e.g. 'drive', 'luxMovies').
108
+ # If we just use 'name', we might send 'MoviesDrive' which the server doesn't know.
109
+ val = p.get("value") or p.get("id") or name
110
+
111
+ # Special handling for known mismatches if JSON is messy or inconsistent
112
+ if name.lower() == "moviesdrive": val = "drive"
113
+ if name.lower() == "uhdmovies": val = "uhd"
114
+
115
+ # Ensure value is URL safe string
116
+ val = str(val).strip()
117
+
118
+ if val:
119
+ providers.append({
120
+ "name": name,
121
+ "value": val,
122
+ "baseUrl": p.get("baseUrl") or VEGA_API_BASE,
123
+ "isActive": True
124
+ })
125
+
126
+ if providers:
127
+ return providers
128
+
129
  except Exception as e:
130
+ logger.error(f"Provider fetch error: {e}")
131
+
132
+ return FALLBACK_PROVIDERS
133
 
134
+ @app.get("/{provider}/catalog")
135
+ async def get_catalog(provider: str):
136
+ """
137
+ Fetches catalog with strict fallbacks.
138
+ If the upstream API fails, it returns the hardcoded DEFAULT_CATALOGS for that provider.
139
+ """
140
+ url = f"{VEGA_API_BASE}/{provider}/catalog"
141
+
142
+ # 1. Try to fetch from upstream
143
+ try:
144
+ async with httpx.AsyncClient() as client:
145
+ resp = await client.get(url, timeout=5.0)
146
+
147
+ if resp.status_code == 200:
148
+ data = resp.json()
149
+
150
+ # Normalize response to { catalog: [...] }
151
+ # Case A: Response is direct list [ {...}, {...} ]
152
+ if isinstance(data, list) and len(data) > 0:
153
+ return {"catalog": data}
154
+ # Case B: Response is object { catalog: [...] }
155
+ if isinstance(data, dict) and "catalog" in data and len(data["catalog"]) > 0:
156
+ return data
157
+
158
+ logger.warning(f"Upstream catalog empty/failed for {provider} (Status: {resp.status_code})")
159
 
160
+ except Exception as e:
161
+ logger.error(f"Error fetching catalog for {provider}: {e}")
 
162
 
163
+ # 2. Fallback: Return hardcoded catalog for this provider
164
+ # Try exact match, then try default
165
+ fallback = DEFAULT_CATALOGS.get(provider) or DEFAULT_CATALOGS.get("default") or GENERIC_CATALOG
166
+ return {"catalog": fallback}
167
 
168
+ @app.get("/{provider}/posts")
169
+ async def get_posts(provider: str, filter: str = "", page: int = 1):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  url = f"{VEGA_API_BASE}/{provider}/posts"
171
+ # Some providers fail if filter is empty, default to "latest" or similar if needed
172
  params = {"filter": filter, "page": page}
173
+
174
  async with httpx.AsyncClient() as client:
175
  try:
176
  resp = await client.get(url, params=params, timeout=15.0)
177
+ if resp.status_code == 200:
178
+ data = resp.json()
179
+ if isinstance(data, list): return data
 
180
  except Exception as e:
181
+ logger.error(f"Error posts {provider}: {e}")
182
+ pass
183
+ return []
184
 
185
+ @app.get("/{provider}/meta")
186
  async def get_meta(provider: str, link: str):
 
187
  url = f"{VEGA_API_BASE}/{provider}/meta"
188
  params = {"link": link}
189
  async with httpx.AsyncClient() as client:
190
  try:
191
  resp = await client.get(url, params=params, timeout=15.0)
192
+ if resp.status_code == 200:
193
+ return resp.json()
194
+ except Exception:
195
+ pass
196
+ raise HTTPException(status_code=404, detail="Meta not found")
197
 
198
+ @app.get("/{provider}/episodes")
199
  async def get_episodes(provider: str, url: str):
 
200
  api_url = f"{VEGA_API_BASE}/{provider}/episodes"
201
  params = {"url": url}
202
  async with httpx.AsyncClient() as client:
203
  try:
204
  resp = await client.get(api_url, params=params, timeout=15.0)
205
+ if resp.status_code == 200 and isinstance(resp.json(), list):
206
+ return resp.json()
207
+ except:
208
+ pass
209
+ return []
 
210
 
211
+ @app.get("/{provider}/stream")
212
  async def get_stream(provider: str, link: str, type: str = "movie"):
 
213
  api_url = f"{VEGA_API_BASE}/{provider}/stream"
214
  params = {"link": link, "type": type}
215
  async with httpx.AsyncClient() as client:
216
  try:
217
+ # Longer timeout for extraction
218
+ resp = await client.get(api_url, params=params, timeout=45.0)
219
+ if resp.status_code == 200 and isinstance(resp.json(), list):
220
+ return resp.json()
221
+ except:
222
+ pass
223
+ return []