diff --git a/__init__.py b/__init__.py
new file mode 100755
index 0000000..b53149b
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1 @@
+# dummy file to init the directory
diff --git a/addon.xml b/addon.xml
new file mode 100755
index 0000000..1104f3a
--- /dev/null
+++ b/addon.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ video
+
+
+
+ Stream Most U.K. Live TV
+ Stream Most U.K. Live TV
+ all
+
+
\ No newline at end of file
diff --git a/changelog.txt b/changelog.txt
new file mode 100755
index 0000000..9b061eb
--- /dev/null
+++ b/changelog.txt
@@ -0,0 +1,2 @@
+v1.0.0
+Initial Release
\ No newline at end of file
diff --git a/default.py b/default.py
new file mode 100755
index 0000000..edd276c
--- /dev/null
+++ b/default.py
@@ -0,0 +1,635 @@
+# -*- coding: utf-8 -*-
+
+import urllib, urllib2, sys, re, xbmcplugin, xbmcgui, xbmcaddon, xbmc, os
+from datetime import datetime, tzinfo, timedelta
+import json
+import util
+from urlparse import urlparse
+import net
+import base64
+
+net = net.Net()
+
+ADDON = xbmcaddon.Addon(id='plugin.video.tvplayer')
+
+datapath = xbmc.translatePath(ADDON.getAddonInfo('profile'))
+cookie_path = os.path.join(datapath, 'cookies')
+cookie_jar = os.path.join(cookie_path, 'tvplayer.lwp')
+if not os.path.exists(cookie_path):
+ os.makedirs(cookie_path)
+
+use_inputstream = ADDON.getSetting('use_inputstream') == 'true'
+allow_drm = ADDON.getSetting('allow_drm') == 'true' and use_inputstream
+premium_enabled = ADDON.getSetting('premium') == 'true' and allow_drm
+authentication_enabled = ADDON.getSetting('email') is not None and ADDON.getSetting('email') != '' and ADDON.getSetting('password') is not None and ADDON.getSetting('password') != ''
+isJarvis = xbmc.getInfoLabel("System.BuildVersion").startswith("16.")
+
+addonPath = xbmc.translatePath(xbmcaddon.Addon().getAddonInfo('path'))
+artPath = os.path.join(addonPath, 'resources', 'media')
+DEFAULT_THUMB = os.path.join(artPath, 'default_thumb.png')
+
+unwanted_genres = ['Desi','Teleshopping','Music','Lifestyle']
+
+platform = 'android'
+version = '4.1.3'
+
+EPG_URL = 'http://api.tvplayer.com/api/v2/epg/?platform=' + platform + '&from=%s&hours=1'
+
+STARTUP_URL = 'http://assets.storage.uk.tvplayer.com/' + platform + '/v4/startups/tv/' + version + '/startup.json'
+
+USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5'
+API_USER_AGENT = 'TVPlayer_4.1.3 (54059) - tv'
+MOBILE_USER_AGENT = 'iPhone/iOS 8.4 (iPhone; U; CPU iPhone OS 8_4 like Mac OS X;)'
+
+
+class Zone(tzinfo):
+ def __init__(self, offset, isdst, name):
+ self.offset = offset
+ self.isdst = isdst
+ self.name = name
+
+ def utcoffset(self, dt):
+ return timedelta(hours=self.offset) + self.dst(dt)
+
+ def dst(self, dt):
+ return timedelta(hours=1) if self.isdst else timedelta(0)
+
+ def tzname(self, dt):
+ return self.name
+
+
+def login():
+ loginurl = 'https://tvplayer.com/account/login/'
+ email = ADDON.getSetting('email')
+ password = ADDON.getSetting('password')
+
+ headers = {'Host': 'tvplayer.com',
+ 'User-Agent': USER_AGENT,
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Accept': 'text/html',
+ 'Referer': 'https://tvplayer.com/watch',
+ 'Accept-Encoding': 'gzip',
+ 'Accept-Language': 'en-US'}
+
+ link = net.http_GET(loginurl, headers).content
+ net.save_cookies(cookie_jar)
+ token = re.compile('name="token" value="(.+?)"').findall(link)[0]
+ data = {'email': email, 'password': str(password), 'token': token}
+
+ net.set_cookies(cookie_jar)
+
+ net.http_POST(loginurl, data, headers)
+ net.save_cookies(cookie_jar)
+
+
+def login_api():
+ loginUrl = 'http://api.tvplayer.com/api/v2/auth/?platform=' + platform
+
+ email = ADDON.getSetting('email')
+ password = ADDON.getSetting('password')
+
+ headers = {'Authorization': 'Basic ' + base64.b64encode('%s:%s' % (email, password)),
+ 'User-Agent': API_USER_AGENT,
+ 'Accept-Encoding': 'gzip'}
+
+ login_response = json.loads(net.http_GET(loginUrl, headers).content)['tvplayer']['response']
+
+ access_token = login_response['access_token']
+ access_token_expires = login_response['expires']
+
+ ADDON.setSetting('access_token', access_token)
+ ADDON.setSetting('access_token_expires', access_token_expires)
+
+ return access_token, access_token_expires
+
+
+def get_token(retry=1):
+ access_token = ADDON.getSetting('access_token')
+ access_token_expires = ADDON.getSetting('access_token_expires')
+
+ expires = util.strptime_workaround(access_token_expires[:-5]) if access_token_expires else None # 2018-04-13T02:53:14+0000
+
+ if not access_token or not expires or expires < datetime.utcnow():
+ login_api()
+ return get_token(retry - 1) if retry > 0 else None
+
+ return access_token
+
+
+def get_packs():
+
+ packs_list = ADDON.getSetting('packs')
+
+ if packs_list:
+ return json.loads(packs_list)
+
+ token = get_token()
+
+ url = 'http://api.tvplayer.com/api/v2/account/get?platform=%s&token=%s' % (platform, token)
+
+ headers = {'User-Agent': API_USER_AGENT,
+ 'Accept-Encoding': 'gzip'}
+
+ xbmc.log('PACKS URL: %s' % url)
+
+ response = json.loads(net.http_GET(url, headers).content)['tvplayer']['response']
+
+ packs = response['packs']
+
+ packs_list = [int(pack['id']) for pack in packs]
+
+ ADDON.setSetting('packs', json.dumps(packs_list))
+
+ return packs_list
+
+
+def findprogramme(programmes, now, tzinfo):
+ for programme in programmes:
+ # try:
+ start = util.strptime_workaround(programme['start'][:-5])
+ start = start.replace(tzinfo=tzinfo) + util.get_utc_delta()
+ end = util.strptime_workaround(programme['end'][:-5]) + util.get_utc_delta()
+ end = end.replace(tzinfo=tzinfo) + util.get_utc_delta()
+
+ if end >= now >= start:
+ xbmc.log('PROGRAMME: %s' % programme)
+ return programme, start, end
+ # except:
+ # pass
+
+ return None, None, None
+
+
+def CATEGORIES():
+ tzinfo = Zone(0, False, 'GMT')
+ now = datetime.now(tzinfo)
+ EST = now.strftime('%Y-%m-%dT%H:%M:%S')
+
+ #xbmc.log("URL: %s" % URL)
+ response = OPEN_URL(EPG_URL % str(EST))
+
+ link = json.loads(response)
+
+ data = link['tvplayer']['response']['channels']
+
+ uniques = []
+
+ if ADDON.getSetting('genre') == 'true':
+ GENRE = 'All'
+ uniques.append(GENRE)
+ addDir(GENRE, 'url', 2, '', GENRE, '', GENRE, GENRE)
+
+ my_packs = get_packs()
+
+ for field in data:
+
+ packs = field['packs']
+
+ if len([pack for pack in packs if int(pack) in my_packs]) == 0:
+ continue
+
+ programme, start, end = findprogramme(field['programmes'], now, tzinfo)
+
+ sort_title = field['order']
+ id = str(field['id'])
+ name = field['name']
+ channel_name = name
+ studio = name
+ clearlogo = field['logo']['colour']
+ icon = field['logo']['composite']
+ tvshowtitle = programme['title'] or 'N/A' if programme is not None else 'N/A'
+ title = programme['subtitle'] or 'N/A' if programme is not None else 'N/A'
+ subtitle = programme['subtitle'] if programme is not None else ''
+ GENRE = (field['genre'] or 'No Genre') if field['genre'] != 'No Genre' else field['group'] or field['genre'] #field["genre"]
+ category = programme['category'] if programme is not None else GENRE
+ is_closed = programme['category'] == 'Close' if programme is not None else True
+ is_online = field['status'] == 'online'
+ blackout = programme['blackout'] if programme is not None else True
+
+ seasonNumber = programme['seasonNumber'] if programme is not None else None
+ episodeNumber = programme['episodeNumber'] if programme is not None else None
+ episodeInfo = u' (S%02d E%02d)' % (seasonNumber, episodeNumber) if seasonNumber and episodeNumber else ''
+
+ # start = field['programmes'][0]['start'] #"start": "2017-05-18T23:40:00+0000"
+ # end = field['programmes'][0]['end'] #"end": "2017-05-19T05:00:00+0000"
+
+ try:
+ duration = util.get_total_seconds(end - start)
+ plotoutline = datetime.strftime(start, '%H:%M') + ' - ' + datetime.strftime(end, '%H:%M')
+ except:
+ duration = None
+ plotoutline = None
+
+ try:
+ desc = programme['synopsis'].encode("utf-8")
+ except:
+ desc = ''
+
+ color = '[COLOR royalblue]'
+
+ if field['type'] == 'free' and field['authRequired'] is False:
+ add = ''
+ elif field['type'] == 'free' and field['authRequired'] is True:
+ # color = '[COLOR navy]'
+ pass
+ else:
+ color = '[COLOR magenta]'
+ name = color + name.encode("utf-8") + '[/COLOR] - [COLOR white]' + tvshowtitle.encode("utf-8") + episodeInfo.encode("utf-8") + ((' / ' + subtitle.encode("utf-8")) if subtitle else '') + '[/COLOR]' + add
+ status = field['status']
+ fanart = programme['thumbnail'] if programme is not None else None
+ if status == 'online' and not is_closed and is_online and not blackout and (allow_drm or (not field['drmEnabled'] and field['type'] != 'paid')):
+ if ADDON.getSetting('genre') == 'false':
+ if ADDON.getSetting('filter_channels') != 'true' or GENRE not in unwanted_genres:
+ if premium_enabled:
+ addDir(name, id, 200, icon, desc, fanart, category, sorttitle=sort_title, clearlogo=clearlogo, tvshowtitle=tvshowtitle, title=title, studio=studio, startdate=start, duration=duration, plotoutline=plotoutline, channel_name=channel_name)
+ else:
+ if field['type'] == 'free' and field['authRequired'] is False:
+ addDir(name, id, 200, icon, desc, fanart, category, sorttitle=sort_title, clearlogo=clearlogo, tvshowtitle=tvshowtitle, title=title, studio=studio, startdate=start, duration=duration, plotoutline=plotoutline, channel_name=channel_name)
+ elif field['type'] == 'free' and field['authRequired'] is True and authentication_enabled is True:
+ addDir(name, id, 200, icon, desc, fanart, category, sorttitle=sort_title, clearlogo=clearlogo, tvshowtitle=tvshowtitle, title=title, studio=studio, startdate=start, duration=duration, plotoutline=plotoutline, channel_name=channel_name)
+ else:
+ if GENRE not in uniques:
+ if premium_enabled or (field['type'] == 'free' and not field['authRequired']) or (field['type'] == 'free' and field['authRequired'] and authentication_enabled):
+ uniques.append(GENRE)
+ addDir(GENRE, 'url', 2, '', GENRE, '', GENRE, GENRE)
+
+ xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_SORT_TITLE)
+ xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_LABEL)
+
+ setView('LiveTV' if isJarvis else 'tvshows', 'default')
+
+
+def GENRE(genre, url):
+ tzinfo = Zone(0, False, 'GMT')
+ now = datetime.now(tzinfo)
+ EST = now.strftime('%Y-%m-%dT%H:%M:%S')
+ response = OPEN_URL(EPG_URL % str(EST))
+
+ link = json.loads(response)
+
+ data = link['tvplayer']['response']['channels']
+
+ xbmc.log("DATA: %s" % data)
+
+ genre = genre or 'No Genre'
+
+ my_packs = get_packs()
+
+ for field in data:
+
+ packs = field['packs']
+
+ if len([pack for pack in packs if int(pack) in my_packs]) == 0:
+ continue
+
+ programme, start, end = findprogramme(field['programmes'], now, tzinfo)
+
+ sort_title = field['order']
+ id = str(field['id'])
+ name = field['name']
+ channel_name = name
+ studio = name
+ clearlogo = field['logo']['colour']
+ icon = field['logo']['composite']
+ tvshowtitle = programme['title'] or 'N/A' if programme is not None else 'N/A'
+ title = programme['subtitle'] or 'N/A' if programme is not None else 'N/A'
+ subtitle = programme['subtitle'] if programme is not None else ''
+ GENRE = (field['genre'] or 'No Genre') if field['genre'] != 'No Genre' else field['group'] or field[
+ 'genre'] # field["genre"]
+ category = programme['category'] if programme is not None else GENRE
+ is_closed = programme['category'] == 'Close' if programme is not None else True
+ is_online = field['status'] == 'online'
+ blackout = programme['blackout'] if programme is not None else True
+
+ seasonNumber = programme['seasonNumber'] if programme is not None else None
+ episodeNumber = programme['episodeNumber'] if programme is not None else None
+ episodeInfo = u' - S%02d/E%02d' % (seasonNumber, episodeNumber) if seasonNumber and episodeNumber else ''
+
+ # start = field['programmes'][0]['start'] #"start": "2017-05-18T23:40:00+0000"
+ # end = field['programmes'][0]['end'] #"end": "2017-05-19T05:00:00+0000"
+
+ try:
+ duration = util.get_total_seconds(end - start)
+ plotoutline = datetime.strftime(start, '%H:%M') + ' - ' + datetime.strftime(end, '%H:%M')
+ except:
+ duration = None
+ plotoutline = None
+
+ try:
+ desc = programme['synopsis'].encode("utf-8")
+ except:
+ desc = ''
+
+ color = '[COLOR royalblue]'
+
+ if field['type'] == 'free' and field['authRequired'] is False:
+ add = ''
+ elif field['type'] == 'free' and field['authRequired'] is True:
+ # color = '[COLOR navy]'
+ pass
+ else:
+ color = '[COLOR magenta]'
+
+ name = color + name.encode("utf-8") + '[/COLOR] - [COLOR white]' + tvshowtitle.encode("utf-8") + episodeInfo + ((' / ' + subtitle) if subtitle else '') + '[/COLOR]' + add
+
+ status = field['status']
+ fanart = programme['thumbnail'] if programme is not None else None
+ if status == 'online' and not is_closed and is_online and not blackout and (allow_drm or (not field['drmEnabled'] and field['type'] != 'paid')):
+ if GENRE in genre or (genre == 'All' and (ADDON.getSetting('filter_channels') != 'true' or GENRE not in unwanted_genres)):
+ if premium_enabled:
+ addDir(name, id, 200, icon, desc, fanart, category, sorttitle=sort_title, clearlogo=clearlogo, tvshowtitle=tvshowtitle, title=title, studio=studio, startdate=start, duration=duration, plotoutline=plotoutline, channel_name=channel_name)
+ else:
+ if field['type'] == 'free' and field['authRequired'] is False:
+ addDir(name, id, 200, icon, desc, fanart, category, sorttitle=sort_title, clearlogo=clearlogo, tvshowtitle=tvshowtitle, title=title, studio=studio, startdate=start, duration=duration, plotoutline=plotoutline, channel_name=channel_name)
+ elif field['type'] == 'free' and field['authRequired'] is True and authentication_enabled is True:
+ addDir(name, id, 200, icon, desc, fanart, category, sorttitle=sort_title, clearlogo=clearlogo, tvshowtitle=tvshowtitle, title=title, studio=studio, startdate=start, duration=duration, plotoutline=plotoutline, channel_name=channel_name)
+
+ if ADDON.getSetting('sort') == 'true':
+ xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE)
+
+ setView('LiveTV' if isJarvis else 'tvshows', 'default')
+
+
+def OPEN_URL(url):
+ req = urllib2.Request(url)
+ req.add_header('User-Agent', USER_AGENT)
+ response = urllib2.urlopen(req)
+ link = response.read()
+ response.close()
+ return link
+
+
+def OPEN_URL_STREAM_URL(url):
+ import time
+ # timestamp = int(time.time()) + 4 * 60 * 60
+ header = {'Token': ADDON.getSetting('token'), 'Token-Expiry': ADDON.getSetting('expiry'),
+ 'Referer': ADDON.getSetting('referer'),
+ 'User-Agent': MOBILE_USER_AGENT}
+ req = urllib2.Request(url, headers=header)
+
+ response = urllib2.urlopen(req)
+ link = response.read()
+ response.close()
+ cookie = response.info()['Set-Cookie']
+ return link, cookie
+
+
+def tvplayer(url, name):
+ if authentication_enabled:
+ login()
+ net.set_cookies(cookie_jar)
+
+ headers = {'Host': 'tvplayer.com',
+ 'Connection': 'keep-alive',
+ 'Origin': 'http://tvplayer.com',
+ 'User-Agent': USER_AGENT,
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
+ 'Accept': '*/*',
+ 'Accept-Encoding': 'gzip, deflate',
+ 'Accept-Language': 'en-US,en;q=0.8'}
+
+ html = net.http_GET('http://tvplayer.com/watch/%s' % name.lower().replace(' ', ''), headers).content
+
+ resource = re.compile('data-resource="(.+?)"').findall(html)[0]
+ nouce = re.compile('data-token="(.+?)"').findall(html)[0]
+
+ jsonString = net.http_GET('https://tvplayer.com/watch/context?resource=%s&gen=%s' % (resource, nouce), headers).content
+
+ resourceJson = json.loads(jsonString)
+
+ VALIDATE = resourceJson['validate']
+
+ TOKEN = resourceJson['token'] if 'token' in resourceJson else 'null'
+
+ data = {'service': '1',
+ 'platform': 'chrome',
+ 'id': url,
+ 'token': TOKEN,
+ 'validate': VALIDATE}
+
+ POSTURL = 'http://api.tvplayer.com/api/v2/stream/live'
+ headers = {'Host': 'api.tvplayer.com',
+ 'Connection': 'keep-alive',
+ 'Origin': 'http://api.tvplayer.com',
+ 'User-Agent': USER_AGENT,
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
+ 'Accept': '*/*',
+ 'Accept-Encoding': 'gzip, deflate',
+ 'Accept-Language': 'en-US,en;q=0.8'}
+
+ LINK = net.http_POST(POSTURL, data, headers=headers).content
+
+ xbmc.log('LINK: %s' % LINK)
+
+ net.save_cookies(cookie_jar)
+
+ link_json = json.loads(LINK)
+
+ return link_json['tvplayer']['response']['stream'], link_json['tvplayer']['response']['drmToken'] if 'drmToken' in link_json['tvplayer']['response'] else None
+
+ # GET WORKS TOO
+ # POSTURL='http://api.tvplayer.com/api/v2/stream/live?service=1&platform=website&id=%stoken=null&validate=%s'% (url,VALIDATE)
+ # LINK=net.http_GET(POSTURL,headers=headers).content
+ # return re.compile('stream": "(.+?)"').findall(LINK)[0]
+
+
+def PLAY_STREAM(name, url, iconimage):
+ STREAM, drm_token = tvplayer(url, name)
+
+ # HOST = STREAM.split('//')[1]
+ # HOST = HOST.split('/')[0]
+
+ # headers = {'Host': HOST,
+ # 'Connection': 'keep-alive',
+ # 'Origin': 'http://' + HOST,
+ # 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'}
+
+ TOKEN = open(cookie_jar).read()
+ TOKEN = 'AWSELB=' + re.compile('AWSELB=(.+?);').findall(TOKEN)[0]
+
+ liz = xbmcgui.ListItem(name, iconImage='DefaultVideo.png', thumbnailImage=iconimage)
+ liz.setInfo(type='Video', infoLabels={'Title': name})
+ liz.setProperty("IsPlayable", "true")
+
+ if use_inputstream:
+ parsed_url = urlparse(STREAM)
+ xbmc.log("PARSED STREAM URL: %s" % parsed_url.path)
+ if parsed_url.path.endswith(".m3u8"):
+ liz.setProperty('inputstream.adaptive.manifest_type', 'hls')
+ liz.setProperty('inputstreamaddon', 'inputstream.adaptive')
+ liz.setProperty('inputstream.adaptive.stream_headers', 'cookie=' + TOKEN)
+ elif parsed_url.path.endswith(".mpd"):
+ liz.setProperty('inputstream.adaptive.manifest_type', 'mpd')
+ liz.setProperty('inputstreamaddon', 'inputstream.adaptive')
+ liz.setProperty('inputstream.adaptive.stream_headers', 'cookie=' + TOKEN)
+
+ xbmc.log("-INPUTSTREAM.ADAPTIVE MPD-")
+ xbmc.log("DRM TOKEN: %s" % drm_token)
+
+ if drm_token:
+ use_drm_proxy = ADDON.getSetting('use_drm_proxy') == 'true'
+
+ if use_drm_proxy:
+ wv_proxy_base = 'http://localhost:' + str(ADDON.getSetting('wv_proxy_port'))
+ wv_proxy_url = '{0}?mpd_url={1}&token={2}&{3}'.format(wv_proxy_base, STREAM, base64.b64encode(drm_token), TOKEN)
+ license_key = wv_proxy_url + '||R{SSM}|'
+ else:
+ wv_proxy_url = 'https://widevine-proxy.drm.technology/proxy'
+ post_data = urllib.quote_plus('{"token":"%s","drm_info":[D{SSM}],"kid":"{KID}"}' % drm_token)
+ license_key = wv_proxy_url + '|Content-Type=application%2Fjson|' + post_data + '|'
+
+
+ # wv_proxy_base = 'http://localhost:' + str(ADDON.getSetting('wv_proxy_port'))
+ # wv_proxy_url = '{0}?mpd_url={1}&token={2}&{3}'.format(wv_proxy_base, STREAM,
+ # base64.b64encode(drm_token), TOKEN)
+ # license_key = wv_proxy_url + '||R{SSM}|'
+
+ xbmc.log("inputstream.adaptive.license_key: %s" % license_key)
+
+ liz.setProperty('inputstream.adaptive.license_type', 'com.widevine.alpha')
+ liz.setProperty('inputstream.adaptive.license_key', license_key)
+ else:
+ STREAM = STREAM + '|Cookies=' + TOKEN
+
+ item_path = STREAM
+
+ liz.setPath(item_path)
+
+ xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz)
+
+
+def get_params():
+ param = []
+ paramstring = sys.argv[2]
+ if len(paramstring) >= 2:
+ params = sys.argv[2]
+ cleanedparams = params.replace('?', '')
+ if (params[len(params) - 1] == '/'):
+ params = params[0:len(params) - 2]
+ pairsofparams = cleanedparams.split('&')
+ param = {}
+ for i in range(len(pairsofparams)):
+ splitparams = {}
+ splitparams = pairsofparams[i].split('=')
+ if (len(splitparams)) == 2:
+ param[splitparams[0]] = splitparams[1]
+
+ return param
+
+
+def addDir(name, url, mode, iconimage, description, fanart, genre='', sorttitle=None, tvshowtitle=None, clearlogo=None, title=None, studio=None, startdate=None, duration=None, plotoutline=None, channel_name=None):
+ u = sys.argv[0] + "?url=" + urllib.quote_plus(url) + "&mode=" + str(mode) + "&name=" + urllib.quote_plus(channel_name or name) + "&iconimage=" + urllib.quote_plus(iconimage) + "&description=" + urllib.quote_plus(description) + "&genre=" + urllib.quote_plus(genre)
+
+ liz = xbmcgui.ListItem(name, iconImage="DefaultFolder.png", thumbnailImage=iconimage)
+
+ info_labels = {"Plot": description or ' ', "Genre": genre}
+
+ if title and isJarvis:
+ info_labels.update({"title": title})
+
+ if sorttitle:
+ info_labels.update({"sorttitle": sorttitle})
+
+ if studio:
+ info_labels.update({"studio": studio})
+
+ if tvshowtitle:
+ info_labels.update({"tvshowtitle": tvshowtitle})
+
+ if plotoutline:
+ info_labels.update({'PlotOutline': plotoutline})
+
+ if clearlogo:
+ liz.setArt({'clearlogo': clearlogo})
+
+ fanart = str(fanart) if len(re.findall(r'path=(.+)', str(fanart))) > 0 else DEFAULT_THUMB
+
+ liz.setInfo(type="Video", infoLabels=info_labels)
+ liz.setProperty('fanart_image', fanart)
+ liz.setArt({'thumb': fanart})
+
+ if duration:
+ if startdate:
+ offset = float(util.get_total_seconds(datetime.now(startdate.tzinfo) - startdate))
+ liz.setProperty('Progress', str((offset / duration) * 100) if duration else str(0))
+ liz.setProperty('totaltime', str(duration))
+
+ if mode == 200:
+ cm = [('Refresh', 'Container.Refresh')]
+ liz.addContextMenuItems(cm)
+
+ liz.setProperty("IsPlayable", "true")
+
+ return xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u, listitem=liz, isFolder=False)
+
+ return xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u, listitem=liz, isFolder=True)
+
+
+def setView(content, viewType):
+ if content:
+ xbmcplugin.setContent(int(sys.argv[1]), content)
+ if ADDON.getSetting('auto-view') == 'true': # <<<----see here if auto-view is enabled(true)
+ xbmc.executebuiltin("Container.SetViewMode(%s)" % ADDON.getSetting(viewType)) # <<<-----then get the view type
+
+
+params = get_params()
+url = None
+name = None
+mode = None
+iconimage = None
+description = None
+fanart = None
+genre = None
+
+try:
+ url = urllib.unquote_plus(params["url"])
+except:
+ pass
+try:
+ name = urllib.unquote_plus(params["name"])
+except:
+ pass
+try:
+ iconimage = urllib.unquote_plus(params["iconimage"])
+except:
+ pass
+try:
+ mode = int(params["mode"])
+except:
+ pass
+try:
+ description = urllib.unquote_plus(params["description"])
+except:
+ pass
+
+try:
+ fanart = urllib.unquote_plus(params["fanart"])
+except:
+ pass
+try:
+ genre = urllib.unquote_plus(params["genre"])
+except:
+ pass
+
+# these are the modes which tells the plugin where to go
+if mode is None or url is None or len(url) < 1:
+
+ CATEGORIES()
+
+elif mode == 2:
+
+ GENRE(name, url)
+
+elif mode == 200:
+
+ PLAY_STREAM(name, url, iconimage)
+
+xbmcplugin.endOfDirectory(int(sys.argv[1]), cacheToDisc=False)
+
+
+
+
+
+
+# WARNING: CreateLoader - unsupported protocol(plugin) in plugin://plugin.video.tvplayer/?url=90&mode=200&name=%5BCOLOR+royalblue%5DBBC+Two%5B%2FCOLOR%5D+-+%5BCOLOR+white%5DThe+World+According+to+Kids%5B%2FCOLOR%5D&iconimage=https%3A%2F%2Fassets.tvplayer.com%2Fcommon%2Flogos%2F256%2FColour%2F90.png&description=A+further+insight+into+how+children+see+the+world+as+youngsters+from+a+boxing+club+in+London%2C+a+pony+club+in+Berkshire+and+a+choir+in+Liverpool+reveal+how+they+tell+right+from+wrong.+They+include+10-year-old+Ma-Leiha%2C+who+is+struggling+at+school+after+fighting+with+boys%2C+and+nine-year-old+Jumi%2C+who+is+so+easily+influenced+his+family+worry+it+could+land+him+in+trouble.&genre=Entertainment
+# ERROR: Open - failed to open source
\ No newline at end of file
diff --git a/fanart.jpg b/fanart.jpg
new file mode 100755
index 0000000..c33e42a
Binary files /dev/null and b/fanart.jpg differ
diff --git a/icon.png b/icon.png
new file mode 100755
index 0000000..7b53c6e
Binary files /dev/null and b/icon.png differ
diff --git a/icon_original.png b/icon_original.png
new file mode 100755
index 0000000..2cb8c2e
Binary files /dev/null and b/icon_original.png differ
diff --git a/net.py b/net.py
new file mode 100755
index 0000000..cd81cd5
--- /dev/null
+++ b/net.py
@@ -0,0 +1,329 @@
+'''
+ common XBMC Module
+ Copyright (C) 2011 t0mm0
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+'''
+
+import cookielib
+import gzip
+import re
+import StringIO
+import urllib
+import urllib2
+import socket
+
+#Set Global timeout - Useful for slow connections and Putlocker.
+socket.setdefaulttimeout(30)
+
+class HeadRequest(urllib2.Request):
+ '''A Request class that sends HEAD requests'''
+ def get_method(self):
+ return 'HEAD'
+
+class Net:
+ '''
+ This class wraps :mod:`urllib2` and provides an easy way to make http
+ requests while taking care of cookies, proxies, gzip compression and
+ character encoding.
+
+ Example::
+
+ from t0mm0.common.net import Net
+ net = Net()
+ response = net.http_GET('http://xbmc.org')
+ print response.content
+ '''
+
+ _cj = cookielib.LWPCookieJar()
+ _proxy = None
+ _user_agent = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 ' + \
+ '(KHTML, like Gecko) Chrome/13.0.782.99 Safari/535.1'
+ _http_debug = False
+
+
+ def __init__(self, cookie_file='', proxy='', user_agent='',
+ http_debug=False):
+ '''
+ Kwargs:
+ cookie_file (str): Full path to a file to be used to load and save
+ cookies to.
+
+ proxy (str): Proxy setting (eg.
+ ``'http://user:pass@example.com:1234'``)
+
+ user_agent (str): String to use as the User Agent header. If not
+ supplied the class will use a default user agent (chrome)
+
+ http_debug (bool): Set ``True`` to have HTTP header info written to
+ the XBMC log for all requests.
+ '''
+ if cookie_file:
+ self.set_cookies(cookie_file)
+ if proxy:
+ self.set_proxy(proxy)
+ if user_agent:
+ self.set_user_agent(user_agent)
+ self._http_debug = http_debug
+ self._update_opener()
+
+
+ def set_cookies(self, cookie_file):
+ '''
+ Set the cookie file and try to load cookies from it if it exists.
+
+ Args:
+ cookie_file (str): Full path to a file to be used to load and save
+ cookies to.
+ '''
+ try:
+ self._cj.load(cookie_file, ignore_discard=True)
+ self._update_opener()
+ return True
+ except:
+ return False
+
+
+ def get_cookies(self):
+ '''Returns A dictionary containing all cookie information by domain.'''
+ return self._cj._cookies
+
+
+ def save_cookies(self, cookie_file):
+ '''
+ Saves cookies to a file.
+
+ Args:
+ cookie_file (str): Full path to a file to save cookies to.
+ '''
+ self._cj.save(cookie_file, ignore_discard=True)
+
+
+ def set_proxy(self, proxy):
+ '''
+ Args:
+ proxy (str): Proxy setting (eg.
+ ``'http://user:pass@example.com:1234'``)
+ '''
+ self._proxy = proxy
+ self._update_opener()
+
+
+ def get_proxy(self):
+ '''Returns string containing proxy details.'''
+ return self._proxy
+
+
+ def set_user_agent(self, user_agent):
+ '''
+ Args:
+ user_agent (str): String to use as the User Agent header.
+ '''
+ self._user_agent = user_agent
+
+
+ def get_user_agent(self):
+ '''Returns user agent string.'''
+ return self._user_agent
+
+
+ def _update_opener(self):
+ '''
+ Builds and installs a new opener to be used by all future calls to
+ :func:`urllib2.urlopen`.
+ '''
+ if self._http_debug:
+ http = urllib2.HTTPHandler(debuglevel=1)
+ else:
+ http = urllib2.HTTPHandler()
+
+ if self._proxy:
+ opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self._cj),
+ urllib2.ProxyHandler({'http':
+ self._proxy}),
+ urllib2.HTTPBasicAuthHandler(),
+ http)
+
+ else:
+ opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self._cj),
+ urllib2.HTTPBasicAuthHandler(),
+ http)
+ urllib2.install_opener(opener)
+
+
+ def http_GET(self, url, headers={}, compression=True):
+ '''
+ Perform an HTTP GET request.
+
+ Args:
+ url (str): The URL to GET.
+
+ Kwargs:
+ headers (dict): A dictionary describing any headers you would like
+ to add to the request. (eg. ``{'X-Test': 'testing'}``)
+
+ compression (bool): If ``True`` (default), try to use gzip
+ compression.
+
+ Returns:
+ An :class:`HttpResponse` object containing headers and other
+ meta-information about the page and the page content.
+ '''
+ return self._fetch(url, headers=headers, compression=compression)
+
+
+ def http_POST(self, url, form_data, headers={}, compression=True):
+ '''
+ Perform an HTTP POST request.
+
+ Args:
+ url (str): The URL to POST.
+
+ form_data (dict): A dictionary of form data to POST.
+
+ Kwargs:
+ headers (dict): A dictionary describing any headers you would like
+ to add to the request. (eg. ``{'X-Test': 'testing'}``)
+
+ compression (bool): If ``True`` (default), try to use gzip
+ compression.
+
+ Returns:
+ An :class:`HttpResponse` object containing headers and other
+ meta-information about the page and the page content.
+ '''
+ return self._fetch(url, form_data, headers=headers,
+ compression=compression)
+
+
+ def http_HEAD(self, url, headers={}):
+ '''
+ Perform an HTTP HEAD request.
+
+ Args:
+ url (str): The URL to GET.
+
+ Kwargs:
+ headers (dict): A dictionary describing any headers you would like
+ to add to the request. (eg. ``{'X-Test': 'testing'}``)
+
+ Returns:
+ An :class:`HttpResponse` object containing headers and other
+ meta-information about the page.
+ '''
+ req = HeadRequest(url)
+ req.add_header('User-Agent', self._user_agent)
+ for k, v in headers.items():
+ req.add_header(k, v)
+ response = urllib2.urlopen(req)
+ return HttpResponse(response)
+
+
+ def _fetch(self, url, form_data={}, headers={}, compression=True):
+ '''
+ Perform an HTTP GET or POST request.
+
+ Args:
+ url (str): The URL to GET or POST.
+
+ form_data (dict): A dictionary of form data to POST. If empty, the
+ request will be a GET, if it contains form data it will be a POST.
+
+ Kwargs:
+ headers (dict): A dictionary describing any headers you would like
+ to add to the request. (eg. ``{'X-Test': 'testing'}``)
+
+ compression (bool): If ``True`` (default), try to use gzip
+ compression.
+
+ Returns:
+ An :class:`HttpResponse` object containing headers and other
+ meta-information about the page and the page content.
+ '''
+ encoding = ''
+ req = urllib2.Request(url)
+ if form_data:
+ if 'Content-Type' not in headers or headers['Content-Type'] != 'application/json':
+ form_data = urllib.urlencode(form_data)
+ req = urllib2.Request(url, form_data)
+ req.add_header('User-Agent', self._user_agent)
+ for k, v in headers.items():
+ req.add_header(k, v)
+ if compression:
+ req.add_header('Accept-Encoding', 'gzip')
+ response = urllib2.urlopen(req)
+ return HttpResponse(response)
+
+
+
+class HttpResponse:
+ '''
+ This class represents a resoponse from an HTTP request.
+
+ The content is examined and every attempt is made to properly encode it to
+ Unicode.
+
+ .. seealso::
+ :meth:`Net.http_GET`, :meth:`Net.http_HEAD` and :meth:`Net.http_POST`
+ '''
+
+ content = ''
+ '''Unicode encoded string containing the body of the reposne.'''
+
+
+ def __init__(self, response):
+ '''
+ Args:
+ response (:class:`mimetools.Message`): The object returned by a call
+ to :func:`urllib2.urlopen`.
+ '''
+ self._response = response
+ html = response.read()
+ try:
+ if response.headers['content-encoding'].lower() == 'gzip':
+ html = gzip.GzipFile(fileobj=StringIO.StringIO(html)).read()
+ except:
+ pass
+
+ try:
+ content_type = response.headers['content-type']
+ if 'charset=' in content_type:
+ encoding = content_type.split('charset=')[-1]
+ except:
+ pass
+
+ r = re.search('
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/service.py b/service.py
new file mode 100755
index 0000000..ee01a76
--- /dev/null
+++ b/service.py
@@ -0,0 +1,68 @@
+# Author: asciidisco
+# Module: service
+# Created on: 13.01.2017
+# License: MIT https://goo.gl/5bMj3H
+
+import threading
+import SocketServer
+import socket
+import xbmc
+from xbmc import Monitor
+from xbmcaddon import Addon
+from resources.lib.WidevineHTTPRequestHandler import WidevineHTTPRequestHandler
+
+use_inputstream = Addon().getSetting('use_inputstream') == 'true'
+allow_drm = Addon().getSetting('allow_drm') == 'true' and use_inputstream
+use_drm_proxy = Addon().getSetting('use_drm_proxy') == 'true' and allow_drm
+
+# helper function to select an unused port on the host machine
+def select_unused_port():
+ try:
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.bind(('127.0.0.1', 0))
+ addr, port = sock.getsockname()
+ sock.shutdown()
+ sock.close()
+ return port
+ except Exception as ex:
+ return 8000
+
+
+def log(msg):
+ xbmc.log(msg=msg.encode('utf-8'), level=xbmc.LOGDEBUG)
+
+
+if __name__ == '__main__':
+
+ if use_drm_proxy:
+
+ # pick & store a port for the proxy service
+ wv_proxy_port = select_unused_port()
+ Addon().setSetting('wv_proxy_port', str(wv_proxy_port))
+ log('Port {0} selected'.format(str(wv_proxy_port)))
+
+ # server defaults
+ SocketServer.TCPServer.allow_reuse_address = True
+ # configure the proxy server
+ wv_proxy = SocketServer.TCPServer(('127.0.0.1', wv_proxy_port), WidevineHTTPRequestHandler)
+ wv_proxy.server_activate()
+ wv_proxy.timeout = 1
+
+ # start thread for proxy server
+ proxy_thread = threading.Thread(target=wv_proxy.serve_forever)
+ proxy_thread.daemon = True
+ proxy_thread.start()
+
+ monitor = Monitor()
+
+ # kill the services if kodi monitor tells us to
+ while not monitor.abortRequested():
+ # xbmc.sleep(100)
+ if monitor.waitForAbort(1):
+ break
+
+ # wv-proxy service shutdown sequence
+ wv_proxy.shutdown()
+ wv_proxy.server_close()
+ wv_proxy.socket.close()
+ log('wv-proxy stopped')
\ No newline at end of file
diff --git a/util.py b/util.py
new file mode 100755
index 0000000..025125a
--- /dev/null
+++ b/util.py
@@ -0,0 +1,27 @@
+def get_utc_delta():
+ import datetime as dt
+ return get_total_hours(dt.datetime.now() - dt.datetime.utcnow())
+
+def strptime(date_string, format):
+ import time
+ from datetime import datetime
+ try:
+ return datetime.strptime(date_string, format)
+ except TypeError:
+ return datetime(*(time.strptime(date_string, format)[0:6]))
+
+def strptime_workaround(date_string, format='%Y-%m-%dT%H:%M:%S'):
+ import time
+ from datetime import datetime
+ try:
+ return datetime.strptime(date_string, format)
+ except TypeError:
+ return datetime(*(time.strptime(date_string, format)[0:6]))
+
+def get_total_seconds(timedelta):
+ return (timedelta.microseconds + (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
+
+def get_total_hours(timedelta):
+ import datetime as dt
+ hours = int(round(((timedelta.microseconds + (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6) / 3600.0))
+ return dt.timedelta(hours=hours)
\ No newline at end of file