"Create settings for IPTV Simple" created the IPTV Simple instance and then exported channels and EPG. IPTV Simple could load before epg.xml existed, Kodi then stored an empty guide for every channel and didn't ask again for hours (seen on a fresh box). Export first, and stop with an error if the guide file wasn't written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
966 lines
37 KiB
Python
966 lines
37 KiB
Python
# encoding: utf-8
|
|
#
|
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
from __future__ import unicode_literals, absolute_import, division
|
|
|
|
import sys
|
|
import json
|
|
import io
|
|
import os.path
|
|
from datetime import datetime, timedelta
|
|
from .timeconv import isodate2date
|
|
|
|
import xbmc
|
|
import xbmcgui
|
|
import xbmcplugin
|
|
import xbmcaddon
|
|
|
|
if sys.version_info[0] >= 3:
|
|
import urllib.request as urllib2
|
|
from urllib.parse import urlencode, parse_qsl, quote_plus
|
|
unicode = str
|
|
else:
|
|
import urllib2
|
|
from urllib import urlencode, quote_plus
|
|
from urlparse import parse_qsl
|
|
|
|
from .log import LOG
|
|
from .movistar import *
|
|
from .addon import *
|
|
from .gui import *
|
|
from .useragent import useragent
|
|
|
|
kodi_version= int(xbmc.getInfoLabel('System.BuildVersion')[:2])
|
|
|
|
# Get the plugin url in plugin:// notation.
|
|
_url = sys.argv[0]
|
|
# Get the plugin handle as an integer number.
|
|
_handle = int(sys.argv[1])
|
|
|
|
def get_url(**kwargs):
|
|
for key, value in kwargs.items():
|
|
if isinstance(value, unicode):
|
|
kwargs[key] = value.encode('utf-8')
|
|
return '{0}?{1}'.format(_url, urlencode(kwargs))
|
|
|
|
def play(params):
|
|
LOG('play - params: {}'.format(params))
|
|
|
|
if not 'url' in params:
|
|
show_notification(addon.getLocalizedString(30204))
|
|
return
|
|
|
|
url = params['url']
|
|
channel_id = params['id']
|
|
stype = params['stype']
|
|
|
|
token = m.account['ssp_token']
|
|
|
|
import inputstreamhelper
|
|
is_helper = inputstreamhelper.Helper('mpd', drm='com.widevine.alpha')
|
|
if not is_helper.check_inputstream():
|
|
show_notification(addon.getLocalizedString(30202))
|
|
return
|
|
|
|
"""
|
|
if addon.getSettingBool('reregister'):
|
|
m.unregister_device()
|
|
m.register_device()
|
|
"""
|
|
|
|
session_opened = False
|
|
if addon.getSettingBool('open_session') or stype == 'vod':
|
|
d = m.open_session(params['session_request'])
|
|
session_opened = True
|
|
LOG('Open session: d: {}'.format(d))
|
|
if d['resultCode'] != 0:
|
|
show_notification(d['resultText'])
|
|
return
|
|
if 'resultData' in d and 'cToken' in d['resultData']:
|
|
token = d['resultData']['cToken']
|
|
|
|
if stype == 'vod':
|
|
d = m.delete_session()
|
|
session_opened = False
|
|
LOG('Delete session: d: {}'.format(d))
|
|
|
|
LOG("token: {}".format(token))
|
|
|
|
if stype in ['u7d', 'rec']:
|
|
d = m.get_u7d_url(url)
|
|
if 'resultText' in d:
|
|
show_notification(d['resultText'])
|
|
return
|
|
else:
|
|
url = d['url']
|
|
#if True:
|
|
# url = url.replace('DASH_WPC_WIDEVINE', 'DASH_TV_WIDEVINE')
|
|
|
|
# U7D from start and end times
|
|
#if stype == 'tv' and all(param in params for param in ['cas_id', 'start_time', 'end_time']):
|
|
# start = datetime.utcfromtimestamp(int(params['start_time']))
|
|
# end = datetime.utcfromtimestamp(int(params['end_time']))
|
|
# catchup_url = 'https://stover-wp0.cdn.telefonica.com/{cas_id}/vxfmt=dp/Manifest.mpd?device_profile=DASH_TV_WIDEVINE&start_time={start_time}&end_time={end_time}'
|
|
# url = catchup_url.format(cas_id=params['cas_id'], start_time=start.strftime('%Y-%m-%dT%H:%M:%SZ'), end_time=end.strftime('%Y-%m-%dT%H:%M:%SZ'))
|
|
# LOG('url from capchup: {}'.format(url))
|
|
|
|
|
|
# Read the proxy address
|
|
proxy = addon.getSetting('proxy_address')
|
|
LOG('proxy address: {}'.format(proxy))
|
|
|
|
if addon.getSettingBool('manifest_modification') and proxy:
|
|
url = '{}/?manifest={}'.format(proxy, url)
|
|
|
|
LOG("url: {}".format(url))
|
|
|
|
headers = 'Content-Type=application/octet-stream'
|
|
headers += '&User-Agent=' + useragent
|
|
headers += '&Accept=*/*&Accept-Language=es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3'
|
|
headers += '&Referer=https://ver.movistarplus.es/'
|
|
headers += '&Origin=https://ver.movistarplus.es'
|
|
#headers += '&Host=wv-ottlic-f3.imagenio.telefonica.net'
|
|
manifest_headers = 'User-Agent=' + useragent
|
|
|
|
if True: #stype in ['tv', 'u7d', 'rec']:
|
|
cdn_token = m.cache.load('cdn.conf', 60)
|
|
if not cdn_token:
|
|
cdn_token = m.get_cdntoken()
|
|
if not cdn_token:
|
|
time.sleep(2)
|
|
cdn_token = m.get_cdntoken()
|
|
if cdn_token:
|
|
m.cache.save_file('cdn.conf', cdn_token)
|
|
else:
|
|
show_notification('Ha pasado algo')
|
|
return
|
|
#LOG('cdn_token: {}'.format(cdn_token))
|
|
manifest_headers += '&x-tcdn-token=' + cdn_token
|
|
|
|
license_url = 'https://wv-ottlic-f3.imagenio.telefonica.net/TFAESP/wvls/contentlicenseservice/v1/licenses'
|
|
certificate = (
|
|
'CsECCAMSEBcFuRfMEgSGiwYzOi93KowYgrSCkgUijgIwggEKAoIBAQCZ7Vs7Mn2rXiTvw7YqlbWY'
|
|
'UgrVvMs3UD4GRbgU2Ha430BRBEGtjOOtsRu4jE5yWl5KngeVKR1YWEAjp+GvDjipEnk5MAhhC28V'
|
|
'jIeMfiG/+/7qd+EBnh5XgeikX0YmPRTmDoBYqGB63OBPrIRXsTeo1nzN6zNwXZg6IftO7L1KEMpH'
|
|
'SQykfqpdQ4IY3brxyt4zkvE9b/tkQv0x4b9AsMYE0cS6TJUgpL+X7r1gkpr87vVbuvVk4tDnbNfF'
|
|
'XHOggrmWEguDWe3OJHBwgmgNb2fG2CxKxfMTRJCnTuw3r0svAQxZ6ChD4lgvC2ufXbD8Xm7fZPvT'
|
|
'CLRxG88SUAGcn1oJAgMBAAE6FGxpY2Vuc2Uud2lkZXZpbmUuY29tEoADrjRzFLWoNSl/JxOI+3u4'
|
|
'y1J30kmCPN3R2jC5MzlRHrPMveoEuUS5J8EhNG79verJ1BORfm7BdqEEOEYKUDvBlSubpOTOD8S/'
|
|
'wgqYCKqvS/zRnB3PzfV0zKwo0bQQQWz53ogEMBy9szTK/NDUCXhCOmQuVGE98K/PlspKkknYVeQr'
|
|
'OnA+8XZ/apvTbWv4K+drvwy6T95Z0qvMdv62Qke4XEMfvKUiZrYZ/DaXlUP8qcu9u/r6DhpV51Wj'
|
|
'x7zmVflkb1gquc9wqgi5efhn9joLK3/bNixbxOzVVdhbyqnFk8ODyFfUnaq3fkC3hR3f0kmYgI41'
|
|
'sljnXXjqwMoW9wRzBMINk+3k6P8cbxfmJD4/Paj8FwmHDsRfuoI6Jj8M76H3CTsZCZKDJjM3BQQ6'
|
|
'Kb2m+bQ0LMjfVDyxoRgvfF//M/EEkPrKWyU2C3YBXpxaBquO4C8A0ujVmGEEqsxN1HX9lu6c5OMm'
|
|
'8huDxwWFd7OHMs3avGpr7RP7DUnTikXrh6X0')
|
|
|
|
play_item = xbmcgui.ListItem(path= url)
|
|
license_type = 'com.widevine.alpha'
|
|
license_key = ''
|
|
|
|
if format(addon.getSetting('drm_type')) == 'Playready':
|
|
license_type = 'com.microsoft.playready'
|
|
|
|
if addon.getSettingBool('use_proxy_for_license') and proxy:
|
|
request_id = str(int(time.time()*1000))
|
|
license_key = '{}/license?token={}&stype={}&session_request={}&request_id={}'.format(proxy, quote_plus(token), stype, quote_plus(params['session_request']), request_id)
|
|
LOG('license_key: {}'.format(license_key))
|
|
else:
|
|
license_key = license_url
|
|
headers += '&nv-authorizations={}'.format(token)
|
|
|
|
if kodi_version > 20:
|
|
play_item.setProperty('inputstream.adaptive.drm_legacy', '{}|{}|{}'.format(license_type, license_key, headers))
|
|
else:
|
|
play_item.setProperty('inputstream.adaptive.license_type', license_type)
|
|
play_item.setProperty('inputstream.adaptive.license_key', '{}|{}|R{{SSM}}|'.format(license_key, headers))
|
|
play_item.setProperty('inputstream.adaptive.manifest_type', 'mpd')
|
|
#play_item.setProperty('inputstream.adaptive.manifest_type', 'hls')
|
|
|
|
play_item.setProperty('inputstream.adaptive.stream_headers', manifest_headers)
|
|
play_item.setProperty('inputstream.adaptive.manifest_headers', manifest_headers)
|
|
|
|
play_item.setProperty('inputstream.adaptive.server_certificate', certificate)
|
|
#play_item.setProperty('inputstream.adaptive.license_flags', 'persistent_storage')
|
|
#play_item.setProperty('inputstream.adaptive.license_flags', 'force_secure_decoder')
|
|
|
|
if stype == 'u7d':
|
|
play_item.setProperty('inputstream.adaptive.play_timeshift_buffer', 'true')
|
|
|
|
if kodi_version < 19:
|
|
play_item.setProperty('inputstreamaddon', 'inputstream.adaptive')
|
|
else:
|
|
play_item.setProperty('inputstream', 'inputstream.adaptive')
|
|
|
|
play_item.setMimeType('application/dash+xml')
|
|
play_item.setContentLookup(False)
|
|
|
|
# Add info
|
|
if addon.getSettingBool('add_extra_info') and stype in ['vod', 'u7d', 'rec']:
|
|
t = {'id': params['id']}
|
|
if 'show_id' in params: t['show_id'] = params['show_id']
|
|
m.add_video_extra_info(t)
|
|
play_item.setInfo('video', t['info'])
|
|
play_item.setArt(t['art'])
|
|
|
|
# Subtitles
|
|
if stype == 'vod' and addon.getSettingBool('use_ttml2ssa'):
|
|
# Convert subtitles
|
|
from ttml2ssa import Ttml2SsaAddon
|
|
ttml = Ttml2SsaAddon()
|
|
#ttml.use_language_filter = False
|
|
subtype = ttml.subtitle_type()
|
|
|
|
subfolder = os.path.join(profile_dir, 'subtitles')
|
|
if not os.path.exists(subfolder):
|
|
os.makedirs(subfolder)
|
|
|
|
sublist = m.get_subtitles(params['url'])
|
|
LOG('sublist: {}'.format(sublist))
|
|
subpaths = []
|
|
for sub in sublist:
|
|
filename = os.path.join(subfolder, sub['lang'])
|
|
LOG('Converting {}'.format(sub['filename']))
|
|
subheaders = m.net.headers.copy()
|
|
subheaders['x-tcdn-token'] = cdn_token
|
|
response = m.net.session.get(sub['url'], allow_redirects=True, headers=subheaders)
|
|
content = response.content
|
|
with io.open(filename + '.ttml', 'w', encoding='utf-8', newline='') as handle:
|
|
handle.write(content.decode('utf-8'))
|
|
ttml.parse_ttml_from_string(content)
|
|
if subtype != 'srt':
|
|
filename_ssa = filename + '.ssa'
|
|
ttml.write2file(filename_ssa)
|
|
subpaths.append(filename_ssa)
|
|
if subtype != 'ssa':
|
|
filename_srt = filename
|
|
if (subtype == 'both'): filename_srt += '.SRT'
|
|
filename_srt += '.srt'
|
|
ttml.write2file(filename_srt)
|
|
subpaths.append(filename_srt)
|
|
play_item.setSubtitles(subpaths)
|
|
|
|
#if True:
|
|
# m.unregister_device()
|
|
# m.register_device()
|
|
|
|
xbmcplugin.setResolvedUrl(_handle, True, listitem=play_item)
|
|
|
|
LOG('**** session_opened: {}'.format(session_opened))
|
|
watermarks = []
|
|
if stype == 'tv' and 'uid' in params:
|
|
watermarks = m.get_watermarks(params['uid'])
|
|
LOG('watermarks: {}'.format(watermarks))
|
|
|
|
if session_opened or watermarks:
|
|
window = xbmcgui.Window(12005)
|
|
label = xbmcgui.ControlLabel(0, 0, 400, 20, m.account['id'], textColor='0xFFFFFFFF', alignment=6)
|
|
from .player import MyPlayer
|
|
player = MyPlayer()
|
|
monitor = xbmc.Monitor()
|
|
while not monitor.abortRequested() and player.running:
|
|
monitor.waitForAbort(10)
|
|
#LOG('**** waiting')
|
|
if player.isPlaying() and watermarks:
|
|
now = isodate2date(datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'))
|
|
for wm in list(watermarks):
|
|
start = isodate2date(wm['start'])
|
|
stop = isodate2date(wm['stop'])
|
|
if now >= start:
|
|
watermarks.remove(wm)
|
|
if now < stop:
|
|
duration = (stop - start).total_seconds()
|
|
#show_notification(m.account['id'], xbmcgui.NOTIFICATION_INFO)
|
|
w = window.getWidth()
|
|
h = window.getHeight()
|
|
#LOG('window h: {} w: {}'.format(h, w))
|
|
pos_x = w-label.getWidth()-60
|
|
pos_y = h-200
|
|
label.setPosition(pos_x, pos_y)
|
|
window.addControl(label)
|
|
time.sleep(duration)
|
|
window.removeControl(label)
|
|
if session_opened:
|
|
d = m.delete_session()
|
|
LOG('Delete session: d: {}'.format(d))
|
|
LOG('Playback finished')
|
|
|
|
|
|
def add_videos(category, ctype, videos, ref=None, url_next=None, url_prev=None, from_wishlist=False):
|
|
#LOG("category: {} ctype: {}".format(category, ctype))
|
|
xbmcplugin.setPluginCategory(_handle, category)
|
|
xbmcplugin.setContent(_handle, ctype)
|
|
|
|
if ctype == 'movies' or ctype == 'seasons':
|
|
xbmcplugin.addSortMethod(_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.addSortMethod(_handle, xbmcplugin.SORT_METHOD_LABEL)
|
|
xbmcplugin.addSortMethod(_handle, xbmcplugin.SORT_METHOD_TITLE)
|
|
xbmcplugin.addSortMethod(_handle, xbmcplugin.SORT_METHOD_VIDEO_YEAR)
|
|
xbmcplugin.addSortMethod(_handle, xbmcplugin.SORT_METHOD_GENRE)
|
|
xbmcplugin.addSortMethod(_handle, xbmcplugin.SORT_METHOD_LASTPLAYED)
|
|
if ctype == 'episodes':
|
|
xbmcplugin.addSortMethod(_handle, xbmcplugin.SORT_METHOD_EPISODE)
|
|
|
|
"""
|
|
if url_prev:
|
|
list_item = xbmcgui.ListItem(label = addon.getLocalizedString(30110)) # Previous page
|
|
xbmcplugin.addDirectoryItem(_handle, get_url(action=ref, url=url_prev, name=category), list_item, True)
|
|
"""
|
|
|
|
for t in videos:
|
|
#LOG("t: {}".format(t))
|
|
if 'subscribed' in t:
|
|
if addon.getSettingBool('only_subscribed') and t['subscribed'] == False: continue
|
|
t['info']['title'] = m.colorize_title(t)
|
|
|
|
if 'availability' in t:
|
|
ends = int(t['availability'].get('end', '0'))
|
|
if ends > 0:
|
|
now = int(time.time()*1000)
|
|
n_hours = int((ends - now) / (1000 * 60 * 60))
|
|
n_days = int(n_hours / 24)
|
|
if (n_days <= addon.getSettingInt('expdays')):
|
|
if n_days < 1:
|
|
t['info']['title'] += ' [COLOR red](' + addon.getLocalizedString(30401).format(n_hours) + ')[/COLOR]'
|
|
else:
|
|
t['info']['title'] += ' [COLOR red](' + addon.getLocalizedString(30400).format(n_days) + ')[/COLOR]'
|
|
|
|
title_name = t['info']['title']
|
|
if not 'type' in t: continue
|
|
|
|
wishlist_action = None
|
|
if t['type'] in ['movie', 'series', 'season']:
|
|
id = t['id']
|
|
stype = 'vod'
|
|
possible_to_add = False
|
|
|
|
if from_wishlist:
|
|
# Remove from wishlist
|
|
if 'favorite_data' in t:
|
|
if 'favorites2' in t['favorite_data']:
|
|
id = t['favorite_data']['favorites2']['id']
|
|
stype = t['favorite_data']['favorites2']['family']
|
|
possible_to_add = True
|
|
elif t['type'] == 'season' and 'favorites2.season' in t['favorite_data']:
|
|
id = t['favorite_data']['favorites2.season']['id']
|
|
stype = t['favorite_data']['favorites2.season']['family']
|
|
possible_to_add = True
|
|
else:
|
|
# Add to wishlist
|
|
if t['type'] in ['series', 'season']:
|
|
possible_to_add = t.get('seguible', False)
|
|
|
|
if t['type'] == 'movie':
|
|
if t.get('stream_type') == 'vod':
|
|
possible_to_add = t['url'] != ''
|
|
if 'show_id' in t:
|
|
possible_to_add = t['url'] != ''
|
|
stype = 'tv'
|
|
id = t['show_id']
|
|
|
|
if possible_to_add:
|
|
if not from_wishlist:
|
|
op = 'add'
|
|
message = 30175
|
|
else:
|
|
op = 'delete'
|
|
message = 30176
|
|
wishlist_action = (addon.getLocalizedString(message), "RunPlugin(" + get_url(action='to_wishlist', id=id, op=op, stype=stype) + ")")
|
|
|
|
if t['type'] == 'movie':
|
|
list_item = xbmcgui.ListItem(label = title_name)
|
|
#if t['url'] == '':
|
|
# t['info']['title'] = '[COLOR gray]' + t['info']['title'] +'[/COLOR]'
|
|
list_item.setProperty('IsPlayable', 'true')
|
|
list_item.setInfo('video', t['info'])
|
|
list_item.setArt(t['art'])
|
|
|
|
actions = []
|
|
if t.get('stream_type') == 'u7d':
|
|
#LOG(t)
|
|
if 'show_id' in t and not t.get('aired', False):
|
|
record_program_action = (addon.getLocalizedString(30171), "RunPlugin(" + get_url(action='add_recording', id=t['show_id']) + ")")
|
|
actions.append(record_program_action)
|
|
if 'serie_id' in t:
|
|
record_season_action = (addon.getLocalizedString(30169), "RunPlugin(" + get_url(action='add_recording_season', id=t['serie_id']) + ")")
|
|
actions.append(record_season_action)
|
|
|
|
if 'rec' in t:
|
|
action = get_url(action='delete_recording', id=t['rec']['id'], name=t['rec']['name'])
|
|
actions.append((addon.getLocalizedString(30173), "RunPlugin(" + action + ")"))
|
|
|
|
if wishlist_action:
|
|
actions.append(wishlist_action)
|
|
|
|
if len(actions) > 0:
|
|
#LOG(actions)
|
|
list_item.addContextMenuItems(actions)
|
|
|
|
url = get_url(action='play', id=t['id'], url=t['url'], session_request=t['session_request'], stype=t['stream_type'])
|
|
if 'show_id' in t: url += '&show_id={}'.format(t['show_id'])
|
|
if 'uid' in t: url += '&uid={}'.format(t['uid'])
|
|
xbmcplugin.addDirectoryItem(_handle, url, list_item, False)
|
|
elif t['type'] == 'series':
|
|
list_item = xbmcgui.ListItem(label = title_name)
|
|
list_item.setInfo('video', t['info'])
|
|
list_item.setArt(t['art'])
|
|
if wishlist_action:
|
|
list_item.addContextMenuItems([wishlist_action])
|
|
xbmcplugin.addDirectoryItem(_handle, get_url(action='series', id=t['id'], name=title_name), list_item, True)
|
|
elif t['type'] == 'season':
|
|
list_item = xbmcgui.ListItem(label = title_name)
|
|
list_item.setInfo('video', t['info'])
|
|
list_item.setArt(t['art'])
|
|
if wishlist_action:
|
|
list_item.addContextMenuItems([wishlist_action])
|
|
xbmcplugin.addDirectoryItem(_handle, get_url(action='season', id=t['id'], name=title_name), list_item, True)
|
|
elif t['type'] == 'category':
|
|
list_item = xbmcgui.ListItem(label = title_name)
|
|
#list_item.setInfo('video', t['info'])
|
|
#list_item.setArt(t['art'])
|
|
xbmcplugin.addDirectoryItem(_handle, get_url(action='category', id=t['id'], name=title_name), list_item, True)
|
|
|
|
if url_next:
|
|
list_item = xbmcgui.ListItem(label = addon.getLocalizedString(30109)) # Next page
|
|
xbmcplugin.addDirectoryItem(_handle, get_url(action=ref, url=url_next, from_wishlist=from_wishlist, name=category), list_item, True)
|
|
|
|
xbmcplugin.endOfDirectory(_handle)
|
|
|
|
def list_devices(params):
|
|
LOG('list_devices: params: {}'.format(params))
|
|
|
|
devices = m.get_devices()
|
|
|
|
if 'id' in params:
|
|
if params['name'] == 'select':
|
|
LOG('Selecting device {}'.format(params['id']))
|
|
m.change_device(params['id'])
|
|
elif params['name'] == 'close':
|
|
LOG('Closing session {}'.format(params['id']))
|
|
m.delete_session(params['id'])
|
|
elif params['name'] == 'delete':
|
|
LOG('Removing device {}'.format(params['id']))
|
|
m.delete_device(params['id'])
|
|
if params['id'] == m.account['device_id']:
|
|
LOG('Registering device')
|
|
m.register_device()
|
|
|
|
xbmc.executebuiltin("Container.Refresh")
|
|
return
|
|
|
|
open_folder(addon.getLocalizedString(30108)) # Devices
|
|
|
|
for d in devices:
|
|
name = d['name'] +' - '+ d['type'] + ' (' + d['reg_date'] + ')'
|
|
if d['playing']: name += ' [' + d['playing'] + ']'
|
|
name += ' ['+ d['id'][:5] +']'
|
|
if d['id'] == m.account['device_id']:
|
|
name = '[B][COLOR blue]' + name + '[/COLOR][/B]'
|
|
|
|
select_action = get_url(action='devices', id=d['id'], name='select')
|
|
close_action = get_url(action='devices', id=d['id'], name='close')
|
|
remove_action = get_url(action='devices', id=d['id'], name='delete')
|
|
cm = [(addon.getLocalizedString(30150), "RunPlugin(" + close_action + ")"),
|
|
(addon.getLocalizedString(30151), "RunPlugin(" + remove_action + ")")]
|
|
if d['type_code'] == 'WP':
|
|
cm.insert(0, (addon.getLocalizedString(30152), "RunPlugin(" + select_action + ")"))
|
|
default_action = select_action
|
|
else:
|
|
default_action = close_action
|
|
add_menu_option(name, default_action, cm)
|
|
|
|
close_folder(cacheToDisc=False)
|
|
|
|
def list_profiles(params):
|
|
LOG('list_profiles: params: {}'.format(params))
|
|
|
|
profiles = m.get_profiles()
|
|
|
|
if 'id' in params:
|
|
if params['name'] == 'select':
|
|
LOG('Selecting profile {}'.format(params['id']))
|
|
m.change_profile(params['id'])
|
|
xbmc.executebuiltin("Container.Refresh")
|
|
return
|
|
|
|
open_folder(addon.getLocalizedString(30180)) # Profiles
|
|
for p in profiles:
|
|
name = p['name']
|
|
if p['id'] == m.account['profile_id']:
|
|
name = '[B][COLOR blue]' + name + '[/COLOR][/B]'
|
|
img_url = m.get_profile_image_url(p['image_id'])
|
|
art = {'icon': img_url} if img_url else None
|
|
select_action = get_url(action='profiles', id=p['id'], name='select')
|
|
add_menu_option(name, select_action, art=art)
|
|
close_folder(cacheToDisc=False)
|
|
|
|
def list_epg(params):
|
|
LOG('list_epg: {}'.format(params))
|
|
from .timeconv import my_strftime
|
|
if 'id' in params:
|
|
if not 'date' in params:
|
|
today = datetime.today()
|
|
for i in range(6, 0, -1):
|
|
past_date = today - timedelta(days=i)
|
|
date = past_date.strftime('%Y-%m-%dT00:00:00')
|
|
display_str = my_strftime(past_date, '%a %d')
|
|
add_menu_option(display_str, get_url(action='epg', id=params['id'], name=params['name'], date=date))
|
|
add_videos(params['name'], 'movies', m.epg_to_movies(params['id'], params.get('date'), duration=1))
|
|
else:
|
|
channels = m.get_channels()
|
|
open_folder(addon.getLocalizedString(30107)) # EPG
|
|
for t in channels:
|
|
name = t['info']['title']
|
|
add_menu_option(name, get_url(action='epg', id=t['id'], name=name.encode('utf-8')), art=t['art'])
|
|
close_folder()
|
|
|
|
def listing(name, url):
|
|
data = m.download_list(url, use_hz=False)
|
|
l = []
|
|
url_next = None
|
|
url_prev = None
|
|
if 'Contenidos' in data:
|
|
l = m.get_list(data['Contenidos'])
|
|
url_next = data['next']['href'] if isinstance(data['next'], dict) and 'href' in data['next'] else None
|
|
url_prev = data['prev']['href'] if isinstance(data['next'], dict) and 'prev' in data['next'] else None
|
|
add_videos(name, 'movies', l, url_next=url_next, url_prev=url_prev, ref='listing')
|
|
|
|
def listing_hz(name, url, from_wishlist=False):
|
|
data = m.download_list(url, use_hz=True)
|
|
l = []
|
|
url_next = None
|
|
url_prev = None
|
|
if 'Contenidos' in data:
|
|
l = m.get_list(data['Contenidos'])
|
|
url_next = data['next']['href'] if isinstance(data['next'], dict) and 'href' in data['next'] else None
|
|
url_prev = data['prev']['href'] if isinstance(data['next'], dict) and 'prev' in data['next'] else None
|
|
add_videos(name, 'movies', l, url_next=url_next, url_prev=url_prev, ref='listing_hz', from_wishlist=from_wishlist)
|
|
|
|
def list_vod0():
|
|
open_folder(addon.getLocalizedString(30111)) # VOD
|
|
name = addon.getLocalizedString(30105).encode('utf-8')
|
|
add_menu_option(name, get_url(action='listing', name=name, url=m.get_vod_list_url(cat='movies'))) # Movies
|
|
|
|
name = addon.getLocalizedString(30106).encode('utf-8')
|
|
add_menu_option(name, get_url(action='listing', name=name, url=m.get_vod_list_url(cat='tvshows'))) # TV Shows
|
|
|
|
name = addon.getLocalizedString(30120).encode('utf-8')
|
|
add_menu_option(name, get_url(action='listing', name=name, url=m.get_vod_list_url(cat='documentaries'))) # Documentaries
|
|
|
|
name = addon.getLocalizedString(30121).encode('utf-8')
|
|
add_menu_option(name, get_url(action='listing', name=name, url=m.get_vod_list_url(cat='kids'))) # Kids
|
|
|
|
sections = m.get_vod_sections()
|
|
|
|
close_folder()
|
|
|
|
def list_vod():
|
|
open_folder(addon.getLocalizedString(30111)) # VOD
|
|
|
|
sections = m.get_vod_sections()
|
|
for key, s in sections.items():
|
|
if not s['visible']: continue
|
|
name = s['name'].encode('utf-8')
|
|
add_menu_option(name, get_url(action='list_section', name=name, key=key))
|
|
|
|
close_folder()
|
|
|
|
def list_section(key):
|
|
open_folder(addon.getLocalizedString(30111)) # VOD
|
|
|
|
sections = m.get_vod_sections()
|
|
items = sections[key]['data']
|
|
for item in items:
|
|
name = item['name'].encode('utf-8')
|
|
add_menu_option(name, get_url(action='listing', name=name, url=item['url']))
|
|
|
|
close_folder()
|
|
|
|
def search(params):
|
|
search_term = params.get('search_term', None)
|
|
if search_term:
|
|
if sys.version_info[0] < 3:
|
|
search_term = search_term.decode('utf-8')
|
|
if params.get('name', None) == 'delete':
|
|
m.delete_search(search_term)
|
|
xbmc.executebuiltin("Container.Refresh")
|
|
else:
|
|
url = m.get_search_url(search_term)
|
|
listing_hz(addon.getLocalizedString(30117), url)
|
|
return
|
|
|
|
if params.get('name', None) == 'new':
|
|
search_term = input_window(addon.getLocalizedString(30116)) # Search term
|
|
if search_term:
|
|
if sys.version_info[0] < 3:
|
|
search_term = search_term.decode('utf-8')
|
|
m.add_search(search_term)
|
|
xbmc.executebuiltin("Container.Refresh")
|
|
return
|
|
|
|
open_folder(addon.getLocalizedString(30113)) # Search
|
|
add_menu_option(addon.getLocalizedString(30113), get_url(action='search', name='new')) # New search
|
|
|
|
for i in reversed(m.search_list):
|
|
remove_action = get_url(action='search', search_term=i, name='delete')
|
|
cm = [(addon.getLocalizedString(30114), "RunPlugin(" + remove_action + ")")]
|
|
add_menu_option(i.encode('utf-8'), get_url(action='search', search_term=i), cm)
|
|
|
|
close_folder(cacheToDisc=False)
|
|
|
|
def order_recording(program_id):
|
|
data = m.order_recording(program_id)
|
|
if data and 'resultText' in data:
|
|
show_notification(data['resultText'])
|
|
else:
|
|
show_notification(addon.getLocalizedString(30172), xbmcgui.NOTIFICATION_INFO)
|
|
|
|
def order_recording_season(season_id):
|
|
data = m.order_recording_season(season_id)
|
|
if data and 'resultText' in data:
|
|
show_notification(data['resultText'])
|
|
else:
|
|
show_notification(addon.getLocalizedString(30172), xbmcgui.NOTIFICATION_INFO)
|
|
|
|
def delete_recording(id, name):
|
|
if sys.version_info[0] < 3:
|
|
name = name.decode('utf-8')
|
|
res = xbmcgui.Dialog().yesno(addon.getLocalizedString(30173), addon.getLocalizedString(30174).format(name))
|
|
if res == True:
|
|
m.delete_recording(id)
|
|
xbmc.executebuiltin("Container.Refresh")
|
|
|
|
def clear_session():
|
|
m.delete_session_files()
|
|
|
|
def logout():
|
|
clear_session()
|
|
m.cache.remove_file('auth.key')
|
|
|
|
def login():
|
|
def ask_credentials(username='', password=''):
|
|
username = input_window(addon.getLocalizedString(30163), username) # Username
|
|
if username:
|
|
password = input_window(addon.getLocalizedString(30164), password, hidden=True) # Password
|
|
if password:
|
|
return username, password
|
|
return None, None
|
|
|
|
store_credentials = addon.getSettingBool('store_credentials')
|
|
if store_credentials:
|
|
username, password = m.load_credentials()
|
|
else:
|
|
username, password = ('', '')
|
|
|
|
username, password = ask_credentials(username, password)
|
|
if username:
|
|
if store_credentials:
|
|
m.save_credentials(username, password)
|
|
success, _ = m.login(username, password)
|
|
if success:
|
|
clear_session()
|
|
else:
|
|
show_notification(addon.getLocalizedString(30166)) # Failed
|
|
|
|
def network_login():
|
|
# The account number and the device are found/registered automatically.
|
|
account_number = addon.getSetting('account_number')
|
|
# Keeps device_id.conf: clearing the session would register a new device.
|
|
if m.try_network_login(account_number, force=True):
|
|
show_notification(addon.getLocalizedString(30503), xbmcgui.NOTIFICATION_INFO) # Logged in
|
|
xbmc.executebuiltin('Container.Refresh')
|
|
else:
|
|
reason = (m.network_login_result or (False, ''))[1]
|
|
show_notification(addon.getLocalizedString(30504).format(reason)) # Failed
|
|
|
|
def login_with_key():
|
|
filename = xbmcgui.Dialog().browseSingle(1, addon.getLocalizedString(30182), '', '.key')
|
|
if filename:
|
|
m.install_key_file(filename)
|
|
clear_session()
|
|
|
|
def export_key():
|
|
directory = xbmcgui.Dialog().browseSingle(0, addon.getLocalizedString(30185), '')
|
|
if directory:
|
|
m.export_key_file(directory + 'movistarplus.key')
|
|
|
|
def import_credentials():
|
|
filename = xbmcgui.Dialog().browseSingle(1, addon.getLocalizedString(30193), '', '.json')
|
|
if filename:
|
|
m.import_credentials(filename)
|
|
|
|
def export_credentials():
|
|
directory = xbmcgui.Dialog().browseSingle(0, addon.getLocalizedString(30194), '')
|
|
if directory:
|
|
m.export_credentials(os.path.join(directory, 'credenciales.json'))
|
|
|
|
def select_account(id, name):
|
|
m.switch_account(id)
|
|
open_folder(name)
|
|
add_menu_option(addon.getLocalizedString(30501), get_url(action='network_login')) # Login from home network
|
|
add_menu_option(addon.getLocalizedString(30183), get_url(action='login')) # Login with username
|
|
if addon.getSettingBool('enable_key_login'):
|
|
add_menu_option(addon.getLocalizedString(30181), get_url(action='login_with_key')) # Login with key
|
|
if os.path.exists(os.path.join(m.cache.config_directory, 'auth.key')):
|
|
add_menu_option(addon.getLocalizedString(30184), get_url(action='export_key')) # Export key
|
|
add_menu_option(addon.getLocalizedString(30150), get_url(action='logout')) # Close session
|
|
add_menu_option(addon.getLocalizedString(30196), get_url(action='import_credentials'))
|
|
if os.path.exists(os.path.join(m.cache.config_directory, 'credentials.json')):
|
|
add_menu_option(addon.getLocalizedString(30195), get_url(action='export_credentials'))
|
|
close_folder(cacheToDisc=False)
|
|
|
|
def list_accounts(params):
|
|
LOG('list_accounts: {}'.format(params))
|
|
|
|
if params.get('name') == 'new_account':
|
|
m.create_new_account()
|
|
|
|
accounts = m.get_accounts()
|
|
open_folder(addon.getLocalizedString(30160)) # Accounts
|
|
for account in accounts:
|
|
name = account['name']
|
|
login = account.get('login')
|
|
if login:
|
|
name += ' ({})'.format(login)
|
|
if account['id'] == m.account_dir:
|
|
display_name = '[B][COLOR blue]' + name + '[/COLOR][/B]'
|
|
else:
|
|
display_name = name
|
|
add_menu_option(display_name, get_url(action='select_account', id=account['id'], name=name))
|
|
add_menu_option(addon.getLocalizedString(30161), get_url(action='accounts', name='new_account')) # Add new
|
|
close_folder(cacheToDisc=False)
|
|
|
|
if params.get('name') == 'new_account':
|
|
xbmc.executebuiltin('Container.Update({},replace)'.format(xbmc.getInfoLabel('Container.FolderPath')))
|
|
|
|
def iptv(params):
|
|
LOG('iptv: params: {}'.format(params))
|
|
if m.logged:
|
|
#try:
|
|
if True:
|
|
from .iptvmanager import IPTVManager
|
|
port = int(params['port'])
|
|
if params['action'] == 'iptv-channels':
|
|
IPTVManager(port).send_channels(m)
|
|
elif params['action'] == 'iptv-epg':
|
|
IPTVManager(port).send_epg(m)
|
|
#except:
|
|
# pass
|
|
|
|
def create_iptv_settings():
|
|
from .iptv import save_iptv_settings
|
|
show_notification(addon.getLocalizedString(30319), xbmcgui.NOTIFICATION_INFO)
|
|
output_file = 'instance-settings-91.xml' if kodi_version > 19 else 'settings.xml'
|
|
epg_url = None
|
|
if False: #addon.getSettingBool('use_external_epg'):
|
|
epg_url = addon.getSetting('epg_url')
|
|
try:
|
|
pvr_addon = xbmcaddon.Addon('pvr.iptvsimple')
|
|
pvr_dir = translatePath(pvr_addon.getAddonInfo('profile'))
|
|
LOG('pvr_dir: {}'.format(pvr_dir))
|
|
filename = os.path.join(pvr_dir, output_file)
|
|
if os.path.exists(filename):
|
|
res = xbmcgui.Dialog().yesno(addon.getLocalizedString(30322), addon.getLocalizedString(30323))
|
|
if res == False: return
|
|
# Write the channel list and the programme guide first: if IPTV Simple
|
|
# starts before epg.xml exists, Kodi stores an empty guide for every
|
|
# channel and doesn't ask again for hours.
|
|
export_epg_now()
|
|
if not os.path.exists(os.path.join(profile_dir, 'epg.xml')):
|
|
show_notification(addon.getLocalizedString(30324))
|
|
return
|
|
save_iptv_settings(filename, 'KodiMov+', 'kodimovplus', epg_url)
|
|
# Show Movistar's own channel numbers instead of 1, 2, 3...
|
|
xbmc.executeJSONRPC(json.dumps({'jsonrpc': '2.0', 'id': 1, 'method': 'Settings.SetSettingValue',
|
|
'params': {'setting': 'pvrmanager.usebackendchannelnumbers', 'value': True}}))
|
|
# Restart IPTV Simple so it loads the new instance and channel list.
|
|
for enabled in (False, True):
|
|
xbmc.executeJSONRPC(json.dumps({'jsonrpc': '2.0', 'id': 1, 'method': 'Addons.SetAddonEnabled',
|
|
'params': {'addonid': 'pvr.iptvsimple', 'enabled': enabled}}))
|
|
xbmc.sleep(3000)
|
|
except:
|
|
show_notification(addon.getLocalizedString(30324))
|
|
|
|
def export_channels_now():
|
|
if not m.logged: return
|
|
channels_filename = os.path.join(profile_dir, 'channels.m3u8')
|
|
if m.export_channels_to_m3u8(channels_filename, addon.getSettingBool('only_subscribed')):
|
|
LOG('export_channels_now: channel list changed')
|
|
|
|
def export_epg_now(quiet=False):
|
|
# quiet: background refresh from the service, no pop-ups while watching TV
|
|
def notify(message):
|
|
if not quiet: show_notification(message, xbmcgui.NOTIFICATION_INFO)
|
|
def report_progress(message):
|
|
notify(addon.getLocalizedString(30314).format(message))
|
|
|
|
if not m.logged: return
|
|
|
|
only_subscribed = addon.getSettingBool('only_subscribed')
|
|
channels_filename = os.path.join(profile_dir, 'channels.m3u8')
|
|
epg_filename = os.path.join(profile_dir, 'epg.xml')
|
|
notify(addon.getLocalizedString(30310))
|
|
m.export_channels_to_m3u8(channels_filename, only_subscribed)
|
|
|
|
if True: #not addon.getSettingBool('use_external_epg'):
|
|
notify(addon.getLocalizedString(30311))
|
|
m.export_epg_to_xml(epg_filename, addon.getSettingInt('export_days'), report_progress, only_subscribed)
|
|
|
|
notify(addon.getLocalizedString(30313))
|
|
|
|
|
|
def to_wishlist(params):
|
|
stype = params['stype']
|
|
if params['op'] == 'add':
|
|
retcode, message = m.add_to_wishlist(params['id'], stype)
|
|
else:
|
|
retcode, message = m.delete_from_wishlist(params['id'], stype)
|
|
if retcode in [201, 204]:
|
|
message = 30177 if params['op'] == 'add' else 30178
|
|
show_notification(addon.getLocalizedString(message), xbmcgui.NOTIFICATION_INFO)
|
|
if params['op'] == 'delete':
|
|
xbmc.executebuiltin("Container.Refresh")
|
|
else:
|
|
show_notification(str(retcode) +': '+ message)
|
|
|
|
|
|
def router(paramstring):
|
|
"""
|
|
Router function that calls other functions
|
|
depending on the provided paramstring
|
|
:param paramstring: URL encoded plugin paramstring
|
|
:type paramstring: str
|
|
"""
|
|
|
|
params = dict(parse_qsl(paramstring))
|
|
LOG('params: {}'.format(params))
|
|
if params:
|
|
if params['action'] == 'play':
|
|
play(params)
|
|
elif params['action'] == 'tv':
|
|
if addon.getSettingBool('channels_with_epg'):
|
|
channels = m.get_channels_with_epg()
|
|
else:
|
|
channels = m.get_channels()
|
|
add_videos(addon.getLocalizedString(30104), 'movies', channels)
|
|
elif params['action'] == 'devices':
|
|
list_devices(params)
|
|
elif params['action'] == 'profiles':
|
|
list_profiles(params)
|
|
elif params['action'] == 'login_with_key':
|
|
login_with_key()
|
|
elif params['action'] == 'login':
|
|
login()
|
|
elif params['action'] == 'network_login':
|
|
network_login()
|
|
elif params['action'] == 'export_channels_now':
|
|
export_channels_now()
|
|
elif params['action'] == 'export_key':
|
|
export_key()
|
|
elif params['action'] == 'export_credentials':
|
|
export_credentials()
|
|
elif params['action'] == 'import_credentials':
|
|
import_credentials()
|
|
elif params['action'] == 'select_account':
|
|
select_account(params['id'], params['name'])
|
|
elif params['action'] == 'accounts':
|
|
list_accounts(params)
|
|
elif params['action'] == 'logout':
|
|
logout()
|
|
elif params['action'] == 'epg':
|
|
list_epg(params)
|
|
elif params['action'] == 'add_recording':
|
|
order_recording(params['id'])
|
|
elif params['action'] == 'delete_recording':
|
|
delete_recording(params['id'], params['name'])
|
|
elif params['action'] == 'add_recording_season':
|
|
order_recording_season(params['id'])
|
|
elif params['action'] == 'wishlist':
|
|
# Wishlist
|
|
listing_hz(addon.getLocalizedString(30102), m.get_wishlist_url(), from_wishlist=True)
|
|
elif params['action'] == 'continue-watching':
|
|
listing_hz(addon.getLocalizedString(30102), m.get_viewings_url())
|
|
elif params['action'] == 'recordings':
|
|
# Recordings
|
|
listing_hz(addon.getLocalizedString(30103), m.get_recordings_url())
|
|
elif params['action'] == 'listing':
|
|
listing(params['name'], params['url'])
|
|
elif params['action'] == 'listing_hz':
|
|
listing_hz(params['name'], params['url'], params.get('from_wishlist'))
|
|
elif params['action'] == 'series':
|
|
add_videos(params['name'], 'seasons', m.get_seasons(params['id']))
|
|
elif params['action'] == 'season':
|
|
add_videos(params['name'], 'episodes', m.get_episodes(params['id']))
|
|
elif params['action'] == 'vod':
|
|
list_vod()
|
|
elif params['action'] == 'list_section':
|
|
list_section(params['key'])
|
|
elif params['action'] == 'search':
|
|
search(params)
|
|
elif params['action'] == 'create_iptv_settings':
|
|
create_iptv_settings()
|
|
elif params['action'] == 'export_epg_now':
|
|
export_epg_now(quiet=params.get('quiet') == '1')
|
|
elif params['action'] == 'to_wishlist':
|
|
to_wishlist(params)
|
|
elif 'iptv' in params['action']:
|
|
iptv(params)
|
|
else:
|
|
# Main
|
|
open_folder(addon.getLocalizedString(30101)) # Menu
|
|
|
|
if m.logged:
|
|
add_menu_option(addon.getLocalizedString(30104), get_url(action='tv'), icon='tv.png') # TV
|
|
add_menu_option(addon.getLocalizedString(30107), get_url(action='epg'), icon='epg.png') # EGP
|
|
add_menu_option(addon.getLocalizedString(30102), get_url(action='wishlist'), icon='mylist.png') # My list
|
|
add_menu_option(addon.getLocalizedString(30103), get_url(action='recordings'), icon='recording.png') # Recordings
|
|
add_menu_option(addon.getLocalizedString(30122), get_url(action='continue-watching'), icon='continue.png') # Continue
|
|
add_menu_option(addon.getLocalizedString(30111), get_url(action='vod'), icon='vod.png') # VOD
|
|
add_menu_option(addon.getLocalizedString(30112), get_url(action='search'), icon='search.png') # Search
|
|
add_menu_option(addon.getLocalizedString(30180), get_url(action='profiles'), icon='profiles.png') # Profiles
|
|
add_menu_option(addon.getLocalizedString(30108), get_url(action='devices'), icon='devices.png') # Devices
|
|
else:
|
|
if m.expired_access_token:
|
|
show_notification(addon.getLocalizedString(30208))
|
|
add_menu_option(addon.getLocalizedString(30501), get_url(action='network_login'), icon='account.png') # Login from home network
|
|
|
|
add_menu_option(addon.getLocalizedString(30160), get_url(action='accounts'), icon='account.png') # Accounts
|
|
add_menu_option(addon.getLocalizedString(30450), get_url(action='show_donation_dialog'), icon='qr.png') # Donation
|
|
close_folder(cacheToDisc=False)
|
|
|
|
|
|
def run():
|
|
# Call the router function and pass the plugin call parameters to it.
|
|
# We use string slicing to trim the leading '?' from the plugin call paramstring
|
|
params = sys.argv[2][1:]
|
|
|
|
if 'show_donation_dialog' in params:
|
|
from .customdialog import show_donation_dialog
|
|
show_donation_dialog()
|
|
return
|
|
|
|
global m
|
|
reuse_devices = addon.getSettingBool('reuse_devices')
|
|
LOG('profile_dir: {}'.format(profile_dir))
|
|
LOG('reuse_devices: {}'.format(reuse_devices))
|
|
m = Movistar(profile_dir, reuse_devices=reuse_devices,
|
|
network_login=addon.getSettingBool('network_login'),
|
|
account_number=addon.getSetting('account_number'))
|
|
m.add_extra_info = addon.getSettingBool('add_extra_info')
|
|
m.hide_unplayable = addon.getSettingBool('hide_unplayable')
|
|
m.add_extra_channels = addon.getSettingBool('extra_channels')
|
|
if addon.getSettingBool('uhd'):
|
|
m.quality = 'UHD'
|
|
|
|
profile_id = addon.getSetting('profile_id').upper()
|
|
LOG('profile_id: {}'.format(profile_id))
|
|
if profile_id not in ['', 'AUTO']:
|
|
m.account['platform'] = profile_id
|
|
|
|
# Clear cache
|
|
LOG('Cleaning cache. {} files removed.'.format(m.cache.clear_cache()))
|
|
|
|
if '/iptv/channels' in sys.argv[0]: params += '&action=iptv-channels'
|
|
elif '/iptv/epg' in sys.argv[0]: params += '&action=iptv-epg'
|
|
router(params)
|