diff --git a/M3U8/fetch.py b/M3U8/fetch.py index 72afa9e..786da1a 100644 --- a/M3U8/fetch.py +++ b/M3U8/fetch.py @@ -3,7 +3,7 @@ import asyncio from pathlib import Path import httpx -from scrape import logger, tvpass # , fstv +from scrape import ace, logger, tvpass # , fstv log = logger.get_logger(__name__) @@ -40,11 +40,13 @@ async def vanilla_fetch() -> tuple[list[str], int]: async def main() -> None: await tvpass.main(client) + await ace.main(client) + # await fstv.main(client) base_m3u8, tvg_chno = await vanilla_fetch() - additions = tvpass.urls # | fstv.urls + additions = tvpass.urls | ace.urls # | fstv.urls lines = [ f'#EXTINF:-1 tvg-chno="{chnl_num}" tvg-id="(N/A)" tvg-name="{event}" tvg-logo="{info["logo"]}" group-title="Live Events",{event}\n{info["url"]}' diff --git a/M3U8/scrape/ace.py b/M3U8/scrape/ace.py new file mode 100644 index 0000000..a245e4a --- /dev/null +++ b/M3U8/scrape/ace.py @@ -0,0 +1,128 @@ +import asyncio +import re +from urllib.parse import urljoin + +import httpx +from selectolax.parser import HTMLParser, Node + +from .fstv import get_base +from .logger import get_logger +from .tvpass import logos + +log = get_logger(__name__) + +urls: dict[str, dict[str, str]] = {} + +mirrors = [ + "https://aceztrims.pages.dev/", + "https://acestrlms.pages.dev/", +] + + +def is_valid_href(a: Node) -> bool: + href = a.attributes.get("href", "") + return href.startswith("/") and href != "/news/" + + +async def get_schedule(client: httpx.AsyncClient, base_url: str) -> list[dict]: + log.info(f'Scraping schedule from "{base_url}"') + + try: + r = await client.get(base_url) + r.raise_for_status() + except Exception as e: + log.error(f'Failed to fetch "{base_url}": {e}') + return [] + + html = re.sub(r"", "", r.text, flags=re.DOTALL) + + tree = HTMLParser(html) + + events = [] + + for a in filter(is_valid_href, tree.css("a[href]")): + href = a.attributes.get("href", "") + + title_text = a.text(strip=True) + + after_time = ( + title_text.split("//", 1)[1].strip() if "//" in title_text else title_text + ) + + if " - " in after_time: + sport, event_name = [x.strip() for x in after_time.split(" - ", 1)] + else: + sport, event_name = "", after_time + + events.append( + {"sport": sport, "event": event_name, "href": urljoin(base_url, href)} + ) + + return events + + +async def get_m3u8_links(client: httpx.AsyncClient, url: str) -> list[str]: + try: + r = await client.get(url) + r.raise_for_status() + except Exception as e: + log.error(f'Failed to fetch "{url}": {e}') + return [] + + html = re.sub(r"", "", r.text, flags=re.DOTALL) + + tree = HTMLParser(html) + + m3u8_links = [] + + for btn in tree.css("button[onclick]"): + onclick = btn.attributes.get("onclick", "") + + if match := re.search(r"src\s*=\s*['\"](.*?)['\"]", onclick): + link = match[1] + + if ".m3u8" in link: + m3u8_links.append(link) + + if iframe := tree.css_first("iframe#iframe"): + src = iframe.attributes.get("src", "") + + if ".m3u8" in src and src not in m3u8_links: + m3u8_links.insert( + 0, + src.split("https://cors.ricohspaces.app/")[-1], + ) + + return m3u8_links + + +async def main(client: httpx.AsyncClient) -> None: + if not (base_url := await get_base(client, mirrors)): + log.warning("No working ace mirrors") + return + + schedule = await get_schedule(client, base_url) + + tasks = [get_m3u8_links(client, item["href"]) for item in schedule] + + results = await asyncio.gather(*tasks) + + for item, m3u8_urls in zip(schedule, results): + if not m3u8_urls: + continue + + for i, link in enumerate(m3u8_urls, start=1): + sport, event = item["sport"], item["event"] + + urls[f"[{sport}] {event} (S{i})"] = { + "url": link, + "logo": logos.get( + sport, + "https://i.gyazo.com/ec27417a9644ae517196494afa72d2b9.png", + ), + } + + log.info(f"Collected {len(urls)} events") + + +# add caching diff --git a/M3U8/scrape/fstv.py b/M3U8/scrape/fstv.py index cba80b6..631d16c 100644 --- a/M3U8/scrape/fstv.py +++ b/M3U8/scrape/fstv.py @@ -29,7 +29,7 @@ async def check_status(client: httpx.AsyncClient, url: str) -> bool: return r.status_code == 200 -async def get_base(client: httpx.AsyncClient) -> str: +async def get_base(client: httpx.AsyncClient, mirrors: list[str]) -> str: tasks = [check_status(client, link) for link in mirrors] results = await asyncio.gather(*tasks) @@ -103,7 +103,7 @@ async def fetch_m3u8(client: httpx.AsyncClient, url: str) -> tuple[str, list[str async def main(client: httpx.AsyncClient) -> None: - if not (base_url := await get_base(client)): + if not (base_url := await get_base(client, mirrors)): log.warning("No working FSTV mirrors") return diff --git a/M3U8/scrape/star.py b/M3U8/scrape/star.py new file mode 100644 index 0000000..e69de29 diff --git a/M3U8/scrape/tvpass.json b/M3U8/scrape/tvpass.json index 12bb8ba..a95b9dd 100644 --- a/M3U8/scrape/tvpass.json +++ b/M3U8/scrape/tvpass.json @@ -1,62 +1,62 @@ -{ - "[WNBA] Atlanta Dream @ Connecticut Sun": { - "url": "http://origin.thetvapp.to/hls/nbc-sports-boston/mono.m3u8", - "logo": "https://i.gyazo.com/02d665a5704118d195dbcd5fa20d5462.png" - }, - "[MLB] Toronto Blue Jays @ Cincinnati Reds": { - "url": "http://origin.thetvapp.to/hls/mlb-04/mono.m3u8", - "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" - }, - "[MLB] New York Mets @ Detroit Tigers": { - "url": "http://origin.thetvapp.to/hls/mlb-21/mono.m3u8", - "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" - }, - "[MLB] Cleveland Guardians @ Boston Red Sox": { - "url": "http://origin.thetvapp.to/hls/mlb-22/mono.m3u8", - "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" - }, - "[MLB] Los Angeles Angels @ Houston Astros": { - "url": "http://origin.thetvapp.to/hls/mlb-06/mono.m3u8", - "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" - }, - "[MLB] Oakland Athletics @ St. Louis Cardinals": { - "url": "http://origin.thetvapp.to/hls/mlb-08/mono.m3u8", - "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" - }, - "[MLB] Atlanta Braves @ Chicago Cubs": { - "url": "http://origin.thetvapp.to/hls/mlb-23/mono.m3u8", - "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" - }, - "[MLB] San Francisco Giants @ Colorado Rockies": { - "url": "http://origin.thetvapp.to/hls/mlb-25/mono.m3u8", - "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" - }, - "[MLB] Philadelphia Phillies @ Milwaukee Brewers": { - "url": "http://origin.thetvapp.to/hls/mlb-16/mono.m3u8", - "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" - }, - "[MLB] Baltimore Orioles @ San Diego Padres": { - "url": "http://origin.thetvapp.to/hls/mlb-02/mono.m3u8", - "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" - }, - "[MLB] Seattle Mariners @ Tampa Bay Rays": { - "url": "http://origin.thetvapp.to/hls/mlb-11/mono.m3u8", - "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" - }, - "[NCAAF] TCU Horned Frogs @ North Carolina Tar Heels": { - "url": "http://origin.thetvapp.to/hls/ESPN/mono.m3u8", - "logo": "https://i.gyazo.com/ca63b40c86e757436de9d34d369b24f8.png" - }, - "[WNBA] Dallas Wings @ Minnesota Lynx": { - "url": "http://origin.thetvapp.to/hls/NBATV/mono.m3u8", - "logo": "https://i.gyazo.com/02d665a5704118d195dbcd5fa20d5462.png" - }, - "[MLB] Texas Rangers @ Arizona Diamondbacks": { - "url": "http://origin.thetvapp.to/hls/mlb-09/mono.m3u8", - "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" - }, - "[WNBA] Los Angeles Sparks @ Seattle Storm": { - "url": "http://origin.thetvapp.to/hls/NBATV/mono.m3u8", - "logo": "https://i.gyazo.com/02d665a5704118d195dbcd5fa20d5462.png" - } +{ + "[WNBA] Atlanta Dream @ Connecticut Sun": { + "url": "http://origin.thetvapp.to/hls/nbc-sports-boston/mono.m3u8", + "logo": "https://i.gyazo.com/02d665a5704118d195dbcd5fa20d5462.png" + }, + "[MLB] Toronto Blue Jays @ Cincinnati Reds": { + "url": "http://origin.thetvapp.to/hls/mlb-04/mono.m3u8", + "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" + }, + "[MLB] New York Mets @ Detroit Tigers": { + "url": "http://origin.thetvapp.to/hls/mlb-21/mono.m3u8", + "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" + }, + "[MLB] Cleveland Guardians @ Boston Red Sox": { + "url": "http://origin.thetvapp.to/hls/mlb-22/mono.m3u8", + "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" + }, + "[MLB] Los Angeles Angels @ Houston Astros": { + "url": "http://origin.thetvapp.to/hls/mlb-06/mono.m3u8", + "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" + }, + "[MLB] Oakland Athletics @ St. Louis Cardinals": { + "url": "http://origin.thetvapp.to/hls/mlb-08/mono.m3u8", + "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" + }, + "[MLB] Atlanta Braves @ Chicago Cubs": { + "url": "http://origin.thetvapp.to/hls/mlb-23/mono.m3u8", + "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" + }, + "[MLB] San Francisco Giants @ Colorado Rockies": { + "url": "http://origin.thetvapp.to/hls/mlb-25/mono.m3u8", + "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" + }, + "[MLB] Philadelphia Phillies @ Milwaukee Brewers": { + "url": "http://origin.thetvapp.to/hls/mlb-16/mono.m3u8", + "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" + }, + "[MLB] Baltimore Orioles @ San Diego Padres": { + "url": "http://origin.thetvapp.to/hls/mlb-02/mono.m3u8", + "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" + }, + "[MLB] Seattle Mariners @ Tampa Bay Rays": { + "url": "http://origin.thetvapp.to/hls/mlb-11/mono.m3u8", + "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" + }, + "[NCAAF] TCU Horned Frogs @ North Carolina Tar Heels": { + "url": "http://origin.thetvapp.to/hls/ESPN/mono.m3u8", + "logo": "https://i.gyazo.com/ca63b40c86e757436de9d34d369b24f8.png" + }, + "[WNBA] Dallas Wings @ Minnesota Lynx": { + "url": "http://origin.thetvapp.to/hls/NBATV/mono.m3u8", + "logo": "https://i.gyazo.com/02d665a5704118d195dbcd5fa20d5462.png" + }, + "[MLB] Texas Rangers @ Arizona Diamondbacks": { + "url": "http://origin.thetvapp.to/hls/mlb-09/mono.m3u8", + "logo": "https://i.gyazo.com/0fe7865ef2f06c9507791b24f04dbca8.png" + }, + "[WNBA] Los Angeles Sparks @ Seattle Storm": { + "url": "http://origin.thetvapp.to/hls/NBATV/mono.m3u8", + "logo": "https://i.gyazo.com/02d665a5704118d195dbcd5fa20d5462.png" + } } \ No newline at end of file