diff options
Diffstat (limited to 'plugin.video.viaplay/resources/lib/vialib.py')
-rw-r--r-- | plugin.video.viaplay/resources/lib/vialib.py | 371 |
1 files changed, 0 insertions, 371 deletions
diff --git a/plugin.video.viaplay/resources/lib/vialib.py b/plugin.video.viaplay/resources/lib/vialib.py deleted file mode 100644 index 562ead3..0000000 --- a/plugin.video.viaplay/resources/lib/vialib.py +++ /dev/null @@ -1,371 +0,0 @@ -# -*- coding: utf-8 -*- -""" -A Kodi-agnostic library for Viaplay -""" -import codecs -import os -import cookielib -import calendar -import time -import re -import json -import uuid -import HTMLParser -from urllib import urlencode -from datetime import datetime, timedelta - -import iso8601 -import requests - - -class vialib(object): - def __init__(self, username, password, settings_folder, country, debug=False): - self.debug = debug - self.username = username - self.password = password - self.country = country - self.settings_folder = settings_folder - self.cookie_jar = cookielib.LWPCookieJar(os.path.join(self.settings_folder, 'cookie_file')) - self.tempdir = os.path.join(settings_folder, 'tmp') - if not os.path.exists(self.tempdir): - os.makedirs(self.tempdir) - self.deviceid_file = os.path.join(settings_folder, 'deviceId') - self.http_session = requests.Session() - self.base_url = 'https://content.viaplay.%s/pc-%s' % (self.country, self.country) - try: - self.cookie_jar.load(ignore_discard=True, ignore_expires=True) - except IOError: - pass - self.http_session.cookies = self.cookie_jar - - class LoginFailure(Exception): - def __init__(self, value): - self.value = value - - def __str__(self): - return repr(self.value) - - class AuthFailure(Exception): - def __init__(self, value): - self.value = value - - def __str__(self): - return repr(self.value) - - def log(self, string): - if self.debug: - try: - print '[vialib]: %s' % string - except UnicodeEncodeError: - # we can't anticipate everything in unicode they might throw at - # us, but we can handle a simple BOM - bom = unicode(codecs.BOM_UTF8, 'utf8') - print '[vialib]: %s' % string.replace(bom, '') - except: - pass - - def url_parser(self, url): - """Sometimes, Viaplay adds some weird templated stuff to the URL - we need to get rid of. Example: https://content.viaplay.se/androiddash-se/serier{?dtg}""" - template = re.search(r'\{.+?\}', url) - if template: - url = url.replace(template.group(), '') - - return url - - def make_request(self, url, method, payload=None, headers=None): - """Make an HTTP request. Return the JSON response in a dict.""" - self.log('URL: %s' % url) - parsed_url = self.url_parser(url) - if parsed_url != url: - url = parsed_url - self.log('Parsed URL: %s' % url) - if method == 'get': - req = self.http_session.get(url, params=payload, headers=headers, allow_redirects=False, verify=False) - else: - req = self.http_session.post(url, data=payload, headers=headers, allow_redirects=False, verify=False) - self.log('Response code: %s' % req.status_code) - self.log('Response: %s' % req.content) - self.cookie_jar.save(ignore_discard=True, ignore_expires=False) - - return json.loads(req.content) - - def login(self, username, password): - """Login to Viaplay. Return True/False based on the result.""" - url = 'https://login.viaplay.%s/api/login/v1' % self.country - payload = { - 'deviceKey': 'pc-%s' % self.country, - 'username': username, - 'password': password, - 'persistent': 'true' - } - data = self.make_request(url=url, method='get', payload=payload) - - return data['success'] - - def validate_session(self): - """Check if our session cookies are still valid.""" - url = 'https://login.viaplay.%s/api/persistentLogin/v1' % self.country - payload = { - 'deviceKey': 'pc-%s' % self.country - } - data = self.make_request(url=url, method='get', payload=payload) - - return data['success'] - - def verify_login_status(self, data): - """Check if we're logged in. If we're not, try to. - Raise errors as LoginFailure.""" - if 'MissingSessionCookieError' in data.values(): - if not self.validate_session(): - if not self.login(self.username, self.password): - raise self.LoginFailure('login failed') - - def get_video_urls(self, guid, pincode=None): - """Return a dict with the stream URL:s and available subtitle URL:s.""" - video_urls = {} - url = 'https://play.viaplay.%s/api/stream/byguid' % self.country - payload = { - 'deviceId': self.get_deviceid(), - 'deviceName': 'web', - 'deviceType': 'pc', - 'userAgent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0', - 'deviceKey': 'atv-%s' % self.country, - 'guid': guid, - 'pgPin': pincode - } - - data = self.make_request(url=url, method='get', payload=payload) - self.verify_login_status(data) - # we might have to request the stream again after logging in - if 'MissingSessionCookieError' in data.values(): - data = self.make_request(url=url, method='get', payload=payload) - self.check_for_subscription(data) - - for x in xrange(3): # retry if we get an encrypted playlist - if not 'viaplay:encryptedPlaylist' in data['_links'].keys(): - break - data = self.make_request(url=url, method='get', payload=payload) - if 'viaplay:media' in data['_links'].keys(): - manifest_url = data['_links']['viaplay:media']['href'] - elif 'viaplay:fallbackMedia' in data['_links'].keys(): - manifest_url = data['_links']['viaplay:fallbackMedia'][0]['href'] - elif 'viaplay:playlist' in data['_links'].keys(): - manifest_url = data['_links']['viaplay:playlist']['href'] - else: - self.log('Unable to retrieve stream URL.') - return False - - video_urls['manifest_url'] = manifest_url - video_urls['subtitle_urls'] = self.get_subtitle_urls(data) - - return video_urls - - def check_for_subscription(self, data): - """Check if the user is authorized to watch the requested stream. - Raise errors as AuthFailure.""" - try: - if data['success'] is False: - subscription_error = data['name'] - raise self.AuthFailure(subscription_error) - except KeyError: - # 'success' won't be in response if it's successful - pass - - def get_categories(self, input, method=None): - if method == 'data': - data = input - else: - data = self.make_request(url=input, method='get') - - if data['pageType'] == 'root': - categories = data['_links']['viaplay:sections'] - elif data['pageType'] == 'section': - categories = data['_links']['viaplay:categoryFilters'] - - return categories - - def get_sortings(self, url): - data = self.make_request(url=url, method='get') - try: - sorttypes = data['_links']['viaplay:sortings'] - except KeyError: - self.log('No sortings available for this category.') - return None - - return sorttypes - - def get_letters(self, url): - """Return a list of available letters for sorting in alphabetical order.""" - letters = [] - products = self.get_products(input=url, method='url') - for item in products: - letter = item['group'].encode('utf-8') - if letter not in letters: - letters.append(letter) - - return letters - - def get_products(self, input, method=None, filter_event=False): - """Return a list of all available products.""" - if method == 'data': - data = input - else: - data = self.make_request(url=input, method='get') - - if 'list' in data['type']: - products = data['_embedded']['viaplay:products'] - elif data['type'] == 'product': - products = data['_embedded']['viaplay:product'] - else: - products = self.get_products_block(data)['_embedded']['viaplay:products'] - - try: - # try adding additional info to sports dict - aproducts = [] - for product in products: - if product['type'] == 'sport': - product['event_date'] = self.parse_datetime(product['epg']['start'], localize=True) - product['event_status'] = self.get_event_status(product) - aproducts.append(product) - products = aproducts - except TypeError: - pass - - if filter_event: - fproducts = [] - for product in products: - for event in filter_event: - if event == product['event_status']: - fproducts.append(product) - products = fproducts - - return products - - def get_seasons(self, url): - """Return all available series seasons as a list.""" - seasons = [] - data = self.make_request(url=url, method='get') - - items = data['_embedded']['viaplay:blocks'] - for item in items: - if item['type'] == 'season-list': - seasons.append(item) - - return seasons - - def get_subtitle_urls(self, data): - """Return all subtitle SAMI URL:s in a list.""" - subtitle_urls = [] - try: - for subtitle in data['_links']['viaplay:sami']: - subtitle_urls.append(subtitle['href']) - except KeyError: - self.log('No subtitles found for guid: %s' % data['socket2']['productGuid']) - - return subtitle_urls - - def download_subtitles(self, suburls): - """Download the SAMI subtitles, decode the HTML entities and save to temp directory. - Return a list of the path to the downloaded subtitles.""" - subtitle_paths = [] - for suburl in suburls: - req = requests.get(suburl) - sami = req.content.decode('utf-8', 'ignore').strip() - htmlparser = HTMLParser.HTMLParser() - subtitle = htmlparser.unescape(sami).encode('utf-8') - subtitle = subtitle.replace(' ', ' ') # replace two spaces with one - - subpattern = re.search(r'[_]([a-z]+)', suburl) - if subpattern: - sublang = subpattern.group(1) - else: - sublang = 'unknown' - self.log('Unable to identify subtitle language.') - - path = os.path.join(self.tempdir, '%s.sami') % sublang - with open(path, 'w') as subfile: - subfile.write(subtitle) - subtitle_paths.append(path) - - return subtitle_paths - - def get_deviceid(self): - """"Read/write deviceId (generated UUID4) from/to file and return it.""" - try: - with open(self.deviceid_file, 'r') as deviceid: - return deviceid.read() - except IOError: - deviceid = str(uuid.uuid4()) - with open(self.deviceid_file, 'w') as idfile: - idfile.write(deviceid) - return deviceid - - def get_event_status(self, data): - """Return whether the event is live/upcoming/archive.""" - now = datetime.utcnow() - producttime_start = self.parse_datetime(data['epg']['start']) - producttime_start = producttime_start.replace(tzinfo=None) - if 'isLive' in data['system']['flags']: - status = 'live' - elif producttime_start >= now: - status = 'upcoming' - else: - status = 'archive' - - return status - - def get_sports_dates(self, url, event_date=None): - """Return the available sports dates. - Filter upcoming/previous dates with the event_date parameter.""" - dates = [] - data = self.make_request(url=url, method='get') - dates_data = data['_links']['viaplay:days'] - now = datetime.now() - - for date in dates_data: - date_obj = datetime(*(time.strptime(date['date'], '%Y-%m-%d')[0:6])) # http://forum.kodi.tv/showthread.php?tid=112916 - if event_date == 'upcoming': - if date_obj.date() > now.date(): - dates.append(date) - elif event_date == 'archive': - if date_obj.date() < now.date(): - dates.append(date) - else: - dates.append(date) - - return dates - - def get_next_page(self, data): - """Return the URL to the next page if the current page count is less than the total page count.""" - # first page is always (?) from viaplay:blocks - if data['type'] == 'page': - data = self.get_products_block(data) - if int(data['pageCount']) > int(data['currentPage']): - next_page_url = data['_links']['next']['href'] - return next_page_url - - def get_products_block(self, data): - """Get the viaplay:blocks containing all product information.""" - blocks = [] - blocks_data = data['_embedded']['viaplay:blocks'] - for block in blocks_data: - # example: https://content.viaplay.se/pc-se/sport - if 'viaplay:products' in block['_embedded'].keys(): - blocks.append(block) - return blocks[-1] # the last block is always (?) the right one - - def utc_to_local(self, utc_dt): - # get integer timestamp to avoid precision lost - timestamp = calendar.timegm(utc_dt.timetuple()) - local_dt = datetime.fromtimestamp(timestamp) - assert utc_dt.resolution >= timedelta(microseconds=1) - return local_dt.replace(microsecond=utc_dt.microsecond) - - def parse_datetime(self, iso8601_string, localize=False): - """Parse ISO8601 string to datetime object.""" - datetime_obj = iso8601.parse_date(iso8601_string) - if localize: - return self.utc_to_local(datetime_obj) - else: - return datetime_obj |