This commit is contained in:
doms9 2025-09-20 23:26:18 -04:00
parent 5a61e2a8d5
commit 00000d9db0
11 changed files with 809 additions and 162 deletions

View file

@ -39,21 +39,21 @@ dummies = {
"Golf.Dummy.us": live_img, "Golf.Dummy.us": live_img,
"Live.Event.us": live_img, "Live.Event.us": live_img,
"MLB.Baseball.Dummy.us": None, "MLB.Baseball.Dummy.us": None,
"MLS.Soccer.Dummy.us": None,
"NBA.Basketball.Dummy.us": None, "NBA.Basketball.Dummy.us": None,
"NFL.Dummy.us": None, "NFL.Dummy.us": None,
"NHL.Hockey.Dummy.us": None, "NHL.Hockey.Dummy.us": None,
"PPV.EVENTS.Dummy.us": live_img, "PPV.EVENTS.Dummy.us": live_img,
"Premier.League.Dummy.us": None,
"Racing.Dummy.us": None, "Racing.Dummy.us": None,
"Soccer.Dummy.us": live_img, "Soccer.Dummy.us": live_img,
"Sports.Dummy.us": live_img,
"Tennis.Dummy.us": None, "Tennis.Dummy.us": None,
"UEFA.Champions.League.Dummy.us": None,
"UFC.Fight.Pass.Dummy.us": live_img,
"WNBA.dummy.us": None, "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: async def fetch_xml(url: str) -> ET.Element:
try: 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 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: async def main() -> None:
tvg_ids: dict[str, str] = json.loads(tvg_ids_file.read_text(encoding="utf-8")) 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") root = ET.Element("tv")
@ -100,15 +150,20 @@ async def main() -> None:
tvg_id = program.get("channel") tvg_id = program.get("channel")
if tvg_id in tvg_ids: if tvg_id in tvg_ids:
if (title_text := program.find("title").text) in [ title_text = program.find("title").text
"NHL Hockey", subtitle = program.find("sub-title")
"Live: NFL Football",
] and (subtitle := program.find("sub-title")) is not None:
if (
title_text in ["NHL Hockey", "Live: NFL Football"]
and subtitle is not None
):
program.find("title").text = f"{title_text} {subtitle.text}" program.find("title").text = f"{title_text} {subtitle.text}"
root.append(program) root.append(program)
for k, v in replace_ids.items():
hijack_id(**v, text=k, root=root)
tree = ET.ElementTree(root) tree = ET.ElementTree(root)
tree.write(epg_file, encoding="utf-8", xml_declaration=True) tree.write(epg_file, encoding="utf-8", xml_declaration=True)

View file

@ -26,12 +26,12 @@ async def main() -> None:
base_m3u8, tvg_chno = vanilla_fetch() base_m3u8, tvg_chno = vanilla_fetch()
tasks = [ tasks = [
asyncio.create_task(fstv.main(CLIENT)), asyncio.create_task(fstv.scrape(CLIENT)),
asyncio.create_task(livetvsx.main(CLIENT)), asyncio.create_task(livetvsx.scrape(CLIENT)),
asyncio.create_task(ppv.main(CLIENT)), asyncio.create_task(ppv.scrape(CLIENT)),
asyncio.create_task(streambtw.main(CLIENT)), asyncio.create_task(streambtw.scrape(CLIENT)),
asyncio.create_task(streameast.main(CLIENT)), asyncio.create_task(streameast.scrape(CLIENT)),
asyncio.create_task(tvpass.main(CLIENT)), asyncio.create_task(tvpass.scrape(CLIENT)),
] ]
await asyncio.gather(*tasks) await asyncio.gather(*tasks)

View file

@ -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) html = re.sub(r"<!--.*?-->", "", r.text, flags=re.DOTALL)
tree = HTMLParser(html) soup = HTMLParser(html)
m3u8_links = [] m3u8_links = []
for btn in tree.css("button[onclick]"): for btn in soup.css("button[onclick]"):
onclick = btn.attributes.get("onclick", "") onclick = btn.attributes.get("onclick", "")
if match := re.search(r"src\s*=\s*['\"](.*?)['\"]", 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: if ".m3u8" in link:
m3u8_links.append(link) m3u8_links.append(link)
if iframe := tree.css_first("iframe#iframe"): if iframe := soup.css_first("iframe#iframe"):
src = iframe.attributes.get("src", "") src = iframe.attributes.get("src", "")
if ".m3u8" in src and src not in m3u8_links: 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 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)): if not (base_url := await get_base(client, MIRRORS)):
log.warning("No working ace mirrors") log.warning("No working ace mirrors")
return return

View file

@ -118,8 +118,8 @@ async def process_event(
return match_name, unquote(src).split("link=")[-1] return match_name, unquote(src).split("link=")[-1]
async def main(client: httpx.AsyncClient) -> None: async def scrape(client: httpx.AsyncClient) -> None:
cached_urls = load_cache(CACHE_FILE, exp=10800) cached_urls = load_cache(CACHE_FILE, exp=10_800)
cached_hrefs = {entry["href"] for entry in cached_urls.values()} cached_hrefs = {entry["href"] for entry in cached_urls.values()}
cached_count = len(cached_urls) cached_count = len(cached_urls)
urls.update(cached_urls) urls.update(cached_urls)

View file

@ -132,6 +132,7 @@ async def process_event(url: str, url_num: int) -> str | None:
return return
else: else:
log.warning(f"URL {url_num}) Browser Links tab not found") log.warning(f"URL {url_num}) Browser Links tab not found")
return
link_img = await page.query_selector( link_img = await page.query_selector(
"tr:nth-child(2) > td:nth-child(1) td:nth-child(6) img" "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()) wait_task = asyncio.create_task(got_one.wait())
try: try:
await asyncio.wait_for(wait_task, timeout=1.5e1) await asyncio.wait_for(wait_task, timeout=15)
except asyncio.TimeoutError: except asyncio.TimeoutError:
log.warning(f"URL {url_num}) Timed out waiting for M3U8.") log.warning(f"URL {url_num}) Timed out waiting for M3U8.")
return return
@ -272,8 +273,8 @@ async def get_events(
return events return events
async def main(client: httpx.AsyncClient) -> None: async def scrape(client: httpx.AsyncClient) -> None:
cached_urls = load_cache(CACHE_FILE, exp=10800) cached_urls = load_cache(CACHE_FILE, exp=10_800)
cached_count = len(cached_urls) cached_count = len(cached_urls)
urls.update(cached_urls) urls.update(cached_urls)
@ -307,10 +308,15 @@ async def main(client: httpx.AsyncClient) -> None:
key = f"[{sport}: {event}] {title} (LTVSX)" key = f"[{sport}: {event}] {title} (LTVSX)"
tvg_id, logo = league_info(sport)
if not tvg_id:
tvg_id, logo = league_info(event)
entry = { entry = {
"url": url, "url": url,
"logo": league_info(sport)["logo"], "logo": logo,
"id": league_info(sport)["id"], "id": tvg_id or "Live.Event.us",
"base": "https://livetv.sx/enx/", "base": "https://livetv.sx/enx/",
"timestamp": now.timestamp(), "timestamp": now.timestamp(),
} }

View file

@ -1,8 +1,4 @@
#!/usr/bin/env python3
import asyncio import asyncio
import json
import re
from datetime import datetime, timedelta from datetime import datetime, timedelta
from functools import partial from functools import partial
from pathlib import Path from pathlib import Path
@ -40,6 +36,50 @@ MIRRORS = [
"https://freeppv.fun", "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( async def refresh_api_cache(
client: httpx.AsyncClient, url: str client: httpx.AsyncClient, url: str
@ -110,14 +150,12 @@ async def process_event(url: str, url_num: int) -> str | None:
async def get_events( async def get_events(
client: httpx.AsyncClient, client: httpx.AsyncClient,
api_url: str, base_url: str,
cached_keys: set[str], cached_keys: set[str],
) -> list[dict[str, str]]: ) -> list[dict[str, str]]:
events: list[dict[str, str]] = [] events: list[dict[str, str]] = []
base_url = re.match(r"(https?://.+?)/", api_url)[1]
if not ( if not (
api_data := load_cache( api_data := load_cache(
API_FILE, API_FILE,
@ -126,8 +164,9 @@ async def get_events(
per_entry=False, per_entry=False,
) )
): ):
api_data = await refresh_api_cache(client, api_url) api_data = await refresh_api_cache(client, urljoin(base_url, "api/streams"))
API_FILE.write_text(json.dumps(api_data, indent=2), encoding="utf-8")
write_cache(API_FILE, api_data)
for stream_group in api_data["streams"]: for stream_group in api_data["streams"]:
sport = stream_group["category"] sport = stream_group["category"]
@ -168,8 +207,8 @@ async def get_events(
return events return events
async def main(client: httpx.AsyncClient) -> None: async def scrape(client: httpx.AsyncClient) -> None:
cached_urls = load_cache(CACHE_FILE, exp=10800) cached_urls = load_cache(CACHE_FILE, exp=10_800)
cached_count = len(cached_urls) cached_count = len(cached_urls)
urls.update(cached_urls) urls.update(cached_urls)
@ -184,7 +223,7 @@ async def main(client: httpx.AsyncClient) -> None:
events = await get_events( events = await get_events(
client, client,
urljoin(base_url, "api/streams"), base_url,
set(cached_urls.keys()), set(cached_urls.keys()),
) )
@ -200,6 +239,11 @@ async def main(client: httpx.AsyncClient) -> None:
if url: if url:
sport, event = ev["sport"], ev["event"] 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)" key = f"[{sport}] {event} (PPV)"
entry = { entry = {
@ -207,7 +251,7 @@ async def main(client: httpx.AsyncClient) -> None:
"logo": ev["logo"], "logo": ev["logo"],
"base": base_url, "base": base_url,
"timestamp": now.timestamp(), "timestamp": now.timestamp(),
"id": league_info(sport)["id"], "id": tvg_id or "Live.Event.us",
} }
urls[key] = cached_urls[key] = entry urls[key] = cached_urls[key] = entry

View file

@ -84,8 +84,8 @@ async def get_events(client: httpx.AsyncClient) -> list[dict[str, str]]:
return events return events
async def main(client: httpx.AsyncClient) -> None: async def scrape(client: httpx.AsyncClient) -> None:
if cached := load_cache(CACHE_FILE, exp=86400, nearest_hr=True): if cached := load_cache(CACHE_FILE, exp=86_400, nearest_hr=True):
urls.update(cached) urls.update(cached)
log.info(f"Collected {len(urls)} event(s) from cache") log.info(f"Collected {len(urls)} event(s) from cache")
return return

View file

@ -152,8 +152,8 @@ async def get_events(
return events return events
async def main(client: httpx.AsyncClient) -> None: async def scrape(client: httpx.AsyncClient) -> None:
cached_urls = load_cache(CACHE_FILE, exp=10800) cached_urls = load_cache(CACHE_FILE, exp=10_800)
cached_count = len(cached_urls) cached_count = len(cached_urls)
urls.update(cached_urls) urls.update(cached_urls)

View file

@ -25,8 +25,8 @@ async def fetch_m3u8(client: httpx.AsyncClient) -> list[str]:
return r.text.splitlines() return r.text.splitlines()
async def main(client: httpx.AsyncClient) -> None: async def scrape(client: httpx.AsyncClient) -> None:
if cached := load_cache(CACHE_FILE, exp=86400, nearest_hr=True): if cached := load_cache(CACHE_FILE, exp=86_400, nearest_hr=True):
urls.update(cached) urls.update(cached)
log.info(f"Collected {len(urls)} event(s) from cache") log.info(f"Collected {len(urls)} event(s) from cache")
return return
@ -52,8 +52,10 @@ async def main(client: httpx.AsyncClient) -> None:
if url.endswith("/hd"): if url.endswith("/hd"):
key = f"[{sport}] {tvg_name} (TVP)" key = f"[{sport}] {tvg_name} (TVP)"
channel = url.split("/")[-2]
entry = { 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"], "logo": league_info(sport)["logo"],
"id": league_info(sport)["id"], "id": league_info(sport)["id"],
"base": "https://tvpass.org", "base": "https://tvpass.org",

View file

@ -22,42 +22,15 @@ LEAGUES: dict[str, dict[str, str]] = json.loads(
leagues_file.read_text(encoding="utf-8") 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(): def league_info(name: str) -> tuple[str | None, str]:
if not v["logo"]: league_name_map: dict[str, tuple[str, str]] = {
LEAGUES[k]["logo"] = live_img 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(): tvg_id, logo = league_name_map.get(name, (None, None))
for alias in aliases:
LEAGUES[alias] = LEAGUES[base]
return tvg_id, logo or live_img
def league_info(name: str) -> dict:
return LEAGUES.get(name, LEAGUES["default"])

View file

@ -1,81 +1,648 @@
{ {
"Basketball": { "id": "Basketball.Dummy.us", "logo": null }, "Basketball.Dummy.us": [
"Bundesliga": { {
"id": "Soccer.Dummy.us", "Basketball": {
"logo": "https://1000logos.net/wp-content/uploads/2020/09/Bundesliga-Logo-500x313.png" "logo": "https://1000logos.net/wp-content/uploads/2024/04/Basketball-Emoji-1536x864.png",
}, "names": []
"F1": { }
"id": "Racing.Dummy.us", }
"logo": "https://1000logos.net/wp-content/uploads/2021/06/F1-logo-500x281.png" ],
}, "Golf.Dummy.us": [
"Golf.Dummy.us": { "id": "Golf.Dummy.us", "logo": null }, {
"La Liga": { "Golf": {
"id": "Soccer.Dummy.us", "logo": "https://i.gyazo.com/14a883f22796f631e6f97c34dbeb6ada.png",
"logo": "https://1000logos.net/wp-content/uploads/2019/01/Spanish-La-Liga-Logo-500x281.png" "names": []
}, }
"Ligue 1": { }
"id": "Soccer.Dummy.us", ],
"logo": "https://1000logos.net/wp-content/uploads/2019/01/Ligue-1-Logo-500x281.png" "MLB.Baseball.Dummy.us": [
}, {
"MLB": { "MLB": {
"id": "MLB.Baseball.Dummy.us", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/leagues/500/mlb.png",
"logo": "https://1000logos.net/wp-content/uploads/2017/04/MLB-Logo-500x281.png" "names": [
}, "Baseball",
"MLS": { "Major League Baseball"
"id": "MLS.Soccer.Dummy.us", ]
"logo": "https://1000logos.net/wp-content/uploads/2017/10/MLS-logo-500x393.png" }
}, }
"Moto GP": { ],
"id": "Racing.Dummy.us", "NBA.Basketball.Dummy.us": [
"logo": "https://1000logos.net/wp-content/uploads/2021/03/MotoGP-Logo-500x281.png" {
}, "NBA": {
"NBA": { "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/leagues/500/nba.png",
"id": "NBA.Basketball.Dummy.us", "names": [
"logo": "https://1000logos.net/wp-content/uploads/2025/08/Jerry-West-the-NBA-Logo-500x281.png" "National Basketball Association"
}, ]
"NCAA": { }
"id": "Sports.Dummy.us", }
"logo": "https://1000logos.net/wp-content/uploads/2021/12/NCAA-Logo-500x281.png" ],
}, "NCAA.Sports.Dummy.us": [
"NFL": { {
"id": "NFL.Dummy.us", "NCAA": {
"logo": "https://1000logos.net/wp-content/uploads/2017/05/NFL-logo-500x338.png" "logo": "https://1000logos.net/wp-content/uploads/2021/12/NCAA-Logo-500x281.png",
}, "names": [
"NHL": { "CBB",
"id": "NHL.Hockey.Dummy.us", "CFB",
"logo": "https://1000logos.net/wp-content/uploads/2017/05/NHL-Logo-500x333.png" "NCAAB",
}, "NCAAF"
"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" ],
}, "NFL.Dummy.us": [
"Primeira Liga": { {
"id": "Soccer.Dummy.us", "NFL": {
"logo": "https://1000logos.net/wp-content/uploads/2022/01/Portuguese-Primeira-Liga-logo-500x281.png" "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/leagues/500/nfl.png",
}, "names": [
"Primera A": { "American Football",
"id": "Soccer.Dummy.us", "National Football League",
"logo": "https://b.fssta.com/uploads/application/soccer/competition-logos/ColombianPrimeraA.png" "USA NFL"
}, ]
"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" "NHL.Hockey.Dummy.us": [
}, {
"Soccer": { "id": "Soccer.Dummy.us", "logo": null }, "NHL": {
"Tennis": { "id": "Tennis.Dummy.us", "logo": null }, "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/leagues/500/nhl.png",
"UEFA Champions League": { "names": [
"id": "UEFA.Champions.League.Dummy.us", "Hockey",
"logo": "https://1000logos.net/wp-content/uploads/2022/01/UEFA-Champions-League-logo-500x281.png" "National Hockey League"
}, ]
"UFC": { }
"id": "UFC.Fight.Pass.Dummy.us", }
"logo": "https://1000logos.net/wp-content/uploads/2017/06/Logo-UFC-500x313.png" ],
}, "PPV.EVENTS.Dummy.us": [
"WNBA": { {
"id": "WNBA.dummy.us", "Pay Per View": {
"logo": "https://1000logos.net/wp-content/uploads/2018/09/logo-wnba-500x287.png" "logo": null,
}, "names": [
"default": { "id": "Live.Event.us", "logo": null } "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"
]
}
}
]
} }