diff --git a/EPG/fetch.py b/EPG/fetch.py index b7cc103..0d9f711 100644 --- a/EPG/fetch.py +++ b/EPG/fetch.py @@ -11,6 +11,7 @@ tvg_ids_file = Path(__file__).parent / "TVG-IDs.json" epg_file = Path(__file__).parent / "TV.xml" epg_urls = [ "https://epgshare01.online/epgshare01/epg_ripper_CA1.xml.gz", + "https://epgshare01.online/epgshare01/epg_ripper_DUMMY_CHANNELS.xml.gz", "https://epgshare01.online/epgshare01/epg_ripper_ES1.xml.gz", "https://epgshare01.online/epgshare01/epg_ripper_FANDUEL1.xml.gz", "https://epgshare01.online/epgshare01/epg_ripper_MY1.xml.gz", diff --git a/M3U8/scrapers/ace.py b/M3U8/scrapers/ace.py index 0345378..aed24c9 100644 --- a/M3U8/scrapers/ace.py +++ b/M3U8/scrapers/ace.py @@ -5,7 +5,7 @@ from urllib.parse import urljoin import httpx from selectolax.parser import HTMLParser, Node -from .utils import LOGOS, get_base, get_logger +from .utils import get_base, get_logger, league_info log = get_logger(__name__) @@ -109,11 +109,16 @@ async def main(client: httpx.AsyncClient) -> None: for i, link in enumerate(m3u8_urls, start=1): sport, event = item["sport"], item["event"] - urls[f"[{sport}] {event} (S{i})"] = { + key = f"[{sport}] {event} (S{i})" + + entry = { "url": link, - "logo": LOGOS.get(sport, LOGOS["default"]), + "logo": league_info(sport)["logo"], + "tvg-id": league_info(sport)["id"], } + urls[key] = entry + log.info(f"Collected {len(urls)} events") diff --git a/M3U8/scrapers/fstv.py b/M3U8/scrapers/fstv.py index 43ac6a9..5581f4d 100644 --- a/M3U8/scrapers/fstv.py +++ b/M3U8/scrapers/fstv.py @@ -5,9 +5,9 @@ import httpx from selectolax.parser import HTMLParser from .utils import ( - LOGOS, get_base, get_logger, + league_info, load_cache, now, safe_process_event, @@ -76,7 +76,6 @@ async def get_events( { "sport": event_name, "link": urljoin(base_url, href), - "logo": LOGOS.get(event_name, LOGOS["default"]), "href": href, } ) @@ -152,17 +151,18 @@ async def main(client: httpx.AsyncClient) -> None: ) if url: + sport = ev["sport"] + key = ( - f"[{ev['sport']}] {match_name} (FSTV)" - if match_name - else f"[{ev['sport']}] (FSTV)" + f"[{sport}] {match_name} (FSTV)" if match_name else f"[{sport}] (FSTV)" ) entry = { "url": url, - "logo": ev["logo"], + "logo": league_info(sport)["logo"], "base": base_url, "timestamp": now.timestamp(), + "tvg-id": league_info(sport)["id"], "href": ev["href"], } diff --git a/M3U8/scrapers/livetvsx.py b/M3U8/scrapers/livetvsx.py index 1e970de..a96b915 100644 --- a/M3U8/scrapers/livetvsx.py +++ b/M3U8/scrapers/livetvsx.py @@ -10,10 +10,10 @@ import httpx from playwright.async_api import async_playwright from .utils import ( - LOGOS, TZ, capture_req, get_logger, + league_info, load_cache, new_browser, now, @@ -38,8 +38,6 @@ CERT_FILE = Path(__file__).parent / "caches" / "cached-cert.pem" CACHE_FILE = Path(__file__).parent / "caches" / "livetvsx.json" -exist_sprts = set(LOGOS.keys()) - async def write_to_cert( client: httpx.AsyncClient, @@ -260,10 +258,6 @@ async def get_events( elem.clear() continue - if exist_sprts & {sport, event}: - elem.clear() - continue - events.append( { "sport": sport, @@ -298,13 +292,8 @@ async def main(client: httpx.AsyncClient) -> None: log.info(f"Processing {len(events)} new URL(s)") for i, ev in enumerate(events, start=1): - sport = ev["sport"] - event = ev["event"] - title = ev["title"] link = ev["link"] - key = f"[{sport}: {event}] {title} (LTVSX)" - url = await safe_process_event( lambda: process_event(link, url_num=i), url_num=i, @@ -312,9 +301,16 @@ async def main(client: httpx.AsyncClient) -> None: ) if url: + sport = ev["sport"] + event = ev["event"] + title = ev["title"] + + key = f"[{sport}: {event}] {title} (LTVSX)" + entry = { "url": url, - "logo": LOGOS.get(sport, LOGOS["default"]), + "logo": league_info(sport)["logo"], + "tvg-id": league_info(sport)["id"], "base": "https://livetv.sx/enx/", "timestamp": now.timestamp(), } diff --git a/M3U8/scrapers/ppv.py b/M3U8/scrapers/ppv.py index 54ac6e9..87b9769 100644 --- a/M3U8/scrapers/ppv.py +++ b/M3U8/scrapers/ppv.py @@ -12,11 +12,11 @@ import httpx from playwright.async_api import async_playwright from .utils import ( - LOGOS, TZ, capture_req, get_base, get_logger, + league_info, load_cache, new_browser, now, @@ -140,7 +140,7 @@ async def get_events( event["name"], event["starts_at"], event["ends_at"], - event.get("poster", LOGOS["default"]), + event["poster"], event["uri_name"], ) @@ -198,13 +198,16 @@ async def main(client: httpx.AsyncClient) -> None: ) if url: - key = f"[{ev['sport']}] {ev['event']} (PPV)" + sport, event = ev["sport"], ev["event"] + + key = f"[{sport}] {event} (PPV)" entry = { "url": url, "logo": ev["logo"], "base": base_url, "timestamp": now.timestamp(), + "tvg-id": league_info(sport)["id"], } urls[key] = cached_urls[key] = entry diff --git a/M3U8/scrapers/streambtw.py b/M3U8/scrapers/streambtw.py index 33367c5..7d21ae9 100644 --- a/M3U8/scrapers/streambtw.py +++ b/M3U8/scrapers/streambtw.py @@ -5,7 +5,14 @@ from urllib.parse import urljoin import httpx from selectolax.parser import HTMLParser -from .utils import get_logger, load_cache, now, safe_process_event, write_cache +from .utils import ( + get_logger, + league_info, + load_cache, + now, + safe_process_event, + write_cache, +) log = get_logger(__name__) @@ -97,13 +104,16 @@ async def main(client: httpx.AsyncClient) -> None: ) if url: - key = f"[{ev['sport']}] {ev['event']} (SBTW)" + sport, event = ev["sport"], ev["event"] + + key = f"[{sport}] {event} (SBTW)" entry = { "url": url, - "logo": ev["logo"], + "logo": ev["logo"] or league_info(sport)["logo"], "base": BASE_URL, "timestamp": now.timestamp(), + "tvg-id": league_info(sport)["id"], } urls[key] = entry diff --git a/M3U8/scrapers/streameast.py b/M3U8/scrapers/streameast.py index 14dfafc..1784ab8 100644 --- a/M3U8/scrapers/streameast.py +++ b/M3U8/scrapers/streameast.py @@ -9,11 +9,11 @@ from playwright.async_api import async_playwright from selectolax.parser import HTMLParser from .utils import ( - LOGOS, TZ, capture_req, get_base, get_logger, + league_info, load_cache, new_browser, now, @@ -146,7 +146,6 @@ async def get_events( "sport": sport, "event": name, "link": href, - "logo": LOGOS.get(sport, LOGOS["default"]), } ) @@ -183,13 +182,16 @@ async def main(client: httpx.AsyncClient) -> None: ) if url: - key = f"[{ev['sport']}] {ev['event']} (SEAST)" + sport, event = ev["sport"], ev["event"] + + key = f"[{sport}] {event} (SEAST)" entry = { "url": url, - "logo": ev["logo"], + "logo": league_info(sport)["logo"], "base": base_url, "timestamp": now.timestamp(), + "tvg-id": league_info(sport)["id"], } urls[key] = cached_urls[key] = entry diff --git a/M3U8/scrapers/tvpass.py b/M3U8/scrapers/tvpass.py index f9bc2e1..5dcba6f 100644 --- a/M3U8/scrapers/tvpass.py +++ b/M3U8/scrapers/tvpass.py @@ -3,7 +3,7 @@ from pathlib import Path import httpx -from .utils import LOGOS, get_logger, load_cache, now, write_cache +from .utils import get_logger, league_info, load_cache, now, write_cache log = get_logger(__name__) @@ -54,7 +54,8 @@ async def main(client: httpx.AsyncClient) -> None: entry = { "url": f"http://origin.thetvapp.to/hls/{url.split('/')[-2]}/mono.m3u8", - "logo": LOGOS.get(sport, LOGOS["default"]), + "logo": league_info(sport)["logo"], + "tvg-id": league_info(sport)["id"], "base": "https://tvpass.org", "timestamp": now.timestamp(), } diff --git a/M3U8/scrapers/utils/__init__.py b/M3U8/scrapers/utils/__init__.py index 079753d..ee8da44 100644 --- a/M3U8/scrapers/utils/__init__.py +++ b/M3U8/scrapers/utils/__init__.py @@ -1,16 +1,16 @@ from .cache import load_cache, write_cache -from .config import LOGOS, TZ, UA, now +from .config import TZ, UA, league_info, now from .logger import get_logger from .network import CLIENT, capture_req, get_base, new_browser, safe_process_event __all__ = [ "CLIENT", - "LOGOS", "TZ", "UA", "capture_req", "get_base", "get_logger", + "league_info", "load_cache", "new_browser", "now", diff --git a/M3U8/scrapers/utils/cache.py b/M3U8/scrapers/utils/cache.py index 3ee4310..82f811c 100644 --- a/M3U8/scrapers/utils/cache.py +++ b/M3U8/scrapers/utils/cache.py @@ -45,4 +45,6 @@ def load_cache( def write_cache(file: Path, data: dict) -> None: + file.parent.mkdir(parents=True, exist_ok=True) + file.write_text(json.dumps(data, indent=2), encoding="utf-8") diff --git a/M3U8/scrapers/utils/config.py b/M3U8/scrapers/utils/config.py index 5b0a223..175bd8a 100644 --- a/M3U8/scrapers/utils/config.py +++ b/M3U8/scrapers/utils/config.py @@ -12,33 +12,118 @@ UA = ( "Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0" ) -LOGOS = { - "Bundesliga": "https://1000logos.net/wp-content/uploads/2020/09/Bundesliga-Logo-500x313.png", - "La Liga": "https://1000logos.net/wp-content/uploads/2019/01/Spanish-La-Liga-Logo-500x281.png", - "Ligue 1": "https://1000logos.net/wp-content/uploads/2019/01/Ligue-1-Logo-500x281.png", - "MLB": "https://1000logos.net/wp-content/uploads/2017/04/MLB-Logo-500x281.png", - "MLS": "https://1000logos.net/wp-content/uploads/2017/10/MLS-logo-500x393.png", - "NBA": "https://1000logos.net/wp-content/uploads/2025/08/Jerry-West-the-NBA-Logo-500x281.png", - "NCAA": "https://1000logos.net/wp-content/uploads/2021/12/NCAA-Logo-500x281.png", - "NFL": "https://1000logos.net/wp-content/uploads/2017/05/NFL-logo-500x338.png", - "NHL": "https://1000logos.net/wp-content/uploads/2017/05/NHL-Logo-500x333.png", - "Premier League": "https://1000logos.net/wp-content/uploads/2017/05/Premier-League-logo-500x210.png", - "Primera A": "https://b.fssta.com/uploads/application/soccer/competition-logos/ColombianPrimeraA.png", - "Primeira Liga": "https://1000logos.net/wp-content/uploads/2022/01/Portuguese-Primeira-Liga-logo-500x281.png", - "Serie A": " https://1000logos.net/wp-content/uploads/2019/01/Italian-Serie-A-Logo-500x281.png", - "UEFA Champions League": "https://1000logos.net/wp-content/uploads/2022/01/UEFA-Champions-League-logo-500x281.png", - "WNBA": "https://1000logos.net/wp-content/uploads/2018/09/logo-wnba-500x287.png", - "default": "https://i.gyazo.com/978f2eb4a199ca5b56b447aded0cb9e3.png", +LEAGUES: dict[str, dict[str, str]] = { + "Basketball": { + "logo": "https://i.gyazo.com/978f2eb4a199ca5b56b447aded0cb9e3.png", + "id": "Basketball.Dummy.us", + }, + "Bundesliga": { + "logo": "https://1000logos.net/wp-content/uploads/2020/09/Bundesliga-Logo-500x313.png", + "id": "Soccer.Dummy.us", + }, + "F1": { + "logo": "https://1000logos.net/wp-content/uploads/2021/06/F1-logo-500x281.png", + "id": "Racing.Dummy.us", + }, + "La Liga": { + "logo": "https://1000logos.net/wp-content/uploads/2019/01/Spanish-La-Liga-Logo-500x281.png", + "id": "Soccer.Dummy.us", + }, + "Ligue 1": { + "logo": "https://1000logos.net/wp-content/uploads/2019/01/Ligue-1-Logo-500x281.png", + "id": "Soccer.Dummy.us", + }, + "MLB": { + "logo": "https://1000logos.net/wp-content/uploads/2017/04/MLB-Logo-500x281.png", + "id": "MLB.Baseball.Dummy.us", + }, + "MLS": { + "logo": "https://1000logos.net/wp-content/uploads/2017/10/MLS-logo-500x393.png", + "id": "MLS.Soccer.Dummy.us", + }, + "Moto GP": { + "logo": "https://1000logos.net/wp-content/uploads/2021/03/MotoGP-Logo-500x281.png", + "id": "Racing.Dummy.us", + }, + "NBA": { + "logo": "https://1000logos.net/wp-content/uploads/2025/08/Jerry-West-the-NBA-Logo-500x281.png", + "id": "NBA.Basketball.Dummy.us", + }, + "NCAA": { + "logo": "https://1000logos.net/wp-content/uploads/2021/12/NCAA-Logo-500x281.png", + "id": "Sports.Dummy.us", + }, + "NFL": { + "logo": "https://1000logos.net/wp-content/uploads/2017/05/NFL-logo-500x338.png", + "id": "NFL.Dummy.us", + }, + "NHL": { + "logo": "https://1000logos.net/wp-content/uploads/2017/05/NHL-Logo-500x333.png", + "id": "NHL.Hockey.Dummy.us", + }, + "Pay-Per-View": { + "logo": "https://i.gyazo.com/978f2eb4a199ca5b56b447aded0cb9e3.png", + "id": "PPV.EVENTS.Dummy.us", + }, + "Premier League": { + "logo": "https://1000logos.net/wp-content/uploads/2017/05/Premier-League-logo-500x210.png", + "id": "Premier.League.Dummy.us", + }, + "Primera A": { + "logo": "https://b.fssta.com/uploads/application/soccer/competition-logos/ColombianPrimeraA.png", + "id": "Soccer.Dummy.us", + }, + "Primeira Liga": { + "logo": "https://1000logos.net/wp-content/uploads/2022/01/Portuguese-Primeira-Liga-logo-500x281.png", + "id": "Soccer.Dummy.us", + }, + "Serie A": { + "logo": "https://1000logos.net/wp-content/uploads/2019/01/Italian-Serie-A-Logo-500x281.png", + "id": "Soccer.Dummy.us", + }, + "Soccer": { + "logo": "https://i.gyazo.com/978f2eb4a199ca5b56b447aded0cb9e3.png", + "id": "Soccer.Dummy.us", + }, + "UEFA Champions League": { + "logo": "https://1000logos.net/wp-content/uploads/2022/01/UEFA-Champions-League-logo-500x281.png", + "id": "UEFA.Champions.League.Dummy.us", + }, + "UFC": { + "logo": "https://1000logos.net/wp-content/uploads/2017/06/Logo-UFC-500x313.png", + "id": "UFC.Fight.Pass.Dummy.us", + }, + "WNBA": { + "logo": "https://1000logos.net/wp-content/uploads/2018/09/logo-wnba-500x287.png", + "id": "WNBA.dummy.us", + }, + "default": { + "logo": "https://i.gyazo.com/978f2eb4a199ca5b56b447aded0cb9e3.png", + "id": "Live.Event.us", + }, } + alias_map = { + "Bundesliga": ["German Bundesliga", "Bundeslig"], + "F1": ["Formula 1", "Formula One"], + "La Liga": ["Spanish La Liga", "Laliga"], + "MLB": ["Major League Baseball", "Baseball"], "MLS": ["Major League Soccer"], + "Moto GP": ["MotoGP"], "NCAA": ["CBB", "CFB", "NCAAB", "NCAAF"], + "NFL": ["American Football", "USA NFL"], "Premier League": ["EPL"], + "Primeira Liga": ["Liga Portugal"], + "Soccer": ["Football", "World Cup", "World Cup Qualifiers", "UEFA Europa League"], "UEFA Champions League": ["Champions League", "UCL"], "WNBA": ["NBA W"], } for base, aliases in alias_map.items(): for alias in aliases: - LOGOS[alias] = LOGOS[base] + LEAGUES[alias] = LEAGUES[base] + + +def league_info(name: str) -> dict: + return LEAGUES.get(name, LEAGUES["default"]) diff --git a/M3U8/scrapers/utils/network.py b/M3U8/scrapers/utils/network.py index 36ce186..a45a233 100644 --- a/M3U8/scrapers/utils/network.py +++ b/M3U8/scrapers/utils/network.py @@ -39,7 +39,7 @@ async def get_base(client: httpx.AsyncClient, mirrors: list[str]) -> str | None: async def safe_process_event( fn: Callable, url_num: int, - timeout: int | float = 20, + timeout: int | float = 15, log: logging.Logger | None = None, ) -> Any | None: