Skip to content

Commit

Permalink
Merge pull request #32 from bbsan2k/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
bbsan2k authored Jul 23, 2020
2 parents 0865f4d + f5e8c0a commit 8932239
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 119 deletions.
4 changes: 3 additions & 1 deletion addon.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.f1tv" name="F1TV Kodi Plugin" version="0.1.3" provider-name="BBsan2k, TheDevFreak, and HypnOS HAXX0R">
<addon id="plugin.video.f1tv" name="F1TV Kodi Plugin" version="0.1.4" provider-name="BBsan2k, TheDevFreak, and HypnOS HAXX0R">
<requires>
<import addon="xbmc.python" version="2.25.0"/>
<import addon="script.module.routing" version="0.2.0"/>
<import addon="script.module.requests" version="2.9.1"/>
<import addon="script.module.pyjwt" version="1.6.4"/>
<import addon="script.module.cache" version="1.0.1"/>
</requires>
<extension point="xbmc.python.pluginsource" library="main.py">
<provides>video</provides>
Expand Down
5 changes: 5 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
v0.1.4
- Performance improvements through caching
- Implemented caching of session and social tokens
- Implemented caching of API responses based upon caching hints and rules returned from the F1TV APIs

v0.1.3
- Fixed crash in 'Sets'
- Fixed crash in 'List by Circuit'
Expand Down
92 changes: 81 additions & 11 deletions resources/lib/F1TVParser/AccountManager.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import requests
import json
import os
import pyjwt as jwt
import re
import xbmc
import xbmcaddon
from cache import Store

from datetime import datetime

__ACCOUNT_API__='https://api.formula1.com/v1/account/'
__ACCOUNT_CREATE_SESSION__=__ACCOUNT_API__+'Subscriber/CreateSession'
Expand Down Expand Up @@ -40,7 +47,7 @@ def exteractSessionData(self):

return self.auth_headers['apikey'], self.auth_headers['cd-systemid']

def __createSession__(self):
def __requestSessionToken(self):
login_dict = {"Login": self.username, "Password": self.password}
r = self.session.post(__ACCOUNT_CREATE_SESSION__, headers=self.auth_headers, data=json.dumps(login_dict))

Expand All @@ -50,24 +57,87 @@ def __createSession__(self):

self.session_token = r.json()["data"]["subscriptionToken"]

# Save the token
try:
session_token_store = Store("app://tokens/session/{username}".format(username=self.username))
session_token_store.clear()
session_token_store.append(self.session_token)
except:
pass
else:
raise ValueError('Account Authentication failed.')

def __requestSocialToken(self):
dict = {"identity_provider_url": __ACCOUNT_IDENTITY_PROVIDER_URL__, "access_token": self.session_token}

token_request = self.session.post(__ACCOUNT_SOCIAL_AUTHENTICATE__, data=json.dumps(dict))
if token_request.ok:
self.session.headers["Authorization"] = "JWT " + token_request.json()["token"]

# Save the token
try:
session_token_store = Store("app://tokens/social/{username}".format(username=self.username))
session_token_store.clear()
session_token_store.append(token_request.json()["token"])
except:
pass



def __createSession__(self):
# Try to load a cached token
try:
session_token_store = Store("app://tokens/session/{username}".format(username=self.username))
session_tokens = session_token_store.retrieve()
self.session_token = None
if len(session_tokens):
for cached_token in session_tokens:
cached_token_expiration_time = datetime.fromtimestamp(jwt.decode(cached_token, verify=False)['exp'])

token_validity_time_remaining = cached_token_expiration_time - datetime.now()

if token_validity_time_remaining.total_seconds() <= 60 * 60 * 24:
self.session_token = None
else:
self.session_token = cached_token
else:
self.session_token = None
except:
self.session_token = None

if self.session_token is None:
self.__requestSessionToken()
else:
raise ValueError('Account Authentification failed.')
pass

def __createAuthorization__(self):
if self.session_token is not None:
dict = {"identity_provider_url": __ACCOUNT_IDENTITY_PROVIDER_URL__, "access_token": self.session_token}
# Try to load a cached social token
try:
social_token_store = Store("app://tokens/social/{username}".format(username=self.username))
social_tokens = social_token_store.retrieve()
if len(social_tokens):
for cached_token in social_tokens:
cached_token_expiration_time = datetime.fromtimestamp(jwt.decode(cached_token, verify=False)['exp'])

