Files
2023-10-18 22:46:37 +02:00

383 lines
16 KiB
Python

import requests
import time
import json
import base64
import m3u8
import os
from pywidevine.cdm import Cdm
from pywidevine.device import Device
from pywidevine.pssh import PSSH
from bs4 import BeautifulSoup
#Config
PATH_TO_WVD = './WVD.wvd'
def ascii_clear():
os.system('cls||clear')
print("""
^555555555557 J555555555? :GBGGGGGGGGGGGGGGGGGGGGB7 ^?PGGGGGGGGGGGGGGGGGPY7:
5@@@@@@@@@@@~ !@@@@@@@@@@7 5@@@@@@@@@@@@@@@@@@@@@@#: J&@@@@@@@@@@@@@@@@@@@@@@&?
!@@@@@@@@@@@J .B@@@@@@@@@P 7@@@@@@@@@@@@@@@@@@@@@@@7 !@@@@@@@@@@@#############&5
.G@@@@@@@@@@B. Y@@@@@@@@@&^ .YGGGGGGGGGGGGGGGGGGGGGGY .G@@@@@@@@@#~..............
?@@@@@@@@@@@~ ~@@@@@@@@@@Y ?@@@@@@@@@@7
:#@@@@@@@@@@Y .G@@@@@@@@@#: :!~~~~~~~~~~~~~~~~~~~~~!. :#@@@@@@@@@G
Y@@@@@@@@@@#: J@@@@@@@@@@? G@@@@@@@@@@@@@@@@@@@@@@B. J@@@@@@@@@&~
^&@@@@@@@@@@! ^&@@@@@@@@@B. ?@@@@@@@@@@@@@@@@@@@@@@&~ ^&@@@@@@@@@Y
P@@@@@@@@@@P P@@@@@@@@@@! :#@@@@@@@@@@@@@@@@@@@@@@J 5@@@@@@@@@#:
!@@@@@@@@@@#: ?@@@@@@@@@@P 5@@@@@@@@@@P^^^^^^^^^^^^ ~@@@@@@@@@@?
.B@@@@@@@@@@5 !&@@@@@@@@@&^ ~@@@@@@@@@@&^ G@@@@@@@@@G
?@@@@@@@@@@@&PY5B@@@@@@@@@@@Y .B@@@@@@@@@@J 7@@@@@@@@@@#5YYYYYYYYYYY55^
:#@@@@@@@@@@@@@@@@@@@@@@@@@@#: ?@@@@@@@@@@#. .B@@@@@@@@@@@@@@@@@@@@@@@@B.
:#@@@@@@@@@@@@@@@@@@@@@@@@@B~ ^&@@@@@@@@@@7 :&@@@@@@@@@@@@@@@@@@@@@@@#~
^YGB##################BG57. Y##########P ~5B###################P?:
.................... ........... ...................
Stream extractor
TAJLN 2023
""")
def do_cdm(pssh, license_url, licence_token, key_id):
pssh = PSSH(pssh)
device = Device.load(PATH_TO_WVD)
cdm = Cdm.from_device(device)
session_id = cdm.open()
challenge = cdm.get_license_challenge(session_id, pssh)
drm_info_json = {
"system":"com.widevine.alpha",
"key_ids":[key_id]
}
headers = {
'Authorization': 'Bearer ' + licence_token,
'X-DRM-INFO': base64.b64encode(json.dumps(drm_info_json).encode()).decode(),
'Content-Type': 'application/octet-stream',
'User-Agent': 'Dice Shield/12.11.0 (Linux;Android 9) ExoPlayerLib/2.18.4',
}
licence = requests.post(license_url, headers=headers, data=challenge)
licence.raise_for_status()
cdm.parse_license(session_id, licence.content)
keys = cdm.get_keys(session_id)
cdm.close(session_id)
for key in keys:
if key.type != 'SIGNING':
return f"{key.kid.hex}:{key.key.hex()}"
def find_wv_pssh_offset(raw: bytes) -> str:
offset = raw.rfind(b'pssh')
return raw[offset - 4:offset - 4 + raw[offset - 1]]
def to_pssh(content: bytes) -> str:
wv_offset = find_wv_pssh_offset(content)
return base64.b64encode(wv_offset).decode()
def current_milli_time():
return round(time.time() * 1000)
def refresh_token(token):
url = 'https://dce-frontoffice.imggaming.com/api/v2/token/refresh'
headers = {
'accept': 'application/json, text/plain, */*',
'x-api-key': '4dc1e8df-5869-41ea-95c2-6f04c67459ed',
'app': 'dice',
'accept-language': 'en-US',
'realm': 'dce.ufc',
'authorization': 'Bearer ' + token,
'Content-Type': 'application/json',
'Content-Length': '525',
'Host': 'dce-frontoffice.imggaming.com',
'Connection': 'Keep-Alive',
'Accept-Encoding': 'gzip',
'User-Agent': 'okhttp/3.11.0',
}
post_data = {
"refreshToken": "eyJhbGciOiJSUzI1NiIsInB1ciI6IlJFRiIsInNpZyI6ImciLCJ0eXAiOiJKV1QiLCJ2IjozfQ.eyJhcCI6eyJhcHQiOiJJRCJ9LCJhcHIiOiJJRCIsImF1ZCI6ImRjZS51ZmMiLCJkZXYiOiJBTkRST0lEX1BIT05FIiwiZW52IjoicHJvZCIsImV4cCI6MTY5OTMyNDQ1OCwiZ3VlIjpmYWxzZSwiaWF0IjoxNjkwNjg0NDU4LCJpc3MiOiJkY2UtaWQiLCJzdWIiOiJhWHZLclp8MjQ2YWRiNGEtOGI4YS00NWYwLWI2NGYtYjg1ODgxODUzYjNmIn0.DHydVhuWBNdIvfubRG9XujU8IDycfGo3GRdsVpyNArR-2t-WzKKrbD4K-EtYWB6kSAMGtEnfZ8-uYUSxTmBIM0vK3MV8_le5zd5jLCzMoWM89CkwaGWHLkjSMnlrxYagzPeWhLtYZplg7rxygYCh6uX7VG76WVAohmUqaLOZPro"
}
post_data = json.dumps(post_data)
response = requests.post(url, headers=headers, data=post_data)
if response.status_code == 201:
print('Token refresh successful')
data = json.loads(response.content)
return data['authorisationToken']
else:
print('Token refresh failed')
print(response.content)
quit()
def choose_event(token):
url = 'https://dce-frontoffice.imggaming.com/api/v2/event/live'
headers = {
'accept': 'application/json, text/plain, */*',
'content-type': 'application/json',
'x-api-key': '4dc1e8df-5869-41ea-95c2-6f04c67459ed',
'app': 'dice',
'accept-language': 'en-US',
'realm': 'dce.ufc',
'authorization': 'Bearer ' + token,
'Host': 'dce-frontoffice.imggaming.com',
'Connection': 'Keep-Alive',
'Accept-Encoding': 'gzip',
'User-Agent': 'okhttp/3.11.0',
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
print('Event list request successful')
data = json.loads(response.content)
events = data['events']
ascii_clear()
print('Found ' + str(len(events)) + ' currently active events:')
i = 1
for e in events:
print(str(i) + '. ' + e['title'])
i+=1
choose = int(input("\nChoose event (To search again type 0): "))
if choose == 0 or choose > len(events):
return choose_event(token)
else:
ascii_clear()
event_id = str(events[choose-1]['id'])
event_name = events[choose-1]['title']
if 'androidIAPCodes' in json.dumps(events[choose-1]):
androidIAPCodes = events[choose-1]['availablePurchases'][0]['androidIAPCodes'][0]
else:
androidIAPCodes = None
return [event_id, event_name, androidIAPCodes]
else:
print('Event list request failed')
print(response.content)
quit()
def spoof_buy(token, androidIAPCodes):
url = 'https://dce-frontoffice.imggaming.com/api/v2/googlePlay/receipt'
headers = {
'accept': 'application/json, text/plain, */*',
'x-api-key': '4dc1e8df-5869-41ea-95c2-6f04c67459ed',
'app': 'dice',
'accept-language': 'en-US',
'realm': 'dce.ufc',
'authorization': 'Bearer ' + token,
'Content-Type': 'application/json',
'Content-Length': '1802',
'Host': 'dce-frontoffice.imggaming.com',
'Connection': 'Keep-Alive',
'Accept-Encoding': 'gzip',
'User-Agent': 'okhttp/3.11.0'
}
order_id = '9990823415274633304.5480098017970283'
purchase_token = 'bfokzzrsaqtxcmwklgnxwztz.AO-J1OsFfDYXhRrvKGFBqjOUCiamHLDdHEetbxCVAoBNpOD-mTIQtoB_lLPEinJmPUMurynpiPutfRNNgHlcekcnGpWZiFsSGRYTHOveFjrKRgeLKDYcevlhZbWnRXOLhOGMjKztehMT'
purchase_time = str(current_milli_time())
reciept_string = '{"orderId":"' + order_id + '","packageName":"com.neulion.smartphone.ufc.android","productId":"' + androidIAPCodes + '","purchaseTime":' + purchase_time + ',"purchaseState":0,"purchaseToken":"' + purchase_token + '","developerPayload":"subs:' + androidIAPCodes + ':{\\"purchaseStrategy\\":{\\"type\\":\\"SUBSCRIPTION\\",\\"subscriptionPeriod\\":\\"P1M\\",\\"subscriptionMarketingPricePeriod\\":\\"P1M\\"},\\"id\\":168}","receiptData":"{\\"orderId\\":\\"6660823415974633704.5480098017970283\\",\\"packageName\\":\\"com.neulion.smartphone.ufc.android\\",\\"productId\\":\\"' + androidIAPCodes + '\\",\\"purchaseTime\\":' + purchase_time + ',\\"purchaseState\\":0,\\"purchaseToken\\":\\"' + purchase_token + '\\",\\"developerPayload\\":\\"subs:' + androidIAPCodes + ':{\\\\\\"purchaseStrategy\\\\\\":{\\\\\\"type\\\\\\":\\\\\\"SUBSCRIPTION\\\\\\",\\\\\\"subscriptionPeriod\\\\\\":\\\\\\"P1M\\\\\\",\\\\\\"subscriptionMarketingPricePeriod\\\\\\":\\\\\\"P1M\\\\\\"},\\\\\\"id\\\\\\":168}\\"}"}'
reciept_encoded = base64.b64encode(reciept_string.encode()).decode()
post_data = {
"base64EncodedReceipt": reciept_encoded
}
post_data = json.dumps(post_data)
response = requests.post(url, headers=headers, data=post_data)
if response.status_code == 202:
print('Modified purchase accepted')
else:
print('Modified purchase rejected')
print(response.content)
quit()
def request_event(token, event_id):
url = 'https://dce-frontoffice.imggaming.com/api/v2/stream/event/' + event_id
headers = {
'accept': 'application/json, text/plain, */*',
'content-type': 'application/json',
'x-api-key': '4dc1e8df-5869-41ea-95c2-6f04c67459ed',
'app': 'dice',
'accept-language': 'en-US',
'realm': 'dce.ufc',
'authorization': 'Bearer ' + token,
'Host': 'dce-frontoffice.imggaming.com',
'Connection': 'Keep-Alive',
'Accept-Encoding': 'gzip',
'User-Agent': 'okhttp/3.11.0'
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
print('Event request successful')
data = response.json()
return data['playerUrlCallback']
else:
print('Event request failed')
print(response.content)
quit()
def request_stream(url):
headers = {
'accept': 'application/json, text/plain, */*',
'content-type': 'application/json',
'x-api-key': '4dc1e8df-5869-41ea-95c2-6f04c67459ed',
'app': 'dice',
'accept-language': 'en-US',
'realm': 'dce.ufc',
'Host': 'dge-streaming.imggaming.com',
'Connection': 'Keep-Alive',
'Accept-Encoding': 'gzip',
'User-Agent': 'okhttp/3.11.0',
}
response = requests.get(url, headers)
if response.status_code == 200:
print('Stream request successful')
data = response.json()
return [data['hls']['url'], data['dash']['url'], data['dash']['drm']['url'], data['dash']['drm']['jwtToken']]
else:
print('Stream request failed')
print(response.content)
quit()
def stream_exteract(url, user_agent):
try:
print('Processing temp m3u8')
base_url = url.split('/hls/live/', 1)[0] + '/hls/live'
perm_streams = []
headers = {
'User-Agent': user_agent,
}
m3u8_obj = m3u8.loads(requests.get(url, headers=headers).content.decode())
ascii_clear()
print('Extracted urls:')
i = 1
for playlist in m3u8_obj.playlists:
m3u8_url = playlist.uri.replace('../..', base_url)
bandwidth = str(playlist.stream_info.bandwidth)
perm_streams.append({'m3u8_url': m3u8_url, 'bandwidth': bandwidth})
print(str(i) + '. '+ bandwidth + ': ' + m3u8_url)
i+=1
choose = int(input('Choose url for export (0 for none): ' ))
if choose == 0 or choose > len(perm_streams):
quit()
else:
return perm_streams[choose-1]['m3u8_url']
except Exception as e:
print('Stream extraction failed')
print(e)
def get_pssh_perm_key_id(dash_url, user_agent):
headers = {
'User-Agent': user_agent
}
response = requests.get(dash_url, headers=headers, allow_redirects=False)
location_url = response.headers['location']
response = requests.get(location_url, headers=headers)
soup = BeautifulSoup(response.content, features="xml")
audio_repr = soup.findAll('Representation')[-1]
audio_base_url = audio_repr.find('BaseURL').text
audio_key_id = audio_repr.find('ContentProtection')['cenc:default_KID']
location_parts = location_url.split('/')
location_parts.pop()
init_url = '/'.join(location_parts) + '/' + audio_base_url + 'init_' + audio_repr['id'] + '.m4s'
init_response = requests.get(init_url, headers=headers)
return [to_pssh(init_response.content), location_url, audio_key_id]
ascii_clear()
#token = 'eyJhbGciOiJSUzI1NiIsInB1ciI6IkFVVCIsInNpZyI6ImciLCJ0eXAiOiJKV1QiLCJ2IjozfQ.eyJhcCI6eyJhcHQiOiJJRCJ9LCJhcHIiOiJJRCIsImF1ZCI6WyJkY2UudWZjIl0sImRldiI6IkFORFJPSURfUEhPTkUiLCJlbnYiOiJwcm9kIiwiZXhwIjoxNjg4MjY5NDY0LCJndWUiOmZhbHNlLCJpYXQiOjE2ODgyNjg4NjQsImlwIjoiMTQ5LjcuMTYuMTQ1IiwiaXNzIjoiZGNlLWlkIiwibG8yIjoiR0IsRW5nbGFuZCxFbmdsYW5kLExvbmRvbixFQzRSLDAsMSwwIiwicm9sIjoiQ1VTVE9NRVIiLCJzdWIiOiJWY29QdW58NjAzYjVhNDAtNDU0MC00ZjdjLTgzNGMtOTU5ZGI4Y2Y2M2MyIiwidXRwIjoiSFVNQU4ifQ.TdtxoCGagJbR6s_-PYsHteZVeizk0Ydbol9CZ5POA_stIGeuIrZGU9a99zMWgrpoVZ5L8a-xVbuVg-1G6wu6B4ub5kZToAzDHVG77CpuK8nI5jWGXwG0OqYvoFU398VDUEUpAzGBv_wnetPQEyNnxcaDJF_7sYhmPYrJGgWklNk'
token = 'eyJhbGciOiJSUzI1NiIsInB1ciI6IkFVVCIsInNpZyI6ImciLCJ0eXAiOiJKV1QiLCJ2IjozfQ.eyJhcCI6eyJhcHQiOiJJRCJ9LCJhcHIiOiJJRCIsImF1ZCI6WyJkY2UudWZjIl0sImRldiI6IkFORFJPSURfUEhPTkUiLCJlbnYiOiJwcm9kIiwiZXhwIjoxNjkwNjg1MDU4LCJndWUiOmZhbHNlLCJpYXQiOjE2OTA2ODQ0NTgsImlwIjoiMTQ2LjcwLjExNi4xODIiLCJpc3MiOiJkY2UtaWQiLCJsbzIiOiJBVCxWaWVubmEsVmllbm5hLFZpZW5uYSwxMjMwLDAsMSwwIiwicm9sIjoiQ1VTVE9NRVIiLCJzdWIiOiJhWHZLclp8MjQ2YWRiNGEtOGI4YS00NWYwLWI2NGYtYjg1ODgxODUzYjNmIiwidXRwIjoiSFVNQU4ifQ.c3fg5QpwJ3s9YB7R8Fd-UE59_N306MgG0S8vHOKdgD8EzNCyuflDTI-B8JgeYOlyfHN07KivjbZ9DFJwD8B9HRwalSP39hfCRUO00uqPbBZapys_zq0MEtG0SLiqJULewZEICm2Kv6mHR-OjtUy_PBmb9gv_ZWDUjWpNf1fN-Zw'
#user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
user_agent = 'ExoDoris/12.11.0 (Linux;Android 9) ExoPlayerLib/2.18.4'
token = refresh_token(token)
event = choose_event(token)
event_id = event[0]
event_name = event[1]
androidIAPCodes = event[2]
if androidIAPCodes is not None:
spoof_buy(token, androidIAPCodes)
stream_request_url = request_event(token, event_id)
hls_url, dash_url, licence_url, licence_token = request_stream(stream_request_url)
pssh, perm_dash, key_id = get_pssh_perm_key_id(dash_url, user_agent)
key = do_cdm(pssh, licence_url, licence_token, key_id)
print('Retrieved key: ' + key)
fkeys = key.split(":")
ekeys = base64.b64encode(('{"' + fkeys[0] + '":"' + fkeys[1] + '"}').encode('ascii'))
print("\nReproductor M3U8 link: " + perm_dash + "?&ck=" + ekeys.decode('ascii'))
confirm = input('\nEnter Y to save to playlist.m3u: ')
if confirm.lower() != 'y':
quit()
f = open("playlist.m3u", "w+")
f.write('#EXTINF:-1 tvg-logo="https://www.sportsvideo.org/wp-content/uploads/2021/02/ufc-fight-pass-logo.png" group-title="PPV",' + event_name)
f.write('\n#EXTVLCOPT:http-user-agent=' + user_agent)
f.write('\n#KODIPROP:inputstream.adaptive.license_type=clearkey')
f.write('\n#KODIPROP:inputstream.adaptive.license_key=' + key)
f.write('\n' + perm_dash)
ascii_clear()
f.flush()
f.seek(0)
print('Exported ' + event_name + ' to playlist.m3u')
print('Preview: \n')
print(f.read())
f.close()