Compare commits

..

No commits in common. "eda2f9e73d6d3615849c2eb9a00a6612346481a2" and "7081c424f41664bceac311951e58655f6a3e2015" have entirely different histories.

7 changed files with 6006 additions and 6498 deletions

9059
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,
streamcenter, streameast,
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(streamcenter.scrape(network.client)), #asyncio.create_task(streameast.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
| streamcenter.urls | streameast.urls
| strmd.urls | strmd.urls
| streamfree.urls | streamfree.urls
| tvpass.urls | tvpass.urls

View file

@ -1,174 +0,0 @@
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,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

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