Compare commits

..

No commits in common. "7ea946f1afb43fb4b63622d0bf2090e0977c2e58" and "f6718196f3c68b1f36f45632b399a560b10b30e2" have entirely different histories.

10 changed files with 125922 additions and 124699 deletions

File diff suppressed because it is too large Load diff

247743
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

@ -14,7 +14,9 @@ from scrapers import (
pawa, pawa,
ppv, ppv,
roxie, roxie,
s2watch,
shark, shark,
sportzone,
streamcenter, streamcenter,
streamhub, streamhub,
streamsgate, streamsgate,
@ -22,7 +24,6 @@ from scrapers import (
totalsportek1, totalsportek1,
totalsportek3, totalsportek3,
tvapp, tvapp,
watchfooty,
webcast, webcast,
) )
from scrapers.utils import get_logger, network from scrapers.utils import get_logger, network
@ -64,6 +65,7 @@ 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(sportzone.scrape(xtrnl_brwsr)),
asyncio.create_task(streamcenter.scrape(hdl_brwsr)), asyncio.create_task(streamcenter.scrape(hdl_brwsr)),
# asyncio.create_task(streamhub.scrape(xtrnl_brwsr)), # asyncio.create_task(streamhub.scrape(xtrnl_brwsr)),
# asyncio.create_task(streamsgate.scrape(xtrnl_brwsr)), # asyncio.create_task(streamsgate.scrape(xtrnl_brwsr)),
@ -75,6 +77,7 @@ async def main() -> None:
asyncio.create_task(istreameast.scrape()), asyncio.create_task(istreameast.scrape()),
# asyncio.create_task(ovogoal.scrape()), # asyncio.create_task(ovogoal.scrape()),
asyncio.create_task(pawa.scrape()), asyncio.create_task(pawa.scrape()),
# asyncio.create_task(s2watch.scrape()),
asyncio.create_task(shark.scrape()), asyncio.create_task(shark.scrape()),
# asyncio.create_task(totalsportek1.scrape()), # asyncio.create_task(totalsportek1.scrape()),
asyncio.create_task(totalsportek3.scrape()), asyncio.create_task(totalsportek3.scrape()),
@ -86,7 +89,6 @@ async def main() -> None:
# others # others
await livetvsx.scrape(xtrnl_brwsr) await livetvsx.scrape(xtrnl_brwsr)
await watchfooty.scrape(xtrnl_brwsr)
finally: finally:
await hdl_brwsr.close() await hdl_brwsr.close()
@ -105,7 +107,9 @@ async def main() -> None:
| pawa.urls | pawa.urls
| ppv.urls | ppv.urls
| roxie.urls | roxie.urls
| s2watch.urls
| shark.urls | shark.urls
| sportzone.urls
| streamcenter.urls | streamcenter.urls
| streamhub.urls | streamhub.urls
| streamsgate.urls | streamsgate.urls
@ -113,7 +117,6 @@ async def main() -> None:
| totalsportek1.urls | totalsportek1.urls
| totalsportek3.urls | totalsportek3.urls
| tvapp.urls | tvapp.urls
| watchfooty.urls
| webcast.urls | webcast.urls
) )

181
M3U8/scrapers/s2watch.py Normal file
View file

