Compare commits

...

36 commits

Author SHA1 Message Date
GitHub Actions Bot
befe6e3a0a update M3U8 2026-04-22 13:31:12 -04:00
GitHub Actions Bot
0d6bc48cc7 update M3U8 2026-04-22 13:00:45 -04:00
GitHub Actions Bot
8d0bbf5833 update M3U8 2026-04-22 12:01:05 -04:00
GitHub Actions Bot
d9de7818ce health log 2026-04-22 15:48:22 +00:00
GitHub Actions Bot
dab1ae7888 update M3U8 2026-04-22 11:01:12 -04:00
GitHub Actions Bot
071947dabd update M3U8 2026-04-22 10:00:40 -04:00
GitHub Actions Bot
b336279921 update M3U8 2026-04-22 09:01:08 -04:00
GitHub Actions Bot
08d7c19a0d update M3U8 2026-04-22 08:02:39 -04:00
GitHub Actions Bot
58c2955f8c update EPG 2026-04-22 11:43:15 +00:00
GitHub Actions Bot
927c6bb7d3 health log 2026-04-22 09:59:45 +00:00
GitHub Actions Bot
ead9fada18 update EPG 2026-04-22 05:14:56 +00:00
GitHub Actions Bot
4ba2783b37 health log 2026-04-22 05:12:22 +00:00
GitHub Actions Bot
6134ccd4ca update M3U8 2026-04-21 23:30:28 -04:00
GitHub Actions Bot
b78df0ee3a update M3U8 2026-04-21 23:01:04 -04:00
GitHub Actions Bot
d47b7244ae update M3U8 2026-04-21 22:30:33 -04:00
GitHub Actions Bot
3e57bc2d0d update M3U8 2026-04-21 22:01:08 -04:00
GitHub Actions Bot
ff8480b5bf update M3U8 2026-04-21 21:30:43 -04:00
GitHub Actions Bot
1ef9db736e update M3U8 2026-04-21 21:01:18 -04:00
GitHub Actions Bot
ebbb6ba822 update M3U8 2026-04-21 20:30:46 -04:00
GitHub Actions Bot
d39d10f316 update M3U8 2026-04-21 20:00:55 -04:00
GitHub Actions Bot
4c390846fb update M3U8 2026-04-21 19:31:15 -04:00
GitHub Actions Bot
cf034fec57 update M3U8 2026-04-21 19:00:42 -04:00
GitHub Actions Bot
6c6d638777 update M3U8 2026-04-21 18:31:13 -04:00
GitHub Actions Bot
82606868ba update M3U8 2026-04-21 18:00:40 -04:00
doms9
00000d9fe2 e
- edit scraping for livetvsx.py
- edit caching for streamsgate.py
- edit caching for streamhub.py
- misc edits.
2026-04-21 17:48:21 -04:00
GitHub Actions Bot
fce1e8f6a9 update M3U8 2026-04-21 17:31:19 -04:00
GitHub Actions Bot
c7f6e03f12 health log 2026-04-21 21:10:52 +00:00
GitHub Actions Bot
af30f7fb14 update M3U8 2026-04-21 17:00:48 -04:00
GitHub Actions Bot
9f95ec0e91 update M3U8 2026-04-21 16:31:21 -04:00
GitHub Actions Bot
417cee9e9a update M3U8 2026-04-21 16:02:43 -04:00
doms9
00000d9700 e
- edit scraping for livetvsx.py
2026-04-21 15:55:40 -04:00
GitHub Actions Bot
a3de6f3418 update EPG 2026-04-21 19:49:03 +00:00
GitHub Actions Bot
e4b01db40f update M3U8 2026-04-21 15:31:00 -04:00
GitHub Actions Bot
7d8c235288 update M3U8 2026-04-21 15:01:08 -04:00
GitHub Actions Bot
d9adda905f update M3U8 2026-04-21 14:31:13 -04:00
GitHub Actions Bot
e7cc874dc3 update M3U8 2026-04-21 14:01:20 -04:00
12 changed files with 118655 additions and 116923 deletions

File diff suppressed because it is too large Load diff

