Files
kodimovplus/resources/lib/plugin.py
T
XoséandClaude Opus 5.5 e16dd66f63 1.0.0+kodimov.9: get a new guide into Kodi right after start or update
Kodi keeps its own copy of the programme guide and fetches it again only
when IPTV Simple reloads its files, on its refresh timer; restarting IPTV
Simple is not enough. After the first refresh following a Kodi start or
add-on update, if the guide changed and nothing is playing, restart IPTV
Simple with a 1-minute timer, wait for the reload, then restart it with
the hourly timer again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 08:51:51 +01:00

973 lines
38 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
from . import kodimov
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(30509)) # Log in first
return
save_iptv_settings(filename, 'KodiMov+', 'kodimovplus', epg_url)
# IPTV Simple now reads epg.xml, so the service must keep it up to date.
addon.setSettingBool('auto_export_epg', True)
# 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.
kodimov.restart_iptv_simple()
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, reload_guide=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))
changed = m.export_epg_to_xml(epg_filename, addon.getSettingInt('export_days'), report_progress, only_subscribed)
if changed and reload_guide:
from . import kodimov
try:
pvr_dir = translatePath(xbmcaddon.Addon('pvr.iptvsimple').getAddonInfo('profile'))
except RuntimeError: # IPTV Simple not installed or disabled
pvr_dir = None
if pvr_dir: kodimov.reload_iptv_guide(pvr_dir, 'kodimovplus')
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', reload_guide=params.get('reload_guide') == '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)