From 00000d9db06b9fc23409fb130c1aad39adae0aa6 Mon Sep 17 00:00:00 2001 From: doms9 <96013514+doms9@users.noreply.github.com> Date: Sat, 20 Sep 2025 23:26:18 -0400 Subject: [PATCH] e --- EPG/fetch.py | 75 +++- M3U8/fetch.py | 12 +- M3U8/scrapers/ace.py | 8 +- M3U8/scrapers/fstv.py | 4 +- M3U8/scrapers/livetvsx.py | 16 +- M3U8/scrapers/ppv.py | 70 ++- M3U8/scrapers/streambtw.py | 4 +- M3U8/scrapers/streameast.py | 4 +- M3U8/scrapers/tvpass.py | 8 +- M3U8/scrapers/utils/config.py | 45 +- M3U8/scrapers/utils/leagues.json | 725 +++++++++++++++++++++++++++---- 11 files changed, 809 insertions(+), 162 deletions(-) diff --git a/EPG/fetch.py b/EPG/fetch.py index ea47e9f..ac9e8a3 100644 --- a/EPG/fetch.py +++ b/EPG/fetch.py @@ -39,21 +39,21 @@ dummies = { "Golf.Dummy.us": live_img, "Live.Event.us": live_img, "MLB.Baseball.Dummy.us": None, - "MLS.Soccer.Dummy.us": None, "NBA.Basketball.Dummy.us": None, "NFL.Dummy.us": None, "NHL.Hockey.Dummy.us": None, "PPV.EVENTS.Dummy.us": live_img, - "Premier.League.Dummy.us": None, "Racing.Dummy.us": None, "Soccer.Dummy.us": live_img, - "Sports.Dummy.us": live_img, "Tennis.Dummy.us": None, - "UEFA.Champions.League.Dummy.us": None, - "UFC.Fight.Pass.Dummy.us": live_img, "WNBA.dummy.us": None, } +replace_ids = { + "NCAA Sports": {"old": "Sports.Dummy.us", "new": "NCAA.Sports.Dummy.us"}, + "UFC": {"old": "UFC.247.Dummy.us", "new": "UFC.Dummy.us"}, +} + async def fetch_xml(url: str) -> ET.Element: try: @@ -71,10 +71,60 @@ async def fetch_xml(url: str) -> ET.Element: raise SystemExit(f'Failed to decompress and parse XML from "{url}"\n{e}') from e +def hijack_id( + old: str, + new: str, + text: str, + root: ET.Element, +) -> None: + + og_channel = root.find(f"./channel[@id='{old}']") + + if og_channel is not None: + new_channel = ET.Element(og_channel.tag, {**og_channel.attrib, "id": new}) + + display_name = og_channel.find("display-name") + + if display_name is not None: + new_channel.append(ET.Element("display-name", display_name.attrib)) + new_channel[-1].text = text + + for child in og_channel: + if child.tag == "display-name": + continue + + new_child = ET.Element(child.tag, child.attrib) + new_child.text = child.text + + root.remove(og_channel) + + root.append(new_channel) + + for program in root.findall(f"./programme[@channel='{old}']"): + new_program = ET.Element(program.tag, {**program.attrib, "channel": new}) + + for child in program: + new_child = ET.Element(child.tag, child.attrib) + new_child.text = child.text + new_program.append(new_child) + + for tag_name in ["title", "desc", "sub-title"]: + tag = new_program.find(tag_name) + + if tag is not None: + tag.text = text + + root.remove(program) + + root.append(new_program) + + async def main() -> None: tvg_ids: dict[str, str] = json.loads(tvg_ids_file.read_text(encoding="utf-8")) - tvg_ids |= dummies + additions = dummies | {v["old"]: live_img for _, v in replace_ids.items()} + + tvg_ids |= additions root = ET.Element("tv") @@ -100,15 +150,20 @@ async def main() -> None: tvg_id = program.get("channel") if tvg_id in tvg_ids: - if (title_text := program.find("title").text) in [ - "NHL Hockey", - "Live: NFL Football", - ] and (subtitle := program.find("sub-title")) is not None: + title_text = program.find("title").text + subtitle = program.find("sub-title") + if ( + title_text in ["NHL Hockey", "Live: NFL Football"] + and subtitle is not None + ): program.find("title").text = f"{title_text} {subtitle.text}" root.append(program) + for k, v in replace_ids.items(): + hijack_id(**v, text=k, root=root) + tree = ET.ElementTree(root) tree.write(epg_file, encoding="utf-8", xml_declaration=True) diff --git a/M3U8/fetch.py b/M3U8/fetch.py index 4f922f1..5115ff5 100644 --- a/M3U8/fetch.py +++ b/M3U8/fetch.py @@ -26,12 +26,12 @@ async def main() -> None: base_m3u8, tvg_chno = vanilla_fetch() tasks = [ - asyncio.create_task(fstv.main(CLIENT)), - asyncio.create_task(livetvsx.main(CLIENT)), - asyncio.create_task(ppv.main(CLIENT)), - asyncio.create_task(streambtw.main(CLIENT)), - asyncio.create_task(streameast.main(CLIENT)), - asyncio.create_task(tvpass.main(CLIENT)), + asyncio.create_task(fstv.scrape(CLIENT)), + asyncio.create_task(livetvsx.scrape(CLIENT)), + asyncio.create_task(ppv.scrape(CLIENT)), + asyncio.create_task(streambtw.scrape(CLIENT)), + asyncio.create_task(streameast.scrape(CLIENT)), + asyncio.create_task(tvpass.scrape(CLIENT)), ] await asyncio.gather(*tasks) diff --git a/M3U8/scrapers/ace.py b/M3U8/scrapers/ace.py index 75f717b..bfcb6f2 100644 --- a/M3U8/scrapers/ace.py +++ b/M3U8/scrapers/ace.py @@ -66,11 +66,11 @@ async def get_m3u8_links(client: httpx.AsyncClient, url: str) -> list[str]: html = re.sub(r"", "", r.text, flags=re.DOTALL) - tree = HTMLParser(html) + soup = HTMLParser(html) m3u8_links = [] - for btn in tree.css("button[onclick]"): + for btn in soup.css("button[onclick]"): onclick = btn.attributes.get("onclick", "") if match := re.search(r"src\s*=\s*['\"](.*?)['\"]", onclick): @@ -79,7 +79,7 @@ async def get_m3u8_links(client: httpx.AsyncClient, url: str) -> list[str]: if ".m3u8" in link: m3u8_links.append(link) - if iframe := tree.css_first("iframe#iframe"): + if iframe := soup.css_first("iframe#iframe"): src = iframe.attributes.get("src", "") if ".m3u8" in src and src not in m3u8_links: @@ -91,7 +91,7 @@ async def get_m3u8_links(client: httpx.AsyncClient, url: str) -> list[str]: return m3u8_links -async def main(client: httpx.AsyncClient) -> None: +async def scrape(client: httpx.AsyncClient) -> None: if not (base_url := await get_base(client, MIRRORS)): log.warning("No working ace mirrors") return diff --git a/M3U8/scrapers/fstv.py b/M3U8/scrapers/fstv.py index cea8baf..2648aea 100644 --- a/M3U8/scrapers/fstv.py +++ b/M3U8/scrapers/fstv.py @@ -118,8 +118,8 @@ async def process_event( return match_name, unquote(src).split("link=")[-1] -async def main(client: httpx.AsyncClient) -> None: - cached_urls = load_cache(CACHE_FILE, exp=10800) +async def scrape(client: httpx.AsyncClient) -> None: + cached_urls = load_cache(CACHE_FILE, exp=10_800) cached_hrefs = {entry["href"] for entry in cached_urls.values()} cached_count = len(cached_urls) urls.update(cached_urls) diff --git a/M3U8/scrapers/livetvsx.py b/M3U8/scrapers/livetvsx.py index 60b03da..221a7fa 100644 --- a/M3U8/scrapers/livetvsx.py +++ b/M3U8/scrapers/livetvsx.py @@ -132,6 +132,7 @@ async def process_event(url: str, url_num: int) -> str | None: return else: log.warning(f"URL {url_num}) Browser Links tab not found") + return link_img = await page.query_selector( "tr:nth-child(2) > td:nth-child(1) td:nth-child(6) img" @@ -165,7 +166,7 @@ async def process_event(url: str, url_num: int) -> str | None: wait_task = asyncio.create_task(got_one.wait()) try: - await asyncio.wait_for(wait_task, timeout=1.5e1) + await asyncio.wait_for(wait_task, timeout=15) except asyncio.TimeoutError: log.warning(f"URL {url_num}) Timed out waiting for M3U8.") return @@ -272,8 +273,8 @@ async def get_events( return events -async def main(client: httpx.AsyncClient) -> None: - cached_urls = load_cache(CACHE_FILE, exp=10800) +async def scrape(client: httpx.AsyncClient) -> None: + cached_urls = load_cache(CACHE_FILE, exp=10_800) cached_count = len(cached_urls) urls.update(cached_urls) @@ -307,10 +308,15 @@ async def main(client: httpx.AsyncClient) -> None: key = f"[{sport}: {event}] {title} (LTVSX)" + tvg_id, logo = league_info(sport) + + if not tvg_id: + tvg_id, logo = league_info(event) + entry = { "url": url, - "logo": league_info(sport)["logo"], - "id": league_info(sport)["id"], + "logo": logo, + "id": tvg_id or "Live.Event.us", "base": "https://livetv.sx/enx/", "timestamp": now.timestamp(), } diff --git a/M3U8/scrapers/ppv.py b/M3U8/scrapers/ppv.py index 9db0b09..66da8a0 100644 --- a/M3U8/scrapers/ppv.py +++ b/M3U8/scrapers/ppv.py @@ -1,8 +1,4 @@ -#!/usr/bin/env python3 - import asyncio -import json -import re from datetime import datetime, timedelta from functools import partial from pathlib import Path @@ -40,6 +36,50 @@ MIRRORS = [ "https://freeppv.fun", ] +NFL_TEAMS = { + "Arizona Cardinals", + "Atlanta Falcons", + "Baltimore Ravens", + "Buffalo Bills", + "Carolina Panthers", + "Chicago Bears", + "Cincinnati Bengals", + "Cleveland Browns", + "Dallas Cowboys", + "Denver Broncos", + "Detroit Lions", + "Green Bay Packers", + "Houston Texans", + "Indianapolis Colts", + "Jacksonville Jaguars", + "Kansas City Chiefs", + "Las Vegas Raiders", + "Los Angeles Chargers", + "Los Angeles Rams", + "Miami Dolphins", + "Minnesota Vikings", + "New England Patriots", + "New Orleans Saints", + "New York Giants", + "New York Jets", + "Philadelphia Eagles", + "Pittsburgh Steelers", + "San Francisco 49ers", + "Seattle Seahawks", + "Tampa Bay Buccaneers", + "Tennessee Titans", + "Washington Redskins", +} + + +def is_nfl(event: str) -> bool: + try: + t1, t2 = event.split(" vs. ") + + return t1 in NFL_TEAMS or t2 in NFL_TEAMS + except ValueError: + return False + async def refresh_api_cache( client: httpx.AsyncClient, url: str @@ -110,14 +150,12 @@ async def process_event(url: str, url_num: int) -> str | None: async def get_events( client: httpx.AsyncClient, - api_url: str, + base_url: str, cached_keys: set[str], ) -> list[dict[str, str]]: events: list[dict[str, str]] = [] - base_url = re.match(r"(https?://.+?)/", api_url)[1] - if not ( api_data := load_cache( API_FILE, @@ -126,8 +164,9 @@ async def get_events( per_entry=False, ) ): - api_data = await refresh_api_cache(client, api_url) - API_FILE.write_text(json.dumps(api_data, indent=2), encoding="utf-8") + api_data = await refresh_api_cache(client, urljoin(base_url, "api/streams")) + + write_cache(API_FILE, api_data) for stream_group in api_data["streams"]: sport = stream_group["category"] @@ -168,8 +207,8 @@ async def get_events( return events -async def main(client: httpx.AsyncClient) -> None: - cached_urls = load_cache(CACHE_FILE, exp=10800) +async def scrape(client: httpx.AsyncClient) -> None: + cached_urls = load_cache(CACHE_FILE, exp=10_800) cached_count = len(cached_urls) urls.update(cached_urls) @@ -184,7 +223,7 @@ async def main(client: httpx.AsyncClient) -> None: events = await get_events( client, - urljoin(base_url, "api/streams"), + base_url, set(cached_urls.keys()), ) @@ -200,6 +239,11 @@ async def main(client: httpx.AsyncClient) -> None: if url: sport, event = ev["sport"], ev["event"] + if sport == "American Football": + tvg_id = "NFL.Dummy.us" if is_nfl(event) else "NCAA.Sports.Dummy.us" + else: + tvg_id = league_info(sport)[0] + key = f"[{sport}] {event} (PPV)" entry = { @@ -207,7 +251,7 @@ async def main(client: httpx.AsyncClient) -> None: "logo": ev["logo"], "base": base_url, "timestamp": now.timestamp(), - "id": league_info(sport)["id"], + "id": tvg_id or "Live.Event.us", } urls[key] = cached_urls[key] = entry diff --git a/M3U8/scrapers/streambtw.py b/M3U8/scrapers/streambtw.py index 1abdbff..6162091 100644 --- a/M3U8/scrapers/streambtw.py +++ b/M3U8/scrapers/streambtw.py @@ -84,8 +84,8 @@ async def get_events(client: httpx.AsyncClient) -> list[dict[str, str]]: return events -async def main(client: httpx.AsyncClient) -> None: - if cached := load_cache(CACHE_FILE, exp=86400, nearest_hr=True): +async def scrape(client: httpx.AsyncClient) -> None: + if cached := load_cache(CACHE_FILE, exp=86_400, nearest_hr=True): urls.update(cached) log.info(f"Collected {len(urls)} event(s) from cache") return diff --git a/M3U8/scrapers/streameast.py b/M3U8/scrapers/streameast.py index 7000185..aac83e6 100644 --- a/M3U8/scrapers/streameast.py +++ b/M3U8/scrapers/streameast.py @@ -152,8 +152,8 @@ async def get_events( return events -async def main(client: httpx.AsyncClient) -> None: - cached_urls = load_cache(CACHE_FILE, exp=10800) +async def scrape(client: httpx.AsyncClient) -> None: + cached_urls = load_cache(CACHE_FILE, exp=10_800) cached_count = len(cached_urls) urls.update(cached_urls) diff --git a/M3U8/scrapers/tvpass.py b/M3U8/scrapers/tvpass.py index 8228f60..843588c 100644 --- a/M3U8/scrapers/tvpass.py +++ b/M3U8/scrapers/tvpass.py @@ -25,8 +25,8 @@ async def fetch_m3u8(client: httpx.AsyncClient) -> list[str]: return r.text.splitlines() -async def main(client: httpx.AsyncClient) -> None: - if cached := load_cache(CACHE_FILE, exp=86400, nearest_hr=True): +async def scrape(client: httpx.AsyncClient) -> None: + if cached := load_cache(CACHE_FILE, exp=86_400, nearest_hr=True): urls.update(cached) log.info(f"Collected {len(urls)} event(s) from cache") return @@ -52,8 +52,10 @@ async def main(client: httpx.AsyncClient) -> None: if url.endswith("/hd"): key = f"[{sport}] {tvg_name} (TVP)" + channel = url.split("/")[-2] + entry = { - "url": f"http://origin.thetvapp.to/hls/{url.split('/')[-2]}/mono.m3u8", + "url": f"http://origin.thetvapp.to/hls/{channel}/mono.m3u8", "logo": league_info(sport)["logo"], "id": league_info(sport)["id"], "base": "https://tvpass.org", diff --git a/M3U8/scrapers/utils/config.py b/M3U8/scrapers/utils/config.py index f2073b3..345076b 100644 --- a/M3U8/scrapers/utils/config.py +++ b/M3U8/scrapers/utils/config.py @@ -22,42 +22,15 @@ LEAGUES: dict[str, dict[str, str]] = json.loads( leagues_file.read_text(encoding="utf-8") ) -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"], - "Racing": ["Motorsports", "Motorsport"], - "Soccer": [ - "Eredivisie", - "FA WSL", - "Football", - "Liga I", - "Liga Profesional Argentina", - "Ligue 2", - "NWSL", - "UEFA Europa League", - "World Cup", - "World Cup Qualifiers", - ], - "UEFA Champions League": ["Champions League", "UCL"], - "WNBA": ["NBA W"], -} -for k, v in LEAGUES.items(): - if not v["logo"]: - LEAGUES[k]["logo"] = live_img +def league_info(name: str) -> tuple[str | None, str]: + league_name_map: dict[str, tuple[str, str]] = { + league_name: (tvg_id, league_data.get("logo")) + for tvg_id, leagues in LEAGUES.items() + for league_entry in leagues + for league_name, league_data in league_entry.items() + } -for base, aliases in alias_map.items(): - for alias in aliases: - LEAGUES[alias] = LEAGUES[base] + tvg_id, logo = league_name_map.get(name, (None, None)) - -def league_info(name: str) -> dict: - return LEAGUES.get(name, LEAGUES["default"]) + return tvg_id, logo or live_img diff --git a/M3U8/scrapers/utils/leagues.json b/M3U8/scrapers/utils/leagues.json index f16f852..6b933f0 100644 --- a/M3U8/scrapers/utils/leagues.json +++ b/M3U8/scrapers/utils/leagues.json @@ -1,81 +1,648 @@ { - "Basketball": { "id": "Basketball.Dummy.us", "logo": null }, - "Bundesliga": { - "id": "Soccer.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2020/09/Bundesliga-Logo-500x313.png" - }, - "F1": { - "id": "Racing.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2021/06/F1-logo-500x281.png" - }, - "Golf.Dummy.us": { "id": "Golf.Dummy.us", "logo": null }, - "La Liga": { - "id": "Soccer.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2019/01/Spanish-La-Liga-Logo-500x281.png" - }, - "Ligue 1": { - "id": "Soccer.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2019/01/Ligue-1-Logo-500x281.png" - }, - "MLB": { - "id": "MLB.Baseball.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2017/04/MLB-Logo-500x281.png" - }, - "MLS": { - "id": "MLS.Soccer.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2017/10/MLS-logo-500x393.png" - }, - "Moto GP": { - "id": "Racing.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2021/03/MotoGP-Logo-500x281.png" - }, - "NBA": { - "id": "NBA.Basketball.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2025/08/Jerry-West-the-NBA-Logo-500x281.png" - }, - "NCAA": { - "id": "Sports.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2021/12/NCAA-Logo-500x281.png" - }, - "NFL": { - "id": "NFL.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2017/05/NFL-logo-500x338.png" - }, - "NHL": { - "id": "NHL.Hockey.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2017/05/NHL-Logo-500x333.png" - }, - "Pay-Per-View": { "id": "PPV.EVENTS.Dummy.us", "logo": null }, - "Premier League": { - "id": "Premier.League.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2017/05/Premier-League-logo-500x210.png" - }, - "Primeira Liga": { - "id": "Soccer.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2022/01/Portuguese-Primeira-Liga-logo-500x281.png" - }, - "Primera A": { - "id": "Soccer.Dummy.us", - "logo": "https://b.fssta.com/uploads/application/soccer/competition-logos/ColombianPrimeraA.png" - }, - "Racing": { "id": "Racing.Dummy.us", "logo": null }, - "Serie A": { - "id": "Soccer.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2019/01/Italian-Serie-A-Logo-500x281.png" - }, - "Soccer": { "id": "Soccer.Dummy.us", "logo": null }, - "Tennis": { "id": "Tennis.Dummy.us", "logo": null }, - "UEFA Champions League": { - "id": "UEFA.Champions.League.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2022/01/UEFA-Champions-League-logo-500x281.png" - }, - "UFC": { - "id": "UFC.Fight.Pass.Dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2017/06/Logo-UFC-500x313.png" - }, - "WNBA": { - "id": "WNBA.dummy.us", - "logo": "https://1000logos.net/wp-content/uploads/2018/09/logo-wnba-500x287.png" - }, - "default": { "id": "Live.Event.us", "logo": null } + "Basketball.Dummy.us": [ + { + "Basketball": { + "logo": "https://1000logos.net/wp-content/uploads/2024/04/Basketball-Emoji-1536x864.png", + "names": [] + } + } + ], + "Golf.Dummy.us": [ + { + "Golf": { + "logo": "https://i.gyazo.com/14a883f22796f631e6f97c34dbeb6ada.png", + "names": [] + } + } + ], + "MLB.Baseball.Dummy.us": [ + { + "MLB": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/leagues/500/mlb.png", + "names": [ + "Baseball", + "Major League Baseball" + ] + } + } + ], + "NBA.Basketball.Dummy.us": [ + { + "NBA": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/leagues/500/nba.png", + "names": [ + "National Basketball Association" + ] + } + } + ], + "NCAA.Sports.Dummy.us": [ + { + "NCAA": { + "logo": "https://1000logos.net/wp-content/uploads/2021/12/NCAA-Logo-500x281.png", + "names": [ + "CBB", + "CFB", + "NCAAB", + "NCAAF" + ] + } + } + ], + "NFL.Dummy.us": [ + { + "NFL": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/leagues/500/nfl.png", + "names": [ + "American Football", + "National Football League", + "USA NFL" + ] + } + } + ], + "NHL.Hockey.Dummy.us": [ + { + "NHL": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/leagues/500/nhl.png", + "names": [ + "Hockey", + "National Hockey League" + ] + } + } + ], + "PPV.EVENTS.Dummy.us": [ + { + "Pay Per View": { + "logo": null, + "names": [ + "PPV", + "Pay-Per-View", + "PayPerView" + ] + } + }, + { + "Wrestling": { + "logo": null, + "names": [ + "AEW", + "WWE" + ] + } + } + ], + "Racing.Dummy.us": [ + { + "F1": { + "logo": "https://1000logos.net/wp-content/uploads/2021/06/F1-logo-500x281.png", + "names": [ + "Formula 1", + "Formula One" + ] + } + }, + { + "Moto GP": { + "logo": "https://1000logos.net/wp-content/uploads/2021/03/MotoGP-Logo-500x281.png", + "names": [ + "MotoGP" + ] + } + }, + { + "Racing": { + "logo": null, + "names": [] + } + } + ], + "Soccer.Dummy.us": [ + { + "2. Bundesliga": { + "logo": "https://i.gyazo.com/6c343e57acf501f4df3502d7ec646897.png", + "names": [] + } + }, + { + "3. Liga": { + "logo": "https://i.gyazo.com/9f4f2e8370377b6214b4103003196de7.png", + "names": [] + } + }, + { + "AFC Champions League": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/2200.png&scale=crop&cquality=40&location=origin&w=500&h=500", + "names": [ + "ACL", + "ACL Elite", + "Asian Champions League" + ] + } + }, + { + "Africa Cup of Nations": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/76.png", + "names": [ + "AFCON" + ] + } + }, + { + "Austria 2 Liga": { + "logo": "https://i.gyazo.com/5d1464502b841fef6e5d78c8b0764b52.png", + "names": [ + "Admiral 2. Liga" + ] + } + }, + { + "Austria Bundesliga": { + "logo": "https://i.gyazo.com/83d851fb1110f1e395690403f9cf01bb.webp", + "names": [ + "Admiral Bundesliga", + "Federal League" + ] + } + }, + { + "Bundesliga": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/10.png", + "names": [ + "Bundeslig", + "German Bundesliga" + ] + } + }, + { + "CAF Champions League": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/2391.png", + "names": [] + } + }, + { + "CONCACAF Champions League": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/2298.png", + "names": [ + "CONCACAF Champions Cup" + ] + } + }, + { + "CONCACAF Gold Cup": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/59.png", + "names": [ + "Copa Oro CONCACAF" + ] + } + }, + { + "Canadian Premier League": { + "logo": "https://i.gyazo.com/f61986e2ccfbf88f7d753b4e7f2c9fdc.png", + "names": [ + "CPL", + "CanPL" + ] + } + }, + { + "Championship": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/24.png", + "names": [ + "English Championship", + "English Football League Championship", + "Sky Bet Championship" + ] + } + }, + { + "Copa América": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/83.png", + "names": [ + "CONMEBOL Copa America", + "CONMEBOL Copa América", + "Copa America", + "South American Football Championship" + ] + } + }, + { + "Copa Libertadores": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/58.png", + "names": [ + "CONMEBOL Libertadores", + "Copa Libertadores de America", + "Copa Libertadores de América", + "Libertadores" + ] + } + }, + { + "Copa Sudamericana": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/1208.png", + "names": [ + "CONMEBOL Sudamericana", + "Copa CONMEBOL Sudamericana" + ] + } + }, + { + "EFL League One": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/25.png", + "names": [ + "English Football League One", + "League One", + "Sky Bet League One" + ] + } + }, + { + "EFL League Two": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/26.png", + "names": [ + "English Football League Two", + "League Two", + "Sky Bet League Two" + ] + } + }, + { + "Ekstraklasa": { + "logo": "https://i.gyazo.com/362e31efdd0dad03b00858f4fb0901b5.png", + "names": [ + "PKO Bank Polski Ekstraklasa", + "Poland Ekstraklasa" + ] + } + }, + { + "Eredivisie": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/11.png", + "names": [ + "VriendenLoterij Eredivisie" + ] + } + }, + { + "FIFA Club World Cup": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/1932.png", + "names": [ + "FIFA CWC" + ] + } + }, + { + "Fifa World Cup": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/4.png", + "names": [ + "FIFA WC", + "WC" + ] + } + }, + { + "Fifa's Women World Cup": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/60.png", + "names": [ + "FIFA Women WC" + ] + } + }, + { + "Football": { + "logo": "https://i.gyazo.com/1c4aa937f5ea01b0f29bb27adb59884c.png", + "names": [] + } + }, + { + "Frauen Bundesliga": { + "logo": "https://i.gyazo.com/d13d4c0330be96801aa4b2d8b83d3a8f.png", + "names": [ + "Google Pixel Frauen-Bundesliga", + "Women's Federal League" + ] + } + }, + { + "J1 League": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/2199.png", + "names": [ + "J.League", + "Japanese J.League", + "Meiji Yasuda J1 League" + ] + } + }, + { + "K League 1": { + "logo": "https://i.gyazo.com/721eba6c954e2015d999ead7a0bd5c69.png", + "names": [] + } + }, + { + "La Liga": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/15.png", + "names": [ + "Campeonato Nacional de Liga de Primera Division", + "Campeonato Nacional de Liga de Primera División", + "LALIGA", + "Laliga", + "Primera Division", + "Primera División", + "Spanish LALIGA", + "Spanish La Liga" + ] + } + }, + { + "La Liga 2": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/107.png", + "names": [ + "Campeonato Nacional de Liga de Segunda Division", + "Campeonato Nacional de Liga de Segunda División", + "LaLiga 2", + "Segunda Division", + "Segunda División", + "Spanish LALIGA 2", + "Spanish La Liga 2" + ] + } + }, + { + "Liga I": { + "logo": "https://i.gyazo.com/3fd4b38d5263ca391e45850eb58d11e6.png", + "names": [ + "Romania Liga 1", + "Romania Liga I", + "Romanian Liga 1", + "Romanian Liga I", + "SuperLiga", + "Superliga" + ] + } + }, + { + "Liga MX": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/22.png", + "names": [ + "Liga BBVA MX", + "Liga Mayor", + "Mexican Liga BBVA MX", + "Primera Division de Mexico", + "Primera División de México" + ] + } + }, + { + "Liga Profesional Argentina": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/1.png", + "names": [ + "Argentine Primera Division", + "Argentine Primera División", + "Liga Profesional de Futbol", + "Liga Profesional de Fútbol", + "Primera Division", + "Primera División", + "Torneo Betano" + ] + } + }, + { + "Ligue 1": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/9.png", + "names": [ + "France Ligue 1", + "French Ligue 1" + ] + } + }, + { + "Ligue 2": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/96.png", + "names": [ + "France Ligue 2", + "French Ligue 2" + ] + } + }, + { + "MLS": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/19.png", + "names": [ + "Major League Soccer" + ] + } + }, + { + "NWSL": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/2323.png", + "names": [ + "NWSL Women", + "National Women's Soccer League" + ] + } + }, + { + "Premier League": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/23.png", + "names": [ + "EPL", + "English Premier League" + ] + } + }, + { + "Primeira Liga": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/14.png", + "names": [ + "Liga Portugal", + "Portuguese Primeira Liga" + ] + } + }, + { + "Primera A": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/1543.png", + "names": [ + "Colombian Primera A" + ] + } + }, + { + "Primera B": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/2295.png", + "names": [ + "Colombian Primera B" + ] + } + }, + { + "Scottish Premiership": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/45.png", + "names": [ + "Premiership", + "SPFL" + ] + } + }, + { + "Serbia Superliga": { + "logo": "https://i.gyazo.com/0992f078dcacfef489477fc7bb1f5220.webp", + "names": [ + "Mozzart SuperLiga", + "Serbian Super League" + ] + } + }, + { + "Serie A": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/12.png", + "names": [ + "Italian Serie A" + ] + } + }, + { + "Serie B": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/99.png", + "names": [ + "Italian Serie B" + ] + } + }, + { + "Soccer": { + "logo": "https://i.gyazo.com/1c4aa937f5ea01b0f29bb27adb59884c.png", + "names": [] + } + }, + { + "Super League Greece": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/98.png", + "names": [ + "A1 Ethniki Katigoria", + "Greece Super League", + "Greek Super League", + "Super League 1" + ] + } + }, + { + "Süper Lig": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/18.png", + "names": [ + "Super Lig", + "SuperLig", + "SüperLig", + "Turkish Super Lig" + ] + } + }, + { + "Turkey 1 Lig": { + "logo": "https://i.gyazo.com/730673f84223a85c9b9ae66123907bba.png", + "names": [ + "TFF 1. Lig", + "Trendyol 1. Lig" + ] + } + }, + { + "UEFA Champions League": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/2.png", + "names": [ + "Champions League", + "UCL" + ] + } + }, + { + "UEFA Conference League": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/20296.png", + "names": [] + } + }, + { + "UEFA Europa League": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/2310.png", + "names": [ + "Europa League" + ] + } + }, + { + "UEFA European Championship": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/74.png", + "names": [ + "Euros", + "UEFA Euros" + ] + } + }, + { + "UEFA Super Cup": { + "logo": "https://i.gyazo.com/3b786181aba130321b85c0e2f9604652.png", + "names": [ + "European Super Cup" + ] + } + }, + { + "UEFA Women's Champions League": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/2408.png", + "names": [ + "UCL Women", + "UEFA Women", + "Women's Champions League" + ] + } + }, + { + "WSL": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/leaguelogos/soccer/500/2314.png", + "names": [ + "Barclay's Women's Super League", + "English Women's Super League", + "FA WSL", + "Women's Super League" + ] + } + }, + { + "World Cup Qualifiers": { + "logo": "https://i.gyazo.com/1c4aa937f5ea01b0f29bb27adb59884c.png", + "names": [] + } + } + ], + "Tennis.Dummy.us": [ + { + "Tennis": { + "logo": "https://i.gyazo.com/b5e83afc3a75dacfb831abe975fd3821.png", + "names": [] + } + } + ], + "UFC.247.Dummy.us": [ + { + "UFC": { + "logo": "https://1000logos.net/wp-content/uploads/2017/06/Logo-UFC-500x313.png", + "names": [ + "UFC Fight Night" + ] + } + } + ], + "WNBA.dummy.us": [ + { + "WNBA": { + "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/leagues/500/wnba.png", + "names": [ + "NBA W", + "Women's National Basketball Association" + ] + } + } + ] }