From 1d752b82d07944c9c22af589f07b9c437e44be3f Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Thu, 10 Jul 2025 21:12:35 +0300 Subject: [PATCH 01/10] Update tests/__data__ --- .../expected/api_generate/.api/streams.json | 2 +- .../__data__/expected/playlist_format/nl.m3u | 6 +- .../.gh-pages/categories/undefined.m3u | 10 +- .../.gh-pages/countries/ad.m3u | 2 +- .../.gh-pages/countries/undefined.m3u | 8 +- .../.gh-pages/index.category.m3u | 10 +- .../.gh-pages/index.country.m3u | 10 +- .../.gh-pages/index.language.m3u | 10 +- .../playlist_generate/.gh-pages/index.m3u | 10 +- .../.gh-pages/index.region.m3u | 12 +- .../.gh-pages/languages/undefined.m3u | 8 +- .../.gh-pages/regions/emea.m3u | 2 +- .../.gh-pages/regions/eur.m3u | 2 +- .../.gh-pages/regions/undefined.m3u | 8 +- .../.gh-pages/sources/ad.m3u | 7 + .../.gh-pages/sources/ca.m3u | 3 + .../.gh-pages/sources/in.m3u | 3 + .../.gh-pages/sources/kg.m3u | 3 + .../.gh-pages/sources/uk.m3u | 3 + .../.gh-pages/sources/unsorted.m3u | 15 ++ .../playlist_generate/logs/generators.log | 6 + .../__data__/expected/playlist_update/fr.m3u | 2 - .../__data__/expected/playlist_update/uk.m3u | 8 +- .../__data__/expected/playlist_update/us.m3u | 9 +- .../expected/readme_update/_readme.md | 16 ++ tests/__data__/input/api_generate/ad.m3u | 2 +- tests/__data__/input/data/channels.json | 137 ++++++++---------- tests/__data__/input/data/logos.json | 11 ++ .../input/{playlist_update => }/issues.js | 10 +- tests/__data__/input/playlist_format/nl.m3u | 6 +- .../input/playlist_generate/unsorted.m3u | 6 +- .../input/playlist_validate/wrong_id.m3u | 6 + .../input/readme_update/.readme/template.md | 16 ++ .../input/readme_update/generators.log | 5 + 34 files changed, 253 insertions(+), 121 deletions(-) create mode 100644 tests/__data__/expected/playlist_generate/.gh-pages/sources/ad.m3u create mode 100644 tests/__data__/expected/playlist_generate/.gh-pages/sources/ca.m3u create mode 100644 tests/__data__/expected/playlist_generate/.gh-pages/sources/in.m3u create mode 100644 tests/__data__/expected/playlist_generate/.gh-pages/sources/kg.m3u create mode 100644 tests/__data__/expected/playlist_generate/.gh-pages/sources/uk.m3u create mode 100644 tests/__data__/expected/playlist_generate/.gh-pages/sources/unsorted.m3u create mode 100644 tests/__data__/input/data/logos.json rename tests/__data__/input/{playlist_update => }/issues.js (97%) diff --git a/tests/__data__/expected/api_generate/.api/streams.json b/tests/__data__/expected/api_generate/.api/streams.json index 55128bca36..62a824dd24 100644 --- a/tests/__data__/expected/api_generate/.api/streams.json +++ b/tests/__data__/expected/api_generate/.api/streams.json @@ -16,7 +16,7 @@ { "channel": "AndorraTV.ad", "feed": "SD", - "url": "https://iptv-all.lanesh4d0w.repl.co/andorra/atv", + "url": "https://iptv-all.lanesh4d0w.repl.co/andorra/atv|Referer=\"https://referer.xyz/\"|User-Agent=\"Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1\"|Origin=\"https://origin.xyz\"", "referrer": null, "user_agent": null }, diff --git a/tests/__data__/expected/playlist_format/nl.m3u b/tests/__data__/expected/playlist_format/nl.m3u index d08a2ac80c..0c8f812e58 100644 --- a/tests/__data__/expected/playlist_format/nl.m3u +++ b/tests/__data__/expected/playlist_format/nl.m3u @@ -6,6 +6,10 @@ http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo2/npo2.i #EXTINF:-1 tvg-id="NPO2.nl" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",NPO 2 (302p) [Geo-blocked] #EXTVLCOPT:http-referrer=http://imn.iq #EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 -http://stream.tvtap.net:8081/live/nl-npo2.stream/playlist.m3u8 +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://stream.tvtap.net:8081/live/nl-npo2.stream/playlist.m3u8?|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0+(iPhone;+CPU+iPhone+OS+17_7+like+Mac+OS+X)+AppleWebKit/605.1.15+(KHTML,+like+Gecko)+Version/18.0+Mobile/15E148+Safari/604.1"|Origin="https://origin.xyz" #EXTINF:-1 tvg-id="NPO2.nl",NPO 2 [Geo-blocked] http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo2/npo22.isml/.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/categories/undefined.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/categories/undefined.m3u index cfdce7d0bf..caf15c4bfb 100644 --- a/tests/__data__/expected/playlist_generate/.gh-pages/categories/undefined.m3u +++ b/tests/__data__/expected/playlist_generate/.gh-pages/categories/undefined.m3u @@ -2,10 +2,14 @@ #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7] #EXTVLCOPT:http-referrer=http://imn.iq #EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 -http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8 -#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="" group-title="Undefined",ATV +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" +#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Undefined",ATV https://iptv-all.lanesh4d0w.repl.co/andorra/atv -#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="" group-title="Undefined",ATV HD +#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/countries/ad.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/countries/ad.m3u index 9bc95be27d..e85626ca16 100644 --- a/tests/__data__/expected/playlist_generate/.gh-pages/countries/ad.m3u +++ b/tests/__data__/expected/playlist_generate/.gh-pages/countries/ad.m3u @@ -1,3 +1,3 @@ #EXTM3U -#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="" group-title="Undefined",ATV +#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Undefined",ATV https://iptv-all.lanesh4d0w.repl.co/andorra/atv diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/countries/undefined.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/countries/undefined.m3u index eb0364cd58..09cc3aba3e 100644 --- a/tests/__data__/expected/playlist_generate/.gh-pages/countries/undefined.m3u +++ b/tests/__data__/expected/playlist_generate/.gh-pages/countries/undefined.m3u @@ -2,8 +2,12 @@ #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7] #EXTVLCOPT:http-referrer=http://imn.iq #EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 -http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8 -#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="" group-title="Undefined",ATV HD +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" +#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/index.category.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/index.category.m3u index b3235a876c..011bfa110e 100644 --- a/tests/__data__/expected/playlist_generate/.gh-pages/index.category.m3u +++ b/tests/__data__/expected/playlist_generate/.gh-pages/index.category.m3u @@ -12,10 +12,14 @@ http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7] #EXTVLCOPT:http-referrer=http://imn.iq #EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 -http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8 -#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="" group-title="Undefined",ATV +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" +#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Undefined",ATV https://iptv-all.lanesh4d0w.repl.co/andorra/atv -#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="" group-title="Undefined",ATV HD +#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/index.country.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/index.country.m3u index 332df0029b..4315263b02 100644 --- a/tests/__data__/expected/playlist_generate/.gh-pages/index.country.m3u +++ b/tests/__data__/expected/playlist_generate/.gh-pages/index.country.m3u @@ -1,5 +1,5 @@ #EXTM3U -#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="" group-title="Andorra",ATV +#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Andorra",ATV https://iptv-all.lanesh4d0w.repl.co/andorra/atv #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Canada",Meteomedia http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 @@ -22,8 +22,12 @@ http://146.59.85.40:89/dunaworld/index.m3u8 #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7] #EXTVLCOPT:http-referrer=http://imn.iq #EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 -http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8 -#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="" group-title="Undefined",ATV HD +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" +#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/index.language.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/index.language.m3u index e9f88f8ac2..8dcfa46db3 100644 --- a/tests/__data__/expected/playlist_generate/.gh-pages/index.language.m3u +++ b/tests/__data__/expected/playlist_generate/.gh-pages/index.language.m3u @@ -1,5 +1,5 @@ #EXTM3U -#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="" group-title="Catalan",ATV +#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Catalan",ATV https://iptv-all.lanesh4d0w.repl.co/andorra/atv #EXTINF:-1 tvg-id="BBCNews.uk" tvg-logo="https://raw.githubusercontent.com/Tapiosinn/tv-logos/master/countries/united-kingdom/bbc-news-uk.png" group-title="English",BBC News HD http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8 @@ -8,8 +8,12 @@ http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8 #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7] #EXTVLCOPT:http-referrer=http://imn.iq #EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 -http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8 -#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="" group-title="Undefined",ATV HD +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" +#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/index.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/index.m3u index fbf8cd7556..071cf1fb5e 100644 --- a/tests/__data__/expected/playlist_generate/.gh-pages/index.m3u +++ b/tests/__data__/expected/playlist_generate/.gh-pages/index.m3u @@ -2,10 +2,14 @@ #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7] #EXTVLCOPT:http-referrer=http://imn.iq #EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 -http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8 -#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="" group-title="Undefined",ATV +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" +#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Undefined",ATV https://iptv-all.lanesh4d0w.repl.co/andorra/atv -#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="" group-title="Undefined",ATV HD +#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd #EXTINF:-1 tvg-id="BBCNews.uk" tvg-logo="https://raw.githubusercontent.com/Tapiosinn/tv-logos/master/countries/united-kingdom/bbc-news-uk.png" group-title="General;News",BBC News HD http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/index.region.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/index.region.m3u index aa3534ff75..ce19fbf0c8 100644 --- a/tests/__data__/expected/playlist_generate/.gh-pages/index.region.m3u +++ b/tests/__data__/expected/playlist_generate/.gh-pages/index.region.m3u @@ -11,13 +11,13 @@ http://gohoski.fvds.ru:3000/mediabay/162/index.m3u8 http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8 #EXTINF:-1 tvg-id="ElTR.kg" tvg-logo="https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png" group-title="Commonwealth of Independent States",ЭлТР (480p) [Not 24/7] http://gohoski.fvds.ru:3000/mediabay/162/index.m3u8 -#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="" group-title="Europe",ATV +#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Europe",ATV https://iptv-all.lanesh4d0w.repl.co/andorra/atv #EXTINF:-1 tvg-id="LDPRTV.ru" tvg-logo="https://iptvx.one/icn/ldpr-tv.png" group-title="Europe",ЛДПР ТВ (1080p) http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8 #EXTINF:-1 tvg-id="ElTR.kg" tvg-logo="https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png" group-title="Europe",ЭлТР (480p) [Not 24/7] http://gohoski.fvds.ru:3000/mediabay/162/index.m3u8 -#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="" group-title="Europe, the Middle East and Africa",ATV +#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Europe, the Middle East and Africa",ATV https://iptv-all.lanesh4d0w.repl.co/andorra/atv #EXTINF:-1 tvg-id="LDPRTV.ru" tvg-logo="https://iptvx.one/icn/ldpr-tv.png" group-title="Europe, the Middle East and Africa",ЛДПР ТВ (1080p) http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8 @@ -34,8 +34,12 @@ http://146.59.85.40:89/dunaworld/index.m3u8 #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7] #EXTVLCOPT:http-referrer=http://imn.iq #EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 -http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8 -#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="" group-title="Undefined",ATV HD +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" +#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/languages/undefined.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/languages/undefined.m3u index 0eeed846f3..72161123c9 100644 --- a/tests/__data__/expected/playlist_generate/.gh-pages/languages/undefined.m3u +++ b/tests/__data__/expected/playlist_generate/.gh-pages/languages/undefined.m3u @@ -2,8 +2,12 @@ #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7] #EXTVLCOPT:http-referrer=http://imn.iq #EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 -http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8 -#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="" group-title="Undefined",ATV HD +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" +#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/regions/emea.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/regions/emea.m3u index 10fbb43f94..76ae384e78 100644 --- a/tests/__data__/expected/playlist_generate/.gh-pages/regions/emea.m3u +++ b/tests/__data__/expected/playlist_generate/.gh-pages/regions/emea.m3u @@ -1,5 +1,5 @@ #EXTM3U -#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="" group-title="Undefined",ATV +#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Undefined",ATV https://iptv-all.lanesh4d0w.repl.co/andorra/atv #EXTINF:-1 tvg-id="LDPRTV.ru" tvg-logo="https://iptvx.one/icn/ldpr-tv.png" group-title="General",ЛДПР ТВ (1080p) http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/regions/eur.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/regions/eur.m3u index 10fbb43f94..76ae384e78 100644 --- a/tests/__data__/expected/playlist_generate/.gh-pages/regions/eur.m3u +++ b/tests/__data__/expected/playlist_generate/.gh-pages/regions/eur.m3u @@ -1,5 +1,5 @@ #EXTM3U -#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="" group-title="Undefined",ATV +#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Undefined",ATV https://iptv-all.lanesh4d0w.repl.co/andorra/atv #EXTINF:-1 tvg-id="LDPRTV.ru" tvg-logo="https://iptvx.one/icn/ldpr-tv.png" group-title="General",ЛДПР ТВ (1080p) http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/regions/undefined.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/regions/undefined.m3u index eb0364cd58..09cc3aba3e 100644 --- a/tests/__data__/expected/playlist_generate/.gh-pages/regions/undefined.m3u +++ b/tests/__data__/expected/playlist_generate/.gh-pages/regions/undefined.m3u @@ -2,8 +2,12 @@ #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7] #EXTVLCOPT:http-referrer=http://imn.iq #EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 -http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8 -#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="" group-title="Undefined",ATV HD +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" +#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/sources/ad.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/sources/ad.m3u new file mode 100644 index 0000000000..308931ec61 --- /dev/null +++ b/tests/__data__/expected/playlist_generate/.gh-pages/sources/ad.m3u @@ -0,0 +1,7 @@ +#EXTM3U +#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD +https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd +#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Undefined",ATV +https://iptv-all.lanesh4d0w.repl.co/andorra/atv +#EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="" group-title="Undefined",Zoo (720p) +https://iptv-all.lanesh4d0w.repl.co/andorra/zoo diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/sources/ca.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/sources/ca.m3u new file mode 100644 index 0000000000..03a6963a33 --- /dev/null +++ b/tests/__data__/expected/playlist_generate/.gh-pages/sources/ca.m3u @@ -0,0 +1,3 @@ +#EXTM3U +#EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia +http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/sources/in.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/sources/in.m3u new file mode 100644 index 0000000000..d2223daac4 --- /dev/null +++ b/tests/__data__/expected/playlist_generate/.gh-pages/sources/in.m3u @@ -0,0 +1,3 @@ +#EXTM3U +#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV +http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/sources/kg.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/sources/kg.m3u new file mode 100644 index 0000000000..847591ab04 --- /dev/null +++ b/tests/__data__/expected/playlist_generate/.gh-pages/sources/kg.m3u @@ -0,0 +1,3 @@ +#EXTM3U +#EXTINF:-1 tvg-id="ElTR.kg" tvg-logo="https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png" group-title="General",ЭлТР (480p) [Not 24/7] +http://gohoski.fvds.ru:3000/mediabay/162/index.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/sources/uk.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/sources/uk.m3u new file mode 100644 index 0000000000..85c75b78bf --- /dev/null +++ b/tests/__data__/expected/playlist_generate/.gh-pages/sources/uk.m3u @@ -0,0 +1,3 @@ +#EXTM3U +#EXTINF:-1 tvg-id="BBCNews.uk" tvg-logo="https://raw.githubusercontent.com/Tapiosinn/tv-logos/master/countries/united-kingdom/bbc-news-uk.png" group-title="General;News",BBC News HD +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/sources/unsorted.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/sources/unsorted.m3u new file mode 100644 index 0000000000..52eb85ee4e --- /dev/null +++ b/tests/__data__/expected/playlist_generate/.gh-pages/sources/unsorted.m3u @@ -0,0 +1,15 @@ +#EXTM3U +#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7] +#EXTVLCOPT:http-referrer=http://imn.iq +#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" +#EXTINF:-1 tvg-id="DunaWorld.hu" tvg-logo="https://i.imgur.com/uOBQJZS.png" group-title="Undefined",Duna World (576i) +http://146.59.85.40:89/dunaworld/index.m3u8 +#EXTINF:-1 tvg-id="LDPRTV.ru" tvg-logo="https://iptvx.one/icn/ldpr-tv.png" group-title="General",ЛДПР ТВ (1080p) +http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8 +#EXTINF:-1 tvg-id="VisitXTV.nl" tvg-logo="https://i.imgur.com/RJ9wbNF.jpg" group-title="XXX",Visit-X TV +https://stream.visit-x.tv/vxtv/ngrp:live_all/30fps.m3u8 diff --git a/tests/__data__/expected/playlist_generate/logs/generators.log b/tests/__data__/expected/playlist_generate/logs/generators.log index 720731423f..3095463f62 100644 --- a/tests/__data__/expected/playlist_generate/logs/generators.log +++ b/tests/__data__/expected/playlist_generate/logs/generators.log @@ -1,3 +1,9 @@ +{"type":"source","filepath":"sources/ad.m3u","count":3} +{"type":"source","filepath":"sources/ca.m3u","count":1} +{"type":"source","filepath":"sources/in.m3u","count":1} +{"type":"source","filepath":"sources/kg.m3u","count":1} +{"type":"source","filepath":"sources/uk.m3u","count":1} +{"type":"source","filepath":"sources/unsorted.m3u","count":4} {"type":"category","filepath":"categories/auto.m3u","count":0} {"type":"category","filepath":"categories/cooking.m3u","count":0} {"type":"category","filepath":"categories/comedy.m3u","count":0} diff --git a/tests/__data__/expected/playlist_update/fr.m3u b/tests/__data__/expected/playlist_update/fr.m3u index f4f93afdcf..f5d19f31ca 100644 --- a/tests/__data__/expected/playlist_update/fr.m3u +++ b/tests/__data__/expected/playlist_update/fr.m3u @@ -1,5 +1,3 @@ #EXTM3U #EXTINF:-1 tvg-id="TFX.fr" http-referrer="https://pkpakiplay.xyz/" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",TFX -#EXTVLCOPT:http-referrer=https://pkpakiplay.xyz/ -#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1 https://stitcher-ipv4.pluto.tv/v1/stitch/embed/hls/channel/64c109a4798def0008a6e03e/master.m3u8?advertisingId={PSID}&appVersion=unknown&deviceDNT={TARGETOPT}&deviceId={PSID}&deviceLat=0&deviceLon=0&deviceMake=samsung&deviceModel=samsung&deviceType=samsung-tvplus&deviceVersion=unknown&embedPartner=samsung-tvplus&profileFloor=&profileLimit=&samsung_app_domain={APP_DOMAIN}&samsung_app_name={APP_NAME}&us_privacy=1YNY diff --git a/tests/__data__/expected/playlist_update/uk.m3u b/tests/__data__/expected/playlist_update/uk.m3u index a60ea6210e..1932a92c71 100644 --- a/tests/__data__/expected/playlist_update/uk.m3u +++ b/tests/__data__/expected/playlist_update/uk.m3u @@ -4,4 +4,10 @@ http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8 #EXTINF:-1 tvg-id="BBCNews.uk",BBC News HD (480p) [Geo-blocked] http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/playlist.m3u8 #EXTINF:-1 tvg-id="BeanoTV.uk",Beano TV -https://a5b4bacecd47433dad06d3189fc7422e.mediatailor.us-east-1.amazonaws.com/v1/manifest/04fd913bb278d8775298c26fdca9d9841f37601f/RakutenTV-eu_BeanoTV/b1f233d5-847c-437d-aa4f-f73e67a85323/2.m3u8 +#EXTVLCOPT:http-referrer=http://imn.iq +#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +https://a5b4bacecd47433dad06d3189fc7422e.mediatailor.us-east-1.amazonaws.com/v1/manifest/04fd913bb278d8775298c26fdca9d9841f37601f/RakutenTV-eu_BeanoTV/b1f233d5-847c-437d-aa4f-f73e67a85323/2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" diff --git a/tests/__data__/expected/playlist_update/us.m3u b/tests/__data__/expected/playlist_update/us.m3u index 8102b65792..1f24e747a4 100644 --- a/tests/__data__/expected/playlist_update/us.m3u +++ b/tests/__data__/expected/playlist_update/us.m3u @@ -1,6 +1,11 @@ #EXTM3U #EXTINF:-1 tvg-id="BBCAmerica.us@East" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edge/12.246",BBC America East (720p) -#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edge/12.246 -https://xui-backend.energeek.cl/live/9/playlist.m3u8?username=ZZDemoIPTVGH&password=mdo96EuqMkTR +#EXTVLCOPT:http-referrer=http://imn.iq +#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +https://xui-backend.energeek.cl/live/9/playlist.m3u8?username=ZZDemoIPTVGH&password=mdo96EuqMkTR|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" #EXTINF:-1 tvg-id="FastTV.us",Fast TV https://3fa797d5.wurl.com/manifest/f36d25e7e52f1ba8d7e56eb859c636563214f541/T05PX01vdG9yVHJlbmRGYXN0VFZfSExT/b5e5e0e2-12b3-4312-93c9-c0a7c50b41ca/4.m3u8 diff --git a/tests/__data__/expected/readme_update/_readme.md b/tests/__data__/expected/readme_update/_readme.md index 67b094c3de..be2cfb8ede 100644 --- a/tests/__data__/expected/readme_update/_readme.md +++ b/tests/__data__/expected/readme_update/_readme.md @@ -214,6 +214,22 @@ Same thing, but split up into separate files: +### Grouped by sources + +Playlists in which channels are grouped by broadcast source. + +
+Expand +
+ +To use the playlist, simply replace `` in the link below with the name of one of the files in the [streams](streams) folder. + +``` +https://iptv-org.github.io/iptv/sources/.m3u +``` + +
+ ## Database All channel data is taken from the [iptv-org/database](https://github.com/iptv-org/database) repository. If you find any errors please open a new [issue](https://github.com/iptv-org/database/issues) there. diff --git a/tests/__data__/input/api_generate/ad.m3u b/tests/__data__/input/api_generate/ad.m3u index 9d8926e0ea..9cb32a9b9e 100644 --- a/tests/__data__/input/api_generate/ad.m3u +++ b/tests/__data__/input/api_generate/ad.m3u @@ -2,4 +2,4 @@ #EXTINF:-1 tvg-id="Zoo.ad",Zoo (720p) https://iptv-all.lanesh4d0w.repl.co/andorra/zoo #EXTINF:-1 tvg-id="AndorraTV.ad@SD",ATV -https://iptv-all.lanesh4d0w.repl.co/andorra/atv +https://iptv-all.lanesh4d0w.repl.co/andorra/atv|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" diff --git a/tests/__data__/input/data/channels.json b/tests/__data__/input/data/channels.json index d12451f91b..250052dfa7 100644 --- a/tests/__data__/input/data/channels.json +++ b/tests/__data__/input/data/channels.json @@ -7,8 +7,7 @@ "subdivision": null, "city": null, "categories": [], - "is_nsfw": false, - "logo": null + "is_nsfw": false }, { "id": "BBCNews.uk", @@ -21,8 +20,7 @@ "news", "general" ], - "is_nsfw": false, - "logo": "https://raw.githubusercontent.com/Tapiosinn/tv-logos/master/countries/united-kingdom/bbc-news-uk.png" + "is_nsfw": false }, { "id": "Eve.us", @@ -32,8 +30,7 @@ "subdivision": null, "city": null, "categories": [], - "is_nsfw": false, - "logo": "https://www.lyngsat.com/logo/tv/ee/eve_us.png" + "is_nsfw": false }, { "id": "EverydayHeroes.us", @@ -43,8 +40,7 @@ "subdivision": null, "city": null, "categories": [], - "is_nsfw": false, - "logo": "https://i.imgur.com/Iam3ol3.png" + "is_nsfw": false }, { "id": "FoxSports1.us", @@ -54,8 +50,7 @@ "subdivision": null, "city": null, "categories": [], - "is_nsfw": false, - "logo": "https://cdn.tvpassport.com/image/station/100x100/fs1.png" + "is_nsfw": false }, { "id": "FoxSports2.us", @@ -65,8 +60,7 @@ "subdivision": null, "city": null, "categories": [], - "is_nsfw": false, - "logo": null + "is_nsfw": false }, { "id": "LDPRTV.ru", @@ -78,8 +72,7 @@ "categories": [ "general" ], - "is_nsfw": false, - "logo": "https://iptvx.one/icn/ldpr-tv.png" + "is_nsfw": false }, { "id": "LibyasChannel.ly", @@ -89,8 +82,7 @@ "subdivision": null, "city": null, "categories": [], - "is_nsfw": false, - "logo": "https://i.imgur.com/RD9wbNF.jpg" + "is_nsfw": false }, { "id": "MeteoMedia.ca", @@ -102,8 +94,7 @@ "categories": [ "weather" ], - "is_nsfw": false, - "logo": "https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" + "is_nsfw": false }, { "id": "TVN.pl", @@ -113,8 +104,7 @@ "subdivision": null, "city": null, "categories": [], - "is_nsfw": false, - "logo": "https://www.sms.cz/kategorie/televize/bmp/loga/velka/TVN.png" + "is_nsfw": false }, { "id": "VisitXTV.nl", @@ -126,8 +116,7 @@ "categories": [ "xxx" ], - "is_nsfw": true, - "logo": "https://i.imgur.com/RJ9wbNF.jpg" + "is_nsfw": true }, { "id": "TUTV.us", @@ -139,8 +128,7 @@ "categories": [ "general" ], - "is_nsfw": false, - "logo": "https://i.imgur.com/RJ9wbN4.jpg" + "is_nsfw": false }, { "id": "ManoramaNews.in", @@ -160,8 +148,7 @@ "launched": "2006-08-17", "closed": null, "replaced_by": null, - "website": "https://www.manoramanews.com", - "logo": "https://i.imgur.com/adjRrVx.png" + "website": "https://www.manoramanews.com" }, { "id": "TV3.my", @@ -179,8 +166,7 @@ "launched": null, "closed": null, "replaced_by": null, - "website": null, - "logo": "https://i.imgur.com/adjRrVx.png" + "website": null }, { "id": "BBCAmerica.us", @@ -198,8 +184,7 @@ "launched": null, "closed": null, "replaced_by": null, - "website": null, - "logo": "https://i.imgur.com/adjRrVx.png" + "website": null }, { "id": "ATVPlus.pe", @@ -219,8 +204,7 @@ "launched": null, "closed": null, "replaced_by": null, - "website": "https://www.atv.pe/", - "logo": "https://i.imgur.com/fY9256H.png" + "website": "https://www.atv.pe/" }, { "id": "BeanoTV.uk", @@ -238,8 +222,7 @@ "launched": null, "closed": null, "replaced_by": null, - "website": "https://www.beanostudios.com/", - "logo": "https://i.imgur.com/IKHFZDJ.png" + "website": "https://www.beanostudios.com/" }, { "id": "ATX.jp", @@ -261,8 +244,7 @@ "launched": "1997-12-24", "closed": null, "replaced_by": null, - "website": "http://www.at-x.com/", - "logo": "https://i.imgur.com/turWday.png" + "website": "http://www.at-x.com/" }, { "id": "AajTak.in", @@ -282,8 +264,7 @@ "launched": "2000-12-31", "closed": null, "replaced_by": null, - "website": "https://www.aajtak.in/livetv", - "logo": "https://i.imgur.com/RpIdCB3.png" + "website": "https://www.aajtak.in/livetv" }, { "id": "Weathernews.jp", @@ -301,8 +282,7 @@ "launched": null, "closed": null, "replaced_by": null, - "website": null, - "logo": "https://i.imgur.com/A8uRSTS.png" + "website": null }, { "id": "MethTV.lk", @@ -322,8 +302,7 @@ "launched": null, "closed": null, "replaced_by": null, - "website": "https://methtv.lk/", - "logo": "https://i.imgur.com/jZaOFxI.png" + "website": "https://methtv.lk/" }, { "id": "Match4.hu", @@ -341,8 +320,7 @@ "launched": null, "closed": null, "replaced_by": null, - "website": null, - "logo": "https://i.imgur.com/hzexBLX.png" + "website": null }, { "id": "FastTV.us", @@ -363,8 +341,7 @@ "launched": "2022-10-26", "closed": null, "replaced_by": null, - "website": "https://watch.motortrend.com/", - "logo": "https://us1-prod-images.disco-api.com/2020/7/13/4f26aaf2-a993-480d-9675-0d501fb8d86f.png?bf=0&f=png&p=true&q=85&w=250" + "website": "https://watch.motortrend.com/" }, { "id": "MCOTHD.th", @@ -386,8 +363,7 @@ "launched": "1955-06-24", "closed": null, "replaced_by": null, - "website": "https://tv.mcot.net/mcothd", - "logo": "https://i.imgur.com/ejPxGmU.png" + "website": "https://tv.mcot.net/mcothd" }, { "id": "AmarinTV.th", @@ -409,8 +385,7 @@ "launched": "2012-12-01", "closed": null, "replaced_by": null, - "website": "https://www.amarintv.com/", - "logo": "https://static.wikia.nocookie.net/logopedia/images/f/ff/Amarin_TV_2015.svg/revision/latest/scale-to-width-down/512" + "website": "https://www.amarintv.com/" }, { "id": "France2.fr", @@ -430,8 +405,7 @@ "launched": "1959-09-10", "closed": null, "replaced_by": null, - "website": "https://www.france.tv/france-2/", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/France_2_2018.svg/512px-France_2_2018.svg.png" + "website": "https://www.france.tv/france-2/" }, { "id": "France3.fr", @@ -451,8 +425,7 @@ "launched": "1972-12-31", "closed": null, "replaced_by": null, - "website": "https://www.france.tv/france-3/", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/France_3_2018.svg/512px-France_3_2018.svg.png" + "website": "https://www.france.tv/france-3/" }, { "id": "France5.fr", @@ -472,8 +445,7 @@ "launched": "1994-12-13", "closed": null, "replaced_by": null, - "website": "https://www.france.tv/france-5/", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/50/France_5_2018.svg/512px-France_5_2018.svg.png" + "website": "https://www.france.tv/france-5/" }, { "id": "France3.fr", @@ -493,8 +465,7 @@ "launched": "1962-01-01", "closed": null, "replaced_by": null, - "website": "https://france3-regions.francetvinfo.fr/nouvelle-aquitaine/", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/France_3_Aquitaine_-_Logo_2018.svg/512px-France_3_Aquitaine_-_Logo_2018.svg.png" + "website": "https://france3-regions.francetvinfo.fr/nouvelle-aquitaine/" }, { "id": "TF1.fr", @@ -514,8 +485,7 @@ "launched": "1975-01-06", "closed": null, "replaced_by": null, - "website": "https://www.tf1.fr", - "logo": "https://i.imgur.com/QxHt9NC.png" + "website": "https://www.tf1.fr" }, { "id": "TF1SeriesFilms.fr", @@ -537,8 +507,7 @@ "launched": null, "closed": null, "replaced_by": null, - "website": "https://www.tf1.fr/tf1-series-films", - "logo": "https://i.imgur.com/4CJIDKQ.png" + "website": "https://www.tf1.fr/tf1-series-films" }, { "id": "TMC.fr", @@ -558,8 +527,7 @@ "launched": "1954-11-19", "closed": null, "replaced_by": null, - "website": "https://www.tf1.fr/tmc", - "logo": "https://upload.wikimedia.org/wikipedia/en/thumb/5/54/TMC_logo.svg/512px-TMC_logo.svg.png" + "website": "https://www.tf1.fr/tmc" }, { "id": "TFX.fr", @@ -575,8 +543,7 @@ "launched": null, "closed": null, "replaced_by": null, - "website": "https://www.tf1.fr/tfx", - "logo": "https://i.imgur.com/d91GcVf.png" + "website": "https://www.tf1.fr/tfx" }, { "id": "ManoramaNews.in", @@ -596,8 +563,7 @@ "launched": "2006-08-17", "closed": null, "replaced_by": null, - "website": "https://www.manoramanews.com", - "logo": "https://i.imgur.com/adjRrVx.png" + "website": "https://www.manoramanews.com" }, { "id": "13thStreet.au", @@ -617,8 +583,7 @@ "launched": "2009-11-15", "closed": "2019-12-31", "replaced_by": "Sleuth.au", - "website": "http://www.13thstreet.com.au/", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/13th_street_logo_uk_master_rgb_black.png/512px-13th_street_logo_uk_master_rgb_black.png" + "website": "http://www.13thstreet.com.au/" }, { "id": "Zoo.ad", @@ -628,8 +593,7 @@ "subdivision": null, "city": null, "categories": [], - "is_nsfw": false, - "logo": null + "is_nsfw": false }, { "id": "DunaWorld.hu", @@ -641,8 +605,7 @@ "categories": [ "General" ], - "is_nsfw": false, - "logo": "https://i.imgur.com/uOBQJZS.png" + "is_nsfw": false }, { "id": "ElTR.kg", @@ -675,8 +638,7 @@ "launched": null, "closed": null, "replaced_by": null, - "website": "https://eltr.kg", - "logo": "https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png" + "website": "https://eltr.kg" }, { "id": "IONTV.us", @@ -686,7 +648,26 @@ "subdivision": null, "city": null, "categories": [], - "is_nsfw": false, - "logo": "https://i.imgur.com/Iam3ol3.png" + "is_nsfw": false + }, + { + "id": "NPO1.nl", + "name": "NPO 1", + "network": null, + "country": "NL", + "subdivision": null, + "city": null, + "categories": [], + "is_nsfw": false + }, + { + "id": "NPO2.nl", + "name": "NPO 2", + "network": null, + "country": "NL", + "subdivision": null, + "city": null, + "categories": [], + "is_nsfw": false } ] \ No newline at end of file diff --git a/tests/__data__/input/data/logos.json b/tests/__data__/input/data/logos.json new file mode 100644 index 0000000000..ead8324b71 --- /dev/null +++ b/tests/__data__/input/data/logos.json @@ -0,0 +1,11 @@ +[ + {"channel":"MeteoMedia.ca","feed":null,"tags":[],"width":512,"height":512,"format":"PNG","url":"https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png"}, + {"channel":"LDPRTV.ru","feed":null,"tags":[],"width":512,"height":512,"format":"PNG","url":"https://iptvx.one/icn/ldpr-tv.png"}, + {"channel":"ElTR.kg","feed":null,"tags":[],"width":512,"height":512,"format":"PNG","url":"https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png"}, + {"channel":"BBCNews.uk","feed":null,"tags":[],"width":512,"height":512,"format":"PNG","url":"https://raw.githubusercontent.com/Tapiosinn/tv-logos/master/countries/united-kingdom/bbc-news-uk.png"}, + {"channel":"DunaWorld.hu","feed":null,"tags":[],"width":512,"height":512,"format":"PNG","url":"https://i.imgur.com/uOBQJZS.png"}, + {"channel":"VisitXTV.nl","feed":null,"tags":[],"width":512,"height":512,"format":"JPEG","url":"https://i.imgur.com/RJ9wbNF.jpg"}, + {"channel":"AndorraTV.ad","feed":"SD","tags":[],"width":512,"height":512,"format":"PNG","url":"https://i.imgur.com/BnhTn8i.png"}, + {"channel":"AndorraTV.ad","feed":null,"tags":[],"width":1000,"height":1000,"format":"JPEG","url":"https://i.imgur.com/AnhTn8i.png"}, + {"channel":"AndorraTV.ad","feed":null,"tags":[],"width":512,"height":512,"format":"PNG","url":"https://i.imgur.com/CnhTn8i.png"} +] \ No newline at end of file diff --git a/tests/__data__/input/playlist_update/issues.js b/tests/__data__/input/issues.js similarity index 97% rename from tests/__data__/input/playlist_update/issues.js rename to tests/__data__/input/issues.js index 8feff1416f..2dae6dfff6 100644 --- a/tests/__data__/input/playlist_update/issues.js +++ b/tests/__data__/input/issues.js @@ -1515,7 +1515,7 @@ module.exports = [ closed_at: null, author_association: 'NONE', active_lock_reason: null, - body: '### Stream ID\r\n\r\nBeanoTV.uk\r\n\r\n### Stream URL (optional)\r\n\r\nhttps://a5b4bacecd47433dad06d3189fc7422e.mediatailor.us-east-1.amazonaws.com/v1/manifest/04fd913bb278d8775298c26fdca9d9841f37601f/RakutenTV-eu_BeanoTV/b1f233d5-847c-437d-aa4f-f73e67a85323/2.m3u8\r\n\r\n### Notes (optional)\r\n\r\n_No response_\r\n\r\n### Please confirm the following\r\n\r\n- [X] I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md#request-a-channel)', + body: '### Stream ID\r\n\r\nBeanoTV.uk\r\n\r\n### Stream URL (optional)\r\n\r\nhttps://a5b4bacecd47433dad06d3189fc7422e.mediatailor.us-east-1.amazonaws.com/v1/manifest/04fd913bb278d8775298c26fdca9d9841f37601f/RakutenTV-eu_BeanoTV/b1f233d5-847c-437d-aa4f-f73e67a85323/2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"\r\n\r\n### Directives (optional)\r\n\r\n#EXTVLCOPT:http-referrer=http://imn.iq\r\n\r\n#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148\r\n\r\n#KODIPROP:inputstream=inputstream.adaptive\r\n\r\n#KODIPROP:inputstream.adaptive.manifest_type=mpd\r\n\r\n#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha\r\n\r\n#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|\r\n\r\n### Notes (optional)\r\n\r\n_No response_\r\n\r\n### Please confirm the following\r\n\r\n- [X] I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md#request-a-channel)', reactions: { url: 'https://api.github.com/repos/iptv-org/iptv/issues/13751/reactions', total_count: 0, @@ -1664,7 +1664,7 @@ module.exports = [ { id: 5923508587, node_id: 'LA_kwDOCWUK8M8AAAABYRGRaw', - url: 'https://api.github.com/repos/iptv-org/iptv/labels/streams:add', + url: 'https://api.github.com/repos/iptv-org/iptv/labels/streams:edit', name: 'streams:edit', color: '017ff9', default: false, @@ -1743,7 +1743,7 @@ module.exports = [ { id: 5923508587, node_id: 'LA_kwDOCWUK8M8AAAABYRGRaw', - url: 'https://api.github.com/repos/iptv-org/iptv/labels/streams:add', + url: 'https://api.github.com/repos/iptv-org/iptv/labels/streams:edit', name: 'streams:edit', color: '017ff9', default: false, @@ -1761,7 +1761,7 @@ module.exports = [ closed_at: null, author_association: 'COLLABORATOR', active_lock_reason: null, - body: '### Stream URL (required)\n\nhttps://servilive.com:3126/live/tele2000live.m3u8\n\n### New Stream URL\n\nhttps://xui-backend.energeek.cl/live/9/playlist.m3u8?username=ZZDemoIPTVGH&password=mdo96EuqMkTR\n\n### Stream ID\n\nBBCAmerica.us@East\n\n### Quality\n\n720p\n\n### Label\n\n~\n\n### HTTP User-Agent\n\nMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edge/12.246\n\n### HTTP Referrer\n\n~\n\n### Notes\n\n_No response_\n\n### Contributing Guide\n\n- [X] I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md)', + body: '### Stream URL (required)\n\nhttps://servilive.com:3126/live/tele2000live.m3u8\n\n### New Stream URL\n\nhttps://xui-backend.energeek.cl/live/9/playlist.m3u8?username=ZZDemoIPTVGH&password=mdo96EuqMkTR|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"\n\n### Stream ID\n\nBBCAmerica.us@East\n\n### Quality\n\n720p\n\n### Label\n\n~\n\n### HTTP User-Agent\n\nMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edge/12.246\n\n### HTTP Referrer\n\n~\n\n### Directives (optional)\r\n\r\n#EXTVLCOPT:http-referrer=http://imn.iq\r\n\r\n#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148\r\n\r\n#KODIPROP:inputstream=inputstream.adaptive\r\n\r\n#KODIPROP:inputstream.adaptive.manifest_type=mpd\r\n\r\n#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha\r\n\r\n#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|\n\n### Notes\n\n_No response_\n\n### Contributing Guide\n\n- [X] I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md)', reactions: { url: 'https://api.github.com/repos/iptv-org/iptv/issues/14110/reactions', total_count: 0, @@ -1822,7 +1822,7 @@ module.exports = [ { id: 5923508587, node_id: 'LA_kwDOCWUK8M8AAAABYRGRaw', - url: 'https://api.github.com/repos/iptv-org/iptv/labels/streams:add', + url: 'https://api.github.com/repos/iptv-org/iptv/labels/streams:edit', name: 'streams:edit', color: '017ff9', default: false, diff --git a/tests/__data__/input/playlist_format/nl.m3u b/tests/__data__/input/playlist_format/nl.m3u index 39d56a3ef4..1d12f3b3e1 100644 --- a/tests/__data__/input/playlist_format/nl.m3u +++ b/tests/__data__/input/playlist_format/nl.m3u @@ -2,7 +2,11 @@ #EXTINF:-1 tvg-id="NPO2.nl",NPO 2 (302p) [Geo-blocked] #EXTVLCOPT:http-referrer=http://imn.iq #EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 -http://stream.tvtap.net:8081/live/nl-npo2.stream/playlist.m3u8? +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://stream.tvtap.net:8081/live/nl-npo2.stream/playlist.m3u8?|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" #EXTINF:-1 tvg-id="NPO2.nl",NPO 2 [Geo-blocked] http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo2/npo22.isml/.m3u8 #EXTINF:-1 tvg-id="NPO2.nl",NPO 2 (342p) diff --git a/tests/__data__/input/playlist_generate/unsorted.m3u b/tests/__data__/input/playlist_generate/unsorted.m3u index 826414ad22..4310ac744a 100644 --- a/tests/__data__/input/playlist_generate/unsorted.m3u +++ b/tests/__data__/input/playlist_generate/unsorted.m3u @@ -6,6 +6,10 @@ https://stream.visit-x.tv/vxtv/ngrp:live_all/30fps.m3u8 #EXTINF:-1 tvg-id="" user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7] #EXTVLCOPT:http-referrer=http://imn.iq #EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 -http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8 +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" #EXTINF:-1 tvg-id="DunaWorld.hu",Duna World (576i) http://146.59.85.40:89/dunaworld/index.m3u8 diff --git a/tests/__data__/input/playlist_validate/wrong_id.m3u b/tests/__data__/input/playlist_validate/wrong_id.m3u index dc7ab8504f..94d9cc2bc8 100644 --- a/tests/__data__/input/playlist_validate/wrong_id.m3u +++ b/tests/__data__/input/playlist_validate/wrong_id.m3u @@ -1,3 +1,9 @@ #EXTM3U #EXTINF:-1 tvg-id="qib22lAq1L.us",ABC (720p) +#EXTVLCOPT:http-referrer=http://imn.iq +#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| https://example.com/playlist2.m3u8 diff --git a/tests/__data__/input/readme_update/.readme/template.md b/tests/__data__/input/readme_update/.readme/template.md index 4883a98e4f..68aaa68b7c 100644 --- a/tests/__data__/input/readme_update/.readme/template.md +++ b/tests/__data__/input/readme_update/.readme/template.md @@ -115,6 +115,22 @@ Same thing, but split up into separate files: +### Grouped by sources + +Playlists in which channels are grouped by broadcast source. + +
+Expand +
+ +To use the playlist, simply replace `` in the link below with the name of one of the files in the [streams](streams) folder. + +``` +https://iptv-org.github.io/iptv/sources/.m3u +``` + +
+ ## Database All channel data is taken from the [iptv-org/database](https://github.com/iptv-org/database) repository. If you find any errors please open a new [issue](https://github.com/iptv-org/database/issues) there. diff --git a/tests/__data__/input/readme_update/generators.log b/tests/__data__/input/readme_update/generators.log index 55adb62b06..62325ede73 100644 --- a/tests/__data__/input/readme_update/generators.log +++ b/tests/__data__/input/readme_update/generators.log @@ -1,3 +1,8 @@ +{"type":"source","filepath":"sources/ad.m3u","count":3} +{"type":"source","filepath":"sources/ca.m3u","count":1} +{"type":"source","filepath":"sources/in.m3u","count":1} +{"type":"source","filepath":"sources/kg.m3u","count":1} +{"type":"source","filepath":"sources/uk.m3u","count":1} {"type":"category","filepath":"categories/auto.m3u","count":0} {"type":"category","filepath":"categories/animation.m3u","count":0} {"type":"category","filepath":"categories/business.m3u","count":0} From 9de968a18d3079f3a4d4d996f20e5dba7964f3fc Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Thu, 10 Jul 2025 21:12:54 +0300 Subject: [PATCH 02/10] Update tests --- tests/commands/playlist/format.test.ts | 5 +++-- tests/commands/playlist/test.test.ts | 4 ++-- tests/commands/report/create.test.ts | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/commands/playlist/format.test.ts b/tests/commands/playlist/format.test.ts index f6c6312528..e71d6f52a1 100644 --- a/tests/commands/playlist/format.test.ts +++ b/tests/commands/playlist/format.test.ts @@ -4,9 +4,10 @@ import * as fs from 'fs-extra' import { glob } from 'glob' import os from 'os' -let ENV_VAR = 'STREAMS_DIR=tests/__data__/output/streams' +let ENV_VAR = 'STREAMS_DIR=tests/__data__/output/streams DATA_DIR=tests/__data__/input/data' if (os.platform() === 'win32') { - ENV_VAR = 'SET "STREAMS_DIR=tests/__data__/output/streams" &&' + ENV_VAR = + 'SET "STREAMS_DIR=tests/__data__/output/streams" && SET "DATA_DIR=tests/__data__/input/data" &&' } beforeEach(() => { diff --git a/tests/commands/playlist/test.test.ts b/tests/commands/playlist/test.test.ts index 813ed401d4..39907eb80b 100644 --- a/tests/commands/playlist/test.test.ts +++ b/tests/commands/playlist/test.test.ts @@ -6,9 +6,9 @@ type ExecError = { stdout: string } -let ENV_VAR = 'ROOT_DIR=tests/__data__/input' +let ENV_VAR = 'ROOT_DIR=tests/__data__/input DATA_DIR=tests/__data__/input/data' if (os.platform() === 'win32') { - ENV_VAR = 'SET "ROOT_DIR=tests/__data__/input" &&' + ENV_VAR = 'SET "ROOT_DIR=tests/__data__/input" && SET "DATA_DIR=tests/__data__/input/data" &&' } describe('playlist:test', () => { diff --git a/tests/commands/report/create.test.ts b/tests/commands/report/create.test.ts index 3f6bcbb9ff..954063dbb9 100644 --- a/tests/commands/report/create.test.ts +++ b/tests/commands/report/create.test.ts @@ -24,7 +24,8 @@ describe('report:create', () => { │ 3 │ 14178 │ 'streams:add' │ 'TV3.my' │ 'https://live-streams-ssai-01.tonton.com.my/live/2dd2b7cd-1b34-4871-b669-57b5c9beca23/live.isml/.m3u8...' │ 'blocked' │ │ 4 │ 16120 │ 'streams:remove' │ undefined │ 'http://190.61.102.67:2000/play/a038/index.m3u8' │ 'wrong_link' │ │ 5 │ 19956 │ 'channel search' │ 'CNBCe.tr' │ undefined │ 'invalid_id' │ -│ 6 │ 20956 │ 'channel search' │ 'IONTV.us' │ undefined │ 'fulfilled' │ +│ 6 │ 19957 │ 'channel search' │ '13thStreet.au' │ undefined │ 'closed' │ +│ 7 │ 20956 │ 'channel search' │ 'IONTV.us' │ undefined │ 'fulfilled' │ └─────────┴─────────────┴──────────────────┴─────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────┴───────────────┘`) ).toBe(true) }) From acb19e72eeebc15e29faf1fb84f25706c72478fb Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Thu, 10 Jul 2025 21:13:43 +0300 Subject: [PATCH 03/10] Update scripts --- scripts/commands/api/generate.ts | 4 +- scripts/commands/api/load.ts | 1 + scripts/commands/playlist/edit.ts | 15 ++- scripts/commands/playlist/format.ts | 6 +- scripts/commands/playlist/generate.ts | 12 +- scripts/commands/playlist/test.ts | 6 +- scripts/commands/playlist/update.ts | 6 +- scripts/commands/playlist/validate.ts | 4 +- scripts/commands/report/create.ts | 6 +- scripts/core/dataLoader.ts | 3 + scripts/core/dataProcessor.ts | 51 +++++---- scripts/core/issueData.ts | 4 +- scripts/core/issueLoader.ts | 2 +- scripts/core/issueParser.ts | 3 +- scripts/core/playlistParser.ts | 11 +- scripts/generators/categoriesGenerator.ts | 5 +- scripts/generators/countriesGenerator.ts | 2 +- scripts/generators/index.ts | 9 +- scripts/generators/indexCategoryGenerator.ts | 2 +- scripts/generators/indexCountryGenerator.ts | 2 +- scripts/generators/indexGenerator.ts | 8 +- scripts/generators/indexLanguageGenerator.ts | 2 +- scripts/generators/indexNsfwGenerator.ts | 2 +- scripts/generators/indexRegionGenerator.ts | 2 +- scripts/generators/languagesGenerator.ts | 2 +- scripts/generators/regionsGenerator.ts | 2 +- scripts/generators/sourcesGenerator.ts | 44 ++++++++ scripts/models/channel.ts | 46 +++++++- scripts/models/index.ts | 1 + scripts/models/logo.ts | 40 +++++++ scripts/models/stream.ts | 110 ++++++++++++++----- scripts/types/channel.d.ts | 2 - scripts/types/dataLoader.d.ts | 1 + scripts/types/dataProcessor.d.ts | 1 + scripts/types/logo.d.ts | 9 ++ scripts/types/stream.d.ts | 1 + 36 files changed, 342 insertions(+), 85 deletions(-) create mode 100644 scripts/generators/sourcesGenerator.ts create mode 100644 scripts/models/logo.ts create mode 100644 scripts/types/logo.d.ts diff --git a/scripts/commands/api/generate.ts b/scripts/commands/api/generate.ts index f264260f9b..14967e87f5 100644 --- a/scripts/commands/api/generate.ts +++ b/scripts/commands/api/generate.ts @@ -13,13 +13,15 @@ async function main() { const dataStorage = new Storage(DATA_DIR) const dataLoader = new DataLoader({ storage: dataStorage }) const data: DataLoaderData = await dataLoader.load() - const { channelsKeyById, feedsGroupedByChannelId }: DataProcessorData = processor.process(data) + const { channelsKeyById, feedsGroupedByChannelId, logosGroupedByStreamId }: DataProcessorData = + processor.process(data) logger.info('loading streams...') const streamsStorage = new Storage(STREAMS_DIR) const parser = new PlaylistParser({ storage: streamsStorage, channelsKeyById, + logosGroupedByStreamId, feedsGroupedByChannelId }) const files = await streamsStorage.list('**/*.m3u') diff --git a/scripts/commands/api/load.ts b/scripts/commands/api/load.ts index 3fdc70043c..e4d89120a2 100644 --- a/scripts/commands/api/load.ts +++ b/scripts/commands/api/load.ts @@ -15,6 +15,7 @@ async function main() { loader.download('regions.json'), loader.download('subdivisions.json'), loader.download('feeds.json'), + loader.download('logos.json'), loader.download('timezones.json'), loader.download('guides.json'), loader.download('streams.json') diff --git a/scripts/commands/playlist/edit.ts b/scripts/commands/playlist/edit.ts index d87590b1f2..035d03c217 100644 --- a/scripts/commands/playlist/edit.ts +++ b/scripts/commands/playlist/edit.ts @@ -49,11 +49,20 @@ export default async function main(filepath: string) { const dataStorage = new Storage(DATA_DIR) const loader = new DataLoader({ storage: dataStorage }) const data: DataLoaderData = await loader.load() - const { channels, channelsKeyById, feedsGroupedByChannelId }: DataProcessorData = - processor.process(data) + const { + channels, + channelsKeyById, + feedsGroupedByChannelId, + logosGroupedByStreamId + }: DataProcessorData = processor.process(data) logger.info('loading streams...') - const parser = new PlaylistParser({ storage, feedsGroupedByChannelId, channelsKeyById }) + const parser = new PlaylistParser({ + storage, + feedsGroupedByChannelId, + logosGroupedByStreamId, + channelsKeyById + }) parsedStreams = await parser.parseFile(filepath) const streamsWithoutId = parsedStreams.filter((stream: Stream) => !stream.id) diff --git a/scripts/commands/playlist/format.ts b/scripts/commands/playlist/format.ts index 43868b73e3..2d5c80aa3e 100644 --- a/scripts/commands/playlist/format.ts +++ b/scripts/commands/playlist/format.ts @@ -16,14 +16,16 @@ async function main() { const dataStorage = new Storage(DATA_DIR) const loader = new DataLoader({ storage: dataStorage }) const data: DataLoaderData = await loader.load() - const { channelsKeyById, feedsGroupedByChannelId }: DataProcessorData = processor.process(data) + const { channelsKeyById, feedsGroupedByChannelId, logosGroupedByStreamId }: DataProcessorData = + processor.process(data) logger.info('loading streams...') const streamsStorage = new Storage(STREAMS_DIR) const parser = new PlaylistParser({ storage: streamsStorage, channelsKeyById, - feedsGroupedByChannelId + feedsGroupedByChannelId, + logosGroupedByStreamId }) const files = program.args.length ? program.args : await streamsStorage.list('**/*.m3u') let streams = await parser.parse(files) diff --git a/scripts/commands/playlist/generate.ts b/scripts/commands/playlist/generate.ts index b903b5a435..87f2b9ee33 100644 --- a/scripts/commands/playlist/generate.ts +++ b/scripts/commands/playlist/generate.ts @@ -14,7 +14,8 @@ import { CountriesGenerator, LanguagesGenerator, RegionsGenerator, - IndexGenerator + IndexGenerator, + SourcesGenerator } from '../../generators' async function main() { @@ -28,6 +29,7 @@ async function main() { const data: DataLoaderData = await loader.load() const { feedsGroupedByChannelId, + logosGroupedByStreamId, channelsKeyById, categories, countries, @@ -39,15 +41,18 @@ async function main() { const parser = new PlaylistParser({ storage: streamsStorage, feedsGroupedByChannelId, + logosGroupedByStreamId, channelsKeyById }) const files = await streamsStorage.list('**/*.m3u') let streams = await parser.parse(files) const totalStreams = streams.count() + logger.info(`found ${totalStreams} streams`) + + logger.info('filtering streams...') streams = streams.uniqBy((stream: Stream) => stream.hasId() ? stream.getChannelId() + stream.getFeedId() : uniqueId() ) - logger.info(`found ${totalStreams} streams (including ${streams.count()} unique)`) logger.info('sorting streams...') streams = streams.orderBy( @@ -79,6 +84,9 @@ async function main() { logFile }).generate() + logger.info('generating sources/...') + await new SourcesGenerator({ streams, logFile }).generate() + logger.info('generating index.m3u...') await new IndexGenerator({ streams, logFile }).generate() diff --git a/scripts/commands/playlist/test.ts b/scripts/commands/playlist/test.ts index 777c19f8d8..5c307fc45e 100644 --- a/scripts/commands/playlist/test.ts +++ b/scripts/commands/playlist/test.ts @@ -61,14 +61,16 @@ async function main() { const dataStorage = new Storage(DATA_DIR) const loader = new DataLoader({ storage: dataStorage }) const data: DataLoaderData = await loader.load() - const { channelsKeyById, feedsGroupedByChannelId }: DataProcessorData = processor.process(data) + const { channelsKeyById, feedsGroupedByChannelId, logosGroupedByStreamId }: DataProcessorData = + processor.process(data) logger.info('loading streams...') const rootStorage = new Storage(ROOT_DIR) const parser = new PlaylistParser({ storage: rootStorage, channelsKeyById, - feedsGroupedByChannelId + feedsGroupedByChannelId, + logosGroupedByStreamId }) const files = program.args.length ? program.args : await rootStorage.list(`${STREAMS_DIR}/*.m3u`) streams = await parser.parse(files) diff --git a/scripts/commands/playlist/update.ts b/scripts/commands/playlist/update.ts index 4393714735..afe6101fc7 100644 --- a/scripts/commands/playlist/update.ts +++ b/scripts/commands/playlist/update.ts @@ -20,13 +20,15 @@ async function main() { const dataStorage = new Storage(DATA_DIR) const dataLoader = new DataLoader({ storage: dataStorage }) const data: DataLoaderData = await dataLoader.load() - const { channelsKeyById, feedsGroupedByChannelId }: DataProcessorData = processor.process(data) + const { channelsKeyById, feedsGroupedByChannelId, logosGroupedByStreamId }: DataProcessorData = + processor.process(data) logger.info('loading streams...') const streamsStorage = new Storage(STREAMS_DIR) const parser = new PlaylistParser({ storage: streamsStorage, feedsGroupedByChannelId, + logosGroupedByStreamId, channelsKeyById }) const files = await streamsStorage.list('**/*.m3u') @@ -168,6 +170,7 @@ async function addStreams({ const quality = data.getString('quality') || null const httpUserAgent = data.getString('httpUserAgent') || null const httpReferrer = data.getString('httpReferrer') || null + const directives = data.getArray('directives') || [] const stream = new Stream({ channel: channelId, @@ -176,6 +179,7 @@ async function addStreams({ url: streamUrl, user_agent: httpUserAgent, referrer: httpReferrer, + directives, quality, label }) diff --git a/scripts/commands/playlist/validate.ts b/scripts/commands/playlist/validate.ts index cf77973843..bc5c3b435b 100644 --- a/scripts/commands/playlist/validate.ts +++ b/scripts/commands/playlist/validate.ts @@ -26,6 +26,7 @@ async function main() { const { channelsKeyById, feedsGroupedByChannelId, + logosGroupedByStreamId, blocklistRecordsGroupedByChannelId }: DataProcessorData = processor.process(data) @@ -34,7 +35,8 @@ async function main() { const parser = new PlaylistParser({ storage: rootStorage, channelsKeyById, - feedsGroupedByChannelId + feedsGroupedByChannelId, + logosGroupedByStreamId }) const files = program.args.length ? program.args : await rootStorage.list('streams/**/*.m3u') const streams = await parser.parse(files) diff --git a/scripts/commands/report/create.ts b/scripts/commands/report/create.ts index 61b84501f9..03704ac83b 100644 --- a/scripts/commands/report/create.ts +++ b/scripts/commands/report/create.ts @@ -21,6 +21,7 @@ async function main() { const { channelsKeyById, feedsGroupedByChannelId, + logosGroupedByStreamId, blocklistRecordsGroupedByChannelId }: DataProcessorData = processor.process(data) @@ -29,7 +30,8 @@ async function main() { const parser = new PlaylistParser({ storage: streamsStorage, channelsKeyById, - feedsGroupedByChannelId + feedsGroupedByChannelId, + logosGroupedByStreamId }) const files = await streamsStorage.list('**/*.m3u') const streams = await parser.parse(files) @@ -151,7 +153,7 @@ async function main() { else if (!feedId && streamsGroupedByChannelId.has(channelId)) result.status = 'fulfilled' else { const channelData = channelsKeyById.get(channelId) - if (channelData.length && channelData[0].closed) result.status = 'closed' + if (channelData && channelData.isClosed) result.status = 'closed' } channelSearchRequestsBuffer.set(streamId, true) diff --git a/scripts/core/dataLoader.ts b/scripts/core/dataLoader.ts index 2379edc9ee..f99e51b521 100644 --- a/scripts/core/dataLoader.ts +++ b/scripts/core/dataLoader.ts @@ -47,6 +47,7 @@ export class DataLoader { blocklist, channels, feeds, + logos, timezones, guides, streams @@ -59,6 +60,7 @@ export class DataLoader { this.storage.json('blocklist.json'), this.storage.json('channels.json'), this.storage.json('feeds.json'), + this.storage.json('logos.json'), this.storage.json('timezones.json'), this.storage.json('guides.json'), this.storage.json('streams.json') @@ -73,6 +75,7 @@ export class DataLoader { blocklist, channels, feeds, + logos, timezones, guides, streams diff --git a/scripts/core/dataProcessor.ts b/scripts/core/dataProcessor.ts index 2ea8d28685..55e0dbb4b8 100644 --- a/scripts/core/dataProcessor.ts +++ b/scripts/core/dataProcessor.ts @@ -11,7 +11,8 @@ import { Region, Stream, Guide, - Feed + Feed, + Logo } from '../models' export class DataProcessor { @@ -21,6 +22,9 @@ export class DataProcessor { const categories = new Collection(data.categories).map(data => new Category(data)) const categoriesKeyById = categories.keyBy((category: Category) => category.id) + const languages = new Collection(data.languages).map(data => new Language(data)) + const languagesKeyByCode = languages.keyBy((language: Language) => language.code) + const subdivisions = new Collection(data.subdivisions).map(data => new Subdivision(data)) const subdivisionsKeyByCode = subdivisions.keyBy((subdivision: Subdivision) => subdivision.code) const subdivisionsGroupedByCountryCode = subdivisions.groupBy( @@ -30,20 +34,6 @@ export class DataProcessor { let regions = new Collection(data.regions).map(data => new Region(data)) const regionsKeyByCode = regions.keyBy((region: Region) => region.code) - const blocklistRecords = new Collection(data.blocklist).map(data => new BlocklistRecord(data)) - const blocklistRecordsGroupedByChannelId = blocklistRecords.groupBy( - (blocklistRecord: BlocklistRecord) => blocklistRecord.channelId - ) - - const streams = new Collection(data.streams).map(data => new Stream(data)) - const streamsGroupedById = streams.groupBy((stream: Stream) => stream.getId()) - - const guides = new Collection(data.guides).map(data => new Guide(data)) - const guidesGroupedByStreamId = guides.groupBy((guide: Guide) => guide.getStreamId()) - - const languages = new Collection(data.languages).map(data => new Language(data)) - const languagesKeyByCode = languages.keyBy((language: Language) => language.code) - const countries = new Collection(data.countries).map(data => new Country(data) .withRegions(regions) @@ -52,13 +42,16 @@ export class DataProcessor { ) const countriesKeyByCode = countries.keyBy((country: Country) => country.code) - regions = regions.map((region: Region) => region.withCountries(countriesKeyByCode)) - const timezones = new Collection(data.timezones).map(data => new Timezone(data).withCountries(countriesKeyByCode) ) const timezonesKeyById = timezones.keyBy((timezone: Timezone) => timezone.id) + const blocklistRecords = new Collection(data.blocklist).map(data => new BlocklistRecord(data)) + const blocklistRecordsGroupedByChannelId = blocklistRecords.groupBy( + (blocklistRecord: BlocklistRecord) => blocklistRecord.channelId + ) + let channels = new Collection(data.channels).map(data => new Channel(data) .withCategories(categoriesKeyById) @@ -66,6 +59,7 @@ export class DataProcessor { .withSubdivision(subdivisionsKeyByCode) .withCategories(categoriesKeyById) ) + const channelsKeyById = channels.keyBy((channel: Channel) => channel.id) const feeds = new Collection(data.feeds).map(data => @@ -78,14 +72,32 @@ export class DataProcessor { .withBroadcastSubdivisions(subdivisionsKeyByCode) ) const feedsGroupedByChannelId = feeds.groupBy((feed: Feed) => feed.channelId) + const feedsGroupedById = feeds.groupBy((feed: Feed) => feed.id) - channels = channels.map((channel: Channel) => channel.withFeeds(feedsGroupedByChannelId)) + let logos = new Collection(data.logos).map(data => new Logo(data).withFeed(feedsGroupedById)) + const logosGroupedByChannelId = logos.groupBy((logo: Logo) => logo.channelId) + const logosGroupedByStreamId = logos.groupBy((logo: Logo) => logo.getStreamId()) + + const streams = new Collection(data.streams).map(data => + new Stream(data).withLogos(logosGroupedByStreamId) + ) + const streamsGroupedById = streams.groupBy((stream: Stream) => stream.getId()) + + const guides = new Collection(data.guides).map(data => new Guide(data)) + const guidesGroupedByStreamId = guides.groupBy((guide: Guide) => guide.getStreamId()) + + regions = regions.map((region: Region) => region.withCountries(countriesKeyByCode)) + + channels = channels.map((channel: Channel) => + channel.withFeeds(feedsGroupedByChannelId).withLogos(logosGroupedByChannelId) + ) return { blocklistRecordsGroupedByChannelId, subdivisionsGroupedByCountryCode, feedsGroupedByChannelId, guidesGroupedByStreamId, + logosGroupedByStreamId, subdivisionsKeyByCode, countriesKeyByCode, languagesKeyByCode, @@ -104,7 +116,8 @@ export class DataProcessor { regions, streams, guides, - feeds + feeds, + logos } } } diff --git a/scripts/core/issueData.ts b/scripts/core/issueData.ts index 61123f4aa1..e185e1b02f 100644 --- a/scripts/core/issueData.ts +++ b/scripts/core/issueData.ts @@ -24,9 +24,11 @@ export class IssueData { return this._data.get(key) === deleteSymbol ? '' : this._data.get(key) } - getArray(key: string): string[] { + getArray(key: string): string[] | undefined { const deleteSymbol = '~' + if (this._data.missing(key)) return undefined + return this._data.get(key) === deleteSymbol ? [] : this._data.get(key).split('\r\n') } } diff --git a/scripts/core/issueLoader.ts b/scripts/core/issueLoader.ts index 1594eeb371..189e923aa9 100644 --- a/scripts/core/issueLoader.ts +++ b/scripts/core/issueLoader.ts @@ -16,7 +16,7 @@ export class IssueLoader { } let issues: object[] = [] if (TESTING) { - issues = (await import('../../tests/__data__/input/playlist_update/issues.js')).default + issues = (await import('../../tests/__data__/input/issues.js')).default } else { issues = await octokit.paginate(octokit.rest.issues.listForRepo, { owner: OWNER, diff --git a/scripts/core/issueParser.ts b/scripts/core/issueParser.ts index d1874ce33c..2a586c1d6c 100644 --- a/scripts/core/issueParser.ts +++ b/scripts/core/issueParser.ts @@ -16,7 +16,8 @@ const FIELDS = new Dictionary({ 'HTTP Referrer': 'httpReferrer', 'What happened to the stream?': 'reason', Reason: 'reason', - Notes: 'notes' + Notes: 'notes', + Directives: 'directives' }) export class IssueParser { diff --git a/scripts/core/playlistParser.ts b/scripts/core/playlistParser.ts index 37e2896dbe..2086bb568e 100644 --- a/scripts/core/playlistParser.ts +++ b/scripts/core/playlistParser.ts @@ -5,17 +5,25 @@ import { Stream } from '../models' type PlaylistPareserProps = { storage: Storage feedsGroupedByChannelId: Dictionary + logosGroupedByStreamId: Dictionary channelsKeyById: Dictionary } export class PlaylistParser { storage: Storage feedsGroupedByChannelId: Dictionary + logosGroupedByStreamId: Dictionary channelsKeyById: Dictionary - constructor({ storage, feedsGroupedByChannelId, channelsKeyById }: PlaylistPareserProps) { + constructor({ + storage, + feedsGroupedByChannelId, + logosGroupedByStreamId, + channelsKeyById + }: PlaylistPareserProps) { this.storage = storage this.feedsGroupedByChannelId = feedsGroupedByChannelId + this.logosGroupedByStreamId = logosGroupedByStreamId this.channelsKeyById = channelsKeyById } @@ -41,6 +49,7 @@ export class PlaylistParser { .fromPlaylistItem(data) .withFeed(this.feedsGroupedByChannelId) .withChannel(this.channelsKeyById) + .withLogos(this.logosGroupedByStreamId) .setFilepath(filepath) return stream diff --git a/scripts/generators/categoriesGenerator.ts b/scripts/generators/categoriesGenerator.ts index 4752dec642..dc29e51d84 100644 --- a/scripts/generators/categoriesGenerator.ts +++ b/scripts/generators/categoriesGenerator.ts @@ -17,7 +17,7 @@ export class CategoriesGenerator implements Generator { logFile: File constructor({ streams, categories, logFile }: CategoriesGeneratorProps) { - this.streams = streams + this.streams = streams.clone() this.categories = categories this.storage = new Storage(PUBLIC_DIR) this.logFile = logFile @@ -30,7 +30,8 @@ export class CategoriesGenerator implements Generator { const categoryStreams = streams .filter((stream: Stream) => stream.hasCategory(category)) .map((stream: Stream) => { - stream.groupTitle = stream.getCategoryNames().join(';') + const groupTitle = stream.getCategoryNames().join(';') + if (groupTitle) stream.groupTitle = groupTitle return stream }) diff --git a/scripts/generators/countriesGenerator.ts b/scripts/generators/countriesGenerator.ts index 7dc707cf91..5f3bb5d76f 100644 --- a/scripts/generators/countriesGenerator.ts +++ b/scripts/generators/countriesGenerator.ts @@ -17,7 +17,7 @@ export class CountriesGenerator implements Generator { logFile: File constructor({ streams, countries, logFile }: CountriesGeneratorProps) { - this.streams = streams + this.streams = streams.clone() this.countries = countries this.storage = new Storage(PUBLIC_DIR) this.logFile = logFile diff --git a/scripts/generators/index.ts b/scripts/generators/index.ts index 18b6c8e62c..3223cd21f7 100644 --- a/scripts/generators/index.ts +++ b/scripts/generators/index.ts @@ -1,10 +1,11 @@ export * from './categoriesGenerator' export * from './countriesGenerator' -export * from './languagesGenerator' -export * from './regionsGenerator' -export * from './indexGenerator' -export * from './indexNsfwGenerator' export * from './indexCategoryGenerator' export * from './indexCountryGenerator' +export * from './indexGenerator' export * from './indexLanguageGenerator' +export * from './indexNsfwGenerator' export * from './indexRegionGenerator' +export * from './languagesGenerator' +export * from './regionsGenerator' +export * from './sourcesGenerator' diff --git a/scripts/generators/indexCategoryGenerator.ts b/scripts/generators/indexCategoryGenerator.ts index 665f4cb0cf..ce652d7391 100644 --- a/scripts/generators/indexCategoryGenerator.ts +++ b/scripts/generators/indexCategoryGenerator.ts @@ -15,7 +15,7 @@ export class IndexCategoryGenerator implements Generator { logFile: File constructor({ streams, logFile }: IndexCategoryGeneratorProps) { - this.streams = streams + this.streams = streams.clone() this.storage = new Storage(PUBLIC_DIR) this.logFile = logFile } diff --git a/scripts/generators/indexCountryGenerator.ts b/scripts/generators/indexCountryGenerator.ts index 82eb335efd..ee9599528b 100644 --- a/scripts/generators/indexCountryGenerator.ts +++ b/scripts/generators/indexCountryGenerator.ts @@ -15,7 +15,7 @@ export class IndexCountryGenerator implements Generator { logFile: File constructor({ streams, logFile }: IndexCountryGeneratorProps) { - this.streams = streams + this.streams = streams.clone() this.storage = new Storage(PUBLIC_DIR) this.logFile = logFile } diff --git a/scripts/generators/indexGenerator.ts b/scripts/generators/indexGenerator.ts index 5cfa86c666..eef4e38695 100644 --- a/scripts/generators/indexGenerator.ts +++ b/scripts/generators/indexGenerator.ts @@ -15,7 +15,7 @@ export class IndexGenerator implements Generator { logFile: File constructor({ streams, logFile }: IndexGeneratorProps) { - this.streams = streams + this.streams = streams.clone() this.storage = new Storage(PUBLIC_DIR) this.logFile = logFile } @@ -24,6 +24,12 @@ export class IndexGenerator implements Generator { const sfwStreams = this.streams .orderBy(stream => stream.getTitle()) .filter((stream: Stream) => stream.isSFW()) + .map((stream: Stream) => { + const groupTitle = stream.getCategoryNames().join(';') + if (groupTitle) stream.groupTitle = groupTitle + + return stream + }) const playlist = new Playlist(sfwStreams, { public: true }) const filepath = 'index.m3u' diff --git a/scripts/generators/indexLanguageGenerator.ts b/scripts/generators/indexLanguageGenerator.ts index 3df9f71f2e..ce348ec02b 100644 --- a/scripts/generators/indexLanguageGenerator.ts +++ b/scripts/generators/indexLanguageGenerator.ts @@ -15,7 +15,7 @@ export class IndexLanguageGenerator implements Generator { logFile: File constructor({ streams, logFile }: IndexLanguageGeneratorProps) { - this.streams = streams + this.streams = streams.clone() this.storage = new Storage(PUBLIC_DIR) this.logFile = logFile } diff --git a/scripts/generators/indexNsfwGenerator.ts b/scripts/generators/indexNsfwGenerator.ts index e1e98375b6..ce48ae559d 100644 --- a/scripts/generators/indexNsfwGenerator.ts +++ b/scripts/generators/indexNsfwGenerator.ts @@ -15,7 +15,7 @@ export class IndexNsfwGenerator implements Generator { logFile: File constructor({ streams, logFile }: IndexNsfwGeneratorProps) { - this.streams = streams + this.streams = streams.clone() this.storage = new Storage(PUBLIC_DIR) this.logFile = logFile } diff --git a/scripts/generators/indexRegionGenerator.ts b/scripts/generators/indexRegionGenerator.ts index c462fcfceb..c95421de34 100644 --- a/scripts/generators/indexRegionGenerator.ts +++ b/scripts/generators/indexRegionGenerator.ts @@ -17,7 +17,7 @@ export class IndexRegionGenerator implements Generator { logFile: File constructor({ streams, regions, logFile }: IndexRegionGeneratorProps) { - this.streams = streams + this.streams = streams.clone() this.regions = regions this.storage = new Storage(PUBLIC_DIR) this.logFile = logFile diff --git a/scripts/generators/languagesGenerator.ts b/scripts/generators/languagesGenerator.ts index f7ae9976e4..933b67c75c 100644 --- a/scripts/generators/languagesGenerator.ts +++ b/scripts/generators/languagesGenerator.ts @@ -12,7 +12,7 @@ export class LanguagesGenerator implements Generator { logFile: File constructor({ streams, logFile }: LanguagesGeneratorProps) { - this.streams = streams + this.streams = streams.clone() this.storage = new Storage(PUBLIC_DIR) this.logFile = logFile } diff --git a/scripts/generators/regionsGenerator.ts b/scripts/generators/regionsGenerator.ts index 4d649a3517..f5efd393b8 100644 --- a/scripts/generators/regionsGenerator.ts +++ b/scripts/generators/regionsGenerator.ts @@ -17,7 +17,7 @@ export class RegionsGenerator implements Generator { logFile: File constructor({ streams, regions, logFile }: RegionsGeneratorProps) { - this.streams = streams + this.streams = streams.clone() this.regions = regions this.storage = new Storage(PUBLIC_DIR) this.logFile = logFile diff --git a/scripts/generators/sourcesGenerator.ts b/scripts/generators/sourcesGenerator.ts new file mode 100644 index 0000000000..30018815b5 --- /dev/null +++ b/scripts/generators/sourcesGenerator.ts @@ -0,0 +1,44 @@ +import { Collection, Storage, File, type Dictionary } from '@freearhey/core' +import { Stream, Playlist } from '../models' +import { PUBLIC_DIR } from '../constants' +import { Generator } from './generator' +import { EOL } from 'node:os' + +type SourcesGeneratorProps = { + streams: Collection + logFile: File +} + +export class SourcesGenerator implements Generator { + streams: Collection + storage: Storage + logFile: File + + constructor({ streams, logFile }: SourcesGeneratorProps) { + this.streams = streams.clone() + this.storage = new Storage(PUBLIC_DIR) + this.logFile = logFile + } + + async generate() { + const files: Dictionary = this.streams.groupBy((stream: Stream) => stream.getFilename()) + + for (let filename of files.keys()) { + if (!filename) continue + + let streams = new Collection(files.get(filename)) + streams = streams.map((stream: Stream) => { + const groupTitle = stream.getCategoryNames().join(';') + if (groupTitle) stream.groupTitle = groupTitle + + return stream + }) + const playlist = new Playlist(streams, { public: true }) + const filepath = `sources/${filename}` + await this.storage.save(filepath, playlist.toString()) + this.logFile.append( + JSON.stringify({ type: 'source', filepath, count: playlist.streams.count() }) + EOL + ) + } + } +} diff --git a/scripts/models/channel.ts b/scripts/models/channel.ts index cdc09af0ad..09b0c36adf 100644 --- a/scripts/models/channel.ts +++ b/scripts/models/channel.ts @@ -1,5 +1,5 @@ import { Collection, Dictionary } from '@freearhey/core' -import { Category, Country, Feed, Guide, Stream, Subdivision } from './index' +import { Category, Country, Feed, Guide, Logo, Stream, Subdivision } from './index' import type { ChannelData, ChannelSearchableData, ChannelSerializedData } from '../types/channel' export class Channel { @@ -19,9 +19,10 @@ export class Channel { launched?: string closed?: string replacedBy?: string + isClosed: boolean website?: string - logo: string feeds?: Collection + logos: Collection = new Collection() constructor(data?: ChannelData) { if (!data) return @@ -40,7 +41,7 @@ export class Channel { this.closed = data.closed || undefined this.replacedBy = data.replaced_by || undefined this.website = data.website || undefined - this.logo = data.logo + this.isClosed = !!data.closed || !!data.replaced_by } withSubdivision(subdivisionsKeyByCode: Dictionary): this { @@ -71,6 +72,12 @@ export class Channel { return this } + withLogos(logosGroupedByChannelId: Dictionary): this { + if (this.id) this.logos = new Collection(logosGroupedByChannelId.get(this.id)) + + return this + } + getCountry(): Country | undefined { return this.country } @@ -142,6 +149,35 @@ export class Channel { return this.isNSFW === false } + getLogos(): Collection { + function feed(logo: Logo): number { + if (!logo.feed) return 1 + if (logo.feed.isMain) return 1 + + return 0 + } + + function format(logo: Logo): number { + const levelByFormat = { SVG: 0, PNG: 3, APNG: 1, WebP: 1, AVIF: 1, JPEG: 2, GIF: 1 } + + return logo.format ? levelByFormat[logo.format] : 0 + } + + function size(logo: Logo): number { + return Math.abs(512 - logo.width) + Math.abs(512 - logo.height) + } + + return this.logos.orderBy([feed, format, size], ['desc', 'desc', 'asc'], false) + } + + getLogo(): Logo | undefined { + return this.getLogos().first() + } + + hasLogo(): boolean { + return this.getLogos().notEmpty() + } + getSearchable(): ChannelSearchableData { return { id: this.id, @@ -171,8 +207,7 @@ export class Channel { launched: this.launched, closed: this.closed, replacedBy: this.replacedBy, - website: this.website, - logo: this.logo + website: this.website } } @@ -192,7 +227,6 @@ export class Channel { this.closed = data.closed this.replacedBy = data.replacedBy this.website = data.website - this.logo = data.logo return this } diff --git a/scripts/models/index.ts b/scripts/models/index.ts index db4d6f5fa8..4e11d28b97 100644 --- a/scripts/models/index.ts +++ b/scripts/models/index.ts @@ -7,6 +7,7 @@ export * from './feed' export * from './guide' export * from './issue' export * from './language' +export * from './logo' export * from './playlist' export * from './region' export * from './stream' diff --git a/scripts/models/logo.ts b/scripts/models/logo.ts new file mode 100644 index 0000000000..3cc85fb9da --- /dev/null +++ b/scripts/models/logo.ts @@ -0,0 +1,40 @@ +import { Collection, type Dictionary } from '@freearhey/core' +import type { LogoData } from '../types/logo' +import { type Feed } from './feed' + +export class Logo { + channelId: string + feedId?: string + feed: Feed + tags: Collection + width: number + height: number + format?: string + url: string + + constructor(data?: LogoData) { + if (!data) return + + this.channelId = data.channel + this.feedId = data.feed || undefined + this.tags = new Collection(data.tags) + this.width = data.width + this.height = data.height + this.format = data.format || undefined + this.url = data.url + } + + withFeed(feedsKeyById: Dictionary): this { + if (!this.feedId) return this + + this.feed = feedsKeyById.get(this.feedId) + + return this + } + + getStreamId(): string { + if (!this.feedId) return this.channelId + + return `${this.channelId}@${this.feedId}` + } +} diff --git a/scripts/models/stream.ts b/scripts/models/stream.ts index 692bd303be..b440f86da0 100644 --- a/scripts/models/stream.ts +++ b/scripts/models/stream.ts @@ -1,8 +1,9 @@ -import { Feed, Channel, Category, Region, Subdivision, Country, Language } from './index' +import { Feed, Channel, Category, Region, Subdivision, Country, Language, Logo } from './index' import { URL, Collection, Dictionary } from '@freearhey/core' import type { StreamData } from '../types/stream' import parser from 'iptv-playlist-parser' import { IssueData } from '../core' +import path from 'node:path' export class Stream { name?: string @@ -12,6 +13,7 @@ export class Stream { channel?: Channel feedId?: string feed?: Feed + logos: Collection = new Collection() filepath?: string line?: number label?: string @@ -21,6 +23,7 @@ export class Stream { userAgent?: string groupTitle: string = 'Undefined' removed: boolean = false + directives: Collection = new Collection() constructor(data?: StreamData) { if (!data) return @@ -38,6 +41,7 @@ export class Stream { this.verticalResolution = verticalResolution || undefined this.isInterlaced = isInterlaced || undefined this.label = data.label || undefined + this.directives = new Collection(data.directives) } update(issueData: IssueData): this { @@ -46,7 +50,8 @@ export class Stream { quality: issueData.getString('quality'), httpUserAgent: issueData.getString('httpUserAgent'), httpReferrer: issueData.getString('httpReferrer'), - newStreamUrl: issueData.getString('newStreamUrl') + newStreamUrl: issueData.getString('newStreamUrl'), + directives: issueData.getArray('directives') } if (data.label !== undefined) this.label = data.label @@ -54,11 +59,43 @@ export class Stream { if (data.httpUserAgent !== undefined) this.userAgent = data.httpUserAgent if (data.httpReferrer !== undefined) this.referrer = data.httpReferrer if (data.newStreamUrl !== undefined) this.url = data.newStreamUrl + if (data.directives !== undefined) this.directives = new Collection(data.directives) return this } fromPlaylistItem(data: parser.PlaylistItem): this { + function parseTitle(title: string): { + name: string + label: string + quality: string + } { + const [, label] = title.match(/ \[(.*)\]$/) || [null, ''] + title = title.replace(new RegExp(` \\[${escapeRegExp(label)}\\]$`), '') + const [, quality] = title.match(/ \(([0-9]+p)\)$/) || [null, ''] + title = title.replace(new RegExp(` \\(${quality}\\)$`), '') + + return { name: title, label, quality } + } + + function parseDirectives(string: string) { + let directives = new Collection() + + if (!string) return directives + + const supportedDirectives = ['#EXTVLCOPT', '#KODIPROP'] + const lines = string.split('\r\n') + const regex = new RegExp(`^${supportedDirectives.join('|')}`, 'i') + + lines.forEach((line: string) => { + if (regex.test(line)) { + directives.add(line.trim()) + } + }) + + return directives + } + if (!data.name) throw new Error('"name" property is required') if (!data.url) throw new Error('"url" property is required') @@ -77,6 +114,7 @@ export class Stream { this.url = data.url this.referrer = data.http.referrer || undefined this.userAgent = data.http['user-agent'] || undefined + this.directives = parseDirectives(data.raw) return this } @@ -99,6 +137,12 @@ export class Stream { return this } + withLogos(logosGroupedByStreamId: Dictionary): this { + if (this.id) this.logos = new Collection(logosGroupedByStreamId.get(this.id)) + + return this + } + setId(id: string): this { this.id = id @@ -130,6 +174,12 @@ export class Stream { return this.line || -1 } + getFilename(): string { + if (!this.filepath) return '' + + return path.basename(this.filepath) + } + setFilepath(filepath: string): this { this.filepath = filepath @@ -294,8 +344,35 @@ export class Stream { return this.feed ? this.feed.isInternational() : false } - getLogo(): string { - return this?.channel?.logo || '' + getLogos(): Collection { + function format(logo: Logo): number { + const levelByFormat = { SVG: 0, PNG: 3, APNG: 1, WebP: 1, AVIF: 1, JPEG: 2, GIF: 1 } + + return logo.format ? levelByFormat[logo.format] : 0 + } + + function size(logo: Logo): number { + return Math.abs(512 - logo.width) + Math.abs(512 - logo.height) + } + + return this.logos.orderBy([format, size], ['desc', 'asc'], false) + } + + getLogo(): Logo | undefined { + return this.getLogos().first() + } + + hasLogo(): boolean { + return this.getLogos().notEmpty() + } + + getLogoUrl(): string { + let logo: Logo | undefined + + if (this.hasLogo()) logo = this.getLogo() + else logo = this?.channel?.getLogo() + + return logo ? logo.url : '' } getName(): string { @@ -339,7 +416,7 @@ export class Stream { let output = `#EXTINF:-1 tvg-id="${this.getId()}"` if (options.public) { - output += ` tvg-logo="${this.getLogo()}" group-title="${this.groupTitle}"` + output += ` tvg-logo="${this.getLogoUrl()}" group-title="${this.groupTitle}"` } if (this.referrer) { @@ -352,13 +429,9 @@ export class Stream { output += `,${this.getTitle()}` - if (this.referrer) { - output += `\r\n#EXTVLCOPT:http-referrer=${this.referrer}` - } - - if (this.userAgent) { - output += `\r\n#EXTVLCOPT:http-user-agent=${this.userAgent}` - } + this.directives.forEach((prop: string) => { + output += `\r\n${prop}` + }) output += `\r\n${this.url}` @@ -366,19 +439,6 @@ export class Stream { } } -function parseTitle(title: string): { - name: string - label: string - quality: string -} { - const [, label] = title.match(/ \[(.*)\]$/) || [null, ''] - title = title.replace(new RegExp(` \\[${escapeRegExp(label)}\\]$`), '') - const [, quality] = title.match(/ \(([0-9]+p)\)$/) || [null, ''] - title = title.replace(new RegExp(` \\(${quality}\\)$`), '') - - return { name: title, label, quality } -} - function escapeRegExp(text) { return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') } diff --git a/scripts/types/channel.d.ts b/scripts/types/channel.d.ts index 1f9d031cb1..da36bce184 100644 --- a/scripts/types/channel.d.ts +++ b/scripts/types/channel.d.ts @@ -21,7 +21,6 @@ export type ChannelSerializedData = { closed?: string replacedBy?: string website?: string - logo: string } export type ChannelData = { @@ -39,7 +38,6 @@ export type ChannelData = { closed: string replaced_by: string website: string - logo: string } export type ChannelSearchableData = { diff --git a/scripts/types/dataLoader.d.ts b/scripts/types/dataLoader.d.ts index 05742ff9d1..ee7ab0f937 100644 --- a/scripts/types/dataLoader.d.ts +++ b/scripts/types/dataLoader.d.ts @@ -13,6 +13,7 @@ export type DataLoaderData = { blocklist: object | object[] channels: object | object[] feeds: object | object[] + logos: object | object[] timezones: object | object[] guides: object | object[] streams: object | object[] diff --git a/scripts/types/dataProcessor.d.ts b/scripts/types/dataProcessor.d.ts index 1005ff5b23..25f21d1aac 100644 --- a/scripts/types/dataProcessor.d.ts +++ b/scripts/types/dataProcessor.d.ts @@ -5,6 +5,7 @@ export type DataProcessorData = { subdivisionsGroupedByCountryCode: Dictionary feedsGroupedByChannelId: Dictionary guidesGroupedByStreamId: Dictionary + logosGroupedByStreamId: Dictionary subdivisionsKeyByCode: Dictionary countriesKeyByCode: Dictionary languagesKeyByCode: Dictionary diff --git a/scripts/types/logo.d.ts b/scripts/types/logo.d.ts new file mode 100644 index 0000000000..47cc384537 --- /dev/null +++ b/scripts/types/logo.d.ts @@ -0,0 +1,9 @@ +export type LogoData = { + channel: string + feed: string | null + tags: string[] + width: number + height: number + format: string | null + url: string +} diff --git a/scripts/types/stream.d.ts b/scripts/types/stream.d.ts index 667ad25861..7abb145405 100644 --- a/scripts/types/stream.d.ts +++ b/scripts/types/stream.d.ts @@ -7,4 +7,5 @@ export type StreamData = { user_agent: string | null quality: string | null label: string | null + directives: string[] } From 80d1fcdcb3c32fe8ecd671f8b6757c6dd15a7113 Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Thu, 10 Jul 2025 21:14:02 +0300 Subject: [PATCH 04/10] Update dependencies --- package-lock.json | 115 +++++++++++++++++++++++++++++++++++++++------- package.json | 4 +- 2 files changed, 100 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15685e5ebc..b043dbb293 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@eslint/eslintrc": "^3.3.0", "@eslint/js": "^9.21.0", - "@freearhey/core": "^0.8.2", + "@freearhey/core": "^0.9.0", "@freearhey/search-js": "^0.1.2", "@inquirer/prompts": "^7.4.1", "@octokit/core": "^6.1.4", @@ -36,7 +36,7 @@ "glob": "^11.0.2", "globals": "^16.0.0", "iptv-checker": "^0.29.1", - "iptv-playlist-parser": "^0.13.0", + "iptv-playlist-parser": "^0.15.0", "jest": "^29.7.0", "jest-expect-message": "^1.1.3", "lodash": "^4.17.21", @@ -1021,9 +1021,9 @@ } }, "node_modules/@freearhey/core": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.8.2.tgz", - "integrity": "sha512-jlb1XUbhUf3lqD3B9Wmx3c8qYG4+s1I0cr2FFQfiMpJh4nMvfUNdJr2OhH31S/dbNP12ycT6RPVoZ2j2G3+mXA==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.9.0.tgz", + "integrity": "sha512-D9/3ZNs6iLbEs0dMF6HQZoFoRM+wEYQZeMIQbby49lN6l0M4Q8hTA+wbxIX+Qh4Sj2zIY+0DFJRNLEoRmasVFg==", "dependencies": { "consola": "^3.4.2", "dayjs": "^1.11.13", @@ -1771,6 +1771,26 @@ "node": ">=8" } }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@jest/reporters/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -2725,9 +2745,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dependencies": { "balanced-match": "^1.0.0" } @@ -3052,9 +3072,9 @@ "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4053,6 +4073,11 @@ "node": ">=14.14" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4200,9 +4225,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dependencies": { "balanced-match": "^1.0.0" } @@ -4383,6 +4408,21 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -4452,9 +4492,9 @@ } }, "node_modules/iptv-playlist-parser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/iptv-playlist-parser/-/iptv-playlist-parser-0.13.0.tgz", - "integrity": "sha512-As51+8A7AcFzV9Y8mt30TIbRkBn6l0TGuL9lIG2bPcqb+YYRVzfjsqqugz3eWbEmziEKEsLzexnqPSO7ZzQc0A==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/iptv-playlist-parser/-/iptv-playlist-parser-0.15.0.tgz", + "integrity": "sha512-9EGiQ5y95UZsV24HBSHuSyOuqzFR4YCAm7X8MqJNHlcm9jP38DHH7inYU0YJuXVq9fAttij3Yh8MJZkV1Bu/SA==", "dependencies": { "is-valid-path": "^0.1.1", "validator": "^13.7.0" @@ -5632,6 +5672,14 @@ "node": ">= 12" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -5757,6 +5805,14 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -6462,6 +6518,26 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/timer-node": { "version": "5.0.9", "resolved": "https://registry.npmjs.org/timer-node/-/timer-node-5.0.9.tgz", @@ -6776,6 +6852,11 @@ "node": ">=8" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, "node_modules/write-file-atomic": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", diff --git a/package.json b/package.json index 1e57c9b650..ae00cb9e4e 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "dependencies": { "@eslint/eslintrc": "^3.3.0", "@eslint/js": "^9.21.0", - "@freearhey/core": "^0.8.2", + "@freearhey/core": "^0.9.0", "@freearhey/search-js": "^0.1.2", "@inquirer/prompts": "^7.4.1", "@octokit/core": "^6.1.4", @@ -66,7 +66,7 @@ "glob": "^11.0.2", "globals": "^16.0.0", "iptv-checker": "^0.29.1", - "iptv-playlist-parser": "^0.13.0", + "iptv-playlist-parser": "^0.15.0", "jest": "^29.7.0", "jest-expect-message": "^1.1.3", "lodash": "^4.17.21", From 9f9c651d924ba698cb4314949cf4f675715f6ec9 Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Thu, 10 Jul 2025 21:14:13 +0300 Subject: [PATCH 05/10] Update issue templates --- .github/ISSUE_TEMPLATE/1_streams_add.yml | 7 +++++++ .github/ISSUE_TEMPLATE/2_streams_edit.yml | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/1_streams_add.yml b/.github/ISSUE_TEMPLATE/1_streams_add.yml index 039f19d197..6a49621a26 100644 --- a/.github/ISSUE_TEMPLATE/1_streams_add.yml +++ b/.github/ISSUE_TEMPLATE/1_streams_add.yml @@ -60,6 +60,13 @@ body: label: HTTP Referrer placeholder: 'https://example.com/' + - type: textarea + id: directives + attributes: + label: Directives + description: 'List of directives telling players how to play the stream. Supported `#KODIPROP` and `#VLCOPT`.' + placeholder: '#KODIPROP:inputstream=inputstream.adaptive' + - type: textarea id: notes attributes: diff --git a/.github/ISSUE_TEMPLATE/2_streams_edit.yml b/.github/ISSUE_TEMPLATE/2_streams_edit.yml index b9dbe2cfbc..ece9afd024 100644 --- a/.github/ISSUE_TEMPLATE/2_streams_edit.yml +++ b/.github/ISSUE_TEMPLATE/2_streams_edit.yml @@ -72,6 +72,13 @@ body: label: HTTP Referrer placeholder: 'https://example.com/' + - type: textarea + id: directives + attributes: + label: Directives + description: 'List of directives telling players how to play the stream. Supported `#KODIPROP` and `#VLCOPT`.' + placeholder: '#KODIPROP:inputstream=inputstream.adaptive' + - type: textarea id: notes attributes: From 57e4f4bd6e363a2549f640cd2e330f5b3c9bc3df Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Thu, 10 Jul 2025 21:14:25 +0300 Subject: [PATCH 06/10] Update CONTRIBUTING.md --- CONTRIBUTING.md | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5186ec0d91..7ab075b23b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,13 +114,13 @@ For a stream to be approved, its description must follow this template: STREAM_URL ``` -| Attribute | Description | Required | Valid values | -| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------- | -| `STREAM_ID` | ID of the stream. Full list of supported channels with corresponding ID could be found on [iptv-org.github.io](https://iptv-org.github.io/). | Optional | `` or `@` | -| `CHANNEL_NAME` | Full name of the channel. May contain any characters except: `,`, `[`, `]`. | Required | - | -| `QUALITY` | Maximum stream quality. | Optional | `2160p`, `1080p`, `720p`, `480p`, `360p` etc | -| `LABEL` | Specified in cases where the broadcast for some reason may not be available to some users. | Optional | `Geo-blocked` or `Not 24/7` | -| `STREAM_URL` | Stream URL. | Required | - | +| Attribute | Description | Required | Valid values | +| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------- | +| `STREAM_ID` | Stream ID consisting of channel ID and feed ID. Full list of supported channels with corresponding ID could be found on [iptv-org.github.io](https://iptv-org.github.io/). | Optional | `` or `@` | +| `CHANNEL_NAME` | Full name of the channel. May contain any characters except: `,`, `[`, `]`. | Required | - | +| `QUALITY` | Maximum stream quality. | Optional | `2160p`, `1080p`, `720p`, `480p`, `360p` etc | +| `LABEL` | Specified in cases where the broadcast for some reason may not be available to some users. | Optional | `Geo-blocked` or `Not 24/7` | +| `STREAM_URL` | Stream URL. | Required | - | Example: @@ -136,15 +136,26 @@ Also, if necessary, you can specify custom [HTTP User-Agent](https://developer.m http://example.com/stream.m3u8 ``` -or via `#EXTVLCOPT` tag: +or use player-specific directives: + +_VLC_ ```xml -#EXTINF:-1 tvg-id="ExampleTV.us",Example TV +#EXTINF:-1 tvg-id="ExampleTV.us@VLC",Example TV #EXTVLCOPT:http-referrer=http://example.com/ #EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) http://example.com/stream.m3u8 ``` +_Kodi_ + +```xml +#EXTINF:-1 tvg-id="ExampleTV.us@Kodi",Example TV +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.stream_headers=Referer=http://example.com/&User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) +http://example.com/stream.m3u8 +``` + ## Project Structure - `.github/` From f64196ab04975be910e515c70d058ed0fdd0876d Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Thu, 10 Jul 2025 21:14:48 +0300 Subject: [PATCH 07/10] Update template.md --- .readme/template.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.readme/template.md b/.readme/template.md index 84a8f70133..95f60ae9c0 100644 --- a/.readme/template.md +++ b/.readme/template.md @@ -110,6 +110,28 @@ Same thing, but split up into separate files: +### Grouped by sources + +Playlists in which channels are grouped by broadcast source. + +
+Expand +
+ +To use the playlist, simply replace `` in the link below with the name of one of the files in the [streams](streams) folder. + +``` +https://iptv-org.github.io/iptv/sources/.m3u +``` + +
+ +Also, any of our internal playlists are available in raw form (without any filtering or sorting) at this link: + +``` +https://raw.githubusercontent.com/iptv-org/iptv/refs/heads/master/streams/.m3u +``` + ## EPG [Electronic Program Guide](https://en.wikipedia.org/wiki/Electronic_program_guide) for most of the channels can be downloaded using utilities published in the [iptv-org/epg](https://github.com/iptv-org/epg) repository. From 5ffa2d1dd0ecae423368832c7fb0ff1ef11c751f Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Sat, 12 Jul 2025 23:54:56 +0300 Subject: [PATCH 08/10] Update tests/__data__ --- .../playlist_generate/.gh-pages/raw/ad.m3u | 9 +++++++++ .../playlist_generate/.gh-pages/raw/ca.m3u | 3 +++ .../playlist_generate/.gh-pages/raw/in.m3u | 3 +++ .../playlist_generate/.gh-pages/raw/kg.m3u | 3 +++ .../playlist_generate/.gh-pages/raw/uk.m3u | 3 +++ .../playlist_generate/.gh-pages/raw/unsorted.m3u | 15 +++++++++++++++ .../playlist_generate/logs/generators.log | 6 ++++++ tests/__data__/expected/readme_update/_readme.md | 6 ++++++ .../input/readme_update/.readme/template.md | 6 ++++++ 9 files changed, 54 insertions(+) create mode 100644 tests/__data__/expected/playlist_generate/.gh-pages/raw/ad.m3u create mode 100644 tests/__data__/expected/playlist_generate/.gh-pages/raw/ca.m3u create mode 100644 tests/__data__/expected/playlist_generate/.gh-pages/raw/in.m3u create mode 100644 tests/__data__/expected/playlist_generate/.gh-pages/raw/kg.m3u create mode 100644 tests/__data__/expected/playlist_generate/.gh-pages/raw/uk.m3u create mode 100644 tests/__data__/expected/playlist_generate/.gh-pages/raw/unsorted.m3u diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/raw/ad.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/raw/ad.m3u new file mode 100644 index 0000000000..bdbc0b4928 --- /dev/null +++ b/tests/__data__/expected/playlist_generate/.gh-pages/raw/ad.m3u @@ -0,0 +1,9 @@ +#EXTM3U +#EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="" group-title="Undefined",Zoo (720p) +https://iptv-all.lanesh4d0w.repl.co/andorra/zoo +#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Undefined",ATV +https://iptv-all.lanesh4d0w.repl.co/andorra/atv +#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD +https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd +#EXTINF:-1 tvg-id="AndorraTV.ad" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV +https://iptv-all.lanesh4d0w.repl.co/andorra/atv2 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/raw/ca.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/raw/ca.m3u new file mode 100644 index 0000000000..03a6963a33 --- /dev/null +++ b/tests/__data__/expected/playlist_generate/.gh-pages/raw/ca.m3u @@ -0,0 +1,3 @@ +#EXTM3U +#EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia +http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/raw/in.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/raw/in.m3u new file mode 100644 index 0000000000..d2223daac4 --- /dev/null +++ b/tests/__data__/expected/playlist_generate/.gh-pages/raw/in.m3u @@ -0,0 +1,3 @@ +#EXTM3U +#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV +http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/raw/kg.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/raw/kg.m3u new file mode 100644 index 0000000000..847591ab04 --- /dev/null +++ b/tests/__data__/expected/playlist_generate/.gh-pages/raw/kg.m3u @@ -0,0 +1,3 @@ +#EXTM3U +#EXTINF:-1 tvg-id="ElTR.kg" tvg-logo="https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png" group-title="General",ЭлТР (480p) [Not 24/7] +http://gohoski.fvds.ru:3000/mediabay/162/index.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/raw/uk.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/raw/uk.m3u new file mode 100644 index 0000000000..85c75b78bf --- /dev/null +++ b/tests/__data__/expected/playlist_generate/.gh-pages/raw/uk.m3u @@ -0,0 +1,3 @@ +#EXTM3U +#EXTINF:-1 tvg-id="BBCNews.uk" tvg-logo="https://raw.githubusercontent.com/Tapiosinn/tv-logos/master/countries/united-kingdom/bbc-news-uk.png" group-title="General;News",BBC News HD +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8 diff --git a/tests/__data__/expected/playlist_generate/.gh-pages/raw/unsorted.m3u b/tests/__data__/expected/playlist_generate/.gh-pages/raw/unsorted.m3u new file mode 100644 index 0000000000..530c218fa6 --- /dev/null +++ b/tests/__data__/expected/playlist_generate/.gh-pages/raw/unsorted.m3u @@ -0,0 +1,15 @@ +#EXTM3U +#EXTINF:-1 tvg-id="LDPRTV.ru" tvg-logo="https://iptvx.one/icn/ldpr-tv.png" group-title="General",ЛДПР ТВ (1080p) +http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8 +#EXTINF:-1 tvg-id="VisitXTV.nl" tvg-logo="https://i.imgur.com/RJ9wbNF.jpg" group-title="XXX",Visit-X TV +https://stream.visit-x.tv/vxtv/ngrp:live_all/30fps.m3u8 +#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7] +#EXTVLCOPT:http-referrer=http://imn.iq +#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 +#KODIPROP:inputstream=inputstream.adaptive +#KODIPROP:inputstream.adaptive.manifest_type=mpd +#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha +#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}| +http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz" +#EXTINF:-1 tvg-id="DunaWorld.hu" tvg-logo="https://i.imgur.com/uOBQJZS.png" group-title="Undefined",Duna World (576i) +http://146.59.85.40:89/dunaworld/index.m3u8 diff --git a/tests/__data__/expected/playlist_generate/logs/generators.log b/tests/__data__/expected/playlist_generate/logs/generators.log index 3095463f62..bfd96fc586 100644 --- a/tests/__data__/expected/playlist_generate/logs/generators.log +++ b/tests/__data__/expected/playlist_generate/logs/generators.log @@ -1,3 +1,9 @@ +{"type":"raw","filepath":"raw/ad.m3u","count":4} +{"type":"raw","filepath":"raw/ca.m3u","count":1} +{"type":"raw","filepath":"raw/in.m3u","count":1} +{"type":"raw","filepath":"raw/kg.m3u","count":1} +{"type":"raw","filepath":"raw/uk.m3u","count":1} +{"type":"raw","filepath":"raw/unsorted.m3u","count":4} {"type":"source","filepath":"sources/ad.m3u","count":3} {"type":"source","filepath":"sources/ca.m3u","count":1} {"type":"source","filepath":"sources/in.m3u","count":1} diff --git a/tests/__data__/expected/readme_update/_readme.md b/tests/__data__/expected/readme_update/_readme.md index be2cfb8ede..e880f20b34 100644 --- a/tests/__data__/expected/readme_update/_readme.md +++ b/tests/__data__/expected/readme_update/_readme.md @@ -230,6 +230,12 @@ https://iptv-org.github.io/iptv/sources/.m3u +Also, any of our internal playlists are available in raw form (without any filtering or sorting) at this link: + +``` +https://iptv-org.github.io/iptv/raw/.m3u +``` + ## Database All channel data is taken from the [iptv-org/database](https://github.com/iptv-org/database) repository. If you find any errors please open a new [issue](https://github.com/iptv-org/database/issues) there. diff --git a/tests/__data__/input/readme_update/.readme/template.md b/tests/__data__/input/readme_update/.readme/template.md index 68aaa68b7c..478ab1743e 100644 --- a/tests/__data__/input/readme_update/.readme/template.md +++ b/tests/__data__/input/readme_update/.readme/template.md @@ -131,6 +131,12 @@ https://iptv-org.github.io/iptv/sources/.m3u +Also, any of our internal playlists are available in raw form (without any filtering or sorting) at this link: + +``` +https://iptv-org.github.io/iptv/raw/.m3u +``` + ## Database All channel data is taken from the [iptv-org/database](https://github.com/iptv-org/database) repository. If you find any errors please open a new [issue](https://github.com/iptv-org/database/issues) there. From ef44a04ae500967781a16af4ff66dfd9894f269d Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Sat, 12 Jul 2025 23:55:18 +0300 Subject: [PATCH 09/10] Update scripts --- scripts/commands/playlist/generate.ts | 6 +++- scripts/generators/index.ts | 1 + scripts/generators/rawGenerator.ts | 41 +++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 scripts/generators/rawGenerator.ts diff --git a/scripts/commands/playlist/generate.ts b/scripts/commands/playlist/generate.ts index 87f2b9ee33..f610af0e86 100644 --- a/scripts/commands/playlist/generate.ts +++ b/scripts/commands/playlist/generate.ts @@ -15,7 +15,8 @@ import { LanguagesGenerator, RegionsGenerator, IndexGenerator, - SourcesGenerator + SourcesGenerator, + RawGenerator } from '../../generators' async function main() { @@ -49,6 +50,9 @@ async function main() { const totalStreams = streams.count() logger.info(`found ${totalStreams} streams`) + logger.info('generating raw/...') + await new RawGenerator({ streams, logFile }).generate() + logger.info('filtering streams...') streams = streams.uniqBy((stream: Stream) => stream.hasId() ? stream.getChannelId() + stream.getFeedId() : uniqueId() diff --git a/scripts/generators/index.ts b/scripts/generators/index.ts index 3223cd21f7..200ae729ac 100644 --- a/scripts/generators/index.ts +++ b/scripts/generators/index.ts @@ -7,5 +7,6 @@ export * from './indexLanguageGenerator' export * from './indexNsfwGenerator' export * from './indexRegionGenerator' export * from './languagesGenerator' +export * from './rawGenerator' export * from './regionsGenerator' export * from './sourcesGenerator' diff --git a/scripts/generators/rawGenerator.ts b/scripts/generators/rawGenerator.ts new file mode 100644 index 0000000000..3249c81852 --- /dev/null +++ b/scripts/generators/rawGenerator.ts @@ -0,0 +1,41 @@ +import { Collection, Storage, File } from '@freearhey/core' +import { Stream, Playlist } from '../models' +import { PUBLIC_DIR } from '../constants' +import { Generator } from './generator' +import { EOL } from 'node:os' + +type RawGeneratorProps = { + streams: Collection + logFile: File +} + +export class RawGenerator implements Generator { + streams: Collection + storage: Storage + logFile: File + + constructor({ streams, logFile }: RawGeneratorProps) { + this.streams = streams.clone() + this.storage = new Storage(PUBLIC_DIR) + this.logFile = logFile + } + + async generate() { + const files = this.streams.groupBy((stream: Stream) => stream.getFilename()) + + for (let filename of files.keys()) { + const streams = new Collection(files.get(filename)).map((stream: Stream) => { + const groupTitle = stream.getCategoryNames().join(';') + if (groupTitle) stream.groupTitle = groupTitle + + return stream + }) + const playlist = new Playlist(streams, { public: true }) + const filepath = `raw/${filename}` + await this.storage.save(filepath, playlist.toString()) + this.logFile.append( + JSON.stringify({ type: 'raw', filepath, count: playlist.streams.count() }) + EOL + ) + } + } +} From e53491a593e519bb408a1a784ef66040f330674d Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Sat, 12 Jul 2025 23:55:36 +0300 Subject: [PATCH 10/10] Update template.md --- .readme/template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readme/template.md b/.readme/template.md index 95f60ae9c0..3c5c0a70d0 100644 --- a/.readme/template.md +++ b/.readme/template.md @@ -129,7 +129,7 @@ https://iptv-org.github.io/iptv/sources/.m3u Also, any of our internal playlists are available in raw form (without any filtering or sorting) at this link: ``` -https://raw.githubusercontent.com/iptv-org/iptv/refs/heads/master/streams/.m3u +https://iptv-org.github.io/iptv/raw/.m3u ``` ## EPG