Import code

This commit is contained in:
Paco8 committed 2023-01-22 16:20:27 +01:00
1 parent 9b88a6ecde
commit 054d387e37
20 files changed
+2348

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
VERSION = $(shell ./get_version.py)
OUTPUT_DIR = plugin.video.movistarplus-$(VERSION)
OUTPUT_FILE = $(OUTPUT_DIR).zip
install:
-install -d $(OUTPUT_DIR)/resources/lib/
install -m 644 default.py service.py $(OUTPUT_DIR)/
install -m 644 resources/lib/*.py $(OUTPUT_DIR)/resources/lib/
install -m 644 resources/*.py $(OUTPUT_DIR)/resources/
install -m 644 addon.xml $(OUTPUT_DIR)/
install -m 644 *.png $(OUTPUT_DIR)/
install -m 644 resources/settings.xml $(OUTPUT_DIR)/resources/
-install -d $(OUTPUT_DIR)/resources/language/resource.language.en_gb/
-install -d $(OUTPUT_DIR)/resources/language/resource.language.es_es/
install -m 644 resources/language/resource.language.en_gb/strings.po $(OUTPUT_DIR)/resources/language/resource.language.en_gb/strings.po
install -m 644 resources/language/resource.language.es_es/strings.po $(OUTPUT_DIR)/resources/language/resource.language.es_es/strings.po
zip -9 -r $(OUTPUT_FILE) $(OUTPUT_DIR)/
clean:
-rm -rf $(OUTPUT_DIR)/
-rm $(OUTPUT_FILE)
-rm *.pyo *.pyc
-rm resources/lib/*.pyo resources/lib/*.pyc
+36
View File
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.movistarplus"
name="Movistar+"
version="0.3.11"
provider-name="Paco8">
<requires>
<!--- <import addon="xbmc.python" version="2.25.0"/> -->
<import addon="script.module.requests"/>
<import addon="script.module.inputstreamhelper" version="0.5.2" />
<import addon="script.module.pytz"/>
<import addon="script.module.ttml2ssa" version="0.1.16" />
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
</extension>
<extension point="xbmc.service" library="service.py" />
<extension point="xbmc.addon.metadata">
<summary lang="en_GB">Movistar+</summary>
<description lang="en_GB">Movistar+</description>
<description lang="en_GB">Watch live channels, recordings and video on demand content from Movistar+ in Kodi. Requires a subscription.</description>
<description lang="es_ES">Vea canales en directo, grabaciones, U7D y TV a la carta de Movistar+ en Kodi. Se necesita estar abonado.</description>
<disclaimer lang="en_GB">This addon is not officially commissioned/supported by Movistar+. All product names, logos, and trademarks mentioned in this project are property of their respective owners.</disclaimer>
<disclaimer lang="es_ES">Este addon no ha sido realizado ni apoyado por Movistar+. Todos los nombres de productos, logotipos y marcas registradas mencionados en este proyecto son propiedad de sus respectivos dueños.</disclaimer>
<platform>all</platform>
<license>LGPL-2.1-or-later</license>
<website></website>
<source></source>
<assets>
<icon>icon.png</icon>
<fanart></fanart>
<screenshot></screenshot>
<screenshot></screenshot>
</assets>
</extension>
</addon>
+9
View File
@@ -0,0 +1,9 @@
# encoding: utf-8
#
# SPDX-License-Identifier: LGPL-2.1-or-later
from resources.lib.plugin import run
run()
Executable
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env python
# encoding: utf-8
#
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import unicode_literals, absolute_import, division
import io
import re
with io.open('addon.xml', 'r', encoding='utf-8') as handle:
text = handle.read()
m = re.search(r'movistar.*?version="(.*?)"', text, re.DOTALL)
if m:
print(m.group(1))
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File
Whitespace-only changes.
@@ -0,0 +1,258 @@
# XBMC Media Center language file
# Addon Name: Orange
# Addon id: plugin.video.orange
# Addon version: 0.0.2
# Addon Provider: Paco8
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: XBMC-Addons\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-12-28 16:47+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE\n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgctxt "#30001"
msgid "Main"
msgstr ""
msgctxt "#30002"
msgid "Proxy"
msgstr ""
msgctxt "#30003"
msgid "Experimental"
msgstr ""
msgctxt "#30010"
msgid "DRM type"
msgstr ""
msgctxt "#30011"
msgid "Manifest alteration"
msgstr ""
msgctxt "#30012"
msgid "Use proxy for license"
msgstr ""
msgctxt "#30013"
msgid "Fix freezes in VOD content"
msgstr ""
msgctxt "#30016"
msgid "Proxy timeout (ms)"
msgstr ""
msgctxt "#30017"
msgid "Show only subscribed content"
msgstr ""
msgctxt "#30018"
msgid "Try to play HD in SD devices"
msgstr ""
msgctxt "#30019"
msgid "Configure InputStream Adaptive"
msgstr ""
msgctxt "#30020"
msgid "Add EPG info to TV channels"
msgstr ""
msgctxt "#30021"
msgid "Download extra info (like posters, actors...)"
msgstr ""
msgctxt "#30022"
msgid "Report sessions to servers"
msgstr ""
msgctxt "#30023"
msgid "Reuse devices"
msgstr ""
msgctxt "#30024"
msgid "Try to fix error 4027"
msgstr ""
msgctxt "#30101"
msgid "Menu"
msgstr ""
msgctxt "#30102"
msgid "My list"
msgstr ""
msgctxt "#30103"
msgid "Recordings"
msgstr ""
msgctxt "#30104"
msgid "Live TV"
msgstr ""
msgctxt "#30105"
msgid "Movies"
msgstr ""
msgctxt "#30106"
msgid "TV shows"
msgstr ""
msgctxt "#30107"
msgid "EPG"
msgstr ""
msgctxt "#30108"
msgid "Manage devices"
msgstr ""
msgctxt "#30109"
msgid "Next page"
msgstr ""
msgctxt "#30110"
msgid "Previous page"
msgstr ""
msgctxt "#30111"
msgid "VOD"
msgstr ""
msgctxt "#30112"
msgid "Search"
msgstr ""
msgctxt "#30113"
msgid "New search"
msgstr ""
msgctxt "#30114"
msgid "Delete search"
msgstr ""
msgctxt "#30115"
msgid "Delete all searchs"
msgstr ""
msgctxt "#30116"
msgid "Search term"
msgstr ""
msgctxt "#30117"
msgid "Search results"
msgstr ""
msgctxt "#30120"
msgid "Documentaries"
msgstr ""
msgctxt "#30121"
msgid "Kids"
msgstr ""
msgctxt "#30150"
msgid "Close session"
msgstr ""
msgctxt "#30151"
msgid "Delete device"
msgstr ""
msgctxt "#30152"
msgid "Select device"
msgstr ""
msgctxt "#30153"
msgid "New device"
msgstr ""
msgctxt "#30154"
msgid "Device name"
msgstr ""
msgctxt "#30160"
msgid "Accounts"
msgstr ""
msgctxt "#30161"
msgid "Add account"
msgstr ""
msgctxt "#30162"
msgid "Remove account"
msgstr ""
msgctxt "#30163"
msgid "Username"
msgstr ""
msgctxt "#30164"
msgid "Password"
msgstr ""
msgctxt "#30165"
msgid "Select account"
msgstr ""
msgctxt "#30166"
msgid "Login failed"
msgstr ""
msgctxt "#30167"
msgid "Edit account"
msgstr ""
msgctxt "#30170"
msgid "Play from the beginning"
msgstr ""
msgctxt "#30171"
msgid "Record program"
msgstr ""
msgctxt "#30172"
msgid "Recording scheduled"
msgstr ""
msgctxt "#30173"
msgid "Delete recording"
msgstr ""
msgctxt "#30174"
msgid "Do you want to delete {}?"
msgstr ""
msgctxt "#30180"
msgid "Profiles"
msgstr ""
msgctxt "#30181"
msgid "Login with key file"
msgstr ""
msgctxt "#30182"
msgid "Select the key file"
msgstr ""
msgctxt "#30200"
msgid "Error"
msgstr ""
msgctxt "#30201"
msgid "Information"
msgstr ""
msgctxt "#30202"
msgid "Inputstream.adaptive is not available"
msgstr ""
msgctxt "#30203"
msgid "Format not supported"
msgstr ""
@@ -0,0 +1,258 @@
# XBMC Media Center language file
# Addon Name: Orange
# Addon id: plugin.video.orange
# Addon version: 0.0.2
# Addon Provider: Paco8
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: XBMC-Addons\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-12-28 16:47+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE\n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgctxt "#30001"
msgid "Main"
msgstr "Principal"
msgctxt "#30002"
msgid "Proxy"
msgstr "Proxy"
msgctxt "#30003"
msgid "Experimental"
msgstr "Experimental"
msgctxt "#30010"
msgid "DRM type"
msgstr "Tipo de DRM"
msgctxt "#30011"
msgid "Manifest alteration"
msgstr "Modificar manifiesto"
msgctxt "#30012"
msgid "Use proxy for license"
msgstr "Usar proxy para la licencia"
msgctxt "#30013"
msgid "Fix freezes in VOD content"
msgstr "Solucionar parones en TV a la carta"
msgctxt "#30016"
msgid "Proxy timeout (ms)"
msgstr "Tiempo de espera (ms)"
msgctxt "#30017"
msgid "Show only subscribed content"
msgstr "Solo mostrar el contenido incluido en la suscripción"
msgctxt "#30018"
msgid "Try to play HD in SD devices"
msgstr "Intentar reproducir en HD en dispositivos SD"
msgctxt "#30019"
msgid "Configure InputStream Adaptive"
msgstr "Configurar InputStream Adaptive"
msgctxt "#30020"
msgid "Add EPG info to TV channels"
msgstr "Mostrar el programa en emisión en los canales de TV"
msgctxt "#30021"
msgid "Download extra info (like posters, actors...)"
msgstr "Descargar información extra (como posters, actores...)"
msgctxt "#30022"
msgid "Report sessions to servers"
msgstr "Informar de las sesiones a los servidores"
msgctxt "#30023"
msgid "Reuse devices"
msgstr "Reutilizar dispositivos"
msgctxt "#30024"
msgid "Try to fix error 4027"
msgstr "Intentar solucionar el error 4027"
msgctxt "#30101"
msgid "Menu"
msgstr "Menú"
msgctxt "#30102"
msgid "My list"
msgstr "Mi lista"
msgctxt "#30103"
msgid "Recordings"
msgstr "Grabaciones"
msgctxt "#30104"
msgid "Live TV"
msgstr "TV en directo"
msgctxt "#30105"
msgid "Movies"
msgstr "Películas"
msgctxt "#30106"
msgid "TV shows"
msgstr "Series"
msgctxt "#30107"
msgid "EPG"
msgstr "Guía de programación"
msgctxt "#30108"
msgid "Manage devices"
msgstr "Gestionar dispositivos"
msgctxt "#30109"
msgid "Next page"
msgstr "Página siguiente"
msgctxt "#30110"
msgid "Previous page"
msgstr "Página anterior"
msgctxt "#30111"
msgid "VOD"
msgstr "TV a la carta"
msgctxt "#30112"
msgid "Search"
msgstr "Búsqueda"
msgctxt "#30113"
msgid "New search"
msgstr "Nueva búsqueda"
msgctxt "#30114"
msgid "Delete search"
msgstr "Borrar búsqueda"
msgctxt "#30115"
msgid "Delete all searchs"
msgstr "Borrar todas las búsquedas"
msgctxt "#30116"
msgid "Search term"
msgstr "Término a buscar"
msgctxt "#30117"
msgid "Search results"
msgstr "Resultados de la búsqueda"
msgctxt "#30120"
msgid "Documentaries"
msgstr "Documentales"
msgctxt "#30121"
msgid "Kids"
msgstr "Infantiles"
msgctxt "#30150"
msgid "Close session"
msgstr "Cerrar sesión"
msgctxt "#30151"
msgid "Delete device"
msgstr "Eliminar dispositivo"
msgctxt "#30152"
msgid "Select device"
msgstr "Seleccionar dispositivo"
msgctxt "#30153"
msgid "New device"
msgstr "Nuevo dispositivo"
msgctxt "#30154"
msgid "Device name"
msgstr "Nombre para el dispositivo"
msgctxt "#30160"
msgid "Accounts"
msgstr "Cuentas"
msgctxt "#30161"
msgid "Add account"
msgstr "Añadir cuenta"
msgctxt "#30162"
msgid "Remove account"
msgstr "Borrar cuenta"
msgctxt "#30163"
msgid "Username"
msgstr "Usuario"
msgctxt "#30164"
msgid "Password"
msgstr "Clave"
msgctxt "#30165"
msgid "Select account"
msgstr "Seleccionar cuenta"
msgctxt "#30166"
msgid "Login failed"
msgstr "Error al iniciar sesión"
msgctxt "#30167"
msgid "Edit account"
msgstr "Editar cuenta"
msgctxt "#30170"
msgid "Play from the beginning"
msgstr "Ver desde el principio"
msgctxt "#30171"
msgid "Record program"
msgstr "Grabar programa"
msgctxt "#30172"
msgid "Recording scheduled"
msgstr "Grabación programada"
msgctxt "#30173"
msgid "Delete recording"
msgstr "Borrar grabación"
msgctxt "#30174"
msgid "Do you want to delete {}?"
msgstr "¿Quieres borrar {}?"
msgctxt "#30180"
msgid "Profiles"
msgstr "Perfiles"
msgctxt "#30181"
msgid "Login with key file"
msgstr "Iniciar sesión con fichero key"
msgctxt "#30182"
msgid "Select the key file"
msgstr "Selecciona el fichero key"
msgctxt "#30200"
msgid "Error"
msgstr "Error"
msgctxt "#30201"
msgid "Information"
msgstr "Información"
msgctxt "#30202"
msgid "Inputstream.adaptive is not available"
msgstr "Inputstream.adaptive no está disponible"
msgctxt "#30203"
msgid "Format not supported"
msgstr "Formato no soportado"
View File
Whitespace-only changes.
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
try: # Kodi >= 19
from xbmcvfs import translatePath
except ImportError: # Kodi 18
from xbmc import translatePath
import os
import xbmcaddon
from .log import LOG
addon = xbmcaddon.Addon()
profile_dir = translatePath(addon.getAddonInfo('profile'))
LOG("Profile directory: {}".format(profile_dir))
if not os.path.exists(profile_dir):
LOG('Creating {}'.format(profile_dir))
os.makedirs(profile_dir)
+50
View File
@@ -0,0 +1,50 @@
# encoding: utf-8
#
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import unicode_literals, absolute_import, division
import io
import os
import time
import glob
import sys
class Cache(object):
config_directory = ''
def __init__(self, config_directory):
self.config_directory = config_directory
def load_file(self, filename):
filename = self.config_directory + filename
if not os.path.exists(filename): return None
with io.open(filename, 'r', encoding='utf-8') as handle:
return handle.read()
def save_file(self, filename, data):
if sys.version_info[0] < 3:
if not isinstance(data, unicode):
data = unicode(data, 'utf-8')
with io.open(self.config_directory + filename, 'w', encoding='utf-8', newline='') as handle:
handle.write(data)
def load(self, filename, cache_minutes = 24*60):
filename = self.config_directory + filename
if os.path.exists(filename) and (time.time() - os.path.getmtime(filename) < cache_minutes*60):
with io.open(filename, 'r', encoding='utf-8') as handle:
return handle.read()
return None
def remove_file(self, filename):
filename = self.config_directory + filename
if os.path.exists(filename):
os.remove(filename)
def clear_config(self):
types = ('*.conf', '*.json')
files = []
for ext in types:
files.extend(glob.glob(self.config_directory + ext))
for filename in files:
os.remove(filename)
+55
View File
@@ -0,0 +1,55 @@
# encoding: utf-8
#
# SPDX-License-Identifier: LGPL-2.1-or-later
import sys
import xbmc
import xbmcgui
import xbmcplugin
from .log import LOG
from .addon import addon
def handle():
try:
return int(sys.argv[1])
except:
return -1
def input_window(heading, text = '', hidden = False):
res = None
keyboard = xbmc.Keyboard(text)
keyboard.setHeading(heading)
keyboard.setHiddenInput(hidden)
keyboard.doModal()
if (keyboard.isConfirmed()):
res = keyboard.getText()
del keyboard
return res
def open_folder(name, content_type = 'videos'):
LOG('handle: {}'.format(handle()))
xbmcplugin.setPluginCategory(handle(), name)
xbmcplugin.setContent(handle(), content_type)
def close_folder(updateListing=False, cacheToDisc=True):
xbmcplugin.endOfDirectory(handle(), updateListing=updateListing, cacheToDisc=cacheToDisc)
def add_menu_option(title, url, context_menu = None, info = None, art = None):
list_item = xbmcgui.ListItem(label=title)
if not info:
info = {'title': title, 'plot': '[B][/B]'}
if not art:
art = {'icon': addon.getAddonInfo('icon'), 'poster': addon.getAddonInfo('icon')}
list_item.setInfo('video', info)
list_item.setArt(art)
if context_menu:
list_item.addContextMenuItems(context_menu)
xbmcplugin.addDirectoryItem(handle(), url, list_item, True)
def show_notification(message, icon = xbmcgui.NOTIFICATION_ERROR):
if icon == xbmcgui.NOTIFICATION_ERROR:
heading = addon.getLocalizedString(30200)
else:
heading = addon.getLocalizedString(30201)
icon = addon.getAddonInfo('icon')
xbmcgui.Dialog().notification(heading, message, icon, 5000)
+16
View File
@@ -0,0 +1,16 @@
# encoding: utf-8
#
# SPDX-License-Identifier: LGPL-2.1-or-later
import json
def LOG(message):
try:
import xbmc
import xbmcaddon
xbmc.log('[{}] {}'.format(xbmcaddon.Addon().getAddonInfo('id'), message), xbmc.LOGDEBUG)
except:
print(message)
def print_json(data):
LOG(json.dumps(data, indent=4))
+743
View File
@@ -0,0 +1,743 @@
# encoding: utf-8
#
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import unicode_literals, absolute_import, division
import sys
import base64
import json
import requests
import io
import os
import time
import re
from .log import LOG, print_json
from .network import Network
from .cache import Cache
from .timeconv import *
class Movistar(object):
account = {'username': '', 'password': '',
'device_id': '',
'id': None,
'pid': None,
'encoded_user': '',
'profile_id': '0',
'platform': '',
'access_token': '',
'session_token': '',
'ssp_token': '',
'demarcation': 0}
def __init__(self, config_directory, reuse_devices=False):
self.logged = False
# Network
headers = {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'es-ES,es;q=0.9',
'Origin': 'https://ver.movistarplus.es',
'Referer': 'https://ver.movistarplus.es/',
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0'
}
self.net = Network()
self.net.headers = headers
# Cache
self.cache = Cache(config_directory)
# Access token
self.load_key_file() # Default access_token
content = self.cache.load_file('access_token.conf')
if content:
self.account['access_token'] = content
#LOG('access_token: {}'.format(self.account['access_token']))
if not self.account['access_token']: return
# Endpoints
self.endpoints = self.get_endpoints()
# Account
data = None
content = self.cache.load('account.json')
if content:
data = json.loads(content)
if not data or 'ofertas' not in data:
data = self.authenticate()
if not data or 'ofertas' not in data: return
self.cache.save_file('account.json', json.dumps(data, ensure_ascii=False))
self.account['id'] = data['ofertas'][0]['accountNumber']
self.account['pid'] = data['ofertas'][0]['cod_persona']
self.account['encoded_user'] = data['cod_usuario_cifrado']
self.account['platform'] = data['ofertas'][0]['@id_perfil']
# Profile ID
content = self.cache.load_file('profile_id.conf')
if content:
self.account['profile_id'] = content
# Device ID
self.account['device_id'] = self.cache.load_file('device_id.conf')
if not reuse_devices:
LOG('not reusing devices')
if not self.account['device_id']:
self.account['device_id'] = self.new_device_id()
self.cache.save_file('device_id.conf', self.account['device_id'])
# Check if device is registered
device_list = self.get_devices()
found_device = False
for device in device_list:
if device['id'] == self.account['device_id']:
found_device = True
break
if not found_device:
LOG('device not found, trying to register device')
self.register_device()
device_list = self.get_devices(use_cache=False) # To store the updated list in cache
else:
LOG('reusing devices')
# Check if device is registered
device_list = self.get_devices()
found_device = False
wp_device = None
for device in device_list:
if not wp_device and device['type_code'] == 'WP':
wp_device = device['id']
if device['id'] == self.account['device_id']:
found_device = True
break
if not found_device:
if wp_device:
LOG('device not found, using {}'.format(wp_device))
self.account['device_id'] = wp_device
else:
LOG('device not found, registering new device')
self.account['device_id'] = self.new_device_id()
self.register_device()
device_list = self.get_devices(use_cache=False) # To store the updated list in cache
self.cache.save_file('device_id.conf', self.account['device_id'])
LOG('device_id: {}'. format(self.account['device_id']))
# Tokens
content = self.cache.load('tokens.json', 3)
if content:
data = json.loads(content)
else:
LOG('Loading tokens')
data = self.get_token()
#print_json(data)
if 'accessToken' in data:
self.cache.save_file('tokens.json', json.dumps(data, ensure_ascii=False))
self.cache.save_file('access_token.conf', data['accessToken'])
self.account['session_token'] = ''
self.account['ssp_token'] = ''
self.entitlements = {}
if 'accessToken' in data:
self.account['access_token'] = data['accessToken']
self.account['session_token'] = data['token']
self.account['ssp_token'] = data['sspToken']
self.account['demarcation'] = data['demarcation']
self.entitlements['activePurchases'] = data['activePurchases']
self.entitlements['partners'] = data['partners']
self.entitlements['activePackages'] = data['activePackages']
self.entitlements['vodSubscription'] = data['vodSubscription'].split(',')
self.entitlements['linearSubscription'] = data['linearSubscription'].split(',')
self.entitlements['suscripcion'] = data['suscripcion']
#print_json(self.entitlements)
self.logged = True
def install_key_file(self, filename):
import shutil
if sys.version_info[0] > 2:
filename = bytes(filename, 'utf-8')
shutil.copyfile(filename, self.cache.config_directory + 'auth.key')
def load_key_file(self):
content = self.cache.load_file('auth.key')
if content:
data = json.loads(content)
data = json.loads(data['data'])
self.account['access_token'] = data['response']['access_token']
def get_token(self):
data = {"accountNumber": self.account['id'],
"userProfile": self.account['profile_id'],
"streamMiscellanea":"HTTPS",
"deviceType":"WP_OTT",
"deviceManufacturerProduct":"Firefox",
"streamDRM":"Widevine",
"streamFormat":"DASH",
}
headers = self.net.headers.copy()
headers['Content-Type'] = 'application/json'
headers['X-Movistarplus-Ui'] = '2.36.30'
headers['X-Movistarplus-Os'] = 'Linux88'
headers['X-Movistarplus-Deviceid'] = self.account['device_id']
headers['Authorization'] = 'Bearer ' + self.account['access_token']
#print_json(data)
#print_json(headers)
url = self.endpoints['initdata'].format(deviceType='webplayer', DEVICEID=self.account['device_id'])
response = self.net.session.post(url, data=json.dumps(data), headers=headers)
content = response.content.decode('utf-8')
#LOG('get_token response: {}'.format(content))
try:
d = json.loads(content)
except:
d = {'error': content}
return d
def clear_session(self):
headers = self.net.headers.copy()
headers['Access-Control-Request-Method'] = 'POST'
headers['Access-Control-Request-Headers'] = 'content-type,x-hzid'
url = self.endpoints['setUpStream'].format(PID=self.account['pid'], deviceCode='WP_OTT', PLAYREADYID=self.account['device_id'])
response = self.net.session.options(url, headers=headers)
content = response.content.decode('utf-8')
return content
def open_session(self, data, session_token, session_id = None):
headers = self.net.headers.copy()
headers['Content-Type'] = 'application/json'
headers['X-Hzid'] = session_token
url = self.endpoints['setUpStream'].format(PID=self.account['id'], deviceCode='WP_OTT', PLAYREADYID=self.account['device_id'])
if session_id != None:
url += '/' + session_id
#LOG('open_session: url: {}'.format(url))
#LOG('open_session: data: {}'.format(data))
response = self.net.session.post(url, data=data, headers=headers)
content = response.content.decode('utf-8')
try:
d = json.loads(content)
except:
d = None
#LOG("**** open_session response: {}".format(d))
return d
def login(self):
headers = self.net.headers.copy()
headers['x-movistarplus-deviceid'] = self.account['device_id']
headers['x-movistarplus-ui'] = '2.36.30'
headers['x-movistarplus-os'] = 'Linux88'
if sys.version_info[0] < 3:
p = base64.b64decode(bytes(self.account['password']))
else:
p = base64.b64decode(bytes(self.account['password'], encoding='ascii')).decode('ascii')
data = {
'grant_type': 'password',
'deviceClass': 'webplayer',
'username': self.account['username'],
'password': p,
'captchaResult': '',
}
url = self.endpoints['token']
response = self.net.session.post(url, headers=headers, data=data)
print(response.content)
def authenticate(self):
headers = self.net.headers.copy()
headers['Content-Type'] = 'application/x-www-form-urlencoded'
#headers['x-movistarplus-deviceid'] = self.account['device_id']
headers['x-movistarplus-ui'] = '2.36.30'
headers['x-movistarplus-os'] = 'Linux88'
headers['Authorization'] = 'Bearer ' + self.account['access_token']
url = self.endpoints['autenticacion_tk'] + '?_=' + str(int(time.time()*1000))
#LOG('authenticate: headers: {}'.format(headers))
data = self.net.load_data(url, headers)
#LOG('authenticate: data: {}'.format(data))
return data
def load_service_directory(self):
url = 'https://homeservicedirectory.dof6.com/VoD/vod.svc/webplayer/environments/prod/ws-directory?format=json'
return self.net.load_data(url)
def get_endpoints(self):
endpoints = {}
content = self.cache.load('endpoints.json')
if content:
data = json.loads(content)
else:
data = self.load_service_directory()
self.cache.save_file('endpoints.json', json.dumps(data, ensure_ascii=False))
for services in data['services']['service']:
if isinstance(services['endpoint'], list):
for endpoint in services['endpoint']:
endpoints[endpoint['@name']] = endpoint['@address']
else:
endpoint = services['endpoint']
endpoints[endpoint['@name']] = endpoint['@address']
return endpoints
def change_device(self, id):
self.account['device_id'] = id
self.cache.save_file('device_id.conf', self.account['device_id'])
self.cache.remove_file('tokens.json')
def get_devices(self, use_cache=True):
content = self.cache.load('devices.json', 3)
if use_cache and content:
data = json.loads(content)
else:
headers = self.net.headers.copy()
headers['Authorization'] = 'Bearer ' + self.account['access_token']
url = self.endpoints['obtenerdipositivos'].format(ACCOUNTNUMBER=self.account['id'])
data = self.net.load_data(url, headers)
self.cache.save_file('devices.json', json.dumps(data, ensure_ascii=False))
#print_json(data)
res = []
for d in data:
if d['Id'] != '-':
dev = {}
dev['id'] = d['Id']
dev['name'] = d['Name']
dev['type'] = d['DeviceType']
dev['type_code'] = d['DeviceTypeCode']
dev['playing'] = d['ContentPlaying']
dev['reg_date'] = isodate2str(d['RegistrationDate'])
dev['in_ssp'] = d['IsInSsp']
res.append(dev)
return res
def register_device(self):
headers = self.net.headers.copy()
headers['Content-Type'] = 'application/json'
headers['x-movistarplus-deviceid'] = self.account['device_id']
headers['x-movistarplus-ui'] = '2.36.30'
headers['x-movistarplus-os'] = 'Linux88'
headers['Authorization'] = 'Bearer ' + self.account['access_token']
url = self.endpoints['activacion_dispositivo_cuenta_tk'].format(ACCOUNTNUMBER=self.account['id'], DEVICEID=self.account['device_id'])
response = self.net.session.post(url, headers=headers)
content = response.content.decode('utf-8')
return content
def unregister_device(self):
return self.delete_device(self.account['device_id'])
def new_device_id(self):
headers = self.net.headers.copy()
headers['Content-Type'] = 'application/json'
headers['x-movistarplus-ui'] = '2.36.30'
headers['x-movistarplus-os'] = 'Linux88'
headers['Authorization'] = 'Bearer ' + self.account['access_token']
url = 'https://auth.dof6.com/movistarplus/webplayer/accounts/'+ self.account['id'] + '/devices/?qspVersion=ssp'
response = self.net.session.post(url, headers=headers)
content = response.content.decode('utf-8')
return content.replace('"', '')
def delete_device(self, device_id):
headers = self.net.headers.copy()
headers['Content-Type'] = 'application/json'
headers['Authorization'] = 'bearer ' + self.account['access_token']
url = self.endpoints['eliminardipositivo'].format(ACCOUNTNUMBER=self.account['id'], DEVICEID=device_id)
response = self.net.session.delete(url, headers=headers)
content = response.content.decode('utf-8')
return content
def rename_device(self, device_id, name):
headers = self.net.headers.copy()
headers['Authorization'] = 'bearer ' + self.account['access_token']
url = self.endpoints['nombrardispositivo'].format(ACCOUNTNUMBER=self.account['id'], DEVICEID=device_id)
response = self.net.session.put(url, headers=headers, json=name)
content = response.content.decode('utf-8')
return content
def delete_session_id(self, session_token, id = '0'):
headers = self.net.headers.copy()
headers['Content-Type'] = 'text/plain;charset=UTF-8'
data = '{"X-HZId":"' + session_token +'","X-Content-Type":"application/json","X-Operation":"DELETE"}'
url = self.endpoints['tearDownStream'].format(PID=self.account['pid'], deviceCode='WP_OTT', PLAYREADYID=self.account['device_id'], SessionID=id)
response = self.net.session.post(url, headers=headers, data=data)
content = response.content.decode('utf-8')
return content
"""
def delete_last_session(self):
token = self.cache.load_file('session_token.conf')
if token:
self.delete_session0(token)
os.remove(self.cache.config_directory + 'session_token.conf')
"""
def delete_session(self, device_id = None):
if device_id == None: device_id = self.account['device_id']
headers = self.net.headers.copy()
headers['Content-Type'] = 'application/json'
headers['Authorization'] = 'bearer ' + self.account['access_token']
url = self.endpoints['cerrarsesiondispositivo'].format(ACCOUNTNUMBER=self.account['id'], DEVICEID=device_id)
response = self.net.session.delete(url, headers=headers)
content = response.content.decode('utf-8')
return content
def get_profiles(self):
headers = self.net.headers.copy()
headers['X-HZId'] = self.account['session_token']
url = self.endpoints['listaperfiles']
data = self.net.load_data(url, headers=headers)
res = []
for d in data['items']:
p = {}
p['id'] = d['id']
p['name'] = d['name']
p['for_kids'] = d['isForKids']
p['type'] = d['typeID']
p['image_id'] = d['imageID']
res.append(p)
return res
def change_profile(self, id):
self.account['profile_id'] = id
self.cache.save_file('profile_id.conf', self.account['profile_id'])
self.cache.remove_file('tokens.json')
def get_epg(self):
content = self.cache.load('epg2.json', 6*60)
if content:
data = json.loads(content)
else:
today = datetime.today()
str_now = today.strftime('%Y-%m-%dT%H:00:00')
url = self.endpoints['rejilla'].format(deviceType='webplayer', profile=self.account['platform'], UTCDATETIME=str_now, DURATION=2, CHANNELS='', NETWORK='movistarplus', mdrm='true', demarcation=self.account['demarcation'])
data = self.net.load_data(url)
self.cache.save_file('epg2.json', json.dumps(data, ensure_ascii=False))
epg = {}
for ch in data:
id = ch[0]['Canal']['CodCadenaTv']
if not id in epg: epg[id] = []
for p in ch:
pr = {}
pr['start'] = int(p['FechaHoraInicio'])
pr['end'] = int(p['FechaHoraFin'])
pr['start_str'] = timestamp2str(pr['start'])
pr['end_str'] = timestamp2str(pr['end'])
pr['desc1'] = p['Titulo']
pr['desc2'] = ''
if 'TituloHorLinea2' in p:
pr['desc2'] = p['TituloHorLinea2']
epg[id].append(pr)
return epg
def find_program_epg(self, epg, id, timestamp):
found = False
c = 0
programs = []
for p in epg[id]:
#print(p)
if (p['start'] <= timestamp) and (timestamp <= p['end']):
found = True
if found:
programs.append(p)
c += 1
if (c > 5): break;
return programs
def colorize_title(self, title):
s = title['info']['title']
stype = title.get('stream_type')
if stype == 'u7d': s += ' (U7D)'
elif stype == 'rec': s += ' (REC)'
if title['subscribed'] == True:
color1 = 'yellow'
color2 = 'red'
else:
color1 = 'gray'
color2 = 'gray'
s = '[COLOR gray]' + s +'[/COLOR]'
if title.get('desc1', '') != '':
s += ' - [COLOR {}]{}[/COLOR]'.format(color1, title['desc1'])
if title.get('desc2', '') != '':
s += ' - [COLOR {}]{}[/COLOR]'.format(color2, title['desc2'])
return s
def add_epg_info(self, channels, epg, timestamp):
for ch in channels:
programs = self.find_program_epg(epg, ch['id'], timestamp)
ch['info']['plot'] = ''
for i in range(len(programs)):
desc1 = programs[i]['desc1']
desc2 = programs[i]['desc2']
if i == 0:
ch['desc1'] = desc1
ch['desc2'] = desc2
ch['info']['plot'] += "[B]{}[/B] {} {}\n".format(programs[i]['start_str'], desc1, desc2)
def is_subscribed_channel(self, products):
for e in self.entitlements['activePackages']:
if e['name'] in products:
return True
for e in self.entitlements['activePurchases']:
if e['name'] in products:
return True
return False
def is_subscribed_vod(self, products):
for p in products:
if p['Nombre'] in self.entitlements['vodSubscription']:
return True
return False
def get_channels(self, add_epg_info = False):
if add_epg_info:
url = self.endpoints['guiaTV'].format(deviceType='webplayer', profile=self.account['platform'], preOffset=0, postOffset=0, mdrm='true', demarcation=self.account['demarcation'])
data = self.net.load_data(url)
else:
content = self.cache.load('channels2.json')
if content:
data = json.loads(content)
else:
url = self.endpoints['canales'].format(deviceType='webplayer', profile=self.account['platform'], mdrm='true', demarcation=self.account['demarcation'])
data = self.net.load_data(url)
self.cache.save_file('channels2.json', json.dumps(data, ensure_ascii=False))
res = []
for c in data:
t = {}
t['info'] = {}
t['art'] = {}
t['type'] = 'movie'
t['stream_type'] = 'tv'
t['info']['mediatype'] = 'movie'
t['info']['title'] = str(c['Dial']) +'. ' + c['Nombre']
t['id'] = c['CodCadenaTv']
if add_epg_info:
t['desc1'] = c['Nombre']
t['desc2'] = ''
t['dial'] = c['Dial']
t['url'] = c['PuntoReproduccion']
t['art']['icon'] = c['Logo']
t['art']['thumb'] = c['Logo']
t['art']['poster'] = c['Logo']
t['session_request'] = '{"contentID":"'+ t['id'] +'", "streamType":"CHN"}'
t['subscribed'] = self.is_subscribed_channel(c['Productos'])
t['info']['playcount'] = 1 # Set as watched
# epg
if add_epg_info:
program = c['Pases'][0]
t['desc1'] = program['Titulo']
if 'TituloHorLinea2' in program:
t['desc2'] = program['TituloHorLinea2']
t['start'] = int(program['FechaHoraInicio'])
t['end'] = int(program['FechaHoraFin'])
t['start_str'] = timestamp2str(t['start'])
t['end_str'] = timestamp2str(t['end'])
t['info']['plot'] = '[B]{}-{}[/B] {}\n{}'.format(t['start_str'], t['end_str'], t['desc1'], t['desc2'])
res.append(t)
return res
def get_channels_with_epg(self):
channels = self.get_channels()
epg = self.get_epg()
import time
now = int(time.time() * 1000)
self.add_epg_info(channels, epg, now)
return channels
def download_list(self, url, use_hz = False):
headers = self.net.headers.copy()
headers['Accept'] = 'application/vnd.miviewtv.v1+json'
headers['Content-Type'] = 'application/json'
if use_hz:
headers['Authorization'] = 'Bearer ' + self.account['access_token']
headers['X-Hzid'] = self.account['session_token']
return self.net.load_data(url, headers)
def get_wishlist_url(self):
url = self.endpoints['favoritos'].format(
deviceType='webplayer', DIGITALPLUSUSERIDC=self.account['encoded_user'], PROFILE=self.account['platform'],
ACCOUNTNUMBER=self.account['id'], idsOnly='false', start=1, end=30, mdrm='true', demarcation=self.account['demarcation'])
#url += '&filter=AD-SINX&topic=CN'
url += '&_='+ str(int(time.time()*1000))
return url
def get_recordings_url(self):
url = self.endpoints['grabaciones'].format(
deviceType='webplayer', DIGITALPLUSUSERIDC=self.account['encoded_user'], PROFILE=self.account['platform'],
idsOnly='false', start=1, end=30, mdrm='true', demarcation=self.account['demarcation'])
url += '&state=Completed&_='+ str(int(time.time()*1000))
return url
def get_title(self, data):
t = {}
t['info'] = {}
t['art'] = {}
ed = data['DatosEditoriales']
t['id'] = ed['Id']
t['info']['title'] = ed['Titulo']
t['art']['poster'] = ed['Imagen'].replace('ywcatalogov', 'dispficha')
t['art']['thumb'] = t['art']['poster']
t['info']['genre'] = ed['GeneroComAntena']
if ed['TipoContenido'] in ['Individual', 'Episodio']:
t['type'] = 'movie'
t['stream_type'] = 'vod'
t['info']['mediatype'] = 'movie'
t['info']['duration'] = ed['DuracionEnSegundos']
t['url'] = ''
t['session_request'] = ''
if len(data['VodItems']) > 0:
video = data['VodItems'][0]
t['subscribed'] = self.is_subscribed_vod(video['Productos'])
if not 'UrlVideo' in video: return None
t['url'] = video['UrlVideo']
if video['AssetType'] == 'VOD':
t['session_request'] = '{"contentID":' + str(t['id']) + ',"drmMediaID":"' + video['CasId'] +'", "streamType":"AST"}'
elif video['AssetType'] == 'U7D':
t['stream_type'] = 'u7d'
t['session_request'] = '{"contentID":' + str(t['id']) + ', "streamType":"CUTV"}'
elif video['AssetType'] == 'NPVR':
t['stream_type'] = 'rec'
t['session_request'] = '{"contentID":' + str(t['id']) + ', "streamType":"NPVR"}'
if ed['TipoContenido'] == 'Serie':
t['type'] = 'series'
t['info']['mediatype'] = 'tvshow'
t['subscribed'] = self.is_subscribed_vod(data['Productos'])
if ed['TipoContenido'] == 'Temporada':
t['type'] = 'season'
t['info']['mediatype'] = 'season'
t['subscribed'] = self.is_subscribed_vod(data['Productos'])
return t
def get_list(self, data):
res = []
for d in data:
#print_json(d)
t = self.get_title(d)
if t and t['id'] != '':
res.append(t)
return res
def get_seasons(self, id):
url = self.endpoints['ficha'].format(deviceType='webplayer', id=id, profile=self.account['platform'], mediatype='FOTOV', version='7.1', mode='GLOBAL', catalog='', channels='', state='', mdrm='true', demarcation=self.account['demarcation'], legacyBoxOffice='')
data = self.net.load_data(url)
#print_json(data)
res = []
c = 1
for d in data['Temporadas']:
t = {}
t['id'] = d['Id']
t['info'] = {}
t['art'] = {}
t['type'] = 'season'
t['info']['mediatype'] = 'season'
t['info']['title'] = d['Titulo']
t['info']['tvshowtitle'] = data['TituloSerie']
t['info']['plot'] = data['Descripcion']
t['info']['season'] = c
t['art']['poster'] = t['art']['thumb'] = data['Imagen']
t['subscribed'] = self.is_subscribed_vod(data['Productos'])
c += 1
res.append(t)
return res
def get_episodes(self, id):
url = self.endpoints['ficha'].format(deviceType='webplayer', id=str(id), profile=self.account['platform'], mediatype='FOTOV', version='7.1', mode='GLOBAL', catalog='', channels='', state='', mdrm='true', demarcation=self.account['demarcation'], legacyBoxOffice='')
#print(url)
data = self.net.load_data(url)
#print_json(data)
res = []
for d in data['Episodios']:
ed = d['DatosEditoriales']
t = {}
t['id'] = ed['Id']
t['info'] = {}
t['art'] = {}
t['type'] = 'movie'
t['info']['mediatype'] = 'episode'
t['stream_type'] = 'vod'
t['info']['title'] = ed['TituloEpisodio']
t['info']['episode'] = ed['NumeroEpisodio']
t['info']['duration'] = ed['DuracionEnSegundos']
t['art']['thumb'] = ed['Imagen']
for im in ed['Imagenes']:
if im['id'] == 'horizontal': t['art']['thumb'] = im['uri']
m = re.search(r'T(\d+)', ed['Temporada'])
if m: t['info']['season'] = m.group(1)
t['url'] = ''
t['session_request'] = ''
if len(d['VodItems']) > 0:
video = d['VodItems'][0]
t['subscribed'] = self.is_subscribed_vod(video['Productos'])
t['url'] = video['UrlVideo']
if video['AssetType'] == 'VOD':
t['session_request'] = '{"contentID":' + str(t['id']) + ',"drmMediaID":"' + video['CasId'] +'", "streamType":"AST"}'
elif video['AssetType'] == 'U7D':
t['stream_type'] = 'u7d'
t['session_request'] = '{"contentID":' + str(t['id']) + ', "streamType":"CUTV"}'
res.append(t)
return res
def get_u7d_url(self, url):
headers = self.net.headers.copy()
headers['Content-Type'] = 'application/json'
headers['X-HZId'] = self.account['session_token']
data = self.net.load_data(url, headers)
return data
def get_subtitles(self, manifest_url):
base_url = os.path.dirname(manifest_url)
content = self.net.load_url(manifest_url)
rx = r'<AdaptationSet id="\d+" contentType="text" mimeType="application\/ttml\+xml" lang="(.*?)">.*?<BaseURL>(.*?)<\/BaseURL>'
matches = re.findall(rx, content, flags=re.MULTILINE | re.DOTALL)
res = []
for m in matches:
sub = {}
sub['lang'] = m[0]
sub['filename'] = m[1]
sub['url'] = base_url +'/' + sub['filename']
if sub['lang'] == 'srd': sub['lang'] = 'es [CC]'
if sub['lang'] == 'qaa': sub['lang'] = 'en [VO]'
res.append(sub)
return res
def download_subtitles(self, sublist):
output_dir = self.cache.config_directory + 'subtitles'
#LOG('output_dir: {}'.format(output_dir))
if not os.path.exists(output_dir):
os.makedirs(output_dir)
res = []
for s in sublist:
filename = output_dir + os.sep + s['lang'] + '.ttml'
LOG('filename: {}'.format(filename))
content = self.net.load_url(s['url'])
with io.open(filename, 'w', encoding='utf-8', newline='') as handle:
handle.write(content)
res.append(filename)
return res
def get_vod_list_url(self, cat='movies'):
#sort = 'FD'
sort = 'MA'
url = self.endpoints['consultar'].format(deviceType='webplayer', profile=self.account['platform'], sort=sort, start=1, end=50, mdrm='true', demarcation=self.account['demarcation'])
filter = '&filter=AC-OM,AC-MA,MA-GBLCICLO59,TD-CUP,RG-SINCAT,AC-PREPUB,' + self.entitlements['suscripcion']
url += '&mode=VOD' + filter
if cat == 'tvshows':
url += '&topic=SR&showSeries=series'
elif cat == 'movies':
url += '&topic=CN'
elif cat == 'documentaries':
url += '&topic=DC'
elif cat == 'kids':
url += '&topic=IN'
#LOG('vod url: {}'.format(url))
return url
+38
View File
@@ -0,0 +1,38 @@
# encoding: utf-8
#
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import unicode_literals, absolute_import, division
import json
import requests
from .log import LOG, print_json
class Network(object):
headers = {}
session = requests.Session()
def load_url(self, url, headers = None):
if headers is None: headers = self.headers
response = self.session.get(url, headers=headers, allow_redirects=True)
content = response.content.decode('utf-8')
return content
def load_data(self, url, headers = None):
content = self.load_url(url, headers)
try:
data = json.loads(content)
return data
except:
return {'error': content}
def post_data(self, url, data, headers = None):
if headers is None: headers = self.headers
#LOG('post_data: {}'.format(data))
#print_json(headers)
response = self.session.post(url, headers=headers, data=data)
content = response.content.decode('utf-8')
#LOG(content)
data = json.loads(content)
return data
+486
View File
@@ -0,0 +1,486 @@
# encoding: utf-8
#
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import unicode_literals, absolute_import, division
import sys
import json
import xbmc
import xbmcgui
import xbmcplugin
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
import xbmcaddon
import os.path
from .log import LOG
from .movistar import *
from .addon import *
from .gui import *
# Get the plugin url in plugin:// notation.
_url = sys.argv[0]
# Get the plugin handle as an integer number.
_handle = int(sys.argv[1])
try: # Kodi >= 19
from xbmcvfs import translatePath
except ImportError: # Kodi 18
from xbmc import translatePath
def get_url(**kwargs):
return '{0}?{1}'.format(_url, urlencode(kwargs))
def play(params):
LOG('play - params: {}'.format(params))
if not 'url' in params: return
url = params['url']
channel_id = params['id']
stype = params['stype']
token = m.account['ssp_token']
session_token = m.account['session_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()
"""
if addon.getSettingBool('open_session') or stype == 'vod':
d = m.open_session(params['session_request'], session_token)
LOG('Open session: d: {}'.format(d))
if d['resultCode'] != 0:
dialog = xbmcgui.Dialog().notification('Error', d['resultText'], xbmcgui.NOTIFICATION_ERROR, 5000)
return
if 'resultData' in d and 'cToken' in d['resultData']:
token = d['resultData']['cToken']
if not addon.getSettingBool('open_session'):
d = m.delete_session()
LOG('Delete session: d: {}'.format(d))
LOG("*** TEST token: {} session_token: {}".format(token, session_token))
#url = 'https://vamos-nmp-movistarplus.emisiondof6.com/hls/vamos.isml/index-02-spa.m3u8'
if stype in ['u7d', 'rec']:
d = m.get_u7d_url(url)
url = d['url']
proxy = m.cache.load_file('proxy.conf')
if addon.getSettingBool('manifest_modification') and proxy:
url = '{}/?manifest={}'.format(proxy, url)
LOG("*** TEST url: {}".format(url))
headers = 'Content-Type=application/octet-stream'
headers += '&User-Agent=Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0'
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&Connection=keep-alive'
headers += '&Host=wv-ottlic-f3.imagenio.telefonica.net'
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)
play_item.setProperty('inputstream.adaptive.manifest_type', 'mpd')
#play_item.setProperty('inputstream.adaptive.manifest_type', 'hls')
if format(addon.getSetting('drm_type')) == 'Playready':
play_item.setProperty('inputstream.adaptive.license_type', 'com.microsoft.playready')
else:
play_item.setProperty('inputstream.adaptive.license_type', 'com.widevine.alpha')
if addon.getSettingBool('use_proxy_for_license') and proxy:
request_id = str(int(time.time()*1000))
license_url = '{}/license?token={}&stype={}&session_request={}&session_token={}&request_id={}||R{{SSM}}|'.format(proxy, quote_plus(token), stype, quote_plus(params['session_request']), quote_plus(session_token), request_id)
LOG('license_url: {}'.format(license_url))
play_item.setProperty('inputstream.adaptive.license_key', license_url)
else:
play_item.setProperty('inputstream.adaptive.license_key', '{}|{}&nv-authorizations={}|R{{SSM}}|'.format(license_url, headers, token))
play_item.setProperty('inputstream.adaptive.stream_headers', 'User-Agent=Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0')
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 sys.version_info[0] < 3:
play_item.setProperty('inputstreamaddon', 'inputstream.adaptive')
else:
play_item.setProperty('inputstream', 'inputstream.adaptive')
play_item.setMimeType('application/dash+xml')
play_item.setContentLookup(False)
# 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 = profile_dir + os.sep +'subtitles/'
if not os.path.exists(subfolder):
os.makedirs(subfolder)
sublist = m.get_subtitles(url)
LOG('sublist: {}'.format(sublist))
subpaths = []
for sub in sublist:
filename = subfolder + sub['lang']
LOG('Converting {}'.format(sub['filename']))
response = m.net.session.get(sub['url'], allow_redirects=True)
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)
xbmcplugin.setResolvedUrl(_handle, True, listitem=play_item)
player.running = True
monitor = xbmc.Monitor()
while not monitor.abortRequested() and player.running:
if monitor.waitForAbort(1):
break
LOG('Exiting play')
def add_videos(category, ctype, videos, ref=None, url_next=None, url_prev=None):
#LOG("*** TEST 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("*** TEST t: {}".format(t))
if 'subscribed' in t:
if addon.getSettingBool('only_subscribed') and t['subscribed'] == False: continue
t['info']['title'] = m.colorize_title(t)
title_name = t['info']['title'].encode('utf-8')
if not 'type' in t: continue
if t['type'] == 'movie':
list_item = xbmcgui.ListItem(label = title_name)
#if t['stream_type'] == 'tv':
# action = get_url(action='epg', id=t['id'], name=t['name'].encode('utf-8'))
# LOG('action: {}'.format(action))
# list_item.addContextMenuItems([('EPG', "RunPlugin(" + action + ")")])
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'])
url = get_url(action='play', id=t['id'], url=t['url'], session_request=t['session_request'], stype=t['stream_type'])
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'])
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'])
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, name=category), list_item, True)
xbmcplugin.endOfDirectory(_handle)
def list_devices(params):
LOG('list_devices: params: {}'.format(params))
devices = m.get_devices(False)
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]'
select_action = get_url(action='profiles', id=p['id'], name='select')
add_menu_option(name, select_action)
close_folder(cacheToDisc=False)
def list_epg(params):
LOG('list_epg: {}'.format(params))
if 'id' in params:
epg = m.get_epg()
xbmcplugin.setPluginCategory(_handle, params['name'])
xbmcplugin.setContent(_handle, 'files')
for p in epg[params['id']]:
name = '[B]' + p['start_str'] + '[/B] ' + p['desc1']
if p['desc2']: name += ' - '+ p['desc2']
plot = name +"\n" + p['desc2']
list_item = xbmcgui.ListItem(label=name)
list_item.setInfo('video', {'name':name, 'plot':plot})
xbmcplugin.addDirectoryItem(_handle, '', list_item, False)
xbmcplugin.endOfDirectory(_handle)
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 = 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):
data = m.download_list(url, use_hz=True)
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')
def list_vod():
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
close_folder()
def login_with_key():
filename = xbmcgui.Dialog().browseSingle(1, addon.getLocalizedString(30182), '', '.key')
if filename:
m.install_key_file(filename)
m.cache.remove_file('access_token.conf')
m.cache.remove_file('account.json')
m.cache.remove_file('device_id.conf')
m.cache.remove_file('devices.json')
m.cache.remove_file('profile_id.conf')
m.cache.remove_file('tokens.json')
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'] == 'logout':
pass
elif params['action'] == 'epg':
list_epg(params)
elif params['action'] == 'wishlist':
# Wishlist
listing_hz(addon.getLocalizedString(30102), m.get_wishlist_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'])
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()
else:
# Main
open_folder(addon.getLocalizedString(30101)) # Menu
xbmcplugin.setContent(_handle, 'files')
if m.logged:
add_menu_option(addon.getLocalizedString(30104), get_url(action='tv')) # TV
add_menu_option(addon.getLocalizedString(30107), get_url(action='epg')) # EGP
add_menu_option(addon.getLocalizedString(30102), get_url(action='wishlist')) # My list
add_menu_option(addon.getLocalizedString(30103), get_url(action='recordings')) # Recordings
add_menu_option(addon.getLocalizedString(30111), get_url(action='vod')) # VOD
add_menu_option(addon.getLocalizedString(30180), get_url(action='profiles')) # Profiles
add_menu_option(addon.getLocalizedString(30108), get_url(action='devices')) # Devices
#add_menu_option(addon.getLocalizedString(30150), get_url(action='logout')) # Close session
add_menu_option(addon.getLocalizedString(30181), get_url(action='login_with_key')) # Login with key
close_folder(cacheToDisc=False)
class Player(xbmc.Player):
running = False
def onAVStarted(self):
LOG('Playback started')
def onPlayBackPaused(self):
LOG('Playback paused')
def onPlayBackResumed(self):
LOG('Playback resumed')
def onPlayBackEnded(self):
LOG('Playback ended')
self.close_session()
def onPlayBackStopped(self):
LOG('Playback stopped')
self.close_session()
def close_session(self):
if self.running:
LOG('Closing session')
d = m.delete_session()
LOG('delete_session: {}'.format(d))
self.running = False
def run():
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)
global player
player = Player()
# 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
router(sys.argv[2][1:])
+257
View File
@@ -0,0 +1,257 @@
#!/usr/bin/env python
# encoding: utf-8
#
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import unicode_literals, absolute_import, division
try: # Python 3
from http.server import BaseHTTPRequestHandler, HTTPServer
except ImportError: # Python 2
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
try: # Python 3
from socketserver import TCPServer, ThreadingMixIn
except ImportError: # Python 2
from SocketServer import TCPServer, ThreadingMixIn
try: # Python 3
from urllib.parse import unquote, quote_plus
except ImportError: # Python 2
from urllib import unquote, quote_plus
try: # Python 3
from urllib.parse import parse_qsl
except: # Python 2
from urlparse import parse_qsl
"""
try: # Python 3
from SocketServer import ThreadingTCPServer
except ImportError: # Python 2
from socketserver import ThreadingTCPServer
"""
import os
import re
import json
import requests
import threading
import socket
import base64
from contextlib import closing
import xbmcaddon
from .log import LOG
from .addon import profile_dir
from ttml2ssa import Ttml2SsaAddon
ttml = Ttml2SsaAddon()
session = requests.Session()
session.headers.update({'user-agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0'})
previous_token = ''
reregister_needed = False
def is_ascii(s):
try:
return s.isascii()
except:
return all(ord(c) < 128 for c in s)
def try_load_json(text):
try:
return json.loads(text)
except:
return None
mvs_o = None
def mvs():
global mvs_o
if not mvs_o:
from .movistar import Movistar
reuse_devices = xbmcaddon.Addon().getSettingBool('reuse_devices')
mvs_o = Movistar(profile_dir, reuse_devices)
return mvs_o
class RequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
"""Handle http get requests, used for manifest"""
path = self.path # Path with parameters received from request e.g. "/manifest?id=234324"
print('HTTP GET Request received to {}'.format(path))
try:
#if True:
if 'manifest' in path:
pos = path.find('=')
url = path[pos+1:]
LOG('url: {}'.format(url))
response = session.get(url, allow_redirects=True)
LOG('headers: {}'.format(response.headers))
baseurl = os.path.dirname(response.url)
LOG('baseurl: {}'.format(baseurl))
content = response.content.decode('utf-8')
pos = content.find('<Period id')
if pos > -1:
content = content[:pos] + '<BaseURL>' + baseurl + '/</BaseURL>' + content[pos:]
content = content.replace('lang="qaa"', 'lang="eng"')
if not xbmcaddon.Addon().getSettingBool('use_ttml2ssa'):
LOG(self.headers.get('Host'))
my_address = 'http://' + self.headers.get('Host')
LOG('my_address: {}'.format(my_address))
content = content.replace('mimeType="application/ttml+xml"', 'mimeType="text/vtt"')
content = re.sub(r'<BaseURL>(.*?)\.ttml<\/BaseURL>', r'<BaseURL>{}/?subtitle={}/\1.ttml</BaseURL>'.format(my_address, baseurl), content)
#LOG('content: {}'.format(content))
manifest_data = content
self.send_response(200)
#self.send_header('Content-type', 'application/xml')
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(manifest_data.encode('utf-8'))
elif 'subtitle' in path:
pos = path.find('=')
url = path[pos+1:]
LOG('subtitle url: {}'.format(url))
response = session.get(url, allow_redirects=True)
content = response.content
ttml.parse_ttml_from_string(content)
sub_data = ttml.generate_vtt()
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(sub_data.encode('utf-8'))
elif 'fragment' in path:
pos = path.find('=')
url = path[pos+1:]
LOG('url: {}'.format(url))
self.send_response(301)
self.send_header('Location', url)
self.end_headers()
else:
self.send_response(404)
self.end_headers()
except Exception:
self.send_response(500)
self.end_headers()
def do_POST(self):
"""Handle http post requests, used for license"""
path = self.path # Path with parameters received from request e.g. "/license?id=234324"
print('HTTP POST Request received to {}'.format(path))
if '/license' not in path:
self.send_response(404)
self.end_headers()
return
try:
#if True:
pos = path.find('?')
path = path[pos+1:]
params = dict(parse_qsl(path))
LOG('params: {}'.format(params))
length = int(self.headers.get('content-length', 0))
isa_data = self.rfile.read(length)
LOG('isa_data length: {}'.format(length))
LOG('isa_data: {}'.format(base64.b64encode(isa_data)))
token = params['token']
LOG('token: {}'.format(token))
global previous_token, reregister_needed
while True:
LOG('reregister_needed: {}'.format(reregister_needed))
if reregister_needed:
mvs().unregister_device()
mvs().register_device()
if previous_token == token and params['stype'] == 'vod':
d = mvs().open_session(params['session_request'], params['session_token'])
LOG('open_session: d: {}'.format(d))
if 'resultData' in d and 'cToken' in d['resultData']:
token = d['resultData']['cToken']
LOG('new token: {}'.format(token))
previous_token = token
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0',
'Accept': '*/*',
'Accept-Language': 'es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3',
'Referer': 'https://ver.movistarplus.es/',
'nv-authorizations': quote_plus(token),
'Origin': 'https://ver.movistarplus.es',
}
LOG('headers: {}'.format(headers))
url = 'https://wv-ottlic-f3.imagenio.telefonica.net/TFAESP/wvls/contentlicenseservice/v1/licenses'
response = session.post(url, data=isa_data, headers=headers)
license_data = response.content
LOG('license response length: {}'.format(len(license_data)))
if is_ascii(license_data):
LOG('license response: {}'.format(license_data))
d = try_load_json(license_data)
if d and 'errorCode' in d:
from .gui import show_notification
show_notification('Error {}: {}'.format(d['errorCode'], d['message']))
if d['errorCode'] == 4027:
if not reregister_needed and xbmcaddon.Addon().getSettingBool('reregister'):
reregister_needed = True
else:
break
else:
LOG('license response: {}'.format(base64.b64encode(license_data)))
break
self.send_response(response.status_code)
self.end_headers()
self.wfile.write(license_data)
except Exception:
self.send_response(500)
self.end_headers()
HOST = '127.0.0.1'
PORT = 57011
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
daemon_threads = True
class Proxy(object):
started = False
def check_port(self, port=0, default=False):
try:
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
s.bind((HOST, port))
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
return s.getsockname()[1]
except:
return default
def start(self):
if self.started:
return
port = self.check_port(PORT)
if not port:
port = self.check_port(0)
LOG('port: {}'.format(port))
self._server = ThreadedHTTPServer((HOST, port), RequestHandler)
self._server.allow_reuse_address = True
self._httpd_thread = threading.Thread(target=self._server.serve_forever)
self._httpd_thread.start()
self.proxy_address = 'http://{}:{}'.format(HOST, port)
self.started = True
LOG("Proxy Started: {}:{}".format(HOST, port))
def stop(self):
if not self.started:
return
self._server.shutdown()
self._server.server_close()
self._server.socket.close()
self._httpd_thread.join()
self.started = False
LOG("Proxy: Stopped")
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env python
# encoding: utf-8
#
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import unicode_literals, absolute_import, division
from datetime import datetime, timedelta
def timestamp2str(timestamp):
time = datetime.fromtimestamp(timestamp / 1000)
return time.strftime("%H:%M")
def isodate2str(iso_str):
iso_str = iso_str[0:19]
try:
dt = datetime.strptime(iso_str, "%Y-%m-%dT%H:%M:%S")
except:
return iso_str
try:
import pytz
utc_tz = pytz.timezone('UTC')
dt = utc_tz.localize(dt)
dt = dt.astimezone(pytz.timezone('Europe/Madrid'))
except:
pass
dt = dt.strftime('%d/%m/%Y %H:%M:%S')
return dt
+21
View File
@@ -0,0 +1,21 @@
<settings>
<category label="30001">
<setting label="30017" type="bool" id="only_subscribed" default="true"/>
<setting label="30020" type="bool" id="channels_with_epg" default="true"/>
<!-- <setting label="30021" type="bool" id="add_extra_info" default="true"/> -->
<setting id="use_ttml2ssa" type="bool" label="$ADDON[script.module.ttml2ssa 32202]" default="false"/>
<setting id="ssa_settings" type="action" label="$ADDON[script.module.ttml2ssa 32201]" option="close" action="Addon.OpenSettings(script.module.ttml2ssa)" enable="eq(-1,true)"/>
<setting label="30019" type="action" id="is_settings" action="Addon.OpenSettings(inputstream.adaptive)" enable="System.HasAddon(inputstream.adaptive)" option="close"/>
<setting id="reuse_devices" type="bool" label="30023" default="false"/>
<setting id="open_session" type="bool" label="30022" default="true"/>
</category>
<category label="30002">
<setting label="30011" type="bool" id="manifest_modification" default="true"/>
<setting label="30012" type="bool" id="use_proxy_for_license" default="true"/>
<setting id="reregister" type="bool" label="30024" default="false" enable="eq(-1,true)"/>
</category>
<category label="30003">
<setting label="30010" type="labelenum" id="drm_type" values="Widevine|Playready"/>
</category>
</settings>
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import xbmc
from resources.lib.proxy import Proxy
from resources.lib.addon import *
from resources.lib.log import LOG
from resources.lib.cache import Cache
if __name__ == '__main__':
LOG('Service started')
proxy = Proxy()
proxy.start()
LOG('proxy_address: {}'.format(proxy.proxy_address))
cache = Cache(profile_dir)
cache.save_file('proxy.conf', proxy.proxy_address)
monitor = xbmc.Monitor()
while not monitor.abortRequested():
if monitor.waitForAbort(1):
break
proxy.stop()