Registriere dich noch heute kostenlos, um Mitglied zu werden! Sobald du angemeldet bist, kannst du auf unserer Seite aktiv teilnehmen, indem du deine eigenen Themen und Beiträge erstellst und dich über deinen eigenen Posteingang mit anderen Mitgliedern unterhalten kannst! Zudem bekommst du Zutritt zu Bereichen, welche für Gäste verwehrt bleiben
Registriere dich noch heute kostenlos, um Mitglied zu werden! Sobald du angemeldet bist, kannst du auf unserer Seite aktiv teilnehmen, indem du deine eigenen Themen und Beiträge erstellst und dich über deinen eigenen Posteingang mit anderen Mitgliedern unterhalten kannst! Zudem bekommst du Zutritt zu Bereichen, welche für Gäste verwehrt bleiben
#########################################################################################
EMAIL = ""
PASSWORD = ""
COMPETITION_UID = "3e8ced55-b7a9-48c2-859f-ed2e1003f6b8"
SEASON_UID = "8c0b72e9-a9f3-4fad-823f-d958ae066a26"
#########################################################################################
import json, os, time, re, unicodedata, requests
from datetime import datetime, timezone
SIGNIN_URL = "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword"
REFRESH_URL = "https://securetoken.googleapis.com/v1/token"
API_KEY = "AIzaSyBL6EHNfDVU5kvvoUvsl8u8dmI2_BuYCQM"
TIMEOUT = 20
STREAM_BASE = "https://leagues-live.fra1.cdn.digitaloceanspaces.com"
OUTPATH = "leagues.m3u"
# How many days ahead to include (0 = only today)
EVENTS_LOOKAHEAD_DAYS = 2
SHOW_PAST_TODAY_EVENTS = True
def signin():
url = f"{SIGNIN_URL}?key={API_KEY}"
payload = {
"returnSecureToken": True,
"email": EMAIL,
"password": PASSWORD,
"clientType": "CLIENT_TYPE_WEB"
}
r = requests.post(url, json=payload, timeout=TIMEOUT)
r.raise_for_status()
return r.json()
def refresh(refresh_token):
url = f"{REFRESH_URL}?key={API_KEY}"
form = f"grant_type=refresh_token&refresh_token={requests.utils.quote(refresh_token, safe='')}"
headers = {"content-type": "application/x-www-form-urlencoded"}
r = requests.post(url, headers=headers, data=form, timeout=TIMEOUT)
r.raise_for_status()
return r.json()
def leagues_get(url):
r = requests.get(url, timeout=TIMEOUT)
r.raise_for_status()
return r.json()
def get_next_possible_month(cid):
return leagues_get(f"https://www.leagues.football/json/2/fixture/calendar/{cid}/nextPossibleMonth.json")
def get_calendar_for(cid, y, m):
return leagues_get(f"https://www.leagues.football/json/2/fixture/calendar/{cid}/{y}/{m}.json")
def get_fixture_single(fid):
return leagues_get(f"https://www.leagues.football/json/2/fixture/single/{fid}.json")
def _normalize_for_slug(s):
s = s.lower().replace("♀"," w ").replace("ä","ae").replace("ö","oe").replace("ü","ue").replace("ß","ss")
s = unicodedata.normalize("NFKD", s)
s = "".join(c for c in s if not unicodedata.combining(c))
return re.sub(r"[^a-z0-9]","",s)
def make_cdn_slug_from_title(title):
if not title:
return None
parts = re.split(r"\s+vs\s+", title, flags=re.IGNORECASE, maxsplit=1)
base = _normalize_for_slug(parts[0].strip())
if not base.endswith("w"):
base += "w"
return base
def make_event_id(title: str) -> str:
if not title: return ""
return re.sub(r"[^a-z0-9]","",title.lower())
def parse_iso_to_epoch(s):
if not s:
return None
try:
dt = datetime.fromisoformat(s)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return int(dt.timestamp())
except Exception:
return None
def build_league_channels(month_meta, fixtures_detail):
fixtures=[]
for guid,det in fixtures_detail.items():
title = det.get("name") or det.get("title")
slug = make_cdn_slug_from_title(title)
stream_url = f"{STREAM_BASE}/{slug}/hd/stream.m3u8" if slug else None
fixtures.append({
"title": title,
"start": det.get("startDate"),
"start_ts": parse_iso_to_epoch(det.get("startDate")),
"stream_url": stream_url
})
fixtures.sort(key=lambda x:(x.get("start_ts") is None,x.get("start_ts") or 0))
return fixtures
def filter_channels_for_export(channels_json: list) -> list:
now = int(time.time())
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
today_start = int(today.timestamp())
today_end = today_start + 24*60*60 - 1
end_limit = now + (EVENTS_LOOKAHEAD_DAYS * 86400) + 86399
out = []
for ch in channels_json:
start_ts = ch.get("start_ts") or 0
if not start_ts:
continue
if not SHOW_PAST_TODAY_EVENTS and start_ts < today_start:
continue
if EVENTS_LOOKAHEAD_DAYS == 0:
if not (today_start <= start_ts <= today_end):
continue
else:
if start_ts > end_limit:
continue
out.append(ch)
out.sort(key=lambda x: (x.get("start_ts") is None, x.get("start_ts") or 0))
return out
def _escape_attr(s: str) -> str:
return (s or "").replace('"', r'\"')
def export_m3u(channels: list, outpath: str = OUTPATH) -> str:
lines = ["#EXTM3U"]
for ch in channels:
title = ch.get("title") or "Match"
tvg_id = make_event_id(title)
start_ts = ch.get("start_ts")
display_dt = ""
if start_ts:
try:
dt = datetime.fromtimestamp(start_ts)
display_dt = f" ({dt.strftime('%Y-%m-%d %H:%M')})"
except Exception:
pass
display_name = f"{title}{display_dt}"
tvg_name = display_name
group = "Leagues"
logo = ""
url = ch.get("stream_url")
if not url:
continue
extinf = (
f'#EXTINF:-1 tvg-id="{_escape_attr(tvg_id)}" '
f'tvg-name="{_escape_attr(tvg_name)}" '
f'tvg-logo="{_escape_attr(logo)}" '
f'group-title="{_escape_attr(group)}",{display_name}'
)
lines.append(extinf)
lines.append(url)
content = "\n".join(lines) + "\n"
with open(outpath, "w", encoding="utf-8") as f:
f.write(content)
return outpath
def main():
s = signin()
refresh(s["refreshToken"])
next_month = get_next_possible_month(COMPETITION_UID)
cal = get_calendar_for(COMPETITION_UID, next_month["year"], str(next_month["month"]).zfill(2))
fixture_details = {}
for _, entries in cal.items():
for entry in entries:
fid = entry.get("fixture_guid")
if fid:
fixture_details[fid] = get_fixture_single(fid)
channels_doc = build_league_channels(next_month, fixture_details)
filtered = filter_channels_for_export(channels_doc)
outpath = export_m3u(filtered, OUTPATH)
print(f"[OK] Wrote {len(filtered)} entries to {outpath}")
if __name__ == "__main__":
main()
Folge dem Video um zu sehen, wie unsere Website als Web-App auf dem Startbildschirm installiert werden kann.
Anmerkung: Diese Funktion ist in einigen Browsern möglicherweise nicht verfügbar.
Das Digital Eliteboard ist ein kostenloses Forum und ist auf Spenden angewiesen, um sich auch in Zukunft selbst zu finanzieren. Wenn auch du mit dem Digital Eliteboard zufrieden bist, würden wir uns über jede Unterstützung freuen.
Hier kannst du uns unterstützen SPENDEN