This commit is contained in:
doms9 2025-08-19 10:54:50 -04:00
parent 5e66ad6f80
commit 00000d94a5
4 changed files with 109 additions and 100 deletions

View file

@ -1,17 +1,13 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import json import json
from datetime import datetime
from pathlib import Path from pathlib import Path
import httpx import httpx
import pytz
from scrape import fstv, tvpass from scrape import fstv, tvpass
m3u8_file = Path(__file__).parent / "TV.m3u8" m3u8_file = Path(__file__).parent / "TV.m3u8"
base = "http://m3u4u.com/m3u/d5k2nvp8w2t3w2k1n984" base_url = "https://spoo.me/yBR2jV"
current_hour = datetime.now(pytz.timezone("America/New_York")).hour
client = httpx.Client( client = httpx.Client(
timeout=5, timeout=5,
@ -26,10 +22,10 @@ def vanilla_fetch() -> tuple[list[str], int]:
print("Fetching base M3U8") print("Fetching base M3U8")
try: try:
r = client.get(base) r = client.get(base_url)
r.raise_for_status() r.raise_for_status()
except Exception as e: except Exception as e:
raise SystemExit(f'Failed to fetch "{base}"\n{e}') from e raise SystemExit(f'Failed to fetch "{base_url}"\n{e}') from e
d = r.text.splitlines() d = r.text.splitlines()
@ -41,13 +37,7 @@ def vanilla_fetch() -> tuple[list[str], int]:
def main() -> None: def main() -> None:
if current_hour <= 11:
tvpass.main(client) tvpass.main(client)
else:
try:
tvpass.urls = json.loads(tvpass.base_file.read_text(encoding="utf-8"))
except (FileNotFoundError, json.JSONDecodeError):
pass
fstv.main(client) fstv.main(client)

View file

@ -104,8 +104,3 @@ def main(client: httpx.Client) -> None:
urls[key] = link urls[key] = link
print(f"Collected {len(urls)} live events") print(f"Collected {len(urls)} live events")
# if __name__ == "__main__":
# # create client beforehand
# main()

View file

@ -1,15 +1,47 @@
import json import json
import re import re
from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
from urllib.parse import urlparse from urllib.parse import urlparse
import httpx import httpx
import pytz
base_url = "https://tvpass.org/playlist/m3u" base_url = "https://tvpass.org/playlist/m3u"
base_file = Path(__file__).parent / "tvpass.json" base_file = Path(__file__).parent / "tvpass.json"
urls: dict[str, str] = {} urls: dict[str, str] = {}
TZ = pytz.timezone("America/New_York")
def cache_expired(t: float) -> bool:
now = datetime.now(TZ)
r = now.replace(hour=11, minute=0, second=0, microsecond=0)
if now < r:
r -= timedelta(days=1)
return t < r.timestamp()
def load_cache() -> dict[str, str]:
try:
data = json.loads(base_file.read_text(encoding="utf-8"))
ts = data.get("_timestamp", 0)
return {} if cache_expired(ts) else data.get("urls", {})
except (FileNotFoundError, json.JSONDecodeError):
return {}
def save_cache(urls: dict[str, str]) -> None:
payload = {"_timestamp": datetime.now(TZ).timestamp(), "urls": urls}
base_file.write_text(json.dumps(payload, indent=2), encoding="utf-8")
def fetch_m3u8(client: httpx.Client) -> list[str] | None: def fetch_m3u8(client: httpx.Client) -> list[str] | None:
try: try:
@ -22,6 +54,11 @@ def fetch_m3u8(client: httpx.Client) -> list[str] | None:
def main(client: httpx.Client) -> None: def main(client: httpx.Client) -> None:
if cached := load_cache():
urls.update(cached)
print(f"TVPass: Collected {len(urls)} live events from cache")
return
print(f'Scraping from "{base_url}"') print(f'Scraping from "{base_url}"')
if not (data := fetch_m3u8(client)): if not (data := fetch_m3u8(client)):
@ -51,12 +88,6 @@ def main(client: httpx.Client) -> None:
urls[f"[{sport}] {tvg_name}"] = url urls[f"[{sport}] {tvg_name}"] = url
print(f"Collected {len(urls)} live events")
if urls: if urls:
base_file.write_text(json.dumps(urls, indent=2), encoding="utf-8") save_cache(urls)
print(f"Cached {len(urls)} live events")
# if __name__ == "__main__":
# # create client beforehand
# main()

View file

@ -1,12 +1,12 @@
#!/bin/bash #!/bin/bash
main="http://m3u4u.com/m3u/d5k2nvp8w2t3w2k1n984" base_url="https://spoo.me/yBR2jV"
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0"
MAX_JOBS=10 MAX_JOBS=10
RETRY_COUNT=3 RETRY_COUNT=3
README="./readme.md" README="./readme.md"
STATUSLOG=$(mktemp) STATUSLOG=$(mktemp)
PASSED=0 PASSED=0
FAILED=0 FAILED=0
REDIRECTED=0
EMPTY=0 EMPTY=0
get_status() { get_status() {
@ -17,7 +17,7 @@ get_status() {
[[ "$url" != http* ]] && return [[ "$url" != http* ]] && return
for attempt in $(seq 1 "$RETRY_COUNT"); do for attempt in $(seq 1 "$RETRY_COUNT"); do
response=$(curl -sL -o /dev/null --max-time 10 -w "%{http_code}" "$url" 2>&1) response=$(curl -sL -A "$UA" -o /dev/null --max-time 10 -w "%{http_code}" "$url" 2>&1)
[[ "$response" =~ ^[0-9]+$ ]] && break [[ "$response" =~ ^[0-9]+$ ]] && break
sleep 1 sleep 1
done done
@ -36,18 +36,13 @@ get_status() {
case "$status_code" in case "$status_code" in
200) 200)
if ! curl -sL --max-time 5 "$url" | head -c 1 | grep -q '.'; then if ! curl -sL -A "$UA" --max-time 5 "$url" | head -c 1 | grep -q '.'; then
echo "| $channel | Empty body (404) | \`$url\` |" >>"$STATUSLOG" echo "| $channel | Empty body (404) | \`$url\` |" >>"$STATUSLOG"
echo "EMPTY" >>"$STATUSLOG" echo "EMPTY" >>"$STATUSLOG"
else else
echo "PASS" >>"$STATUSLOG" echo "PASS" >>"$STATUSLOG"
fi fi
;; ;;
301 | 302 | 307 | 308)
redirect_url=$(curl -sI --max-time 5 "$url" | grep -i '^Location:' | sed 's/Location: //I' | tr -d '\r\n')
echo "| $channel | Redirect ($status_code) | \`$url$redirect_url\` |" >>"$STATUSLOG"
echo "REDIRECT" >>"$STATUSLOG"
;;
4* | 5*) 4* | 5*)
echo "| $channel | HTTP Error ($status_code) | \`$url\` |" >>"$STATUSLOG" echo "| $channel | HTTP Error ($status_code) | \`$url\` |" >>"$STATUSLOG"
echo "FAIL" >>"$STATUSLOG" echo "FAIL" >>"$STATUSLOG"
@ -64,7 +59,7 @@ get_status() {
} }
check_links() { check_links() {
echo "Checking links from: $main" echo "Checking links from: $base_url"
channel_num=0 channel_num=0
name="" name=""
jobs_running=0 jobs_running=0
@ -83,7 +78,7 @@ check_links() {
get_status "$line" "$name" & get_status "$line" "$name" &
((channel_num++)) ((channel_num++))
fi fi
done < <(curl -sL "$main") done < <(curl -sL -A "$UA" "$base_url")
wait wait
echo "Done." echo "Done."
@ -92,27 +87,25 @@ check_links() {
write_readme() { write_readme() {
local passed redirected empty failed local passed redirected empty failed
passed=$(grep -c '^PASS$' "$STATUSLOG") passed=$(grep -c '^PASS$' "$STATUSLOG")
redirected=$(grep -c '^REDIRECT$' "$STATUSLOG")
empty=$(grep -c '^EMPTY$' "$STATUSLOG") empty=$(grep -c '^EMPTY$' "$STATUSLOG")
failed=$(grep -c '^FAIL$' "$STATUSLOG") failed=$(grep -c '^FAIL$' "$STATUSLOG")
{ {
echo "## Log @ $(date '+%Y-%m-%d %H:%M:%S UTC')" echo "## Log @ $(date '+%Y-%m-%d %H:%M UTC')"
echo echo
echo "### ✅ Working Streams: $passed<br>🔁 Redirected Links: $redirected<br> Empty Streams: $empty<br>❌ Dead Streams: $failed" echo "### ✅ Working Streams: $passed<br> Empty Streams: $empty<br>❌ Dead Streams: $failed"
echo echo
if [ $failed -gt 0 ] || [ $empty -gt 0 ] || [ $redirected -gt 0 ]; then if (($failed > 0 || $empty > 0)); then
head -n 1 "$STATUSLOG" head -n 1 "$STATUSLOG"
grep -v -e '^PASS$' -e '^FAIL$' -e '^EMPTY$' -e '^REDIRECT$' -e '^---' "$STATUSLOG" | grep -v -e '^PASS$' -e '^FAIL$' -e '^EMPTY$' -e '^---' "$STATUSLOG" | grep -v '^| Channel' | sort -u
grep -v '^| Channel' | sort -u
fi fi
echo "---" echo "---"
echo "#### M3U8 URL" echo "#### M3U8 URL"
printf "\`\`\`\nhttps://raw.githubusercontent.com/doms9/iptv/refs/heads/default/M3U8/TV.m3u8\n\`\`\`\n" printf "\`\`\`\nhttps://spoo.me/d9M3U8\n\`\`\`\n"
echo "#### EPG URL" echo "#### EPG URL"
printf "\`\`\`\nhttps://raw.githubusercontent.com/doms9/iptv/refs/heads/default/EPG/TV.xml\n\`\`\`\n" printf "\`\`\`\nhttps://spoo.me/d9EPG\n\`\`\`\n"
echo "---" echo "---"
echo "#### Legal Disclaimer" echo "#### Legal Disclaimer"
echo "This repository lists publicly accessible IPTV streams as found on the internet at the time of checking." echo "This repository lists publicly accessible IPTV streams as found on the internet at the time of checking."