KodiMov+ 1.0.0+kodimov.1: fork of Movistar+ 1.0.0
- New id plugin.video.kodimovplus, name KodiMov+, proxy port 57031 - Takes over the login of plugin.video.movistarplus on first run - Password-less login from the Movistar home network (autologin token traded for a classic token via the new servers' initData) - Hides the channels that can't play (LALIGA, Liga de Campeones) - Adds TVG2 and TeleVigo to the TV guide list - Re-exports the TV guide channel list every 6 hours Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ccc17b4e5c
commit
066a203e9e
35 files changed
+412
-30
No files matched your search
Binary file not shown.
@@ -621,3 +621,35 @@ msgstr ""
|
||||
msgctxt "#30451"
|
||||
msgid "If you find this addon useful you can buy the developer a coffee:"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30501"
|
||||
msgid "Log in from the Movistar home network"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30502"
|
||||
msgid "Movistar account number (e.g. 12345678901234-TEF)"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30503"
|
||||
msgid "Logged in from the Movistar network"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30504"
|
||||
msgid "Network login failed: {}"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30505"
|
||||
msgid "Log in automatically from the Movistar home network"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30506"
|
||||
msgid "Hide channels this add-on can't play (LALIGA, Liga de Campeones)"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30507"
|
||||
msgid "Add free Galician channels (TVG2, TeleVigo) to the TV guide"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30508"
|
||||
msgid "Keep the TV guide channel list up to date"
|
||||
msgstr ""
|
||||
@@ -585,6 +585,38 @@ msgctxt "#30451"
|
||||
msgid "If you find this addon useful you can buy the developer a coffee:"
|
||||
msgstr "Si este addon es útil para ti puedes invitar al programador a un café:"
|
||||
|
||||
msgctxt "#30501"
|
||||
msgid "Log in from the Movistar home network"
|
||||
msgstr "Iniciar sesión desde la red de Movistar"
|
||||
|
||||
msgctxt "#30502"
|
||||
msgid "Movistar account number (e.g. 12345678901234-TEF)"
|
||||
msgstr "Número de cuenta de Movistar (p. ej. 12345678901234-TEF)"
|
||||
|
||||
msgctxt "#30503"
|
||||
msgid "Logged in from the Movistar network"
|
||||
msgstr "Sesión iniciada desde la red de Movistar"
|
||||
|
||||
msgctxt "#30504"
|
||||
msgid "Network login failed: {}"
|
||||
msgstr "Falló el inicio de sesión por red: {}"
|
||||
|
||||
msgctxt "#30505"
|
||||
msgid "Log in automatically from the Movistar home network"
|
||||
msgstr "Iniciar sesión automáticamente desde la red de Movistar"
|
||||
|
||||
msgctxt "#30506"
|
||||
msgid "Hide channels this add-on can't play (LALIGA, Liga de Campeones)"
|
||||
msgstr "Ocultar los canales que no se pueden reproducir (LALIGA, Liga de Campeones)"
|
||||
|
||||
msgctxt "#30507"
|
||||
msgid "Add free Galician channels (TVG2, TeleVigo) to the TV guide"
|
||||
msgstr "Añadir canales gallegos gratuitos (TVG2, TeleVigo) a la guía"
|
||||
|
||||
msgctxt "#30508"
|
||||
msgid "Keep the TV guide channel list up to date"
|
||||
msgstr "Mantener al día la lista de canales de la guía"
|
||||
|
||||
#~ msgctxt "#30040"
|
||||
#~ msgid "Use only for these languages (e.g.: es en pt):"
|
||||
#~ msgstr "Usar solo para estos idiomas (ej: es en pt):"
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,170 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# KodiMov+ additions to the Movistar+ add-on:
|
||||
# - takes over the login of plugin.video.movistarplus on first run
|
||||
# - logs in from the Movistar home network without a password
|
||||
# - hides channels this add-on can't play
|
||||
# - extra free channels for the TV guide
|
||||
|
||||
from __future__ import unicode_literals, absolute_import, division
|
||||
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
|
||||
from .log import LOG
|
||||
|
||||
ORIGINAL_ADDON = 'plugin.video.movistarplus'
|
||||
CLASSIC_ISSUER = 'http://www.movistarplus.es'
|
||||
|
||||
# Movistar marks the football and premium sports channels with QMWatermark.
|
||||
# The LALIGA and Liga de Campeones ones refuse their manifest to this add-on;
|
||||
# these play fine (tested 2026-11-07 on a Raspberry Pi 5, LibreELEC 12.2).
|
||||
WATERMARKED_BUT_PLAYABLE = {
|
||||
'CPDEP', 'ARTHUR', 'USOP2', 'USOP3', 'USOP11', 'MULTI8', # M+ Deportes 1-6
|
||||
'M1SD', 'M2SD', 'DAZN3', 'DAZN4', # DAZN 1-4
|
||||
'DAZBA', 'DAZB2', 'DAZB3', # DAZN Baloncesto 1-3
|
||||
}
|
||||
|
||||
# Free channels that Movistar+ doesn't carry. 'chno' must not clash with a
|
||||
# Movistar dial number.
|
||||
EXTRA_CHANNELS = [
|
||||
{'id': 'TVG2', 'name': 'TVG2', 'chno': 10, 'group': 'Galicia',
|
||||
'logo': 'https://i.imgur.com/yFy6gxg.png',
|
||||
'url': 'https://crtvg-tvg2.flumotion.cloud/playlist.m3u8'},
|
||||
{'id': 'TeleVigo', 'name': 'TeleVigo', 'chno': 155, 'group': 'Galicia',
|
||||
'logo': 'https://i.imgur.com/4iVmlFa.png',
|
||||
'url': 'https://cloud.fastchannel.es/mic/manifiest/hls/televigo/televigo.m3u8'},
|
||||
]
|
||||
|
||||
AUTOLOGIN_URL = 'https://soter-pf.svp.video.telefonicaservices.com/service/login/autologin?deviceId={device}&deviceType=webplayer&_={ts}'
|
||||
SOTER_INITDATA_URL = 'https://soter-pf.sve.video.telefonicaservices.com/service/login/initData?legacy=true'
|
||||
|
||||
|
||||
def is_playable(channel):
|
||||
return not channel.get('watermark') or channel['id'] in WATERMARKED_BUT_PLAYABLE
|
||||
|
||||
|
||||
def migrate_from_original(config_directory):
|
||||
"""Copies the login of plugin.video.movistarplus (tokens, registered device,
|
||||
credentials) the first time KodiMov+ runs, so no new device is added to the
|
||||
Movistar account and no password is needed."""
|
||||
config_directory = config_directory.rstrip('/\\')
|
||||
if os.path.exists(os.path.join(config_directory, 'account.txt')): return False
|
||||
if any(d.startswith('account_') for d in os.listdir(config_directory)): return False
|
||||
source = os.path.join(os.path.dirname(config_directory), ORIGINAL_ADDON)
|
||||
if not os.path.isdir(source): return False
|
||||
copied = False
|
||||
for d in os.listdir(source):
|
||||
src = os.path.join(source, d)
|
||||
if d.startswith('account_') and os.path.isdir(src):
|
||||
shutil.copytree(src, os.path.join(config_directory, d), ignore=shutil.ignore_patterns('cache'))
|
||||
copied = True
|
||||
if copied and os.path.exists(os.path.join(source, 'account.txt')):
|
||||
shutil.copy(os.path.join(source, 'account.txt'), config_directory)
|
||||
LOG('migrate_from_original: {}'.format('copied login from ' + ORIGINAL_ADDON if copied else 'nothing to copy'))
|
||||
return copied
|
||||
|
||||
|
||||
def _read_json(cache, filename):
|
||||
content = cache.load_file(filename)
|
||||
try:
|
||||
return json.loads(content) if content else None
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def find_account_number(m, setting=''):
|
||||
data = _read_json(m.cache, 'account.json')
|
||||
try:
|
||||
return data['ofertas'][0]['accountNumber']
|
||||
except (TypeError, KeyError, IndexError):
|
||||
pass
|
||||
data = _read_json(m.cache, 'tokens.json')
|
||||
if isinstance(data, dict) and data.get('accountNumber'):
|
||||
return data['accountNumber']
|
||||
return (setting or '').strip()
|
||||
|
||||
|
||||
def network_login(m, account_number):
|
||||
"""Password-less login from the Movistar home network.
|
||||
1. autologin (only reachable inside Movistar's network) gives a 5-minute
|
||||
token for the new servers;
|
||||
2. their initData trades it for a legacyAccessToken, which the classic
|
||||
servers this add-on uses accept (and renew for 10 days at a time).
|
||||
Returns (success, reason)."""
|
||||
try:
|
||||
from urllib.parse import urlsplit
|
||||
except ImportError:
|
||||
from urlparse import urlsplit
|
||||
from .signature import get_auth
|
||||
|
||||
device_id = (m.cache.load_file('device_id.conf') or '').strip().strip('"')
|
||||
if not account_number: return False, 'no account number'
|
||||
if not device_id: return False, 'no registered device'
|
||||
|
||||
headers = m.net.headers.copy()
|
||||
headers['Content-Type'] = 'application/json'
|
||||
url = AUTOLOGIN_URL.format(device=device_id, ts=int(time.time() * 1000))
|
||||
try:
|
||||
r = m.net.session.get(url, headers=headers, allow_redirects=False, timeout=(4, 10))
|
||||
location = r.headers.get('Location', '')
|
||||
if r.status_code in (301, 302, 303, 307, 308) and location:
|
||||
if not urlsplit(location).netloc.endswith('.telefonicaservices.com'):
|
||||
return False, 'unexpected redirect'
|
||||
h2 = headers.copy()
|
||||
h2['Origin'] = 'null'
|
||||
r = m.net.session.get(location, headers=h2, allow_redirects=False, timeout=(4, 10))
|
||||
token = r.json().get('access_token')
|
||||
except Exception as e:
|
||||
LOG('network_login: autologin failed: {}'.format(e))
|
||||
return False, 'not on the Movistar network'
|
||||
if not token: return False, 'autologin refused'
|
||||
|
||||
body = {'accountNumber': account_number, 'sessionUserProfile': 0, 'isKidProfile': False,
|
||||
'deviceId': device_id, 'streamMiscellanea': 'HTTPS', 'deviceManufacturerProduct': 'Firefox',
|
||||
'streamDRM': 'Widevine', 'streamFormat': 'DASH'}
|
||||
data = None
|
||||
for attempt in range(4):
|
||||
h = headers.copy()
|
||||
h['Authorization'] = get_auth('post', SOTER_INITDATA_URL, token)
|
||||
try:
|
||||
r = m.net.session.post(SOTER_INITDATA_URL, data=json.dumps(body), headers=h, timeout=(4, 15))
|
||||
data = r.json()
|
||||
break
|
||||
except Exception:
|
||||
# The new servers sometimes answer with an HTML error page; retry.
|
||||
LOG('network_login: initData attempt {} failed ({})'.format(attempt + 1, getattr(r, 'status_code', '?')))
|
||||
time.sleep(3)
|
||||
legacy = data.get('legacyAccessToken') if isinstance(data, dict) else None
|
||||
props = m.get_token_properties(legacy) if legacy else None
|
||||
if not props or props.get('iss') != CLASSIC_ISSUER or props.get('exp', 0) < time.time():
|
||||
return False, 'initData refused'
|
||||
m.save_key_file({'access_token': legacy, 'token_type': 'bearer'})
|
||||
m.cache.save_file('access_token.conf', legacy)
|
||||
LOG('network_login: OK')
|
||||
return True, ''
|
||||
|
||||
|
||||
def extra_channels_m3u8():
|
||||
items = []
|
||||
for c in EXTRA_CHANNELS:
|
||||
items.append('#EXTINF:-1 tvg-name="{name}" tvg-id="{id}" tvg-logo="{logo}" tvg-chno="{chno}" group-title="{group}",{name}\n{url}\n\n'.format(**c))
|
||||
return ''.join(items)
|
||||
|
||||
|
||||
def write_if_changed(filename, content):
|
||||
try:
|
||||
with io.open(filename, 'r', encoding='utf-8', newline='') as f:
|
||||
if f.read() == content: return False
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
tmp = filename + '.tmp'
|
||||
with io.open(tmp, 'w', encoding='utf-8', newline='') as f:
|
||||
f.write(content)
|
||||
os.replace(tmp, filename) if hasattr(os, 'replace') else shutil.move(tmp, filename)
|
||||
return True
|
||||
@@ -22,6 +22,7 @@ from .network import Network
|
||||
from .cache import Cache
|
||||
from .timeconv import *
|
||||
from .useragent import useragent
|
||||
from . import kodimov
|
||||
|
||||
class Movistar(object):
|
||||
account = {'username': '', 'password': '',
|
||||
@@ -37,6 +38,8 @@ class Movistar(object):
|
||||
'demarcation': 0}
|
||||
|
||||
add_extra_info = True
|
||||
hide_unplayable = True
|
||||
add_extra_channels = True
|
||||
#dplayer = 'webplayer'
|
||||
#device_code = 'WP_OTT'
|
||||
#manufacturer = 'Firefox'
|
||||
@@ -45,9 +48,14 @@ class Movistar(object):
|
||||
manufacturer = 'LG'
|
||||
account_dir = 'account_1'
|
||||
|
||||
def __init__(self, config_directory, reuse_devices=False):
|
||||
def __init__(self, config_directory, reuse_devices=False, network_login=False, account_number=''):
|
||||
self.logged = False
|
||||
self.expired_access_token = False
|
||||
self.network_login_result = None
|
||||
try:
|
||||
kodimov.migrate_from_original(config_directory)
|
||||
except Exception as e:
|
||||
LOG('migrate_from_original failed: {}'.format(e))
|
||||
|
||||
# Network
|
||||
headers = {
|
||||
@@ -111,6 +119,9 @@ class Movistar(object):
|
||||
#LOG('access_token: {}'.format(self.account['access_token']))
|
||||
LOG('expired_access_token: {}'.format(self.expired_access_token))
|
||||
|
||||
if not self.account['access_token'] and network_login:
|
||||
self.try_network_login(account_number)
|
||||
|
||||
if not self.account['access_token']: return
|
||||
|
||||
# Account
|
||||
@@ -209,6 +220,21 @@ class Movistar(object):
|
||||
data = self.cache.load_file('searchs.json')
|
||||
self.search_list = json.loads(data) if data else []
|
||||
|
||||
def try_network_login(self, account_number='', force=False):
|
||||
# At most one attempt every 10 minutes, unless asked from the menu.
|
||||
stamp = self.cache.config_directory + 'network_login.stamp'
|
||||
if not force and os.path.exists(stamp) and time.time() - os.path.getmtime(stamp) < 600:
|
||||
return False
|
||||
self.cache.save_file('network_login.stamp', str(int(time.time())))
|
||||
success, reason = kodimov.network_login(self, kodimov.find_account_number(self, account_number))
|
||||
LOG('network login: {} {}'.format(success, reason))
|
||||
self.network_login_result = (success, reason)
|
||||
if success:
|
||||
self.account['access_token'] = self.cache.load_file('access_token.conf')
|
||||
self.expired_access_token = False
|
||||
self.cache.remove_file('tokens.json')
|
||||
return success
|
||||
|
||||
def get_token(self):
|
||||
data = {"accountNumber": self.account['id'],
|
||||
"userProfile": self.account['profile_id'],
|
||||
@@ -666,6 +692,7 @@ class Movistar(object):
|
||||
t['subscribed'] = self.is_subscribed_channel(c.get('tvProducts', []))
|
||||
t['info']['playcount'] = 1 # Set as watched
|
||||
t['uid'] = c.get('Uid')
|
||||
t['watermark'] = bool(c.get('QMWatermark'))
|
||||
if False and 'links' in c:
|
||||
for l in c['links']:
|
||||
if l['rel'] == 'watermarks':
|
||||
@@ -684,6 +711,8 @@ class Movistar(object):
|
||||
|
||||
res.append(t)
|
||||
|
||||
if self.hide_unplayable:
|
||||
res = [t for t in res if kodimov.is_playable(t)]
|
||||
return res
|
||||
|
||||
def get_channels_with_epg(self):
|
||||
@@ -1375,7 +1404,7 @@ class Movistar(object):
|
||||
t['logo'] = c['art']['icon']
|
||||
t['preset'] = c['dial']
|
||||
args = urlencode({'action': 'play', 'stype': 'tv', 'id': c['id'], 'url': c['url'], 'session_request': c['session_request']})
|
||||
t['stream'] = 'plugin://plugin.video.movistarplus/?' + args
|
||||
t['stream'] = 'plugin://plugin.video.kodimovplus/?' + args
|
||||
if 'cas_id' in c: t['cas_id'] = c['cas_id']
|
||||
res.append(t)
|
||||
return res
|
||||
@@ -1390,9 +1419,10 @@ class Movistar(object):
|
||||
item = '#EXTINF:-1 tvg-name="{name}" tvg-id="{id}" tvg-logo="{logo}" tvg-chno="{preset}" {catchup} group-title="Movistar+",{name}\n{stream}\n\n'.format(
|
||||
name=t['name'], id=t['id'], logo=t['logo'], preset=t['preset'], stream=url, catchup='catchup="default" catchup-source="{catchup-id}" catchup-days="7"')
|
||||
items.append(item)
|
||||
res = '#EXTM3U\n## Movistar+\n{}'.format(''.join(items))
|
||||
with io.open(filename, 'w', encoding='utf-8', newline='') as handle:
|
||||
handle.write(res)
|
||||
if self.add_extra_channels:
|
||||
items.append(kodimov.extra_channels_m3u8())
|
||||
res = '#EXTM3U\n## KodiMov+\n{}'.format(''.join(items))
|
||||
return kodimov.write_if_changed(filename, res)
|
||||
|
||||
def export_epg(self, date=None, duration=2):
|
||||
if sys.version_info[0] >= 3:
|
||||
@@ -1419,7 +1449,7 @@ class Movistar(object):
|
||||
url = 'https://grmovistar.imagenio.telefonica.net/asfe/rest/tvMediaURLs?tvProgram.id='+ program_id +'&svc=startover'
|
||||
session_request = '{"contentID":' + program_id +',"streamType":"CUTV"}'
|
||||
args = urlencode({'action': 'play', 'stype': 'u7d', 'id': program_id, 'url': url, 'session_request': session_request})
|
||||
t['stream'] = 'plugin://plugin.video.movistarplus/?' + args
|
||||
t['stream'] = 'plugin://plugin.video.kodimovplus/?' + args
|
||||
|
||||
if False and self.add_extra_info and id in ['HOLLYW', 'TCM', 'AMC', 'MV3', 'MV2', 'CPSER', 'FOXGE', 'TNT']:
|
||||
i = {'id': e['id'], 'show_id': e['show_id']}
|
||||
|
||||
+35
-4
@@ -662,6 +662,20 @@ def login():
|
||||
else:
|
||||
show_notification(addon.getLocalizedString(30166)) # Failed
|
||||
|
||||
def network_login():
|
||||
account_number = addon.getSetting('account_number')
|
||||
if not account_number and not m.cache.load_file('account.json') and not m.cache.load_file('tokens.json'):
|
||||
account_number = input_window(addon.getLocalizedString(30502)) # Account number
|
||||
if not account_number: return
|
||||
addon.setSetting('account_number', 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:
|
||||
@@ -686,6 +700,7 @@ def export_credentials():
|
||||
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
|
||||
@@ -750,11 +765,17 @@ def create_iptv_settings():
|
||||
if os.path.exists(filename):
|
||||
res = xbmcgui.Dialog().yesno(addon.getLocalizedString(30322), addon.getLocalizedString(30323))
|
||||
if res == False: return
|
||||
save_iptv_settings(filename, 'Movistarplus', 'movistarplus', epg_url)
|
||||
save_iptv_settings(filename, 'KodiMov+', 'kodimovplus', epg_url)
|
||||
export_epg_now()
|
||||
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():
|
||||
def report_progress(message):
|
||||
show_notification(addon.getLocalizedString(30314).format(message), xbmcgui.NOTIFICATION_INFO)
|
||||
@@ -816,6 +837,10 @@ def router(paramstring):
|
||||
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':
|
||||
@@ -880,8 +905,10 @@ def router(paramstring):
|
||||
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
|
||||
elif m.expired_access_token:
|
||||
show_notification(addon.getLocalizedString(30208))
|
||||
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
|
||||
@@ -902,8 +929,12 @@ def run():
|
||||
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)
|
||||
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'
|
||||
|
||||
|
||||
@@ -78,7 +78,9 @@ def mvs():
|
||||
if not mvs_o:
|
||||
from .movistar import Movistar
|
||||
reuse_devices = addon.getSettingBool('reuse_devices')
|
||||
mvs_o = Movistar(profile_dir, reuse_devices)
|
||||
mvs_o = Movistar(profile_dir, reuse_devices,
|
||||
network_login=addon.getSettingBool('network_login'),
|
||||
account_number=addon.getSetting('account_number'))
|
||||
return mvs_o
|
||||
|
||||
|
||||
@@ -263,7 +265,7 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
|
||||
HOST = '127.0.0.1'
|
||||
PORT = 57011
|
||||
PORT = 57031
|
||||
|
||||
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
daemon_threads = True
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
from __future__ import unicode_literals, absolute_import, division
|
||||
|
||||
import hmac
|
||||
import hashlib
|
||||
import time
|
||||
import random
|
||||
import string
|
||||
from urllib.parse import urlparse, urlsplit, parse_qs, urlencode, quote, unquote
|
||||
from .b64 import encode_base64, decode_base64
|
||||
from .log import LOG
|
||||
|
||||
def generate_nonce(length=24):
|
||||
characters = string.ascii_lowercase + string.digits
|
||||
nonce = ''.join(random.choice(characters) for _ in range(length))
|
||||
return encode_base64(nonce)
|
||||
|
||||
def create_signature(method, url, token, scheme):
|
||||
consumer_key = 'web_1.0.0'
|
||||
consumer_secret = 'Rk1Qc2RaREJ0M1h1MkUwRWtJRmlxWGtpOVpOZFlGN3I='
|
||||
|
||||
nonce = generate_nonce()
|
||||
#print('nonce', nonce)
|
||||
|
||||
timestamp = int(time.time())
|
||||
|
||||
parsed_url = urlsplit(url)
|
||||
path = parsed_url.netloc + parsed_url.path
|
||||
query = parse_qs(parsed_url.query, keep_blank_values=True)
|
||||
|
||||
options = { scheme: token, 'consumer_key': consumer_key, 'nonce': nonce,
|
||||
'signature_method': 'HMAC-SHA1', 'version': '1.0', 'timestamp': timestamp}
|
||||
for k, v in query.items():
|
||||
options[k] = quote(v[0])
|
||||
|
||||
query_string = "&".join("{}={}".format(key, value) for key, value in sorted(options.items()))
|
||||
#print(query_string)
|
||||
|
||||
text = '{}&{}&{}'.format(method.upper(), quote(path, safe=''), quote(query_string, safe=''))
|
||||
#print(text)
|
||||
|
||||
key = decode_base64(consumer_secret.encode('utf-8')).encode('utf-8')
|
||||
hashed = hmac.new(key, text.encode('utf-8'), hashlib.sha1).digest()
|
||||
signature = encode_base64(hashed)
|
||||
|
||||
res = 'consumer_key="{}",nonce="{}",signature_method="HMAC-SHA1",signature="{}",timestamp="{}",version="1.0"'.format(consumer_key, nonce, signature, timestamp)
|
||||
return encode_base64(res)
|
||||
|
||||
def get_auth(method, url, token, scheme='Bearer'):
|
||||
signature = create_signature(method, url, token, scheme)
|
||||
return 'OPPlus {}={},Signature={}'.format(scheme, token, signature)
|
||||
+11
-5
@@ -1,5 +1,10 @@
|
||||
<settings>
|
||||
<category label="30001">
|
||||
<setting id="network_login" type="bool" label="30505" default="true"/>
|
||||
<setting id="account_number" type="text" label="30502" default=""/>
|
||||
<setting id="hide_unplayable" type="bool" label="30506" default="true"/>
|
||||
<setting id="extra_channels" type="bool" label="30507" default="true"/>
|
||||
<setting type="sep"/>
|
||||
<setting id="store_credentials" type="bool" label="30050" default="true"/>
|
||||
<setting label="30017" type="bool" id="only_subscribed" default="true"/>
|
||||
<setting label="30020" type="bool" id="channels_with_epg" default="false"/>
|
||||
@@ -11,7 +16,7 @@
|
||||
<setting label="30019" type="action" id="is_settings" action="Addon.OpenSettings(inputstream.adaptive)" enable="System.HasAddon(inputstream.adaptive)" option="close"/>
|
||||
<!--
|
||||
<setting type="sep"/>
|
||||
<setting label="30450" type="action" id="donation" action="RunPlugin(plugin://plugin.video.movistarplus?action=show_donation_dialog)" option="close"/>
|
||||
<setting label="30450" type="action" id="donation" action="RunPlugin(plugin://plugin.video.kodimovplus?action=show_donation_dialog)" option="close"/>
|
||||
-->
|
||||
<setting id="proxy_address" type="text" label="" value="" default="" visible="false"/>
|
||||
</category>
|
||||
@@ -34,8 +39,8 @@
|
||||
<setting label="30301" type="action" action="InstallAddon(service.iptv.manager)" option="close" visible="!System.HasAddon(service.iptv.manager)"/>
|
||||
<setting label="30302" type="bool" id="iptv.enabled" default="true" visible="System.HasAddon(service.iptv.manager)" />
|
||||
<setting label="30303" type="action" action="Addon.OpenSettings(service.iptv.manager)" enable="eq(-1,true)" option="close" visible="System.HasAddon(service.iptv.manager)" subsetting="true"/>
|
||||
<setting id="iptv.channels_uri" default="plugin://plugin.video.movistarplus/iptv/channels" visible="false"/>
|
||||
<setting id="iptv.epg_uri" default="plugin://plugin.video.movistarplus/iptv/epg" visible="false"/>
|
||||
<setting id="iptv.channels_uri" default="plugin://plugin.video.kodimovplus/iptv/channels" visible="false"/>
|
||||
<setting id="iptv.epg_uri" default="plugin://plugin.video.kodimovplus/iptv/epg" visible="false"/>
|
||||
</category>
|
||||
-->
|
||||
|
||||
@@ -47,7 +52,7 @@
|
||||
</category>
|
||||
|
||||
<category label="30304">
|
||||
<setting label="30316" type="action" id="create_iptv_settings" action="RunPlugin(plugin://plugin.video.movistarplus/?action=create_iptv_settings)" option="close"/>
|
||||
<setting label="30316" type="action" id="create_iptv_settings" action="RunPlugin(plugin://plugin.video.kodimovplus/?action=create_iptv_settings)" option="close"/>
|
||||
|
||||
<setting type="sep"/>
|
||||
<!--
|
||||
@@ -56,10 +61,11 @@
|
||||
<setting type="sep"/>
|
||||
-->
|
||||
|
||||
<setting label="30508" type="bool" id="export_channels" default="true"/>
|
||||
<setting label="30305" type="bool" id="export_epg" default="false"/>
|
||||
<setting label="30315" type="number" id="export_days" default="2" enable="eq(-1,true)"/>
|
||||
<setting type="sep"/>
|
||||
<setting label="30312" type="action" id="export_epg_now" action="RunPlugin(plugin://plugin.video.movistarplus/?action=export_epg_now)" option="close"/>
|
||||
<setting label="30312" type="action" id="export_epg_now" action="RunPlugin(plugin://plugin.video.kodimovplus/?action=export_epg_now)" option="close"/>
|
||||
|
||||
<setting type="sep"/>
|
||||
<setting label="30308" type="action" action="Addon.OpenSettings(pvr.iptvsimple)" option="close" visible="System.HasAddon(pvr.iptvsimple)"/>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<width>1030</width>
|
||||
<height>70</height>
|
||||
<font>font20_title</font>
|
||||
<label>[B]$INFO[System.AddonTitle(plugin.video.movistarplus)][/B]</label>
|
||||
<label>[B]$INFO[System.AddonTitle(plugin.video.kodimovplus)][/B]</label>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<shadowcolor>FF000000</shadowcolor>
|
||||
|
||||
Reference in new issue
Block a user