|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import json
|
|
|
|
import argparse
|
|
|
|
import re
|
|
|
|
import requests
|
|
|
|
|
|
|
|
__author__ = "Meliurwen"
|
|
|
|
__version__ = "0.1.1"
|
|
|
|
__license__ = "GPL3"
|
|
|
|
|
|
|
|
def gen_link_m3u(chan_dict, source, channel):
|
|
|
|
|
|
|
|
headers = {
|
|
|
|
"User-Agent": ("Mozilla/5.0 (Windows NT 10.0; rv:75.0) "
|
|
|
|
"Gecko/20100101 Firefox/75.0"),
|
|
|
|
"Accept-Language": "en-US;q=0.9,en;q=0.8,en-GB;q=0.7",
|
|
|
|
"X-Requested-With": "XMLHttpRequest",
|
|
|
|
"Connection": "keep-alive",
|
|
|
|
"Referer": str(chan_dict[source][channel]["data"]["referer"]),
|
|
|
|
"cb-enabled": "enabled",
|
|
|
|
"DNT": "1",
|
|
|
|
"Pragma": "no-cache",
|
|
|
|
"Cache-Control": "no-cache"
|
|
|
|
}
|
|
|
|
|
|
|
|
content = requests.get(
|
|
|
|
chan_dict[source][channel]["url"],
|
|
|
|
headers=headers,
|
|
|
|
timeout=(3, 300)
|
|
|
|
)
|
|
|
|
|
|
|
|
content = {
|
|
|
|
"status_code": content.status_code,
|
|
|
|
"content": content.content,
|
|
|
|
"url": content.url
|
|
|
|
}
|
|
|
|
|
|
|
|
if content["status_code"] != 200:
|
|
|
|
print(
|
|
|
|
"[ERROR] Http status code {stat_code} "
|
|
|
|
"for {source}:{channel} at {url}".format(
|
|
|
|
stat_code=content["status_code"],
|
|
|
|
source=source,
|
|
|
|
channel=channel,
|
|
|
|
url=content["url"]),
|
|
|
|
file=sys.stderr
|
|
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
|
|
ugly_payload = json.loads(content["content"].decode('unicode_escape')[1:-1])
|
|
|
|
|
|
|
|
return ugly_payload["data"]["attributes"]["streaming"]["hls"]["url"]
|
|
|
|
|
|
|
|
def is_supp_source(chan_dict, source):
|
|
|
|
|
|
|
|
if source not in chan_dict.keys():
|
|
|
|
print(
|
|
|
|
"[ERROR] Source not supported: {source}".format(source=source),
|
|
|
|
file=sys.stderr
|
|
|
|
)
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def is_supp_channel(chan_dict, source, channel):
|
|
|
|
|
|
|
|
if channel not in chan_dict[source].keys():
|
|
|
|
print(
|
|
|
|
"[ERROR] Channel not supported: {channel}".format(channel=channel),
|
|
|
|
file=sys.stderr
|
|
|
|
)
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def gen_list(source, chan_dict):
|
|
|
|
|
|
|
|
is_success = False
|
|
|
|
|
|
|
|
if source:
|
|
|
|
if not is_supp_source(chan_dict, source):
|
|
|
|
return False
|
|
|
|
for channel in chan_dict[source].keys():
|
|
|
|
print("{source}:{channel}".format(source=source, channel=channel))
|
|
|
|
is_success = True
|
|
|
|
else:
|
|
|
|
for src in chan_dict.keys():
|
|
|
|
for channel in chan_dict[src].keys():
|
|
|
|
print("{source}:{channel}".format(source=src, channel=channel))
|
|
|
|
is_success = True
|
|
|
|
|
|
|
|
return is_success
|
|
|
|
|
|
|
|
def gen_playlist(chan_dict):
|
|
|
|
|
|
|
|
is_success = False
|
|
|
|
|
|
|
|
re_list = re.compile('^([a-z0-9-_]{1,32}):([a-z0-9-_]{1,32})\n?$', re.IGNORECASE)
|
|
|
|
is_first_line = True
|
|
|
|
for line in sys.stdin:
|
|
|
|
stream = re_list.findall(line)
|
|
|
|
if stream:
|
|
|
|
if is_first_line:
|
|
|
|
print("#EXTM3U")
|
|
|
|
is_first_line = False
|
|
|
|
source, channel = stream[0][0], stream[0][1]
|
|
|
|
if is_supp_source(chan_dict, source) and is_supp_channel(chan_dict, source, channel):
|
|
|
|
link = gen_link_m3u(chan_dict, source, channel)
|
|
|
|
if link:
|
|
|
|
print("#EXTINF:-1"
|
|
|
|
"group-title=\"{source}\",{chanName}".format(
|
|
|
|
source=source,
|
|
|
|
chanName=chan_dict[source][channel]["name"])
|
|
|
|
)
|
|
|
|
print(link)
|
|
|
|
is_success = True
|
|
|
|
|
|
|
|
if not is_success:
|
|
|
|
print("[ERROR] No playlist created because empty", file=sys.stderr)
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def main(arguments):
|
|
|
|
|
|
|
|
source = arguments.source[0] if args.source else None
|
|
|
|
channel = arguments.channel[0] if args.channel else None
|
|
|
|
is_list = arguments.list if args.list else False
|
|
|
|
is_playlist = arguments.playlist if args.playlist else False
|
|
|
|
|
|
|
|
with open('channels.json', 'r') as myfile:
|
|
|
|
chan_dict = json.loads(myfile.read())
|
|
|
|
|
|
|
|
if source and channel:
|
|
|
|
if not (is_supp_source(chan_dict, source) or is_supp_channel(chan_dict, source, channel)):
|
|
|
|
sys.exit(1)
|
|
|
|
link = gen_link_m3u(chan_dict, source, channel)
|
|
|
|
if link:
|
|
|
|
print(
|
|
|
|
link,
|
|
|
|
sep='',
|
|
|
|
end='\n',
|
|
|
|
file=sys.stdout,
|
|
|
|
flush=False
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
if is_list:
|
|
|
|
if not gen_list(source, chan_dict):
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
if is_playlist:
|
|
|
|
if not gen_playlist(chan_dict):
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
prog='IPTV Network Extract',
|
|
|
|
usage='%(prog)s [-h] [-s SOURCE [-c CHANNEL | -l]] [-l] [-p] [--version]',
|
|
|
|
description='Extracts original m3u8 links from multiple IPTV networks.'
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"-s", "--source",
|
|
|
|
nargs=1,
|
|
|
|
required=False,
|
|
|
|
help="source where getting the channel"
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"-c", "--channel",
|
|
|
|
nargs=1,
|
|
|
|
required=False,
|
|
|
|
help="channel name"
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"-l", "--list",
|
|
|
|
required=False,
|
|
|
|
action='store_true',
|
|
|
|
help="list supported networks and channels"
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"-p", "--playlist",
|
|
|
|
required=False,
|
|
|
|
action='store_true',
|
|
|
|
help="generate a m3u8 playlist from stdin"
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"--version",
|
|
|
|
action="version",
|
|
|
|
version="%(prog)s (version {version})".format(version=__version__)
|
|
|
|
)
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
if len(sys.argv) <= 1:
|
|
|
|
parser.error("at least 1 argument is required")
|
|
|
|
|
|
|
|
if len(sys.argv) > 2 and args.playlist:
|
|
|
|
parser.error("--playlist must be a single parameter")
|
|
|
|
|
|
|
|
if len(sys.argv) == 3 and args.channel:
|
|
|
|
parser.error("--source is required for this parameter")
|
|
|
|
|
|
|
|
if args.source and not bool(args.channel) ^ bool(args.list):
|
|
|
|
parser.error("--source requires either --channel or --list")
|
|
|
|
|
|
|
|
if args.list and args.channel:
|
|
|
|
parser.error("--list should either be used singularly or with --source")
|
|
|
|
|
|
|
|
main(args)
|