2026-06-15 18:44:59 +08:00
|
|
|
#!/usr/bin/python3
|
|
|
|
|
import sys
|
|
|
|
|
import os
|
|
|
|
|
import o11
|
|
|
|
|
import base64
|
|
|
|
|
import json
|
|
|
|
|
import time
|
|
|
|
|
from dateutil import parser
|
|
|
|
|
from deep_translator import GoogleTranslator
|
|
|
|
|
|
|
|
|
|
user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36'
|
|
|
|
|
|
|
|
|
|
user=o11.parse_params(sys.argv, 'user')
|
|
|
|
|
password=o11.parse_params(sys.argv, 'password')
|
|
|
|
|
device=o11.parse_params(sys.argv, 'device')
|
|
|
|
|
pin=o11.parse_params(sys.argv, 'pin')
|
|
|
|
|
|
|
|
|
|
if user == "" or password == "":
|
|
|
|
|
print("no account specificed")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
if len(device) != 36:
|
|
|
|
|
print("device must be a UUID")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
id=o11.parse_params(sys.argv, 'id')
|
|
|
|
|
text=o11.parse_params(sys.argv, 'text')
|
|
|
|
|
srclang=o11.parse_params(sys.argv, 'srclang')
|
|
|
|
|
dstlang=o11.parse_params(sys.argv, 'dstlang')
|
|
|
|
|
action=o11.parse_params(sys.argv, 'action')
|
|
|
|
|
url=o11.parse_params(sys.argv, 'url')
|
|
|
|
|
|
|
|
|
|
bind=o11.parse_params(sys.argv, 'bind')
|
|
|
|
|
proxy=o11.parse_params(sys.argv, 'proxy')
|
|
|
|
|
doh=o11.parse_params(sys.argv, 'doh')
|
|
|
|
|
dns=o11.parse_params(sys.argv, 'dns')
|
|
|
|
|
worker=o11.parse_params(sys.argv, 'worker')
|
|
|
|
|
|
|
|
|
|
cdm=o11.parse_params(sys.argv, 'cdm')
|
|
|
|
|
drm=o11.parse_params(sys.argv, 'drm')
|
|
|
|
|
kid=o11.parse_params(sys.argv, 'kid')
|
|
|
|
|
pssh=o11.parse_params(sys.argv, 'pssh')
|
|
|
|
|
challenge=o11.parse_params(sys.argv, 'challenge')
|
|
|
|
|
licenseurl=o11.parse_params(sys.argv, 'licenseurl')
|
|
|
|
|
licenseparams=o11.parse_params(sys.argv, 'licenseparams')
|
|
|
|
|
|
|
|
|
|
heartbeaturl=o11.parse_params(sys.argv, 'heartbeaturl')
|
|
|
|
|
heartbeatparams=o11.parse_params(sys.argv, 'heartbeatparams')
|
|
|
|
|
|
|
|
|
|
o11Session = o11.session(bind=bind, proxy=proxy, worker=worker, doh=doh, dns=dns)
|
|
|
|
|
req = o11Session.get_session()
|
|
|
|
|
|
|
|
|
|
if doh != "":
|
|
|
|
|
o11.dns(doh)
|
|
|
|
|
elif dns != "":
|
|
|
|
|
o11.dns(dns)
|
|
|
|
|
|
|
|
|
|
if action == "subtitles":
|
|
|
|
|
if srclang == "":
|
|
|
|
|
srclang = "auto"
|
|
|
|
|
output = {
|
|
|
|
|
"Text": GoogleTranslator(source=srclang, target=dstlang).translate(text)
|
|
|
|
|
}
|
|
|
|
|
print(json.dumps(output))
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
if challenge == "cert":
|
|
|
|
|
challenge = "CAQ="
|
|
|
|
|
|
|
|
|
|
authFile = '/example_' + user + '.tokens'
|
|
|
|
|
|
|
|
|
|
headers = {}
|
|
|
|
|
|
|
|
|
|
def pair():
|
|
|
|
|
print("pairing...", flush=True)
|
|
|
|
|
|
|
|
|
|
response = req.get()
|
|
|
|
|
try:
|
|
|
|
|
code = response.json()['code']
|
|
|
|
|
print("Please go to ... and enter code " + code, flush=True)
|
|
|
|
|
except:
|
|
|
|
|
print(response.text, flush=True)
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
print("please enter received code: ", flush=True)
|
|
|
|
|
code = input()
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
response = req.get()
|
|
|
|
|
try:
|
|
|
|
|
response.json()['token']
|
|
|
|
|
json.dump(response.json(), open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
|
|
|
|
|
print("pairing done", flush=True)
|
|
|
|
|
break
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
|
|
|
def login():
|
|
|
|
|
print("logging in...", file=sys.stderr)
|
|
|
|
|
|
|
|
|
|
# Get token
|
|
|
|
|
response = req.get()
|
|
|
|
|
try:
|
|
|
|
|
response.json()['token']
|
|
|
|
|
json.dump(response.json(), open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
|
|
|
|
|
except:
|
|
|
|
|
output = {
|
|
|
|
|
"ErrorCode": 401,
|
|
|
|
|
"ErrorMessage": "auth failed"
|
|
|
|
|
}
|
|
|
|
|
print(json.dumps(output))
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
|
|
print("logged in successfully", file=sys.stderr)
|
|
|
|
|
|
|
|
|
|
def refresh():
|
|
|
|
|
print("refreshing tokens...", file=sys.stderr)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
auth = json.load(open(os.path.abspath(os.path.dirname(__file__)) + authFile))
|
|
|
|
|
except:
|
|
|
|
|
return "error"
|
|
|
|
|
|
|
|
|
|
# Get token
|
|
|
|
|
response = req.get()
|
|
|
|
|
try:
|
|
|
|
|
response.json()['token']
|
|
|
|
|
except:
|
|
|
|
|
print(response.text, file=sys.stderr)
|
|
|
|
|
return "error"
|
|
|
|
|
|
|
|
|
|
auth.update(response.json())
|
|
|
|
|
json.dump(auth, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
|
|
|
|
|
|
|
|
|
|
print("tokens refreshed successfully", file=sys.stderr)
|
|
|
|
|
|
|
|
|
|
def do_action():
|
|
|
|
|
|
|
|
|
|
if action == "pair":
|
|
|
|
|
pair()
|
|
|
|
|
sys.exit()
|
|
|
|
|
elif action == "login":
|
|
|
|
|
login()
|
|
|
|
|
sys.exit()
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
auth = json.load(open(os.path.abspath(os.path.dirname(__file__)) + authFile))
|
|
|
|
|
except:
|
|
|
|
|
return "error"
|
|
|
|
|
|
|
|
|
|
if action == "channels":
|
|
|
|
|
output = {}
|
|
|
|
|
output['Channels'] = []
|
|
|
|
|
|
|
|
|
|
response = req.get()
|
|
|
|
|
try:
|
|
|
|
|
for chan in response.json():
|
|
|
|
|
channel = {}
|
|
|
|
|
channel['Name'] = chan['Name']
|
|
|
|
|
channel['SortId'] = 0
|
|
|
|
|
channel['EpgId'] = ""
|
|
|
|
|
channel['Mode'] = "live"
|
|
|
|
|
channel['ScriptParams'] = 'id=' + str(chan['Id'])
|
|
|
|
|
channel['SessionManifest'] = True
|
|
|
|
|
channel['CdmType'] = "widevine"
|
|
|
|
|
channel['UseCdm'] = True
|
|
|
|
|
channel['Video'] = 'best'
|
|
|
|
|
channel['OnDemand'] = True
|
|
|
|
|
channel['SpeedUp'] = True
|
|
|
|
|
channel['LogoUrl'] = ""
|
|
|
|
|
channel['Info'] = "unsubscribed"
|
|
|
|
|
output['Channels'].append(channel)
|
|
|
|
|
|
|
|
|
|
print(json.dumps(output, indent=2))
|
|
|
|
|
except:
|
|
|
|
|
print(response.text, file=sys.stderr)
|
|
|
|
|
return "error"
|
|
|
|
|
|
|
|
|
|
elif action == "vod":
|
|
|
|
|
output = {}
|
|
|
|
|
output['Vod'] = []
|
|
|
|
|
|
|
|
|
|
response = req.get()
|
|
|
|
|
try:
|
|
|
|
|
for item in response.json():
|
|
|
|
|
vod = {}
|
|
|
|
|
vod['Name'] = item['Name']
|
|
|
|
|
vod['Category'] = ''
|
|
|
|
|
vod['Description'] = ''
|
|
|
|
|
vod['Mode'] = "vod"
|
|
|
|
|
vod['SessionManifest'] = True
|
|
|
|
|
vod['ScriptParams'] = 'id=' + str(item['Id'])
|
|
|
|
|
vod['CdmType'] = "widevine"
|
|
|
|
|
vod['UseCdm'] = True
|
|
|
|
|
vod['Video'] = 'best'
|
|
|
|
|
output['Vod'].append(vod)
|
|
|
|
|
|
|
|
|
|
print(json.dumps(output, indent=2))
|
|
|
|
|
except:
|
|
|
|
|
print(response.text, file=sys.stderr)
|
|
|
|
|
return "error"
|
|
|
|
|
|
|
|
|
|
elif action == "events":
|
|
|
|
|
output = {}
|
|
|
|
|
output['Events'] = []
|
|
|
|
|
|
|
|
|
|
response = req.get()
|
|
|
|
|
try:
|
|
|
|
|
for ev in response.json():
|
|
|
|
|
event = {}
|
|
|
|
|
event['Name'] = ev['Name']
|
|
|
|
|
event['Mode'] = "live"
|
|
|
|
|
event['SessionManifest'] = True
|
|
|
|
|
event['ScriptParams'] = 'id=' + str(ev['Id'])
|
|
|
|
|
event['CdmType'] = "widevine"
|
|
|
|
|
event['UseCdm'] = True
|
|
|
|
|
event['Video'] = 'best'
|
|
|
|
|
event['OnDemand'] = True
|
|
|
|
|
event['SpeedUp'] = True
|
|
|
|
|
event['Start'] = int(parser.parse(ev['start_date']).timestamp())
|
|
|
|
|
event['End'] = int(parser.parse(ev['end_date']).timestamp())
|
|
|
|
|
output['Events'].append(event)
|
|
|
|
|
|
|
|
|
|
print(json.dumps(output, indent=2))
|
|
|
|
|
except:
|
|
|
|
|
print(response.text, file=sys.stderr)
|
|
|
|
|
return "error"
|
|
|
|
|
|
|
|
|
|
elif action == "epg":
|
|
|
|
|
output = {}
|
|
|
|
|
output['Epg'] = {}
|
|
|
|
|
output['Epg']['Channels'] = []
|
|
|
|
|
|
|
|
|
|
channels = req.get()
|
|
|
|
|
try:
|
|
|
|
|
for chan in channels.json():
|
|
|
|
|
channel = {}
|
|
|
|
|
channel['Name'] = chan['Name']
|
|
|
|
|
channel['EpgId'] = ""
|
|
|
|
|
channel['Lang'] = 'en'
|
|
|
|
|
channel['Entries'] = []
|
|
|
|
|
|
|
|
|
|
items = req.get()
|
|
|
|
|
for item in items.json():
|
|
|
|
|
entry = {}
|
|
|
|
|
entry['Title'] = item['Title']
|
|
|
|
|
entry['Description'] = item['Description']
|
|
|
|
|
entry['Lang'] = item['Lang']
|
|
|
|
|
entry['Start'] = int(parser.parse(item['start_date']).timestamp())
|
|
|
|
|
entry['End'] = int(parser.parse(item['end_date']).timestamp())
|
|
|
|
|
channel['Entries'].append(entry)
|
|
|
|
|
|
|
|
|
|
output['Epg']['Channels'].append(channel)
|
|
|
|
|
|
|
|
|
|
print(json.dumps(output, indent=2))
|
|
|
|
|
except:
|
|
|
|
|
print(response.text, file=sys.stderr)
|
|
|
|
|
return "error"
|
|
|
|
|
|
|
|
|
|
elif action == "heartbeat":
|
|
|
|
|
output = {
|
|
|
|
|
"ManifestUrl": response.json()['mpdUrl'],
|
|
|
|
|
"Headers": {
|
|
|
|
|
"Manifest": {
|
|
|
|
|
'User-Agent': user_agent
|
|
|
|
|
},
|
|
|
|
|
"Media": {
|
|
|
|
|
'User-Agent': user_agent
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"Heartbeat": {
|
|
|
|
|
"Url": '',
|
|
|
|
|
"Params": '',
|
|
|
|
|
"PeriodMs": 5*60*1000
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
print(json.dumps(output))
|
|
|
|
|
|
|
|
|
|
elif action == "manifest":
|
|
|
|
|
try:
|
|
|
|
|
output = {
|
|
|
|
|
"Cdn": [],
|
|
|
|
|
"ManifestUrl": response.json()['mpdUrl'],
|
|
|
|
|
"ManifestExpiration": 0, # Unix timestamp
|
|
|
|
|
"Headers": {
|
|
|
|
|
"Manifest": {
|
|
|
|
|
'User-Agent': user_agent
|
|
|
|
|
},
|
|
|
|
|
"Media": {
|
|
|
|
|
'User-Agent': user_agent
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"Heartbeat": {
|
|
|
|
|
"Url": '',
|
|
|
|
|
"Params": '',
|
|
|
|
|
"PeriodMs": 5*60*1000
|
|
|
|
|
},
|
|
|
|
|
"License": {
|
|
|
|
|
"Url": '',
|
|
|
|
|
"Params": ''
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for cdn in [ '' ]:
|
|
|
|
|
response = req.get("", headers=headers)
|
|
|
|
|
output['Cdn'].append({ "Name": cdn, "ManifestUrl": mpd_url })
|
|
|
|
|
output['ManifestUrl'] = mpd_url
|
|
|
|
|
print(json.dumps(output))
|
|
|
|
|
except:
|
|
|
|
|
print(response.text, file=sys.stderr)
|
|
|
|
|
return "error"
|
|
|
|
|
|
|
|
|
|
elif action == "cdm" and cdm == "internal":
|
|
|
|
|
# return license from challenge
|
|
|
|
|
response = req.post(licenseurl, headers=headers, data=base64.b64decode(challenge))
|
|
|
|
|
response_b64 = str(base64.b64encode(response.content), 'ascii')
|
|
|
|
|
if response_b64.startswith('CA'):
|
|
|
|
|
output = {
|
|
|
|
|
"CdmAnswer": response_b64
|
|
|
|
|
}
|
|
|
|
|
print(json.dumps(output))
|
|
|
|
|
else:
|
|
|
|
|
print(response.text, file=sys.stderr)
|
|
|
|
|
return "error"
|
|
|
|
|
|
|
|
|
|
elif action == "cdm" and cdm == "external":
|
|
|
|
|
output = {
|
|
|
|
|
"CdmAnswer": keys
|
|
|
|
|
}
|
|
|
|
|
print(json.dumps(output))
|
|
|
|
|
|
|
|
|
|
elif action == "pssh":
|
|
|
|
|
output = {
|
|
|
|
|
"ProcessedPssh": pssh
|
|
|
|
|
}
|
|
|
|
|
print(json.dumps(output))
|
|
|
|
|
elif action == "downloadmanifest":
|
|
|
|
|
response = req.get(url)
|
|
|
|
|
output = {
|
|
|
|
|
"ErrorCode": response.status_code,
|
|
|
|
|
"Payload": base64.b64encode(response.content).decode()
|
|
|
|
|
}
|
|
|
|
|
print(json.dumps(output))
|
|
|
|
|
elif action == "downloadmedia":
|
|
|
|
|
response = req.get(url)
|
|
|
|
|
output = {
|
|
|
|
|
"ErrorCode": response.status_code,
|
|
|
|
|
"Payload": base64.b64encode(response.content).decode()
|
|
|
|
|
}
|
|
|
|
|
print(json.dumps(output))
|
|
|
|
|
else:
|
|
|
|
|
print("invalid action: " + action)
|
|
|
|
|
|
|
|
|
|
if do_action() == "error":
|
|
|
|
|
if refresh() == "error":
|
|
|
|
|
login()
|
|
|
|
|
if do_action() == "error":
|
2026-06-16 22:52:55 +08:00
|
|
|
sys.exit(1)
|