@ -0,0 +1,181 @@
#!/usr/bin/env python3
import re
from functools import partial
from urllib.parse import urljoin
from .utils import Cache, Time, get_logger, leagues, network
log = get_logger(__name__)
urls: dict[str, dict[str, str | float]] = {}
TAG = "S2WATCH"
CACHE_FILE = Cache(TAG, exp=10_800)
API_CACHE = Cache(f"{TAG}-api", exp=28_800)
API_URL = "https://s2watch.me/api/v1/schedule/list"
BASE_URL = "https://zarviro.link"
async def process_event(event_id: int, url_num: int) -> tuple[str | None, str | None]:
nones = None, None
if not (
base_api_src := await network.request(
urljoin(BASE_URL, "api/player.php"),
params={"id": event_id},
log=log,
)
):
log.warning(f"URL {url_num}) Failed to get iframe url.")
return nones
if not (embed_url := base_api_src.json().get("url")):
log.warning(f"URL {url_num}) No iframe url available.")
return nones
if not (
event_data := await network.request(
embed_url,
log=log,
headers={
"User-Agent": "curl/8.19.0",
"Referer": f"{base_api_src.url}",
},
)
):
log.warning(f"URL {url_num}) Failed to load iframe url.")
return nones
pattern = re.compile(r'var\s+src\s+=\s+"([^"]*)"', re.I)
if not (match := pattern.search(event_data.text)):
log.warning(f"URL {url_num}) No source found.")
return nones
log.info(f"URL {url_num}) Captured M3U8")
return match[1], embed_url
async def get_events(cached_keys: list[str]) -> list[dict[str, str]]:
now = Time.clean(Time.now())
if not (api_data := API_CACHE.load(per_entry=False, index=-1)):
log.info("Refreshing API cache")
api_data = [{"timestamp": now.timestamp()}]
if r := await network.request(API_URL, log=log):
api_data: list[dict] = r.json().get("matches", [])
api_data[-1]["timestamp"] = now.timestamp()
API_CACHE.write(api_data)
events = []
start_dt = now.delta(minutes=-30)
end_dt = now.delta(minutes=30)
for info in api_data:
team_1, team_2 = info["team1"], info["team2"]
sport = info["league"]
event_name = f"{team_1} vs {team_2}"
if f"[{sport}] {event_name} ({TAG})" in cached_keys:
continue
channels: list[dict] = info["channels"]
if (not channels) or (not (event_id := channels[0].get("number"))):
continue
event_ts = int(f'{info["startTimestamp"]}'[:-3])
event_dt = Time.from_ts(event_ts)
if not start_dt <= event_dt <= end_dt:
continue
events.append(
{
"sport": sport,
"event": event_name,
"event_id": event_id,
"timestamp": event_ts,
}
)
return events
async def scrape() -> None:
cached_urls = CACHE_FILE.load()
valid_urls = {k: v for k, v in cached_urls.items() if v["url"]}
valid_count = cached_count = len(valid_urls)
urls.update(valid_urls)
log.info(f"Loaded {cached_count} event(s) from cache")
log.info('Scraping from "https://s2watch.me"')
if events := await get_events(cached_urls.keys()):
log.info(f"Processing {len(events)} new URL(s)")
for i, ev in enumerate(events, start=1):
handler = partial(
process_event,
event_id=(event_id := ev["event_id"]),
url_num=i,
)
url, iframe = await network.safe_process(
handler,
url_num=i,
semaphore=network.HTTP_S,
log=log,
)
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": iframe,
"timestamp": ts,
"id": tvg_id or "Live.Event.us",
"link": urljoin(BASE_URL, f"ch?id={event_id}"),
"UA": "curl/8.19.0",
}
cached_urls[key] = entry
if url:
valid_count += 1
urls[key] = entry
log.info(f"Collected and cached {valid_count - cached_count} new event(s)")
else:
log.info("No new events found")
CACHE_FILE.write(cached_urls)

154
M3U8/scrapers/sportzone.py Normal file
View file

