Compare commits

...

4 commits

Author SHA1 Message Date
GitHub Actions Bot
eda2f9e73d update M3U8 2025-12-05 08:12:04 -05:00
GitHub Actions Bot
74c83ad082 update EPG 2025-12-05 10:53:03 +00:00
doms9
00000d9c9a e 2025-12-05 04:10:29 -05:00
GitHub Actions Bot
1c377388ac health log 2025-12-05 08:48:10 +00:00
7 changed files with 6574 additions and 6082 deletions

9251
EPG/TV.xml

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,7 @@ from scrapers import (
shark, shark,
sport9, sport9,
streambtw, streambtw,
streameast, streamcenter,
streamfree, streamfree,
strmd, strmd,
tvpass, tvpass,
@ -53,7 +53,7 @@ async def main() -> None:
asyncio.create_task(shark.scrape(network.client)), asyncio.create_task(shark.scrape(network.client)),
asyncio.create_task(sport9.scrape(network.client)), asyncio.create_task(sport9.scrape(network.client)),
asyncio.create_task(streambtw.scrape(network.client)), asyncio.create_task(streambtw.scrape(network.client)),
#asyncio.create_task(streameast.scrape(network.client)), asyncio.create_task(streamcenter.scrape(network.client)),
asyncio.create_task(streamfree.scrape(network.client)), asyncio.create_task(streamfree.scrape(network.client)),
asyncio.create_task(strmd.scrape(network.client)), asyncio.create_task(strmd.scrape(network.client)),
asyncio.create_task(tvpass.scrape(network.client)), asyncio.create_task(tvpass.scrape(network.client)),
@ -71,7 +71,7 @@ async def main() -> None:
| shark.urls | shark.urls
| sport9.urls | sport9.urls
| streambtw.urls | streambtw.urls
| streameast.urls | streamcenter.urls
| strmd.urls | strmd.urls
| streamfree.urls | streamfree.urls
| tvpass.urls | tvpass.urls

View file

@ -1,176 +1,176 @@
from functools import partial from functools import partial
from urllib.parse import urljoin from urllib.parse import urljoin
import httpx import httpx
from playwright.async_api import async_playwright from playwright.async_api import async_playwright
from selectolax.parser import HTMLParser from selectolax.parser import HTMLParser
from .utils import Cache, Time, get_logger, leagues, network from .utils import Cache, Time, get_logger, leagues, network
log = get_logger(__name__) log = get_logger(__name__)
urls: dict[str, dict[str, str | float]] = {} urls: dict[str, dict[str, str | float]] = {}
CACHE_FILE = Cache("streameast.json", exp=10_800) CACHE_FILE = Cache("streameast.json", exp=10_800)
prefixes = { prefixes = {
"ga": None, "ga": None,
"ph": None, "ph": None,
"sg": None, "sg": None,
"ch": None, "ch": None,
"ec": None, "ec": None,
"fi": None, "fi": None,
"ms": None, "ms": None,
"ps": None, "ps": None,
"cf": None, "cf": None,
"sk": None, "sk": None,
"co": "the", "co": "the",
"fun": "the", "fun": "the",
"ru": "the", "ru": "the",
"su": "the", "su": "the",
} }
MIRRORS = [ MIRRORS = [
*[f"https://streameast.{ext}" for ext in prefixes if not prefixes[ext]], *[f"https://streameast.{ext}" for ext in prefixes if not prefixes[ext]],
*[f"https://thestreameast.{ext}" for ext in prefixes if prefixes[ext] == "the"], *[f"https://thestreameast.{ext}" for ext in prefixes if prefixes[ext] == "the"],
] ]
TAG = "STRMEST" TAG = "STRMEST"
async def get_events( async def get_events(
client: httpx.AsyncClient, client: httpx.AsyncClient,
url: str, url: str,
cached_keys: set[str], cached_keys: set[str],
) -> list[dict[str, str]]: ) -> list[dict[str, str]]:
try: try:
r = await client.get(url) r = await client.get(url)
r.raise_for_status() r.raise_for_status()
except Exception as e: except Exception as e:
log.error(f'Failed to fetch "{url}": {e}') log.error(f'Failed to fetch "{url}": {e}')
return [] return []
soup = HTMLParser(r.content) soup = HTMLParser(r.content)
events = [] events = []
now = Time.clean(Time.now()) now = Time.clean(Time.now())
start_dt = now.delta(minutes=-30) start_dt = now.delta(minutes=-30)
end_dt = now.delta(minutes=30) end_dt = now.delta(minutes=30)
for section in soup.css("div.se-sport-section"): for section in soup.css("div.se-sport-section"):
if not (sport := section.attributes.get("data-sport-name", "").strip()): if not (sport := section.attributes.get("data-sport-name", "").strip()):
continue continue
for a in section.css("a.uefa-card"): for a in section.css("a.uefa-card"):
if not (href := a.attributes.get("href")): if not (href := a.attributes.get("href")):
continue continue
link = urljoin(url, href) link = urljoin(url, href)
team_spans = [t.text(strip=True) for t in a.css("span.uefa-name")] team_spans = [t.text(strip=True) for t in a.css("span.uefa-name")]
if len(team_spans) == 2: if len(team_spans) == 2:
name = f"{team_spans[0]} vs {team_spans[1]}" name = f"{team_spans[0]} vs {team_spans[1]}"
elif len(team_spans) == 1: elif len(team_spans) == 1:
name = team_spans[0] name = team_spans[0]
else: else:
continue continue
if not (time_span := a.css_first(".uefa-time")): if not (time_span := a.css_first(".uefa-time")):
continue continue
time_text = time_span.text(strip=True) time_text = time_span.text(strip=True)
timestamp = int(a.attributes.get("data-time", Time.default_8())) timestamp = int(a.attributes.get("data-time", Time.default_8()))
key = f"[{sport}] {name} ({TAG})" key = f"[{sport}] {name} ({TAG})"
if cached_keys & {key}: if cached_keys & {key}:
continue continue
event_dt = Time.from_ts(timestamp) event_dt = Time.from_ts(timestamp)
if time_text == "LIVE" or (start_dt <= event_dt <= end_dt): if time_text == "LIVE" or (start_dt <= event_dt <= end_dt):
events.append( events.append(
{ {
"sport": sport, "sport": sport,
"event": name, "event": name,
"link": link, "link": link,
"timestamp": timestamp, "timestamp": timestamp,
} }
) )
return events return events
async def scrape(client: httpx.AsyncClient) -> None: async def scrape(client: httpx.AsyncClient) -> None:
cached_urls = CACHE_FILE.load() cached_urls = CACHE_FILE.load()
cached_count = len(cached_urls) cached_count = len(cached_urls)
urls.update(cached_urls) urls.update(cached_urls)
log.info(f"Loaded {cached_count} event(s) from cache") log.info(f"Loaded {cached_count} event(s) from cache")
if not (base_url := await network.get_base(MIRRORS)): if not (base_url := await network.get_base(MIRRORS)):
log.warning("No working Streameast mirrors") log.warning("No working Streameast mirrors")
CACHE_FILE.write(cached_urls) CACHE_FILE.write(cached_urls)
return return
log.info(f'Scraping from "{base_url}"') log.info(f'Scraping from "{base_url}"')
events = await get_events( events = await get_events(
client, client,
base_url, base_url,
set(cached_urls.keys()), set(cached_urls.keys()),
) )
log.info(f"Processing {len(events)} new URL(s)") log.info(f"Processing {len(events)} new URL(s)")
if events: if events:
async with async_playwright() as p: async with async_playwright() as p:
browser, context = await network.browser(p, browser="brave") browser, context = await network.browser(p, browser="brave")
for i, ev in enumerate(events, start=1): for i, ev in enumerate(events, start=1):
handler = partial( handler = partial(
network.process_event, network.process_event,
url=ev["link"], url=ev["link"],
url_num=i, url_num=i,
context=context, context=context,
log=log, log=log,
) )
url = await network.safe_process( url = await network.safe_process(
handler, handler,
url_num=i, url_num=i,
log=log, log=log,
) )
if url: if url:
sport, event, ts = ev["sport"], ev["event"], ev["timestamp"] sport, event, ts = ev["sport"], ev["event"], ev["timestamp"]
tvg_id, logo = leagues.get_tvg_info(sport, event) tvg_id, logo = leagues.get_tvg_info(sport, event)
key = f"[{sport}] {event} ({TAG})" key = f"[{sport}] {event} ({TAG})"
entry = { entry = {
"url": url, "url": url,
"logo": logo, "logo": logo,
"base": "https://embedsports.top/", "base": "https://embedsports.top/",
"timestamp": ts, "timestamp": ts,
"id": tvg_id or "Live.Event.us", "id": tvg_id or "Live.Event.us",
} }
urls[key] = cached_urls[key] = entry urls[key] = cached_urls[key] = entry
await browser.close() await browser.close()
if new_count := len(cached_urls) - cached_count: if new_count := len(cached_urls) - cached_count:
log.info(f"Collected and cached {new_count} new event(s)") log.info(f"Collected and cached {new_count} new event(s)")
else: else:
log.info("No new events found") log.info("No new events found")
CACHE_FILE.write(cached_urls) CACHE_FILE.write(cached_urls)

View file

@ -0,0 +1,174 @@
from functools import partial
import httpx
from playwright.async_api import async_playwright
from .utils import Cache, Time, get_logger, leagues, network
log = get_logger(__name__)
urls: dict[str, dict[str, str | float]] = {}
CACHE_FILE = Cache("streamcenter.json", exp=10_800)
API_FILE = Cache("streamcenter-api.json", exp=28_800)
BASE_URL = "https://backendstreamcenter.youshop.pro:488/api/Parties"
TAG = "STRMCNTR"
categories = {
4: "Basketball",
9: "Football",
13: "Baseball",
14: "American Football",
15: "Motor Sport",
16: "Hockey",
17: "Fight MMA",
18: "Boxing",
19: "NCAA Sports",
20: "WWE",
21: "Tennis",
}
async def refresh_api_cache(
client: httpx.AsyncClient,
url: str,
now_ts: float,
) -> list[dict[str, str | int]]:
log.info("Refreshing API cache")
try:
r = await client.get(url, params={"pageNumber": 1, "pageSize": 500})
r.raise_for_status()
except Exception as e:
log.error(f'Failed to fetch "{url}": {e}')
return {}
data = r.json()
data[-1]["timestamp"] = now_ts
return data
async def get_events(
client: httpx.AsyncClient,
cached_keys: set[str],
) -> list[dict[str, str]]:
now = Time.clean(Time.now())
if not (api_data := API_FILE.load(per_entry=False, index=-1)):
api_data = await refresh_api_cache(
client,
BASE_URL,
now.timestamp(),
)
API_FILE.write(api_data)
events = []
start_dt = now.delta(minutes=-30)
end_dt = now.delta(minutes=30)
for stream_group in api_data:
category_id: int = stream_group.get("categoryId")
name: str = stream_group.get("gameName")
iframe: str = stream_group.get("videoUrl")
event_time: str = stream_group.get("beginPartie")
if not (name and category_id and iframe and event_time):
continue
event_dt = Time.from_str(event_time, timezone="UTC")
if not start_dt <= event_dt <= end_dt:
continue
if not (sport := categories.get(category_id)):
continue
key = f"[{sport}] {name} ({TAG})"
if cached_keys & {key}:
continue
events.append(
{
"sport": sport,
"event": name,
"link": iframe.split("<")[0],
"timestamp": event_dt.timestamp(),
}
)
return events
async def scrape(client: httpx.AsyncClient) -> None:
cached_urls = CACHE_FILE.load()
cached_count = len(cached_urls)
urls.update(cached_urls)
log.info(f"Loaded {cached_count} event(s) from cache")
log.info(f'Scraping from "{BASE_URL}"')
events = await get_events(client, set(cached_urls.keys()))
log.info(f"Processing {len(events)} new URL(s)")
if events:
async with async_playwright() as p:
browser, context = await network.browser(p)
for i, ev in enumerate(events, start=1):
handler = partial(
network.process_event,
url=ev["link"],
url_num=i,
context=context,
log=log,
)
url = await network.safe_process(
handler,
url_num=i,
log=log,
)
if url:
sport, event, ts = (
ev["sport"],
ev["event"],
ev["timestamp"],
)
key = f"[{sport}] {event} ({TAG})"
tvg_id, logo = leagues.get_tvg_info(sport, event)
entry = {
"url": url,
"logo": logo,
"base": "https://streamcenter.xyz",
"timestamp": ts,
"id": tvg_id or "Live.Event.us",
}
urls[key] = cached_urls[key] = entry
await browser.close()
if new_count := len(cached_urls) - cached_count:
log.info(f"Collected and cached {new_count} new event(s)")
else:
log.info("No new events found")
CACHE_FILE.write(cached_urls)

View file

@ -1,14 +1,17 @@
## Base Log @ 2025-12-05 03:38 UTC ## Base Log @ 2025-12-05 08:48 UTC
### ✅ Working Streams: 142<br>❌ Dead Streams: 5 ### ✅ Working Streams: 139<br>❌ Dead Streams: 8
| Channel | Error (Code) | Link | | Channel | Error (Code) | Link |
| ------- | ------------ | ---- | | ------- | ------------ | ---- |
| Comet TV | HTTP Error (502) | `http://nocable.cc:8080/91161088/91161088/125831` | | Comedy Central | HTTP Error (403) | `http://nocable.cc:8080/91161088/91161088/7466` |
| FDSN Florida | HTTP Error (403) | `http://nocable.cc:8080/91161088/91161088/46794` | | Disney XD | HTTP Error (403) | `http://nocable.cc:8080/91161088/91161088/75621` |
| HBO Family | HTTP Error (404) | `https://fl1.moveonjoy.com/HBO_FAMILY/index.m3u8` | | FDSN Southwest | HTTP Error (403) | `http://nocable.cc:8080/91161088/91161088/21843` |
| Premier Sports 2 | HTTP Error (403) | `http://109.61.81.147:1935/cdn7/1275/mpegts?token=0abb07758b53c286d3b6b7943b7c5405` | | Fox Sports 2 | HTTP Error (403) | `http://nocable.cc:8080/91161088/91161088/1847` |
| SportsNet New York | HTTP Error (502) | `http://nocable.cc:8080/91161088/91161088/20938` | | Freeform TV | HTTP Error (403) | `http://nocable.cc:8080/91161088/91161088/13370` |
| HBO Comedy | HTTP Error (404) | `http://fl1.moveonjoy.com/HBO_COMEDY/index.m3u8` |
| History Channel | HTTP Error (403) | `http://nocable.cc:8080/91161088/91161088/15017` |
| SportsNet New York | HTTP Error (403) | `http://nocable.cc:8080/91161088/91161088/20938` |
--- ---
#### Base Channels URL #### Base Channels URL
``` ```