Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dec 17 Commits #24

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 34 additions & 19 deletions resources/lib/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@

import json
import urlquick
import time
import urllib.parse

from resources.lib.vars import *
from codequick.storage import PersistentDict
from codequick import Script
from base64 import b64decode
from time import time
from random import choice


Expand Down Expand Up @@ -100,7 +101,7 @@ def get_cookies():
logindata['refreshToken'] = refreshToken
logindata.flush()
Script.log('get_cookies: stored cookies to cache', lvl=Script.DEBUG)
return CIAM_TOKEN
return CIAM_TOKEN, refreshToken



Expand All @@ -110,14 +111,19 @@ def get_token():
tokeninfo = PersistentDict(".accountinfo.token", ttl=7198)
try:
access_token = tokeninfo['access_token']
expiry_time = None #tokeninfo['expiry_time']
Script.log('get_token: Got access token from cache', lvl=Script.DEBUG)
except:
access_token = None
expiry_time = None
Script.log('get_token: Unable to get access token from cache', lvl=Script.DEBUG)
if access_token:
if expiry_time:
try:
exp = json.loads(b64decode(access_token.split('.')[1]))['exp']
now = time()
# b64 = access_token.split('.')[1]
# token_json = b64decode(b64 + '=' * (-len(b64) % 4)).decode('utf-8')
# exp = json.loads(token_json)['exp']
exp = time.mktime(time.strptime(expiry_time[:26], '%Y-%m-%dT%H:%M:%S.%f'))
now = time.time()
# renew if under 5 minutes to expire
if (exp > now) and (exp - now < 300):
try:
Expand All @@ -138,38 +144,47 @@ def get_token():
Script.log('get_token: Cache token expired', lvl=Script.DEBUG)
auth_data = None
access_token = None
expiry_time = None
else:
return access_token
except:
except Exception as exp:
Script.log(exp, lvl=Script.ERROR)
access_token = None

