Skip to content

Instantly share code, notes, and snippets.

@darkarnium
Last active April 10, 2020 22:31
Show Gist options
  • Select an option

  • Save darkarnium/023fb8e9c573fa5e7c399a42428f2989 to your computer and use it in GitHub Desktop.

Select an option

Save darkarnium/023fb8e9c573fa5e7c399a42428f2989 to your computer and use it in GitHub Desktop.

Revisions

  1. darkarnium revised this gist Apr 10, 2020. 1 changed file with 45 additions and 0 deletions.
    45 changes: 45 additions & 0 deletions diff.patch
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,45 @@
    diff -w /tmp/a /tmp/b
    203,205c203,207
    < tz = str(timezone())
    < start = datetime.datetime.now().strftime('%Y-%m-%dT%H:00:00').replace('T','%20').replace(':00:00','%3A00%3A00.000'+tz)
    < stop = (datetime.datetime.now() + datetime.timedelta(hours=4)).strftime('%Y-%m-%dT%H:00:00').replace('T','%20').replace(':00:00','%3A00%3A00.000'+tz)
    ---
    > tz = timezone()
    > tz = '{0:04d}'.format(timezone())
    > tz = '%2B{}{}:{}{}'.format(*list(tz))
    > start = datetime.datetime.now().strftime('%Y-%m-%dT%H:00:00') + tz
    > stop = (datetime.datetime.now() + datetime.timedelta(hours=4)).strftime('%Y-%m-%dT%H:00:00') + tz
    474a477,507
    > xbmc.log(str(url), xbmc.LOGERROR)
    >
    > if 'deviceModel' not in url:
    > url += '&deviceModel=Chrome'
    > if 'deviceType' not in url:
    > url += '&deviceType=web'
    > if 'deviceMake' not in url:
    > url += '&deviceMake=Chrome'
    > if 'appName' not in url:
    > url += '&appName=web'
    > if 'appVersion' not in url:
    > url += '&appVersion=unknown'
    > if 'deviceDNT' not in url:
    > url += '&deviceDNT=0'
    > if 'deviceLat' not in url:
    > url += '&deviceLat=53.7548428'
    > if 'deviceLon' not in url:
    > url += '&deviceLon=-1.4675813'
    > if 'deviceVersion' not in url:
    > url += '&deviceVersion=unknown'
    > if 'deviceId' not in url:
    > url += '&deviceId=' + str(uuid.uuid4())
    > if 'architecture' not in url:
    > url += '&architecture='
    > if 'sid' not in url:
    > url += '&sid=' + str(uuid.uuid4())
    > if 'userId' not in url:
    > url += '&userId='
    > if 'clientTime' not in url:
    > url += '&clientTime='
    >
    478a512
    > xbmc.log(str(liz), xbmc.LOGERROR)
  2. darkarnium created this gist Apr 10, 2020.
    585 changes: 585 additions & 0 deletions plutotv.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,585 @@
    # Copyright (C) 2020 Lunatixz
    #
    #
    # This file is part of PlutoTV.
    #
    # PlutoTV 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.
    #
    # PlutoTV 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 PlutoTV. If not, see <http://www.gnu.org/licenses/>.

    # -*- coding: utf-8 -*-
    import os, sys, time, _strptime, datetime, net, re, traceback, uuid
    import socket, json, collections, inputstreamhelper
    import xbmc, xbmcgui, xbmcplugin, xbmcvfs, xbmcaddon

    from six.moves import urllib
    from itertools import repeat
    from simplecache import SimpleCache, use_cache

    try:
    from multiprocessing import cpu_count
    from multiprocessing.pool import ThreadPool
    ENABLE_POOL = True
    CORES = cpu_count()
    except: ENABLE_POOL = False

    # Plugin Info
    ADDON_ID = 'plugin.video.plutotv'
    REAL_SETTINGS = xbmcaddon.Addon(id=ADDON_ID)
    ADDON_NAME = REAL_SETTINGS.getAddonInfo('name')
    SETTINGS_LOC = REAL_SETTINGS.getAddonInfo('profile')
    ADDON_PATH = REAL_SETTINGS.getAddonInfo('path')
    ADDON_VERSION = REAL_SETTINGS.getAddonInfo('version')
    ICON = REAL_SETTINGS.getAddonInfo('icon')
    FANART = REAL_SETTINGS.getAddonInfo('fanart')
    LANGUAGE = REAL_SETTINGS.getLocalizedString

    ## GLOBALS ##
    TIMEOUT = 15
    CONTENT_TYPE = 'files'
    DISC_CACHE = True
    USER_EMAIL = REAL_SETTINGS.getSetting('User_Email')
    PASSWORD = REAL_SETTINGS.getSetting('User_Password')
    DEBUG = REAL_SETTINGS.getSetting('Enable_Debugging') == 'true'
    COOKIE_JAR = xbmc.translatePath(os.path.join(SETTINGS_LOC, "cookiejar.lwp"))
    PTVL_RUN = xbmcgui.Window(10000).getProperty('PseudoTVRunning') == 'True'
    BASE_API = 'https://api.pluto.tv'
    BASE_LINEUP = BASE_API + '/v2/channels.json'
    BASE_GUIDE = BASE_API + '/v2/channels?start=%s&stop=%s&%s'
    LOGIN_URL = BASE_API + '/v1/auth/local'
    BASE_CLIPS = BASE_API + '/v2/episodes/%s/clips.json'
    BASE_VOD = BASE_API + '/v3/vod/categories?includeItems=true&deviceType=web&%s'
    SEASON_VOD = BASE_API + '/v3/vod/series/%s/seasons?includeItems=true&deviceType=web&%s'
    PLUTO_MENU = [(LANGUAGE(30011), '', 0),
    (LANGUAGE(30018), '', 1),
    (LANGUAGE(30017), '', 2),
    (LANGUAGE(30012), '', 3),
    (LANGUAGE(30013), '', 20)]

    def inputDialog(heading=ADDON_NAME, default='', key=xbmcgui.INPUT_ALPHANUM, opt=0, close=0):
    retval = xbmcgui.Dialog().input(heading, default, key, opt, close)
    if len(retval) > 0: return retval

    def notificationDialog(message, header=ADDON_NAME, show=True, sound=False, time=1000, icon=ICON):
    try: xbmcgui.Dialog().notification(header, message, icon, time, sound=False)
    except: xbmc.executebuiltin("Notification(%s, %s, %d, %s)" % (header, message, time, icon))

    def yesnoDialog(str1, str2='', str3='', header=ADDON_NAME, yes='', no='', autoclose=0):
    return xbmcgui.Dialog().yesno(header, str1, str2, str3, no, yes, autoclose)

    def strpTime(datestring, format='%Y-%m-%d %H:%M:%S'):
    try: return datetime.datetime.strptime(datestring, format)
    except TypeError: return datetime.datetime.fromtimestamp(time.mktime(time.strptime(datestring, format)))

    def timezone():
    if time.localtime(time.time()).tm_isdst and time.daylight: return time.altzone / -(60*60) * 100
    else: return time.timezone / -(60*60) * 100

    def setUUID():
    if REAL_SETTINGS.getSetting("sid1"): return
    log('setUUID, creating uuid')
    REAL_SETTINGS.setSetting("sid1",str(uuid.uuid1()))
    REAL_SETTINGS.setSetting("deviceId1",str(uuid.uuid4()))

    def getUUID():
    return REAL_SETTINGS.getSetting("sid1"), REAL_SETTINGS.getSetting("deviceId1")

    def cookieJar():
    if not xbmcvfs.exists(COOKIE_JAR):
    try:
    xbmcvfs.mkdirs(SETTINGS_LOC)
    f = xbmcvfs.File(COOKIE_JAR, 'w')
    f.close()
    except: log('login, Unable to create cookie folder', xbmc.LOGERROR)

    def log(msg, level=xbmc.LOGDEBUG):
    if DEBUG == False and level != xbmc.LOGERROR: return
    if level == xbmc.LOGERROR: msg += ' ,' + traceback.format_exc()
    xbmc.log(ADDON_ID + '-' + ADDON_VERSION + '-' + msg, level)

    socket.setdefaulttimeout(TIMEOUT)
    class PlutoTV(object):
    def __init__(self, sysARG):
    log('__init__, sysARG = ' + str(sysARG))
    self.sysARG = sysARG
    self.net = net.Net()
    self.cache = SimpleCache()


    def login(self):
    log('login')
    setUUID()
    cookieJar()
    if USER_EMAIL == LANGUAGE(30009): return #ignore, using guest login
    if len(USER_EMAIL) > 0:
    header_dict = {}
    header_dict['Accept'] = 'application/json, text/javascript, */*; q=0.01'
    header_dict['Host'] = 'api.pluto.tv'
    header_dict['Connection'] = 'keep-alive'
    header_dict['Referer'] = 'http://pluto.tv/'
    header_dict['Origin'] = 'http://pluto.tv'
    header_dict['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.2; rv:24.0) Gecko/20100101 Firefox/24.0'
    form_data = ({'optIn': 'true', 'password': PASSWORD,'synced': 'false', 'userIdentity': USER_EMAIL})
    self.net.set_cookies(COOKIE_JAR)
    try:
    loginlink = json.loads(self.net.http_POST(LOGIN_URL, form_data=form_data, headers=header_dict).content.encode("utf-8").rstrip())
    if loginlink and loginlink['email'].lower() == USER_EMAIL.lower():
    notificationDialog(LANGUAGE(30006)%(loginlink['displayName']), time=4000)
    self.net.save_cookies(COOKIE_JAR)
    else: notificationDialog(LANGUAGE(30007), time=4000)
    except Exception as e: log('login, failed! ' + str(e), xbmc.LOGERROR)
    else:
    #firstrun wizard
    if yesnoDialog(LANGUAGE(30008),no=LANGUAGE(30009), yes=LANGUAGE(30010)):
    REAL_SETTINGS.setSetting('User_Email',inputDialog(LANGUAGE(30001)))
    REAL_SETTINGS.setSetting('User_Password',inputDialog(LANGUAGE(30002)))
    else: REAL_SETTINGS.setSetting('User_Email',LANGUAGE(30009))


    def openURL(self, url, life=datetime.timedelta(minutes=15)):
    log('openURL, url = ' + url)
    try:
    header_dict = {}
    header_dict['Accept'] = 'application/json, text/javascript, */*; q=0.01'
    header_dict['Host'] = 'api.pluto.tv'
    header_dict['Connection'] = 'keep-alive'
    header_dict['Referer'] = 'http://pluto.tv/'
    header_dict['Origin'] = 'http://pluto.tv'
    header_dict['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.2; rv:24.0) Gecko/20100101 Firefox/24.0'
    self.net.set_cookies(COOKIE_JAR)
    trans_table = ''.join( [chr(i) for i in range(128)] + [' '] * 128 )
    cacheResponse = self.cache.get(ADDON_NAME + '.openURL, url = %s'%url)
    if not cacheResponse:
    try: cacheResponse = self.net.http_GET(url, headers=header_dict).content.encode("utf-8", 'ignore')
    except: cacheResponse = (self.net.http_GET(url, headers=header_dict).content.translate(trans_table)).encode("utf-8")
    self.net.save_cookies(COOKIE_JAR)
    self.cache.set(ADDON_NAME + '.openURL, url = %s'%url, cacheResponse, expiration=life)
    if isinstance(cacheResponse, basestring): cacheResponse = json.loads(cacheResponse)
    return cacheResponse
    except Exception as e:
    log('openURL, Unable to open url ' + str(e), xbmc.LOGERROR)
    notificationDialog(LANGUAGE(30028), time=4000)
    return {}


    def mainMenu(self):
    log('mainMenu')
    self.login()
    for item in PLUTO_MENU: self.addDir(*item)


    def browseMenu(self):
    log('browseMenu')
    categoryMenu = self.getCategories()
    for item in categoryMenu: self.addDir(*item)


    def getOndemand(self):
    return self.openURL(BASE_VOD%(LANGUAGE(30022)%(getUUID())), life=datetime.timedelta(hours=1))


    def getVOD(self, epid):
    return self.openURL(SEASON_VOD%(epid,LANGUAGE(30022)%(getUUID())), life=datetime.timedelta(hours=1))


    def getClips(self, epid):
    return self.openURL(BASE_CLIPS%(epid), life=datetime.timedelta(hours=1))


    def getChannels(self):
    return sorted(self.openURL(BASE_LINEUP, life=datetime.timedelta(hours=1)), key=lambda i: i['number'])


    def getGuidedata(self):
    tz = timezone()
    tz = '{0:04d}'.format(timezone())
    tz = '%2B{}{}:{}{}'.format(*list(tz))
    start = datetime.datetime.now().strftime('%Y-%m-%dT%H:00:00') + tz
    stop = (datetime.datetime.now() + datetime.timedelta(hours=4)).strftime('%Y-%m-%dT%H:00:00') + tz
    return sorted((self.openURL(BASE_GUIDE %(start,stop,'sid=%s&deviceId=%s'%(getUUID())), life=datetime.timedelta(hours=1))), key=lambda i: i['number'])


    def getCategories(self):
    log('getCategories')
    collect= []
    data = self.getChannels()
    for channel in data: collect.append(channel['category'])
    counter = collections.Counter(collect)
    for key, value in sorted(counter.iteritems()): yield (key,'categories', 0)


    def getMediaTypes(self, genre):
    if type == 'Movies': return 'movie'
    elif type == 'TV': return 'episode'
    elif type == 'Music + Radio': return 'musicvideo'
    else: return 'video'


    def pagination(self, seq, rowlen):
    for start in xrange(0, len(seq), rowlen): yield seq[start:start+rowlen]


    def buildGuide(self, data):
    channel, name, opt = data
    log('buildGuide, name=%s,opt=%s'%(name, opt))
    urls = []
    guidedata = []
    newChannel= {}
    mtype = 'video'
    chid = channel.get('_id','')
    chname = channel.get('name','')
    chnum = channel.get('number','')
    chplot = (channel.get('description','') or channel.get('summary',''))
    chgeo = channel.get('visibility','everyone') != 'everyone'
    chcat = (channel.get('category','') or channel.get('genre',''))
    chfanart = channel.get('featuredImage',{}).get('path',FANART)
    chthumb = channel.get('thumbnail',{}).get('path',ICON)
    chlogo = channel.get('logo',{}).get('path',ICON)
    ondemand = channel.get('onDemand','false') == 'true'
    featured = channel.get('featured','false') == 'true'
    favorite = channel.get('favorite','false') == 'true'
    timelines = channel.get('timelines',[])

    if name == 'featured' and not featured: return None
    elif name == 'favorite' and not favorite: return None
    elif name == 'categories' and chcat != opt: return None
    elif name == 'lineup' and chid != opt: return None
    elif name == 'live': DISC_CACHE = False

    if name in ['channels','categories','ondemand','season']:
    if name == 'season':
    seasons = (channel.get('seasons',{}))
    vodimages = channel.get('covers',[])
    try: vodlogo = [image.get('url',[]) for image in vodimages if image.get('aspectRatio','') == '1:1'][0]
    except: vodlogo = ICON
    try: vodfanart = [image.get('url',[]) for image in vodimages if image.get('aspectRatio','') == '16:9'][0]
    except: vodfanart = FANART
    for season in seasons:
    mtype = 'episode'
    label = 'Season %s'%(season['number'])
    infoLabels = {"mediatype":mtype,"label":label,"label2":label,"title":chname,"plot":chplot, "code":chid, "genre":[chcat]}
    infoArt = {"thumb":vodlogo,"poster":vodlogo,"fanart":vodfanart,"icon":vodlogo,"logo":vodlogo,"clearart":chthumb}
    self.addDir(label, chid, 5, infoLabels, infoArt)
    else:
    if name == 'ondemand':
    mode = 3
    label = chname
    else:
    mode = 1
    label = '%s| %s'%(chnum,chname)
    infoLabels = {"mediatype":mtype,"label":label,"label2":label,"title":label,"plot":chplot, "code":chid, "genre":[chcat]}
    infoArt = {"thumb":chthumb,"poster":chthumb,"fanart":chfanart,"icon":chlogo,"logo":chlogo,"clearart":chthumb}
    self.addDir(label, chid, mode, infoLabels, infoArt)
    else:
    newChannel['channelname'] = chname
    newChannel['channelnumber'] = chnum
    newChannel['channellogo'] = chlogo
    newChannel['isfavorite'] = favorite
    urls = channel.get('stitched',{}).get('urls',[])
    if not timelines:
    name = 'ondemand'
    timelines = (channel.get('items',[]) or channel.get('episodes',[]))

    now = datetime.datetime.now()
    totstart = now
    tz = (timezone()//100)*60*60

    for item in timelines:
    episode = (item.get('episode',{}) or item)
    series = (episode.get('series',{}) or item)
    epdur = int(episode.get('duration','0') or '0') // 1000

    urls = (item.get('stitched',{}).get('urls',[]) or urls)
    if len(urls) == 0: continue
    if isinstance(urls, list): urls = [url['url'] for url in urls if url['type'].lower() == 'hls'][0] # todo select quality

    try:
    start = strpTime(item['start'],'%Y-%m-%dT%H:%M:00.000Z') + datetime.timedelta(seconds=tz)
    stop = strpTime(item['stop'],'%Y-%m-%dT%H:%M:00.000Z') + datetime.timedelta(seconds=tz)
    except:
    start = totstart
    stop = start + datetime.timedelta(seconds=epdur)
    totstart = stop

    type = series.get('type','')
    tvtitle = series.get('name','' or chname)
    title = (item.get('title',''))
    tvplot = (series.get('description','') or series.get('summary','') or chplot)
    tvoutline = (series.get('summary','') or series.get('description','') or chplot)
    tvthumb = (series.get('title',{}).get('path','') or chthumb)
    tvfanart = (series.get('featuredImage',{}).get('path','') or chfanart)
    epid = episode['_id']
    epnumber = episode.get('number',0)
    epseason = episode.get('season',0)
    epname = (episode['name'])
    epplot = (episode.get('description','') or tvplot or epname)
    epgenre = (episode.get('genre','') or chcat)
    eptag = episode.get('subGenre','')
    epmpaa = episode.get('rating','')

    vodimages = episode.get('covers',[])
    vodposter = vodfanart = vodthumb = vodlogo = ''
    if vodimages:
    try: vodposter = [image.get('url',[]) for image in vodimages if image.get('aspectRatio','') == '347:500'][0]
    except: pass
    try: vodfanart = [image.get('url',[]) for image in vodimages if image.get('aspectRatio','') == '16:9'][0]
    except: pass
    try: vodthumb = [image.get('url',[]) for image in vodimages if image.get('aspectRatio','') == '4:3'][0]
    except: pass
    try: vodlogo = [image.get('url',[]) for image in vodimages if image.get('aspectRatio','') == '1:1'][0]
    except: pass

    chlogo = (vodlogo or chlogo)
    epposter = (episode.get('poster',{}).get('path','') or vodlogo or vodposter or vodthumb or tvthumb)
    epthumb = (episode.get('thumbnail',{}).get('path','') or vodlogo or vodthumb or vodposter or tvthumb)
    epfanart = (episode.get('featuredImage',{}).get('path','') or vodfanart or tvfanart)
    epislive = episode.get('liveBroadcast','false') == 'true'

    label = title
    thumb = chthumb
    if type in ['movie','film']:
    mtype = 'movie'
    thumb = epposter
    elif type in ['tv','episode','series']:
    mtype = 'episode'
    thumb = epposter
    if epseason > 0 and epnumber > 0:
    label = '%sx%s'%(epseason, epnumber)
    label = '%s - %s'%(label, epname)
    # else: label = '%s - %s'%(tvtitle, label)
    else: label = epname
    epname = label
    if type == 'music' or epgenre.lower() == 'music': mtype = 'musicvideo'

    if name == 'live':
    if stop < now or start > now: continue
    # epdur = (now - start).seconds
    label = '%s| %s'%(chnum,chname)
    if type in ['movie','film']:
    mtype = 'movie'
    thumb = epposter
    label = '%s :[B]%s[/B]'%(label, title)
    elif type in ['tv','series']:
    mtype = 'episode'
    thumb = epposter
    label = "%s :[B]%s - %s[/B]" % (label, tvtitle, epname)
    elif len(epname) > 0: label = '%s :[B]%s - %s[/B]'%(label, title, epname)
    epname = label
    if type == 'music' or epgenre.lower() == 'music': mtype = 'musicvideo'

    elif name == 'lineup':
    if now > stop: continue
    # elif start >= now and stop < now: epdur = (now - start).seconds
    if type in ['movie','film']:
    mtype = 'movie'
    thumb = epposter
    label = '%s'%(title)
    elif type in ['tv','series']:
    mtype = 'episode'
    thumb = epposter
    label = "%s - %s" % (tvtitle, epname)
    elif len(epname) > 0: label = '%s - %s'%(title, epname)
    epname = label
    if type == 'music' or epgenre.lower() == 'music': mtype = 'musicvideo'
    if now >= start and now < stop:
    label = '%s - [B]%s[/B]'%(start.strftime('%I:%M %p').lstrip('0'),label)
    else:
    label = '%s - %s'%(start.strftime('%I:%M %p').lstrip('0'),label)
    urls = 'NEXT_SHOW'
    epname = label

    tmpdata = {"mediatype":mtype,"label":label,"title":label,'duration':epdur,'plot':epplot,'genre':[epgenre],'season':epseason,'episode':epnumber}
    tmpdata['starttime'] = time.mktime((start).timetuple())
    tmpdata['url'] = self.sysARG[0]+'?mode=9&name=%s&url=%s'%(title,urls)
    tmpdata['art'] = {"thumb":thumb,"poster":epposter,"fanart":epfanart,"icon":chlogo,"logo":chlogo,"clearart":chthumb}
    guidedata.append(tmpdata)

    if name == 'ondemand' and type == "series":
    mtype = 'season'
    infoLabels = {"mediatype":mtype,"label":label,"label2":label,"title":label,"plot":epplot, "code":chid, "genre":[epgenre]}
    infoArt = {"thumb":epthumb,"poster":epposter,"fanart":epfanart,"icon":chlogo,"logo":chlogo,"clearart":chthumb}
    self.addDir(label, epid, 4, infoLabels, infoArt)
    elif name != 'guide':
    infoLabels = {"mediatype":mtype,"label":label,"label2":label,"tvshowtitle":tvtitle,"title":epname,"plot":epplot, "code":epid, "genre":[epgenre], "duration":epdur,'season':epseason,'episode':epnumber}
    infoArt = {"thumb":thumb,"poster":epposter,"fanart":epfanart,"icon":chlogo,"logo":chlogo,"clearart":chthumb}
    self.addLink(title, urls, 9, infoLabels, infoArt)

    CONTENT_TYPE = '%ss'%mtype
    if len(guidedata) > 0:
    newChannel['guidedata'] = guidedata
    return newChannel


    def uEPG(self):
    log('uEPG')
    data = self.getGuidedata()
    return urllib.parse.quote(json.dumps(list(self.poolList(self.buildGuide, zip(data,repeat('guide'),repeat(''))))))


    def browseGuide(self, name, opt=None, data=None):
    log('browseGuide, name=%s, opt=%s'%(name,opt))
    if data is None: data = self.getGuidedata()
    if opt == 'categories':
    opt = name
    name = 'categories'
    self.poolList(self.buildGuide, zip(data,repeat(name.lower()),repeat(opt)))


    def browseLineup(self, name, opt=None):
    log('browseLineup, opt=%s'%opt)
    if opt is None: name = 'channels'
    else: name = 'lineup'
    self.browseGuide(name, opt)


    def browseOndemand(self, opt=None):
    log('browseOndemand')
    data = self.getOndemand()['categories']
    if opt is None: name = 'ondemand'
    else: name = 'lineup'
    self.browseGuide(name, opt, data)


    def browseSeason(self, opt=None):
    log('browseSeason')
    data = [self.getVOD(opt)]
    self.browseGuide('season', opt, data)


    def browseEpisodes(self, name, opt=None):
    log('browseEpisodes')
    season = int(name.split('Season ')[1])
    data = [self.getVOD(opt).get('seasons',[])[season - 1]]
    self.browseGuide('episode', opt, data)


    def browseCategories(self):
    log('browseCategories')
    data = list(self.getCategories())
    for item in data: self.addDir(*item)


    def playVideo(self, name, url, liz=None):
    if url.lower() == 'next_show': return notificationDialog(LANGUAGE(30029), time=4000)
    if url.endswith('?deviceType='): url = url.replace('deviceType=','deviceType=&deviceMake=&deviceModel=&&deviceVersion=unknown&appVersion=unknown&deviceDNT=0&userId=&advertisingId=&app_name=&appName=&buildVersion=&appStoreUrl=&architecture=&includeExtendedEvents=false')#todo lazy fix replace
    if 'sid' not in url: url = url.replace('deviceModel=&','deviceModel=&' + LANGUAGE(30022)%(getUUID()))
    url = url.replace('deviceType=&','deviceType=web&').replace('deviceMake=&','deviceMake=Chrome&') .replace('deviceModel=&','deviceModel=Chrome&').replace('appName=&','appName=web&')#todo replace with regex!
    log('playVideo, url = %s'%url)
    xbmc.log(str(url), xbmc.LOGERROR)

    if 'deviceModel' not in url:
    url += '&deviceModel=Chrome'
    if 'deviceType' not in url:
    url += '&deviceType=web'
    if 'deviceMake' not in url:
    url += '&deviceMake=Chrome'
    if 'appName' not in url:
    url += '&appName=web'
    if 'appVersion' not in url:
    url += '&appVersion=unknown'
    if 'deviceDNT' not in url:
    url += '&deviceDNT=0'
    if 'deviceLat' not in url:
    url += '&deviceLat=53.7548428'
    if 'deviceLon' not in url:
    url += '&deviceLon=-1.4675813'
    if 'deviceVersion' not in url:
    url += '&deviceVersion=unknown'
    if 'deviceId' not in url:
    url += '&deviceId=' + str(uuid.uuid4())
    if 'architecture' not in url:
    url += '&architecture='
    if 'sid' not in url:
    url += '&sid=' + str(uuid.uuid4())
    if 'userId' not in url:
    url += '&userId='
    if 'clientTime' not in url:
    url += '&clientTime='

    if liz is None: liz = xbmcgui.ListItem(name, path=url)
    if 'm3u8' in url.lower() and inputstreamhelper.Helper('hls').check_inputstream() and not DEBUG:
    liz.setProperty('inputstreamaddon','inputstream.adaptive')
    liz.setProperty('inputstream.adaptive.manifest_type','hls')
    xbmc.log(str(liz), xbmc.LOGERROR)
    xbmcplugin.setResolvedUrl(int(self.sysARG[1]), True, liz)


    def addLink(self, name, u, mode, infoList=False, infoArt=False, total=0):
    name = name.encode("utf-8")
    log('addLink, name = ' + name)
    liz=xbmcgui.ListItem(name)
    liz.setProperty('IsPlayable', 'true')
    if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name})
    else: liz.setInfo(type="Video", infoLabels=infoList)
    if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART})
    else: liz.setArt(infoArt)
    u=self.sysARG[0]+"?url="+urllib.parse.quote(u)+"&mode="+str(mode)+"&name="+urllib.parse.quote(name)
    xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]),url=u,listitem=liz,totalItems=total)


    def addDir(self, name, u, mode, infoList=False, infoArt=False):
    name = name.encode("utf-8")
    log('addDir, name = ' + name)
    liz=xbmcgui.ListItem(name)
    liz.setProperty('IsPlayable', 'false')
    if infoList == False: liz.setInfo(type="Video", infoLabels={"mediatype":"video","label":name,"title":name} )
    else: liz.setInfo(type="Video", infoLabels=infoList)
    if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART})
    else: liz.setArt(infoArt)
    u=self.sysARG[0]+"?url="+urllib.parse.quote(u)+"&mode="+str(mode)+"&name="+urllib.parse.quote(name)
    xbmcplugin.addDirectoryItem(handle=int(self.sysARG[1]),url=u,listitem=liz,isFolder=True)


    def poolList(self, method, items):
    results = []
    if ENABLE_POOL and not DEBUG:
    pool = ThreadPool(CORES)
    results = pool.imap_unordered(method, items, chunksize=25)
    pool.close()
    pool.join()
    else: results = [method(item) for item in items]
    results = filter(None, results)
    return results


    def getParams(self):
    return dict(urllib.parse.parse_qsl(self.sysARG[2][1:]))


    def run(self):
    params=self.getParams()
    try: url=urllib.parse.unquote_plus(params["url"])
    except: url=None
    try: name=urllib.parse.unquote_plus(params["name"])
    except: name=None
    try: mode=int(params["mode"])
    except: mode=None
    log("Mode: "+str(mode))
    log("URL : "+str(url))
    log("Name: "+str(name))

    if mode==None: self.mainMenu()
    elif mode == 0: self.browseGuide(name, url)
    elif mode == 1: self.browseLineup(name, url)
    elif mode == 2: self.browseCategories()
    elif mode == 3: self.browseOndemand(url)
    elif mode == 4: self.browseSeason(url)
    elif mode == 5: self.browseEpisodes(name, url)
    elif mode == 9: self.playVideo(name, url)
    elif mode == 20: xbmc.executebuiltin("RunScript(script.module.uepg,json=%s&skin_path=%s&refresh_path=%s&refresh_interval=%s&row_count=%s&include_hdhr=false)"%(self.uEPG(),urllib.parse.quote(ADDON_PATH),urllib.parse.quote(self.sysARG[0]+"?mode=20"),"7200","5"))

    xbmcplugin.setContent(int(self.sysARG[1]) , CONTENT_TYPE)
    xbmcplugin.addSortMethod(int(self.sysARG[1]) , xbmcplugin.SORT_METHOD_UNSORTED)
    xbmcplugin.addSortMethod(int(self.sysARG[1]) , xbmcplugin.SORT_METHOD_NONE)
    xbmcplugin.addSortMethod(int(self.sysARG[1]) , xbmcplugin.SORT_METHOD_LABEL)
    xbmcplugin.addSortMethod(int(self.sysARG[1]) , xbmcplugin.SORT_METHOD_TITLE)
    xbmcplugin.endOfDirectory(int(self.sysARG[1]), cacheToDisc=DISC_CACHE)