token_request = self.session.post(__ACCOUNT_SOCIAL_AUTHENTICATE__, data=json.dumps(dict))
if token_request.ok:
self.session.headers["Authorization"] = "JWT " + token_request.json()["token"]
token_validity_time_remaining = cached_token_expiration_time - datetime.now()

if token_validity_time_remaining.total_seconds() <= 60 * 60 * 24:
self.__requestSocialToken()
else:
self.session.headers["Authorization"] = "JWT " + cached_token
else:
self.__requestSocialToken()

except:
self.__requestSocialToken()

def __init__(self):

def __init__(self, username = None, password = None, token = None):
self.username = username
self.password = password
self.session_token = token
self.auth_headers = {"CD-Language": "de-DE",
"Content-Type": "application/json"}
self.session = requests.session()
self.session_token = None

def getSession(self):
if self.session_token is None:
Expand All @@ -84,4 +154,4 @@ def setSessionData(self, apikey, system_id):
def login(self, username, password):
self.username = username
self.password = password
return self.getSession()
return self.getSession()
159 changes: 77 additions & 82 deletions resources/lib/F1TVParser/F1TV_Minimal_API.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import AccountManager
import json
import xbmc
import os
import urllib

from cache import Cache, conditional_headers

''' General Entry point for F1TV API'''
__TV_API__='https://f1tv-api.formula1.com/agl/1.0/gbr/en/all_devices/global/'
__OLD_TV_API__='https://f1tv.formula1.com'

