Import code
This commit is contained in:
1 parent
9b88a6ecde
commit
054d387e37
20 files changed
+2348
No files matched your search
Whitespace-only changes.
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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:])
|
||||
@@ -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")
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user