231084
M3U8/TV.xml

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -64,7 +64,8 @@ async def main() -> None:
asyncio.create_task(embedhd.scrape(hdl_brwsr)), asyncio.create_task(embedhd.scrape(hdl_brwsr)),
# asyncio.create_task(ppv.scrape(xtrnl_brwsr)), # asyncio.create_task(ppv.scrape(xtrnl_brwsr)),
asyncio.create_task(roxie.scrape(hdl_brwsr)), asyncio.create_task(roxie.scrape(hdl_brwsr)),
# asyncio.create_task(streamhub.scrape(hdl_brwsr)), asyncio.create_task(streamhub.scrape(xtrnl_brwsr)),
asyncio.create_task(watchfooty.scrape(xtrnl_brwsr)),
] ]
httpx_tasks = [ httpx_tasks = [
@ -80,14 +81,11 @@ async def main() -> None:
asyncio.create_task(totalsportek.scrape()), asyncio.create_task(totalsportek.scrape()),
asyncio.create_task(tvapp.scrape()), asyncio.create_task(tvapp.scrape()),
asyncio.create_task(webcast.scrape()), asyncio.create_task(webcast.scrape()),
asyncio.create_task(livetvsx.scrape()),
] ]
await asyncio.gather(*(pw_tasks + httpx_tasks)) await asyncio.gather(*(pw_tasks + httpx_tasks))
# others
# await livetvsx.scrape(xtrnl_brwsr)
await watchfooty.scrape(xtrnl_brwsr)
finally: finally:
await hdl_brwsr.close() await hdl_brwsr.close()

View file