@ -0,0 +1,154 @@
from functools import partial
from typing import Any
from playwright.async_api import Browser
from .utils import Cache, Time, get_logger, leagues, network
log = get_logger(__name__)
urls: dict[str, dict[str, str | float]] = {}
TAG = "SPRTZONE"
CACHE_FILE = Cache(TAG, exp=5_400)
API_FILE = Cache(f"{TAG}-api", exp=28_800)
API_URL = "https://sportzone.su/data.json"
async def refresh_api_cache(now_ts: float) -> list[dict[str, Any]]:
api_data = [{"timestamp": now_ts}]
if r := await network.request(API_URL, log=log):
api_data: list[dict] = r.json().get("matches", [])
if api_data:
for event in api_data:
event["ts"] = event.pop("timestamp")
api_data[-1]["timestamp"] = now_ts
return api_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 = []
start_dt = now.delta(hours=-3)
end_dt = now.delta(minutes=30)
for stream_group in api_data:
sport = stream_group.get("league")
team_1, team_2 = stream_group.get("team1"), stream_group.get("team2")
if not (sport and team_1 and team_2):
continue
event_name = f"{team_1} vs {team_2}"
if f"[{sport}] {event_name} ({TAG})" in cached_keys:
continue
if not (event_ts := stream_group.get("ts")):
continue
event_dt = Time.from_ts(int(f"{event_ts}"[:-3]))
if not start_dt <= event_dt <= end_dt:
continue
if not (event_channels := stream_group.get("channels")):
continue
if not (event_links := event_channels[0].get("links")):
continue
event_url: str = event_links[0]
events.append(
{
"sport": sport,
"event": event_name,
"link": event_url,
}
)
return events
async def scrape(browser: Browser) -> None:
cached_urls = CACHE_FILE.load()
valid_urls = {k: v for k, v in cached_urls.items() if v["url"]}
valid_count = cached_count = len(valid_urls)
urls.update(valid_urls)
log.info(f"Loaded {cached_count} event(s) from cache")
log.info('Scraping from "https://sportzone.su"')
if events := await get_events(cached_urls.keys()):
log.info(f"Processing {len(events)} new URL(s)")
now = Time.clean(Time.now())
async with network.event_context(browser, stealth=False) as context:
for i, ev in enumerate(events, start=1):
async with network.event_page(context) as page:
handler = partial(
network.process_event,
url=(link := ev["link"]),
url_num=i,
page=page,
log=log,
)
url = await network.safe_process(
handler,
url_num=i,
semaphore=network.PW_S,
log=log,
)
sport, event = ev["sport"], ev["event"]
key = f"[{sport}] {event} ({TAG})"
tvg_id, logo = leagues.get_tvg_info(sport, event)
entry = {
"url": url,
"logo": logo,
"base": "https://vividmosaica.com/",
"timestamp": now.timestamp(),
"id": tvg_id or "Live.Event.us",
"link": link,
}
cached_urls[key] = entry
if url:
valid_count += 1
urls[key] = entry
log.info(f"Collected and cached {valid_count - cached_count} new event(s)")
else:
log.info("No new events found")
CACHE_FILE.write(cached_urls)

View file

