55import time , uuid
66import random
77import string
8+ import base64
89
910if sys .version_info [0 ] > 2 :
1011 from urllib .parse import quote
@@ -67,6 +68,7 @@ def login(self):
6768 self .addon .setSetting ('device_id' , '' )
6869 self .addon .setSetting ('session_key' , '' )
6970 self .addon .setSetting ('entitlements' , '' )
71+ self .addon .setSetting ('okta_id' , '' )
7072 sys .exit ()
7173
7274
@@ -77,6 +79,7 @@ def logout(self):
7779 self .addon .setSetting ('device_id' , '' )
7880 self .addon .setSetting ('session_key' , '' )
7981 self .addon .setSetting ('entitlements' , '' )
82+ self .addon .setSetting ('okta_id' , '' )
8083 self .addon .setSetting ('username' , '' )
8184 self .addon .setSetting ('password' , '' )
8285
@@ -92,7 +95,7 @@ def login_token(self):
9295 def access_token (self ):
9396 return self .login_token ()
9497
95- def get_stream (self , content_id ):
98+ def get_playback (self , content_id ):
9699 if self .addon .getSetting ('device_id' ) == '' or self .addon .getSetting ('session_key' ) == '' :
97100 self .get_device_session_id ()
98101 headers = {
@@ -167,6 +170,12 @@ def get_stream(self, content_id):
167170 stream_url = r .json ()['data' ]['initPlaybackSession' ]['playback' ]['url' ]
168171 xbmc .log (f'Stream URL: { stream_url } ' )
169172 headers = 'User-Agent=' + UA_PC
173+ token = r .json ()['data' ]['initPlaybackSession' ]['playback' ]['token' ]
174+ xbmc .log (f'Token: { token } ' )
175+ return stream_url , headers , token
176+
177+ def get_stream (self , content_id ):
178+ stream_url , headers , token = self .get_playback (content_id )
170179 return stream_url , headers
171180
172181 def get_device_session_id (self ):
@@ -240,4 +249,141 @@ def get_broadcast_start_time(self, stream_url):
240249 return parse (line [25 :])
241250 except Exception as e :
242251 xbmc .log ('error getting get_broadcast_start_time ' + str (e ))
243- return None
252+ return None
253+
254+ def okta_id (self ):
255+ if self .addon .getSetting ('okta_id' ) == '' :
256+ self .get_okta_id ()
257+ return self .addon .getSetting ('okta_id' )
258+
259+ def get_okta_id (self ):
260+ try :
261+ # get a new playback token for a past free game
262+ url , headers , token = self .get_playback ('b7f0fff7-266f-4171-aa2d-af7988dc9302' )
263+ if token :
264+ encoded_okta_id = token .split ('_' )[1 ]
265+ okta_id = base64 .b64decode (encoded_okta_id .encode ('ascii' ) + b'==' ).decode ('ascii' )
266+ self .addon .setSetting ('okta_id' , okta_id )
267+ except Exception as e :
268+ xbmc .log ('error getting okta_id ' + str (e ))
269+ sys .exit (0 )
270+
271+ def get_event_stream (self , url ):
272+ xbmc .log ('fetching event video stream from url' )
273+
274+ headers = {
275+ 'User-Agent' : UA_PC ,
276+ 'Authorization' : 'Bearer ' + self .login_token (),
277+ 'Accept' : '*/*' ,
278+ 'Origin' : 'https://www.mlb.com' ,
279+ 'Referer' : 'https://www.mlb.com'
280+ }
281+ r = requests .get (url , headers = headers , verify = VERIFY )
282+
283+ text_source = r .text
284+ #xbmc.log(text_source)
285+
286+ # sometimes the returned content is already a stream playlist
287+ if text_source .startswith ('#EXTM3U' ):
288+ xbmc .log ('video url is already a stream playlist' )
289+ video_stream_url = url
290+ # otherwise it is JSON containing the stream URL
291+ else :
292+ json_source = r .json ()
293+ if 'data' in json_source and len (json_source ['data' ]) > 0 and 'value' in json_source ['data' ][0 ]:
294+ video_stream_url = json_source ['data' ][0 ]['value' ]
295+ xbmc .log ('found video stream url : ' + video_stream_url )
296+ return video_stream_url
297+
298+
299+ def get_linear_stream (self , network ):
300+ if self .addon .getSetting ('device_id' ) == '' or self .addon .getSetting ('session_key' ) == '' :
301+ self .get_device_session_id ()
302+ url = 'https://media-gateway.mlb.com/graphql'
303+ headers = {
304+ 'User-Agent' : UA_PC ,
305+ 'Authorization' : 'Bearer ' + self .login_token (),
306+ 'Content-Type' : 'application/json' ,
307+ 'Accept' : 'application/json, text/plain, */*' ,
308+ 'accept-encoding' : 'gzip, deflate, br' ,
309+ 'accept-language' : 'en-US,en;q=0.5' ,
310+ 'connection' : 'keep-alive' ,
311+ 'content-type' : 'application/json' ,
312+ 'x-client-name' : 'WEB' ,
313+ 'x-client-version' : '7.8.1' ,
314+ 'cache-control' : 'no-cache' ,
315+ 'origin' : 'https://www.mlb.com' ,
316+ 'pragma' : 'no-cache' ,
317+ 'priority' : 'u=1, i' ,
318+ 'referer' : 'https://www.mlb.com/' ,
319+ 'sec-ch-ua' : '"Chromium";v="133", "Google Chrome";v="133", "Not-A.Brand";v="8"' ,
320+ 'sec-ch-ua-mobile' : '?0' ,
321+ 'sec-ch-ua-platform' : '"macOS"' ,
322+ 'sec-fetch-dest' : 'empty' ,
323+ 'sec-fetch-mode' : 'cors' ,
324+ 'sec-fetch-site' : 'same-site'
325+ }
326+ data = {
327+ "operationName" : "contentCollections" ,
328+ "query" : '''query contentCollections(
329+ $categories: [ContentGroupCategory!]
330+ $includeRestricted: Boolean = false
331+ $includeSpoilers: Boolean = false
332+ $limit: Int = 10,
333+ $skip: Int = 0\n ) {
334+ contentCollections(
335+ categories: $categories
336+ includeRestricted: $includeRestricted
337+ includeSpoilers: $includeSpoilers
338+ limit: $limit
339+ skip: $skip
340+ ) {
341+ title
342+ category
343+ contents {
344+ assetTrackingKey
345+ contentDate
346+ contentId
347+ contentRestrictions
348+ description
349+ duration
350+ language
351+ mediaId
352+ officialDate
353+ title
354+ mediaState {
355+ state
356+ mediaType
357+ }
358+ thumbnails {
359+ thumbnailType
360+ templateUrl
361+ thumbnailUrl
362+ }
363+ }
364+ }
365+ }''' ,
366+ "variables" : {
367+ "categories" : network ,
368+ "limit" : "25"
369+ }
370+ }
371+ xbmc .log (str (data ))
372+ r = requests .post (url , headers = headers , json = data , verify = VERIFY )
373+ xbmc .log (r .text )
374+ if not r .ok :
375+ dialog = xbmcgui .Dialog ()
376+ msg = ""
377+ for item in r .json ()['errors' ]:
378+ msg += item ['code' ] + '\n '
379+ dialog .notification (LOCAL_STRING (30270 ), msg , self .icon , 5000 , False )
380+ sys .exit ()
381+
382+ availableStreams = r .json ()['data' ]['contentCollections' ][0 ]['contents' ]
383+ for stream in availableStreams :
384+ try :
385+ stream_url , headers = self .get_stream (stream ['mediaId' ])
386+ xbmc .log (f'Stream URL: { stream_url } ' )
387+ return stream_url
388+ except :
389+ pass
0 commit comments