''' Parameters for different F1TV API calls'''
__TV_API_PARAMS__ = {"event-occurrence": {"fields_to_expand": "image_urls,sessionoccurrence_urls,sessionoccurrence_urls__image_urls",
Expand All @@ -22,10 +23,55 @@
}



class F1TV_API:
""" Main API Object - is used to retrieve API information """

def callAPI(self, endpoint, method="GET", api_ver=2, params=None, data=None):
if int(api_ver) == 1:
complete_url = 'https://f1tv.formula1.com' + endpoint
elif int(api_ver) == 2:
complete_url = 'https://f1tv-api.formula1.com/agl/1.0/gbr/en/all_devices/global/' + endpoint
else:
xbmc.log("Unable to make an API with invalid API version: {}".format(api_ver), xbmc.LOGERROR)
return

if method.upper() == 'GET':
# Check to see if we've cached the response
with Cache() as c:
if params:
url_with_parameters = "{complete_url}?{parameters}".format(complete_url=complete_url,
parameters=urllib.urlencode(params))
else:
url_with_parameters = complete_url
cached = c.get(url_with_parameters)
if cached:
# If we have a fresh cached version, return it.
if cached["fresh"]:
return json.loads(cached["blob"])
# otherwise append applicable "If-None-Match"/"If-Modified-Since" headers
self.account_manager.getSession().headers.update(conditional_headers(cached))
# request a new version of the data
r = self.account_manager.getSession().get(complete_url, params=params, data=data)
if 200 == r.status_code:
# add the new data and headers to the cache
c.set(url_with_parameters, r.content, r.headers)
return r.json()
if 304 == r.status_code:
# the data hasn't been modified so just touch the cache with the new headers
# and return the existing data
c.touch(url_with_parameters, r.headers)
return json.loads(cached["blob"])


elif method.upper() == 'POST':
r = self.account_manager.getSession().post(complete_url, params=params, data=data)
if r.ok:
return r.json()
else:
return
else:
return

def getFields(self, url):
for key in __TV_API_PARAMS__:
if key in url:
Expand All @@ -42,114 +88,63 @@ def login(self, username, password):
def getStream(self, url):
""" Get stream for supplied viewings item
This will get the m3u8 url for Content and Channel."""
complete_url = __OLD_TV_API__ + "/api/viewings/"
item_dict = {"asset_url" if 'ass' in url else "channel_url": url}

viewing = self.account_manager.getSession().post(complete_url, data=json.dumps(item_dict))
viewing_json = self.callAPI("/api/viewings/", api_ver=1, method='POST', data=json.dumps(item_dict))

if viewing.ok:
viewing_json = viewing.json()
if 'chan' in url:
return viewing_json["tokenised_url"]
else:
return viewing_json["objects"][0]["tata"]["tokenised_url"]
if 'chan' in url:
return viewing_json["tokenised_url"]
else:
return viewing_json["objects"][0]["tata"]["tokenised_url"]

def getSession(self, url):
""" Get Session Object from API by supplying an url"""
complete_url = __TV_API__ + url
r = self.account_manager.getSession().get(complete_url, params=__TV_API_PARAMS__["session-occurrence"])

if r.ok:
return r.json()
session = self.callAPI(url, params=__TV_API_PARAMS__["session-occurrence"])
return session


def getEvent(self, url, season = None):
""" Get Event object from API by supplying an url"""
complete_url = __TV_API__ + url
r = self.account_manager.getSession().get(complete_url, params=__TV_API_PARAMS__["event-occurrence"])

if r.ok:
return r.json()
event = self.callAPI(url, params=__TV_API_PARAMS__["event-occurrence"])
return event


def getSeason(self, url):
""" Get Season object from API by supplying an url"""
complete_url = __OLD_TV_API__ + url
r = self.account_manager.getSession().get(complete_url, params=self.getFields(url)) #__TV_API_PARAMS__["season"])

if r.ok:
return r.json()
season = self.callAPI(url, api_ver=1, params=self.getFields(url))
return season

def getSeasons(self):
""" Get all season urls that are available at API"""
complete_url = __OLD_TV_API__ + "/api/race-season/"
r = self.account_manager.getSession().get(complete_url, params={'order': '-year'})

if r.ok:
return r.json()
seasons = self.callAPI("/api/race-season/", api_ver=1, params={'order': '-year'})
return seasons

def getCircuits(self):
""" Get all Circuit urls that are available at API"""
complete_url = __OLD_TV_API__ + "/api/circuit/"
r = self.account_manager.getSession().get(complete_url, params={"fields": "name,eventoccurrence_urls,self"})

if r.ok:
return r.json()
circuits = self.callAPI("/api/circuit/", api_ver=1, params={"fields": "name,eventoccurrence_urls,self"})
return circuits

def getCircuit(self, url):
""" Get Circuit object from API by supplying an url"""
complete_url = __OLD_TV_API__ + url
r = self.account_manager.getSession().get(complete_url, params=__TV_API_PARAMS__["circuit"])

if r.ok:
return r.json()
circuit = self.callAPI(url, api_ver=1, params=__TV_API_PARAMS__["circuit"])
return circuit

def getF2(self):
complete_url = __TV_API__+"/api/sets/coll_4440e712d31d42fb95c9a2145ab4dac7"
r = self.account_manager.getSession().get(complete_url)
if r.ok:
return r.json()

def getAnyURL(self, url):
complete_url = __TV_API__+url
r = self.account_manager.getSession().get(complete_url)
if r.ok:
return r.json()

def getAnyOldURL(self, url):
complete_url = __OLD_TV_API__+url
r = self.account_manager.getSession().get(complete_url)
if r.ok:
return r.json()
f2 = self.callAPI("/api/sets/coll_4440e712d31d42fb95c9a2145ab4dac7")
return f2

def getSets(self):
complete_url = __OLD_TV_API__ + "/api/sets/?slug=home"
r = self.account_manager.getSession().get(complete_url)
if r.ok:
rj = r.json()
sets = self.callAPI("/api/sets/?slug=home", api_ver=1)
content = {}
for item in rj['objects'][0]['items']:
itemj = self.account_manager.getSession().get(__OLD_TV_API__+item['content_url']).json()
if 'title' in list(itemj):
content[itemj['title']] = item['content_url']
elif 'name' in list(itemj):
content[itemj['name']] = item['content_url']
for item in sets['objects'][0]['items']:
item_details = self.callAPI(item['content_url'], api_ver=1)
if 'title' in list(item_details):
content[item_details['title']] = item['content_url']
elif 'name' in list(item_details):
content[item_details['name']] = item['content_url']
else:
content[itemj['UNKNOWN SET: ' + 'uid']] = item['content_url']
content[item_details['UNKNOWN SET: ' + 'uid']] = item['content_url']
return content

def getSetContent(self, url):
complete_url = __OLD_TV_API__ + url
r = self.account_manager.getSession().get(complete_url)
if r.ok:
return r.json()

def getEpisode(self, url):
complete_url = __OLD_TV_API__ + url
r = self.account_manager.getSession().get(complete_url)
if r.ok:
return r.json()


def setLanguage(self, language):
self.account_manager.session.headers['Accept-Language'] = "{}, en".format(language.upper())
Loading

0 comments on commit 8932239

Please sign in to comment.