@ -1,9 +1,7 @@
import asyncio
import re import re
from functools import partial from functools import partial
import feedparser from selectolax.parser import HTMLParser
from playwright.async_api import Browser, Page, TimeoutError
from .utils import Cache, Time, get_logger, leagues, network from .utils import Cache, Time, get_logger, leagues, network
@ -11,192 +9,119 @@ log = get_logger(__name__)
urls: dict[str, dict[str, str | float]] = {} urls: dict[str, dict[str, str | float]] = {}
TAG = "LIVETVSX" TAG = "LTVSX"
CACHE_FILE = Cache(TAG, exp=10_800) CACHE_FILE = Cache(TAG, exp=10_800)
XML_CACHE = Cache(f"{TAG}-xml", exp=28_000) BASE_URL = "https://livetv.sx/export/webmasters.php"
BASE_URL = "https://cdn.livetv872.me/rss/upcoming_en.xml"
VALID_SPORTS = [
"MLB. Preseason",
"MLB",
"Basketball",
"Football",
"Ice Hockey",
"Wrestling",
]
def fix_url(s: str) -> str | None: async def process_event(url: str, url_num: int) -> tuple[str | None, str | None]:
pattern = re.compile(r"eventinfo\/(\d*)", re.I) nones = None, None
if not (match := pattern.search(s)): if not (ev_data_1 := await network.request(url, log=log)):
return log.warning(f"URL {url_num}) Failed to load url. (EVD1)")
return nones
elif not (event_id := match[1]).isalnum(): soup_1 = HTMLParser(ev_data_1.content)
return
return f"https://cdn.livetv872.me/cache/links/en.{event_id}.html" for a_elem in soup_1.css("a"):
if not (src_title := a_elem.attributes.get("title")) or (
"aliez" not in src_title.lower()
):
continue
href = a_elem.attributes["href"]
async def process_event(
url: str,
url_num: int,
page: Page,
) -> str | None:
captured: list[str] = []
got_one = asyncio.Event()
handler = partial(
network.capture_req,
captured=captured,
got_one=got_one,
)
page.on("request", handler)
try:
resp = await page.goto(
url,
wait_until="domcontentloaded",
timeout=10_000,
)
if not resp or resp.status != 200:
log.warning(
f"URL {url_num}) Status Code: {resp.status if resp else 'None'}"
)
return
try:
event_a = page.locator('a[title*="Aliez"]').first
href = await event_a.get_attribute("href", timeout=1_250)
except TimeoutError:
log.warning(f"URL {url_num}) No valid sources found.")
return
event_url = href if href.startswith("http") else f"https:{href}" event_url = href if href.startswith("http") else f"https:{href}"
break
await page.goto( else:
event_url, log.warning(f"URL {url_num}) No valid sources found.")
wait_until="domcontentloaded", return nones
timeout=5_000,
)
wait_task = asyncio.create_task(got_one.wait()) if not (ev_data_2 := await network.request(event_url, log=log)):
log.warning(f"URL {url_num}) Failed to load url. (EVD2)")
return nones
try: soup_2 = HTMLParser(ev_data_2.content)
await asyncio.wait_for(wait_task, timeout=6)
except asyncio.TimeoutError:
log.warning(f"URL {url_num}) Timed out waiting for M3U8.")
return
finally: ifr_1 = soup_2.css_first("tr > td > iframe")
if not wait_task.done():
wait_task.cancel()
try: if not ifr_1 or not (ifr_1_src := ifr_1.attributes.get("src")):
await wait_task log.warning(f"URL {url_num}) No iframe element found.")
except asyncio.CancelledError: return nones
pass
if captured: ifr_1_src = "".join(
log.info(f"URL {url_num}) Captured M3U8") (ifr_1_src if ifr_1_src.startswith("http") else f"https:{ifr_1_src}").split()
return captured[0] )
log.warning(f"URL {url_num}) No M3U8 captured after waiting.") if not (ev_data_3 := await network.request(ifr_1_src, log=log)):
return log.warning(f"URL {url_num}) Failed to load url. (EVD3)")
return nones
except Exception as e: pattern = re.compile(r'pl\.init\((\'|\")([^"]*)(\'|\")\)', re.I)
log.warning(f"URL {url_num}) {e}")
return
finally: if not (match := pattern.search(ev_data_3.text)):
page.remove_listener("request", handler) log.warning(f"URL {url_num}) No M3U8 source found.")
return nones
log.info(f"URL {url_num}) Captured M3U8")
m3u: str = match[2] if match[2].startswith("http") else f"https:{match[2]}"
return m3u, ifr_1_src
async def refresh_xml_cache(now_ts: float) -> dict[str, dict[str, str | float]]: async def get_events(cached_keys: list[str]) -> list[dict[str, str]]:
log.info("Refreshing XML cache") events = []
events = {} php_data = await network.unvd_client.get(BASE_URL, params={"lang": "en"})
if not (xml_data := await network.request(BASE_URL, log=log)): if php_data.status_code != 200:
log.warning("Failed to get php data.")
return events return events
feed = feedparser.parse(xml_data.content) soup = HTMLParser(php_data.content)
for entry in feed.entries: if not (table := soup.css_first("table.tbl")):
if not (date := entry.get("published")): return events
for row in table.css("tr > td"):
if not (event_tbl := row.css_first("table")):
continue continue
if (not (link := entry.get("link"))) or (not (fixed_link := fix_url(link))): sport_elem = event_tbl.css_first(".spr")
league_elem = event_tbl.css_first(".cmp")
link_elem = event_tbl.css_first("a.title")
event_id_elem = row.css_first("div[id^='el']")
if not (league_elem and sport_elem and link_elem and event_id_elem):
continue continue
if not (title := entry.get("title")): elif not (event_id := event_id_elem.attributes.get("id")):
continue continue
if not (sport_sum := entry.get("summary")): sport = sport_elem.text(strip=True)
league = league_elem.text(strip=True)
event_name = link_elem.text(strip=True)
if f"[{sport} - {league}] {event_name} ({TAG})" in cached_keys:
continue continue
sprt = sport_sum.split(".", 1) events.append(
{
sport, league = sprt[0], "".join(sprt[1:]).strip() "sport": sport,
"league": league,
event_dt = Time.from_str(date) "event": event_name,
"link": f"https://cdn.livetv872.me/cache/links/en.{event_id[2:]}.html",
if (key := f"[{sport} - {league}] {title} ({TAG})") in events: }
continue )
events[key] = {
"sport": sport,
"league": league,
"event": title,
"link": fixed_link,
"event_ts": event_dt.timestamp(),
"timestamp": now_ts,
}
return events return events
async def get_events(cached_keys: list[str]) -> list[dict[str, str]]: async def scrape() -> None:
now = Time.clean(Time.now())
if not (events := XML_CACHE.load()):
events = await refresh_xml_cache(now.timestamp())
XML_CACHE.write(events)
start_ts = now.delta(hours=-1).timestamp()
end_ts = now.delta(minutes=5).timestamp()
live = []
for k, v in events.items():
if k in cached_keys:
continue
if (
v["sport"] not in VALID_SPORTS
and v["league"] not in VALID_SPORTS
and v["event"].lower() != "olympic games"
):
continue
if not start_ts <= v["event_ts"] <= end_ts:
continue
live.append(v)
return live
async def scrape(browser: Browser) -> None:
cached_urls = CACHE_FILE.load() cached_urls = CACHE_FILE.load()
valid_urls = {k: v for k, v in cached_urls.items() if v["url"]} valid_urls = {k: v for k, v in cached_urls.items() if v["url"]}
@ -212,50 +137,47 @@ async def scrape(browser: Browser) -> None:
if events := await get_events(cached_urls.keys()): if events := await get_events(cached_urls.keys()):
log.info(f"Processing {len(events)} new URL(s)") log.info(f"Processing {len(events)} new URL(s)")
async with network.event_context(browser, ignore_https=True) as context: now = Time.clean(Time.now())
for i, ev in enumerate(events, start=1):
async with network.event_page(context) as page:
handler = partial(
process_event,
url=(link := ev["link"]),
url_num=i,
page=page,
)
url = await network.safe_process( for i, ev in enumerate(events, start=1):
handler, handler = partial(
url_num=i, process_event,
semaphore=network.PW_S, url=(link := ev["link"]),
log=log, url_num=i,
timeout=20, )
)
sport, league, event, ts = ( url, iframe = await network.safe_process(
ev["sport"], handler,
ev["league"], url_num=i,
ev["event"], semaphore=network.HTTP_S,
ev["event_ts"], log=log,
) )
key = f"[{sport} - {league}] {event} ({TAG})" sport, league, event = (
ev["sport"],
ev["league"],
ev["event"],
)
tvg_id, logo = leagues.get_tvg_info(sport, event) key = f"[{sport} - {league}] {event} ({TAG})"
entry = { tvg_id, logo = leagues.get_tvg_info(sport, event)
"url": url,
"logo": logo,
"base": "https://livetv.sx/enx/",
"timestamp": ts,
"id": tvg_id or "Live.Event.us",
"link": link,
}
cached_urls[key] = entry entry = {
"url": url,
"logo": logo,
"base": iframe,
"timestamp": now.timestamp(),
"id": tvg_id or "Live.Event.us",
"link": link,
}
if url: cached_urls[key] = entry
valid_count += 1
urls[key] = entry if url:
valid_count += 1
urls[key] = entry
log.info(f"Collected and cached {valid_count - cached_count} new event(s)") log.info(f"Collected and cached {valid_count - cached_count} new event(s)")