@ -18,7 +18,7 @@ CACHE_FILE = Cache(TAG, exp=10_800)
API_FILE = Cache(f"{TAG}-api", exp=19_800) API_FILE = Cache(f"{TAG}-api", exp=19_800)
BASE_URL = "https://streamsgates.io" BASE_URL = "https://streamingon.org"
SPORT_URLS = [ SPORT_URLS = [
urljoin(BASE_URL, f"data/{sport}.json") urljoin(BASE_URL, f"data/{sport}.json")

View file

@ -11,7 +11,7 @@ urls: dict[str, dict[str, str | float]] = {}
TAG = "TIMSTRMS" TAG = "TIMSTRMS"
CACHE_FILE = Cache(TAG, exp=3_600) CACHE_FILE = Cache(TAG, exp=10_800)
API_FILE = Cache(f"{TAG}-api", exp=19_800) API_FILE = Cache(f"{TAG}-api", exp=19_800)
@ -57,8 +57,8 @@ async def get_events(cached_keys: list[str]) -> list[dict[str, str]]:
events = [] events = []
start_dt = now.delta(hours=-3) start_dt = now.delta(minutes=-30)
end_dt = now.delta(minutes=5) end_dt = now.delta(minutes=30)
for info in api_data.get("events", []): for info in api_data.get("events", []):
if (genre := info.get("genre", 999)) not in SPORT_GENRES: if (genre := info.get("genre", 999)) not in SPORT_GENRES:

View file

@ -1,277 +0,0 @@
import asyncio
import json
from functools import partial
from typing import Any
from urllib.parse import urlencode, urljoin
from playwright.async_api import Browser, Page
from .utils import Cache, Time, get_logger, leagues, network
log = get_logger(__name__)
urls: dict[str, dict[str, str | float]] = {}
TAG = "WATCHFTY"
CACHE_FILE = Cache(TAG, exp=10_800)
BASE_DOMAIN = "watchfooty.st"
API_URL, BASE_URL = f"https://api.{BASE_DOMAIN}", f"https://www.{BASE_DOMAIN}"
def build_wfty_url(live: bool, event_id: str = None) -> str:
url = urljoin(
API_URL,
(
"_internal/trpc/sports.getSportsLiveMatchesCount,sports.getPopularMatches,sports.getPopularLiveMatches"
if live
else "_internal/trpc/sports.getSportsLiveMatchesCount,sports.getMatchById"
),
)
input_data = {
"0": {
"json": {
"start": (now := Time.now()).isoformat(),
"end": now.delta(days=1).isoformat(),
}
},
} | (
{
"1": {
"json": None,
"meta": {"values": ["undefined"]},
},
"2": {
"json": None,
"meta": {"values": ["undefined"]},
},
}
if live
else {
"1": {
"json": {
"id": event_id,
"withoutAdditionalInfo": True,
"withoutLinks": False,
}
}
}
)
params = {
"batch": "1",
"input": json.dumps(input_data, separators=(",", ":")),
}
return f"{url}?{urlencode(params)}"
async def pre_process(url: str, url_num: int) -> str | None:
if not (event_data := await network.request(url, log=log)):
return
api_data: dict = (
(event_data.json() or [{}])[-1].get("result", {}).get("data", {}).get("json")
)
if not api_data:
log.warning(f"URL {url_num}) No API data found.")
return
if not (links := api_data.get("fixtureData", {}).get("links")):
log.warning(f"URL {url_num}) No stream links found.")
return
quality_links: list[dict] = sorted(
[link for link in links if (wld := link.get("wld")) and "e" not in wld],
key=lambda x: x.get("viewerCount") or -1,
reverse=True,
)
if not quality_links:
log.warning(f"URL {url_num}) No valid quality link found.")
return
link_data = quality_links[0]
embed_path = (
link_data["gi"],
link_data["t"],
link_data["wld"]["cn"],
link_data["wld"]["sn"],
)
return f"https://sportsembed.su/embed/{'/'.join(embed_path)}"
async def process_event(
url: str,
url_num: int,
page: Page,
) -> str | None:
nones = None, None
captured: list[str] = []
got_one = asyncio.Event()
handler = partial(
network.capture_req,
captured=captured,
got_one=got_one,
)
page.on("request", handler)
if not (iframe_url := await pre_process(url, url_num)):
return nones
try:
resp = await page.goto(
iframe_url,
wait_until="domcontentloaded",
timeout=6_000,
)
if not resp or resp.status != 200:
log.warning(
f"URL {url_num}) Status Code: {resp.status if resp else 'None'}"
)
return nones
wait_task = asyncio.create_task(got_one.wait())
try:
await asyncio.wait_for(wait_task, timeout=6)
except asyncio.TimeoutError:
log.warning(f"URL {url_num}) Timed out waiting for M3U8.")
return nones
finally:
if not wait_task.done():
wait_task.cancel()
try:
await wait_task
except asyncio.CancelledError:
pass
if captured:
log.info(f"URL {url_num}) Captured M3U8")
return captured[0], iframe_url
log.warning(f"URL {url_num}) No M3U8 captured after waiting.")
return nones
except Exception as e:
log.warning(f"URL {url_num}) {e}")
return nones
finally:
page.remove_listener("request", handler)
async def get_events(cached_keys: list[str]) -> list[dict[str, str]]:
events = []
live_url = build_wfty_url(live=True)
if not (live_data := await network.request(live_url, log=log)):
return events
api_data: list[dict[str, Any]] = (
(live_data.json() or [{}])[-1].get("result", {}).get("data", {}).get("json")
)
for link in api_data:
if not link.get("viewerCount"):
continue
event_id: str = link["id"]
event_league: str = link["league"]
event_name: str = link["title"]
if f"[{event_league}] {event_name} ({TAG})" in cached_keys:
continue
events.append(
{
"sport": event_league,
"event": event_name,
"link": build_wfty_url(live=False, event_id=event_id),
}
)
return events
async def scrape(browser: Browser) -> None:
cached_urls = CACHE_FILE.load()
valid_urls = {k: v for k, v in cached_urls.items() if v["url"]}
valid_count = cached_count = len(valid_urls)
urls.update(valid_urls)
log.info(f"Loaded {cached_count} event(s) from cache")
log.info(f'Scraping from "{BASE_URL}"')
if events := await get_events(cached_urls.keys()):
log.info(f"Processing {len(events)} new URL(s)")
now = Time.clean(Time.now())
async with network.event_context(browser, stealth=False) as context:
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, iframe = await network.safe_process(
handler,
url_num=i,
semaphore=network.PW_S,
log=log,
timeout=20,
)
sport, event = ev["sport"], ev["event"]
key = f"[{sport}] {event} ({TAG})"
tvg_id, logo = leagues.get_tvg_info(sport, event)
entry = {
"url": url,
"logo": logo,
"base": iframe,
"timestamp": now.timestamp(),
"id": tvg_id or "Live.Event.us",
"link": link,
}
cached_urls[key] = entry
if url:
valid_count += 1
urls[key] = entry
log.info(f"Collected and cached {valid_count - cached_count} new event(s)")
else:
log.info("No new events found")
CACHE_FILE.write(cached_urls)

View file

@ -1,19 +1,12 @@
## Base Log @ 2026-03-28 09:03 UTC ## Base Log @ 2026-03-27 09:12 UTC
### ✅ Working Streams: 152<br>❌ Dead Streams: 10 ### ✅ Working Streams: 159<br>❌ Dead Streams: 3
| Channel | Error (Code) | Link | | Channel | Error (Code) | Link |
| ------- | ------------ | ---- | | ------- | ------------ | ---- |
| AMC | HTTP Error (404) | `http://41.205.93.154/AMC/index.m3u8` | | Hallmark Family | HTTP Error (000) | `http://mytvstream.net:8080/live/30550113/30550113/9307.m3u8` |
| Altitude Sports | HTTP Error (403) | `http://mytvstream.net:8080/live/30550113/30550113/79545.m3u8` |
| Bravo TV | HTTP Error (404) | `http://41.205.93.154/BRAVO/index.m3u8` |
| ESPN | HTTP Error (404) | `http://41.205.93.154/ESPN/index.m3u8` |
| HBO Family | HTTP Error (000) | `http://mytvstream.net:8080/live/30550113/30550113/17772.m3u8` |
| Hallmark Family | HTTP Error (403) | `http://mytvstream.net:8080/live/30550113/30550113/9307.m3u8` |
| Lifetime | HTTP Error (404) | `http://41.205.93.154/LIFETIME/index.m3u8` |
| NBC Sports California | HTTP Error (403) | `http://mytvstream.net:8080/live/30550113/30550113/20940.m3u8` | | NBC Sports California | HTTP Error (403) | `http://mytvstream.net:8080/live/30550113/30550113/20940.m3u8` |
| NHL Network | HTTP Error (000) | `http://mytvstream.net:8080/live/30550113/30550113/20179.m3u8` | | NHL Network | HTTP Error (000) | `http://mytvstream.net:8080/live/30550113/30550113/20179.m3u8` |
| Spectrum SportsNet LA Dodgers | HTTP Error (403) | `http://mytvstream.net:8080/live/30550113/30550113/31636.m3u8` |
--- ---
#### Base Channels URL #### Base Channels URL
``` ```