if not access_token:
CIAM_TOKEN = get_cookies()
if not access_token or not expiry_time:
CIAM_TOKEN, refreshToken = get_cookies()
if not CIAM_TOKEN:
return False
headers['CIAM_TOKEN'] = CIAM_TOKEN
#headers['CIAM_TOKEN'] = CIAM_TOKEN
Script.log('get_token: trying to get new token', lvl=Script.DEBUG)
auth_data = urlquick.post(
auth_data = urlquick.get(
AUTH_URL,
headers=headers,
data=auth_payload,
cookies={
'nbaidentity': '{"jwt":"%s","refreshToken":"%s"}' % (CIAM_TOKEN, refreshToken)
},
max_age=0
).json()
login_status = auth_data['code']

login_status = auth_data['status']
Script.log('get_cookies: Login status %s' % login_status, lvl=Script.DEBUG)
access_token = auth_data['data']['accessToken']
if not 'success' in login_status:
return False

access_token = auth_data['data']['AccessToken']
tokeninfo['access_token'] = access_token
tokeninfo['expiry_time'] = auth_data['data']['ExpiryTime']
tokeninfo.flush()
Script.log('get_token: stored access token to cache', lvl=Script.DEBUG)
login_headers.update({'authorization': 'Bearer %s' % access_token})
login_headers.update({'authorization': 'Bearer %s' % CIAM_TOKEN})
params = {'associations': 'false'}
Script.log('get_token: getting subscrition infos', lvl=Script.DEBUG)
subscrition_data = urlquick.post(
subscrition_data = urlquick.get(
SUBSCRIPTION_URL,
headers=login_headers,
max_age=86400
).json()
if 'subs' in subscrition_data:
if 'subscriptions' in subscrition_data['data']:
Script.log('get_token: found subscrition infos', lvl=Script.DEBUG)
"""
subscrition_type = subscrition_data['subs'][0]['productSubType']
Expand Down Expand Up @@ -225,8 +240,8 @@ def get_headers(free=False):
if access_token:
headers = {
'User-Agent': USER_AGENT,
'Content-Type': 'application/x-www-form-urlencoded',
'authorization': 'Bearer %s' % access_token
'Content-Type': 'application/json',
'Authorization': 'OAUTH2 access_token="%s"' % access_token
}
else:
headers = None
Expand All @@ -248,7 +263,7 @@ def get_profile_info():
FAVORITE_TEAMS = None
FAVORITE_PLAYERS = None
if not FAVORITE_TEAMS or not FAVORITE_PLAYERS:
access_token = get_cookies()
access_token, refreshToken = get_cookies()
if not access_token:
return {'FAVORITE_TEAMS': None, 'FAVORITE_PLAYERS': None}
headers = {
Expand Down
167 changes: 63 additions & 104 deletions resources/lib/games.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,26 @@
import xbmcgui
import os
import re
import uuid

from resources.lib.vars import *
from resources.lib.tools import *
from resources.lib.auth import get_headers
from resources.lib.auth import get_profile_info
from resources.lib.auth import get_device_ids
from resources.lib.auth import get_token
from codequick import Route
from codequick import Listitem
from codequick import Resolver
from codequick.utils import bold
from inputstreamhelper import Helper
from base64 import b64encode





def process_games(game, teams_info):
def process_games(game, teams_info, cache_max_age):
gameID = game['id']
gameCode = game['seoName']
game_time = game['st']
Expand Down Expand Up @@ -149,7 +152,8 @@ def process_games(game, teams_info):
start_time=game_timestamp,
end_time=game_end_timestamp,
game_state=game_state,
feeds=feeds
feeds=feeds,
cache_max_age=cache_max_age
)
return liz

Expand Down Expand Up @@ -300,7 +304,7 @@ def BROWSE_GAMES(plugin, DATE=None, games=None, cache_max_age=0):
if 'game' in game:
if not game['game']:
continue
liz = process_games(game, teams_info)
liz = process_games(game, teams_info, cache_max_age)
yield liz

if not liz:
Expand Down Expand Up @@ -350,7 +354,7 @@ def BROWSE_MONTHS(plugin, year=None, team=None, cal=False):
else:
this_year = False
month = 12
headers = get_headers(True)
headers = None #get_headers(True)
if not headers:
yield False
return
Expand Down Expand Up @@ -437,7 +441,7 @@ def BROWSE_MONTH(plugin, year, month, team, **kwargs):
for game in games:
if game != []:
game = game[0]
liz = process_games(game,teams_info)
liz = process_games(game,teams_info, 0)
yield liz


Expand All @@ -458,122 +462,77 @@ def BROWSE_YEARS(plugin, year, team=False):


@Route.register(content_type="videos")
def BROWSE_GAME(plugin, gameID, start_time, end_time, game_state, feeds):
for feed in feeds:
feed['gameID'] = gameID
feed['start_time'] = start_time
feed['end_time'] = end_time
feed['game_state'] = game_state
def BROWSE_GAME(plugin, gameID, start_time, end_time, game_state, feeds, cache_max_age):
headers = get_headers()
play_options = urlquick.get(
PLAY_OPTIONS_URL % gameID,
headers=headers,
max_age=cache_max_age
).json()
for play_option in play_options['Vods']:
yield Listitem.from_dict(
PLAY_GAME,
bold(play_option['DisplayName'][0]['Value']),
params = {
'gameID': gameID,
'videoProfileId': play_option['PlayActions'][0]['VideoProfile']['Id'],
'applicationToken': '0'
}
)
if len(play_options['Schedules']) > 0:
for play_option in play_options['Schedules'][0]['Productions']:
yield Listitem.from_dict(
PLAY_GAME,
bold(feed['name']),
params = feed
bold(play_option['DisplayName'][0]['Value']),
params = {
'gameID': gameID,
'videoProfileId': play_option['ExternalId'],
'applicationToken': play_option['Id']
}
)



@Resolver.register
def PLAY_GAME(plugin, gameID, start_time, end_time, game_state, name, gt, cn, rd):
plugin.log('PLAY_GAME start_time: %s' % start_time, lvl=plugin.DEBUG)
plugin.log('PLAY_GAME end_time: %s' % end_time, lvl=plugin.DEBUG)
headers = get_headers()
if not headers:
yield False
return
def PLAY_GAME(plugin, gameID, videoProfileId, applicationToken):
access_token = get_token()
deviceinfos = get_device_ids()
DEVICEID = deviceinfos['PCID']
PCID = deviceinfos['PCID']
payload_data = {
'type': 'game',
'extid': gameID,
'drmtoken': True,
'deviceid': DEVICEID,
'pcid': PCID,
'gt': gt,
'gs': game_state,
'format': 'json'
}
if cn:
payload_data['cam'] = cn
if end_time:
duration = end_time - start_time
payload_data.update({'st': start_time})
payload_data.update({'dur': duration})
game_type = 'archive'
else:
game_type = 'live'
plugin.log('PLAY_GAME: Fetching url %s' % PUBLISH_ENDPOINT, lvl=plugin.DEBUG)
plugin.log('PLAY_GAME: params %s' % payload_data, lvl=plugin.DEBUG)
plugin.log('PLAY_GAME: game type %s' % game_type, lvl=plugin.DEBUG)
Response = urlquick.post(
PUBLISH_ENDPOINT,
data=payload_data,
headers=headers,
max_age=0
).json()
url = Response['path']
drm = Response['drmToken']
try:
stream_type = Response['streamType']
if stream_type == 'dash':
protocol = 'mpd'
else:
protocol = 'hls'
except:
protocol = 'hls'

headers = {'User-Agent': USER_AGENT}
start_point = None
live_play_type = int(Script.setting.get_string('live_play_type'))
ret = None
if game_type == 'live':
if live_play_type == 0:
line1 = "Start from Beginning"
line2 = "Go LIVE"
ret = xbmcgui.Dialog().select("Game Options", [line1, line2])
if ret == -1:
yield None
return
if ret == 0 or live_play_type == 2:
url = url.replace('br_long_master', 'master')
content = urlquick.get(url, headers=headers).text
sample = re.findall('(.*video.*\.m3u8?)', content)[0]
match = re.search('(https?)://([^:]+)/([^?]+?)\?(.+)$', url)
baseurl = os.path.dirname(match.group(1)+'://'+match.group(2)+'/'+match.group(3))
ql_url = baseurl + '/' + sample
content = urlquick.get(ql_url, headers=headers, max_age=0).text
durations = re.findall('\#EXTINF\:([0-9]+\.[0-9]+)\,', content)
duration = sum([float(i) for i in durations])
if ret == 0 or live_play_type == 2:
stream_start = re.findall('PROGRAM\-DATE\-TIME\:(.*)', content)[0]
stream_start = time.strptime(stream_start, '%Y-%m-%dT%H:%M:%S.%fZ')
stream_start_ts = calendar.timegm(stream_start) * 1000
start_point = str(int((start_time - stream_start_ts) / 1000))
elif ret == 1 or live_play_type == 1 :
start_point = str(duration).split('.')[0]

headers = {
'content-type': 'text/plain;charset=UTF-8',
'authorizationtoken': access_token,
'azukiimc': 'IMC7.1.0_AN_D3.0.0_S0',
'deviceprofile': b64encode(('{"model":"Unknown","osVersion":"89.0.4389.114","vendorName":"Unknown","osName":"HTML5","deviceUUID":"%s"}' % deviceinfos['DEVICEID']).encode('ascii')),
'ApplicationToken': applicationToken
}
sessionId = uuid.uuid1()
play_options = urlquick.post(
PLAY_ROLL_URL % (videoProfileId, sessionId),
headers=headers,
data=b'{}',
max_age=0,
raise_for_status=False
).json()['response']
Script.log(play_options)
url = '%s/%s&ISO3166=US&sessionId=%s' % (play_options['cdns']['cdn'][0]['base_uri'], play_options['manifest_uri'], sessionId)
protocol = play_options['package_type']
license_url = WIDEVINE_LICENSE_URL % (videoProfileId, sessionId)

liz = Listitem()
liz.path = url
liz.label = name
if rd:
yield liz
return
#liz.label = name
liz.property[INPUTSTREAM_PROP] = 'inputstream.adaptive'

is_helper = Helper(protocol, drm=DRM)
if is_helper.check_inputstream():
liz.property['inputstream.adaptive.manifest_type'] = protocol
liz.property['inputstream.adaptive.license_type'] = DRM
license_key = '%s|authorization=bearer %s|R{SSM}|' % (LICENSE_URL, drm)
license_key = '%s|AuthorizationToken=%s&ApplicationToken=%s|R{SSM}|' % (license_url, access_token, applicationToken)
liz.property['inputstream.adaptive.license_key'] = license_key
liz.property['inputstream.adaptive.manifest_update_parameter'] = 'full'
liz.property['inputstream.adaptive.play_timeshift_buffer'] = 'true'
liz.property['ResumeTime'] = '2000'
yield liz

if start_point:
plugin.log('PLAY_GAME start_point: %s' % start_point, lvl=plugin.DEBUG)
liz.property['ResumeTime'] = start_point
liz.property['TotalTime'] = '14400'
yield False
return

yield liz
else:
yield False
return
9 changes: 6 additions & 3 deletions resources/lib/vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,19 @@
PROFILE_URL = IDENTIFY + 'profile'
LOGIN_URL = IDENTIFY + 'auth'
TOKEN_URL = IDENTIFY + 'oauth/token'
AUTH_URL = IDENTIFY + 'sts'
SUBSCRIPTION_URL = IDENTIFY + 'profile?entitlements=true'

WATCH = 'https://watch.nba.com/'
AUTH_URL = WATCH + 'secure/authenticate'
GAME_DATA_ENDPOINT = WATCH + 'game/%s?format=json'
CONFIG_ENDPOINT = WATCH + 'service/config?format=json&cameras=true'
FREE_TOKEN_URL = WATCH + 'secure/accesstoken'

PLAY_OPTIONS_URL = 'https://ottapp-appgw-client.nba.com/S1/subscriber/v1/events/%s/play-options?IsexternalId=true'
PLAY_ROLL_URL = 'https://ottapp-appgw-amp.nba.com/v1/client/roll?ownerUid=azuki&mediaId=%s&sessionId=%s'
WIDEVINE_LICENSE_URL = 'https://ottapp-appgw-amp.nba.com/v1/client/get-widevine-license?ownerUid=azuki&mediaId=%s&sessionId=%s'

NBAAPI = 'https://nbaapi.neulion.com/api_nba/v1/'
SUBSCRIPTION_URL = NBAAPI + 'account/subscriptions'
RENEW_TOKEN_URL = NBAAPI + 'accesstoken'
PUBLISH_ENDPOINT = NBAAPI + 'publishpoint'
GAME_URL = NBAAPI + 'game'
Expand Down Expand Up @@ -92,4 +96,3 @@
'hls': {'extensions': ['m3u8', 'm3u'], 'mimetype': 'application/vnd.apple.mpegurl'},
}
DRM = 'com.widevine.alpha'
LICENSE_URL = 'https://shield-twoproxy.imggaming.com/proxy'