View file

@ -163,7 +163,7 @@ async def scrape() -> None:
url = await network.safe_process( url = await network.safe_process(
handler, handler,
url_num=i, url_num=i,
semaphore=network.PW_S, semaphore=network.HTTP_S,
log=log, log=log,
) )

View file

@ -117,7 +117,7 @@ async def scrape() -> None:
url = await network.safe_process( url = await network.safe_process(
handler, handler,
url_num=i, url_num=i,
semaphore=network.PW_S, semaphore=network.HTTP_S,
log=log, log=log,
) )

View file

@ -13,9 +13,7 @@ urls: dict[str, dict[str, str | float]] = {}
TAG = "STRMHUB" TAG = "STRMHUB"
CACHE_FILE = Cache(TAG, exp=10_800) CACHE_FILE = Cache(TAG, exp=28_800)
HTML_FILE = Cache(f"{TAG}-html", exp=19_800)
BASE_URL = "https://livesports4u.net" BASE_URL = "https://livesports4u.net"
@ -116,116 +114,78 @@ async def process_event(
page.remove_listener("request", handler) page.remove_listener("request", handler)
async def refresh_html_cache( async def get_events() -> list[dict[str, str]]:
date: str, now = Time.clean(Time.now())
sport_id: str,
ts: float,
) -> dict[str, dict[str, str | float]]:
events = {} tasks = [
network.request(
if not (
html_data := await network.request(
urljoin(BASE_URL, f"events/{date}"), urljoin(BASE_URL, f"events/{date}"),
params={"sport_id": sport_id}, params={"sport_id": sport_id},
log=log, log=log,
) )
): for date in [now.date(), now.delta(days=1).date()]
for sport_id in SPORT_ENDPOINTS
]
results = await asyncio.gather(*tasks)
events = []
if not (soups := [HTMLParser(html.content) for html in results if html]):
return events return events
soup = HTMLParser(html_data.content) for soup in soups:
for section in soup.css(".events-section"):
for section in soup.css(".events-section"): if not (sport_node := section.css_first(".section-titlte")):
if not (sport_node := section.css_first(".section-titlte")):
continue
sport = sport_node.text(strip=True)
for event in section.css(".section-event"):
event_name = "Live Event"
if teams := event.css_first(".event-competitors"):
home, away = teams.text(strip=True).split("vs.")
event_name = f"{away} vs {home}"
if not (event_button := event.css_first(".event-button a")) or not (
href := event_button.attributes.get("href")
):
continue continue
event_date = event.css_first(".event-countdown").attributes.get( sport = sport_node.text(strip=True)
"data-start"
)
event_dt = Time.from_str(event_date, timezone="UTC") for event in section.css(".section-event"):
event_name = "Live Event"
key = f"[{sport}] {event_name} ({TAG})" if teams := event.css_first(".event-competitors"):
home, away = teams.text(strip=True).split("vs.")
events[key] = { event_name = f"{away} vs {home}"
"sport": sport,
"event": event_name, if not (event_button := event.css_first(".event-button a")) or not (
"link": href, href := event_button.attributes.get("href")
"event_ts": event_dt.timestamp(), ):
"timestamp": ts, continue
}
event_date = event.css_first(".event-countdown").attributes.get(
"data-start"
)
event_dt = Time.from_str(event_date, timezone="UTC")
if event_dt.date() != now.date():
continue
events.append(
{
"sport": sport,
"event": event_name,
"link": href,
"timestamp": now.timestamp(),
}
)
return events return events
async def get_events(cached_keys: list[str]) -> list[dict[str, str]]:
now = Time.clean(Time.now())
if not (events := HTML_FILE.load()):
log.info("Refreshing HTML cache")
tasks = [
refresh_html_cache(
date,
sport_id,
now.timestamp(),
)
for date in [now.date(), now.delta(days=1).date()]
for sport_id in SPORT_ENDPOINTS
]
results = await asyncio.gather(*tasks)
events = {k: v for data in results for k, v in data.items()}
HTML_FILE.write(events)
live = []
start_ts = now.delta(minutes=-30).timestamp()
end_ts = now.delta(minutes=30).timestamp()
for k, v in events.items():
if k in cached_keys:
continue
if not start_ts <= v["event_ts"] <= end_ts:
continue
live.append(v)
return live
async def scrape(browser: Browser) -> None: async def scrape(browser: Browser) -> None:
cached_urls = CACHE_FILE.load() if cached_urls := CACHE_FILE.load():
urls.update({k: v for k, v in cached_urls.items() if v["url"]})
valid_urls = {k: v for k, v in cached_urls.items() if v["url"]} log.info(f"Loaded {len(urls)} event(s) from cache")
valid_count = cached_count = len(valid_urls) return
urls.update(valid_urls)
log.info(f"Loaded {cached_count} event(s) from cache")
log.info(f'Scraping from "{BASE_URL}"') log.info(f'Scraping from "{BASE_URL}"')
if events := await get_events(cached_urls.keys()): if events := await get_events():
log.info(f"Processing {len(events)} new URL(s)") log.info(f"Processing {len(events)} new URL(s)")
async with network.event_context(browser) as context: async with network.event_context(browser) as context:
@ -249,7 +209,7 @@ async def scrape(browser: Browser) -> None:
sport, event, ts = ( sport, event, ts = (
ev["sport"], ev["sport"],
ev["event"], ev["event"],
ev["event_ts"], ev["timestamp"],
) )
key = f"[{sport}] {event} ({TAG})" key = f"[{sport}] {event} ({TAG})"
@ -268,13 +228,11 @@ async def scrape(browser: Browser) -> None:
cached_urls[key] = entry cached_urls[key] = entry
if url: if url:
valid_count += 1
entry["url"] = url.split("?st")[0] entry["url"] = url.split("?st")[0]
urls[key] = entry urls[key] = entry
log.info(f"Collected and cached {valid_count - cached_count} new event(s)") log.info(f"Collected and cached {len(urls)} new event(s)")
else: else:
log.info("No new events found") log.info("No new events found")

View file

@ -2,7 +2,6 @@ import asyncio
import re import re
from functools import partial from functools import partial
from itertools import chain from itertools import chain
from typing import Any
from urllib.parse import urljoin from urllib.parse import urljoin
from selectolax.parser import HTMLParser from selectolax.parser import HTMLParser
@ -15,9 +14,7 @@ urls: dict[str, dict[str, str | float]] = {}
TAG = "STRMSGATE" TAG = "STRMSGATE"
CACHE_FILE = Cache(TAG, exp=10_800) CACHE_FILE = Cache(TAG, exp=28_800)
API_FILE = Cache(f"{TAG}-api", exp=19_800)
BASE_URL = "https://streamsgates.io" BASE_URL = "https://streamsgates.io"
@ -85,36 +82,17 @@ async def process_event(url: str, url_num: int) -> tuple[str | None, str | None]
return match[3], ifr_src return match[3], ifr_src
async def refresh_api_cache(now_ts: float) -> list[dict[str, Any]]: async def get_events() -> list[dict[str, str]]:
now = Time.clean(Time.now())
tasks = [network.request(url, log=log) for url in SPORT_URLS] tasks = [network.request(url, log=log) for url in SPORT_URLS]
results = await asyncio.gather(*tasks) results = await asyncio.gather(*tasks)
if not (data := [*chain.from_iterable(r.json() for r in results if r)]):
return [{"timestamp": now_ts}]
for ev in data:
ev["ts"] = ev.pop("timestamp")
data[-1]["timestamp"] = now_ts
return data
async def get_events(cached_keys: list[str]) -> list[dict[str, str]]:
now = Time.clean(Time.now())
if not (api_data := API_FILE.load(per_entry=False, index=-1)):
log.info("Refreshing API cache")
api_data = await refresh_api_cache(now.timestamp())
API_FILE.write(api_data)
events = [] events = []
start_dt = now.delta(hours=-2.5) if not (api_data := [*chain.from_iterable(r.json() for r in results if r)]):
end_dt = now.delta(minutes=30) return events
for stream_group in api_data: for stream_group in api_data:
date = stream_group.get("time") date = stream_group.get("time")
@ -123,34 +101,30 @@ async def get_events(cached_keys: list[str]) -> list[dict[str, str]]:
t1, t2 = stream_group.get("away"), stream_group.get("home") t1, t2 = stream_group.get("away"), stream_group.get("home")
if not (t1 and t2):
continue
event = get_event(t1, t2)
if not (date and sport): if not (date and sport):
continue continue
if f"[{sport}] {event} ({TAG})" in cached_keys:
continue
event_dt = Time.from_str(date, timezone="UTC") event_dt = Time.from_str(date, timezone="UTC")
if not start_dt <= event_dt <= end_dt: if event_dt.date() != now.date():
continue continue
if not (streams := stream_group.get("streams")): if not (streams := stream_group.get("streams")) or not (
url := streams[0].get("url")
):
continue continue
if not (url := streams[0].get("url")): if not (t1 and t2):
continue continue
event = get_event(t1, t2)
events.append( events.append(
{ {
"sport": sport, "sport": sport,
"event": event, "event": event,
"link": url, "link": url,
"timestamp": event_dt.timestamp(), "timestamp": now.timestamp(),
} }
) )
@ -158,19 +132,16 @@ async def get_events(cached_keys: list[str]) -> list[dict[str, str]]:
async def scrape() -> None: async def scrape() -> None:
cached_urls = CACHE_FILE.load() if cached_urls := CACHE_FILE.load():
urls.update({k: v for k, v in cached_urls.items() if v["url"]})
valid_urls = {k: v for k, v in cached_urls.items() if v["url"]} log.info(f"Loaded {len(urls)} event(s) from cache")
valid_count = cached_count = len(valid_urls) return
urls.update(valid_urls)
log.info(f"Loaded {cached_count} event(s) from cache")
log.info(f'Scraping from "{BASE_URL}"') log.info(f'Scraping from "{BASE_URL}"')
if events := await get_events(cached_urls.keys()): if events := await get_events():
log.info(f"Processing {len(events)} new URL(s)") log.info(f"Processing {len(events)} new URL(s)")
for i, ev in enumerate(events, start=1): for i, ev in enumerate(events, start=1):
@ -183,7 +154,7 @@ async def scrape() -> None:
url, iframe = await network.safe_process( url, iframe = await network.safe_process(
handler, handler,
url_num=i, url_num=i,
semaphore=network.PW_S, semaphore=network.HTTP_S,
log=log, log=log,
) )
@ -209,11 +180,11 @@ async def scrape() -> None:
cached_urls[key] = entry cached_urls[key] = entry
if url: if url:
valid_count += 1 entry["url"] = url.split("?st")[0]
urls[key] = entry urls[key] = entry
log.info(f"Collected and cached {valid_count - cached_count} new event(s)") log.info(f"Collected and cached {len(urls)} new event(s)")
else: else:
log.info("No new events found") log.info("No new events found")

View file

@ -38,12 +38,16 @@ class Network:
PW_S = asyncio.Semaphore(3) PW_S = asyncio.Semaphore(3)
def __init__(self) -> None: def __init__(self) -> None:
self.client = httpx.AsyncClient( client_params = {
timeout=httpx.Timeout(5.0), "timeout": httpx.Timeout(5.0),
follow_redirects=True, "follow_redirects": True,
headers={"User-Agent": Network.UA}, "headers": {"User-Agent": Network.UA},
http2=True, "http2": True,
) }
self.client = httpx.AsyncClient(**client_params)
self.unvd_client = httpx.AsyncClient(**client_params, verify=False)
async def request( async def request(
self, self,

View file

@ -157,7 +157,7 @@ async def scrape() -> None:
url = await network.safe_process( url = await network.safe_process(
handler, handler,
url_num=i, url_num=i,
semaphore=network.PW_S, semaphore=network.HTTP_S,
log=log, log=log,
) )

View file

@ -1,20 +1,89 @@
## Base Log @ 2026-04-21 16:32 UTC ## Base Log @ 2026-04-22 15:48 UTC
### ✅ Working Streams: 150<br>❌ Dead Streams: 11 ### ✅ Working Streams: 81<br>❌ Dead Streams: 80
| Channel | Error (Code) | Link | | Channel | Error (Code) | Link |
| ------- | ------------ | ---- | | ------- | ------------ | ---- |
| Aspire | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/150605.ts` |
| C-SPAN | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/14804.ts` |
| CBS Sports Network | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/267357.ts` |
| CNBC | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/2295.ts` |
| CW | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/171820.ts` |
| Cleo TV | HTTP Error (401) | `http://supersonictv.live:8080/317136/Kennzack1218/86101` | | Cleo TV | HTTP Error (401) | `http://supersonictv.live:8080/317136/Kennzack1218/86101` |
| FDSN North | HTTP Error (444) | `http://mytvstream.net:8080/live/5AGbfz/324331/20928.m3u8` | | Comedy TV | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/199482.ts` |
| Cozi TV | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/8392.ts` |
| Discovery Family Channel | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/2300.ts` |
| Discovery Life | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/201208.ts` |
| Discovery Science | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/2301.ts` |
| Disney | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/2303.ts` |
| ESPN News | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/2312.ts` |
| ESPN2 | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/41918.ts` |
| FDSN Detroit | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/6463.ts` |
| FDSN Florida | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/296662.ts` |
| FDSN Midwest | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/58557.ts` |
| FDSN Ohio | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/296675.ts` |
| FDSN Oklahoma | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/6452.ts` |
| FDSN SoCal | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/296681.ts` | | FDSN SoCal | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/296681.ts` |
| FDSN South | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/277374.ts` |
| FDSN Southeast | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/82301.ts` | | FDSN Southeast | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/82301.ts` |
| FDSN Southwest | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/296685.ts` | | FDSN Southwest | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/296685.ts` |
| FDSN Sun | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/2325.ts` |
| FDSN West | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/3367.ts` | | FDSN West | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/3367.ts` |
| FDSN Wisconsin | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/295668.ts` | | FDSN Wisconsin | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/295668.ts` |
| FX Movie Channel | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/39873.ts` | | FX Movie Channel | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/39873.ts` |
| getTV | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/194187.ts` | | FYI TV | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/9234.ts` |
| Fox News | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/1611.ts` |
| Fox Sports 1 | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/756.ts` |
| Fox Sports 2 | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/757.ts` |
| Freeform TV | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/2329.ts` |
| Game Show Network | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/466.ts` |
| Golf Channel | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/5845.ts` |
| Grit TV | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/15086.ts` |
| HBO Family | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/760.ts` |
| Hallmark Family | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/304609.ts` |
| Hallmark Mystery | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/3388.ts` |
| History Channel | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/761.ts` |
| INSP | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/30900.ts` |
| ION TV | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/147661.ts` |
| Investigation Discovery | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/8557.ts` |
| Lifetime | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/4667.ts` |
| MLB Network | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/2342.ts` |
| MSNBC | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/406.ts` |
| Marquee Sports Network | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/150609.ts` |
| MotorTrend TV | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/272264.ts` |
| NBA TV | HTTP Error (404) | `http://212.102.60.231/NBA_TV/index.m3u8` |
| NBC Sports Bay Area | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/45785.ts` | | NBC Sports Bay Area | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/45785.ts` |
| NBC Sports Boston | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/35132.ts` |
| NBC Sports California | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/16116.ts` |
| NBC Sports Philadelphia | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/35472.ts` |
| NFL RedZone | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/2369.ts` |
| NHL Network | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/2348.ts` |
| National Geographic | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/749.ts` |
| NewsNation | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/6296.ts` |
| Nick Jr | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/14835.ts` |
| Ovation | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/194336.ts` |
| Oxygen | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/6378.ts` |
| Pop TV | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/305494.ts` |
| Premier Sports 1 | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/1097.ts` |
| Premier Sports 2 | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/4723.ts` |
| Showtime Extreme | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/12036.ts` |
| Sky Sports News | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/304775.ts` |
| Smithsonian Channel | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/8585.ts` | | Smithsonian Channel | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/8585.ts` |
| Sony Movie Channel | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/5831.ts` |
| SportsNet New York | HTTP Error (404) | `http://212.102.60.231/SNY/index.m3u8` |
| SportsNet Pittsburgh | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/59945.ts` |
| Sportsnet 360 | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/3377.ts` |
| Sportsnet East | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/1720.ts` |
| Sportsnet One | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/3378.ts` |
| TLC | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/2362.ts` |
| TSN1 | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/770.ts` |
| TSN2 | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/771.ts` |
| TV Land | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/2364.ts` |
| TV One | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/13010.ts` |
| The Weather Channel | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/2361.ts` |
| USA East | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/3390.ts` |
| Willow Cricket | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/296763.ts` |
| getTV | HTTP Error (404) | `http://iptvtree.net:8080/live/7e4b0dbd/1dd755dc3f/194187.ts` |
--- ---
#### Base Channels URL #### Base Channels URL
``` ```