diff options
author | Eric <eracknaphobia@hotmail.com> | 2017-06-07 14:37:23 -0400 |
---|---|---|
committer | Eric <eracknaphobia@hotmail.com> | 2017-06-07 14:37:23 -0400 |
commit | 7fe9b1533ed06526a7d66a770c87f6927a550e02 (patch) | |
tree | 535b325f5c1196a5f6cbaad51134cfec795d15fd /plugin.video.psvue | |
parent | 5f55ca760019d7f296c60e8035fe58d553d24423 (diff) |
[plugin.video.psvue] 2017.6.7
Diffstat (limited to 'plugin.video.psvue')
-rw-r--r-- | plugin.video.psvue/addon.xml | 6 | ||||
-rw-r--r-- | plugin.video.psvue/main.py | 24 | ||||
-rw-r--r-- | plugin.video.psvue/resources/language/resource.language.en_gb/strings.po | 25 | ||||
-rw-r--r-- | plugin.video.psvue/resources/lib/ps_vue.py (renamed from plugin.video.psvue/resources/lib/globals.py) | 315 | ||||
-rw-r--r-- | plugin.video.psvue/resources/lib/sony.py | 334 | ||||
-rw-r--r-- | plugin.video.psvue/resources/settings.xml | 3 |
6 files changed, 389 insertions, 318 deletions
diff --git a/plugin.video.psvue/addon.xml b/plugin.video.psvue/addon.xml index 56e57af..6c79e1f 100644 --- a/plugin.video.psvue/addon.xml +++ b/plugin.video.psvue/addon.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<addon id="plugin.video.psvue" name="PS Vue" version="2017.6.1" provider-name="eracknaphobia"> +<addon id="plugin.video.psvue" name="PS Vue" version="2017.6.7" provider-name="eracknaphobia"> <requires> <import addon="xbmc.python" version="2.25.0"/> <import addon="script.module.requests" version="2.9.1"/> @@ -13,8 +13,8 @@ <description lang="en_GB">PlayStation™Vue is a TV service that streams live TV, movies, and sports on a variety of your favorite devices without a cable or satellite subscription. With powerful features that allow you to save thousands of hours of your favorite shows without recording conflicts, and Premium channels that can be purchased individually or with a multi-channel plan, TV has never been the same. No annual contracts, no conflicts, no problems.</description> <disclaimer lang="en_GB">Requires a valid subscription to PS Vue which is currently only available in the US</disclaimer> <news> -- Fix for special characters in username and password -- Improved error notification descriptions +- Fixed streams not launching +- Code Clean Up </news> <language>en</language> <platform>all</platform> diff --git a/plugin.video.psvue/main.py b/plugin.video.psvue/main.py index b204a62..f49787d 100644 --- a/plugin.video.psvue/main.py +++ b/plugin.video.psvue/main.py @@ -1,4 +1,4 @@ -from resources.lib.globals import * +from resources.lib.ps_vue import * params=get_params() url=None @@ -23,13 +23,17 @@ try: except: pass -if mode != 900: check_reqpayload() -if mode == None: - if ADDON.getSetting(id='default_profile') == '': - get_profiles() - - main_menu() +sony = SONY() +if ADDON.getSetting(id='last_auth') != '': + last_auth = stringToDate(ADDON.getSetting(id='last_auth'), "%Y-%m-%dT%H:%M:%S.%fZ") + if (datetime.now() - last_auth).total_seconds() >= 5400: sony.check_auth() +else: + sony.check_auth() + +if mode == None: + if ADDON.getSetting(id='default_profile') == '': sony.get_profiles() + main_menu() elif mode == 50: timeline() @@ -59,7 +63,7 @@ elif mode == 700: featured() elif mode == 800: - get_profiles() + sony.get_profiles() main_menu() elif mode == 900: @@ -69,7 +73,7 @@ elif mode == 998: sys.exit() elif mode == 999: - logout() + sony.logout() main_menu() @@ -78,4 +82,4 @@ if mode != None and mode != 800: elif mode == 800: xbmcplugin.endOfDirectory(addon_handle, updateListing=True) else: - xbmcplugin.endOfDirectory(addon_handle)
\ No newline at end of file + xbmcplugin.endOfDirectory(addon_handle) diff --git a/plugin.video.psvue/resources/language/resource.language.en_gb/strings.po b/plugin.video.psvue/resources/language/resource.language.en_gb/strings.po index b2a483d..a636d27 100644 --- a/plugin.video.psvue/resources/language/resource.language.en_gb/strings.po +++ b/plugin.video.psvue/resources/language/resource.language.en_gb/strings.po @@ -88,3 +88,28 @@ msgstr "" msgctxt "#30204" msgid "Please enter your verification code" msgstr "" + +msgctxt "#30205" +msgid "Profile Not Found" +msgstr "" + +msgctxt "#30206" +msgid "Your profile list could not be retrieved" +msgstr "" + +msgctxt "#30207" +msgid "Authorization Failed" +msgstr "" + +msgctxt "#30208" +msgid "Could not retrieve grant code" +msgstr "" + +msgctxt "#30209" +msgid "Could not retrieve the reqpayload" +msgstr "" + + +msgctxt "#30210" +msgid "Choose Profile" +msgstr "" diff --git a/plugin.video.psvue/resources/lib/globals.py b/plugin.video.psvue/resources/lib/ps_vue.py index 5a1221a..333d652 100644 --- a/plugin.video.psvue/resources/lib/globals.py +++ b/plugin.video.psvue/resources/lib/ps_vue.py @@ -1,11 +1,12 @@ import sys, os import xbmc, xbmcplugin, xbmcgui, xbmcaddon import random -import cookielib, urllib, urlparse +import cookielib, urllib import json import requests import time, calendar from datetime import date, datetime, timedelta +from sony import SONY def main_menu(): @@ -144,12 +145,12 @@ def list_episode(show): title = show['display_episode_title'] airing_id = str(show['airings'][0]['airing_id']) airing_date = show['airing_date'] - airing_date = stringToDate(airing_date, "%Y-%m-%dT%H:%M:%S.000Z") + airing_date = stringToDate(airing_date, "%Y-%m-%dT%H:%M:%S.%fZ") airing_date = UTCToLocal(airing_date) broadcast_date = '' if 'broadcast_date' in show: broadcast_date = show['broadcast_date'] - broadcast_date = stringToDate(broadcast_date, "%Y-%m-%dT%H:%M:%S.000Z") + broadcast_date = stringToDate(broadcast_date, "%Y-%m-%dT%H:%M:%S.%fZ") broadcast_date = UTCToLocal(broadcast_date) genre = '' @@ -236,8 +237,6 @@ def get_stream(url): r = requests.get(url, headers=headers, cookies=load_cookies(), verify=VERIFY) json_source = r.json() - # save_cookies(r.cookies) - stream_url = json_source['body']['video'] stream_url = stream_url + '|User-Agent=Dalvik/2.1.0 (Linux; U; Android 6.0.1 Build/MOB31H)&Cookie=reqPayload=' + urllib.quote('"' + ADDON.getSetting(id='reqPayload') + '"') @@ -256,213 +255,6 @@ def get_stream(url): ''' -def put_resume_time(): - """ - PUT https://sentv-user-action.totsuko.tv/sentv_user_action/ws/v2/watch_history HTTP/1.1 - Host: sentv-user-action.totsuko.tv - Connection: keep-alive - Content-Length: 247 - Accept: */* - reqPayload: redacted - User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; Build/MOB31H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/44.0.2403.119 Safari/537.36 - Origin: https://themis.dl.playstation.net - Content-Type: application/json - Referer: https://themis.dl.playstation.net/themis/zartan/2.2.2b/ - Accept-Encoding: gzip, deflate - Accept-Language: en-US - X-Requested-With: com.snei.vue.android - - {"series_id":21188,"program_id":1320750,"channel_id":25039,"tms_id":"EP005544655496","airing_id":14626670,"last_watch_date":"2017-04-28T00:40:43Z","last_timecode":"01:46:29","start_timecode":"00:00:00:00","fully_watched":false,"stream_type":"dvr"} - """ - url = 'https://sentv-user-action.totsuko.tv/sentv_user_action/ws/v2/watch_history' - headers = {"Accept": "*/*", - "Content-type": "application/json", - "Origin": "https://themis.dl.playstation.net", - "Accept-Language": "en-US", - "Referer": "https://themis.dl.playstation.net/themis/zartan/2.2.2b/", - "Accept-Encoding": "gzip, deflate", - "User-Agent": UA_ANDROID, - "Connection": "Keep-Alive", - 'reqPayload': ADDON.getSetting(id='reqPayload'), - 'X-Requested-With': 'com.snei.vue.android' - } - - payload = '{"series_id":21188,' - payload += '"program_id":1320750,' - payload += '"channel_id":25039,' - payload += '"tms_id":"EP005544655496",' - payload += '"airing_id":14626670,' - payload += '"last_watch_date":"2017-04-28T00:40:43Z",' - payload += '"last_timecode":"01:46:29",' - payload += '"start_timecode":"00:00:00:00",' - payload += '"fully_watched":false,' - payload += '"stream_type":"dvr"}' - - r = requests.put(url, headers=headers, data=payload, verify=VERIFY) - - -def login(): - global USERNAME - if USERNAME == '': - dialog = xbmcgui.Dialog() - USERNAME = dialog.input(LOCAL_STRING(30202), type=xbmcgui.INPUT_ALPHANUM) - if USERNAME != '': - ADDON.setSetting(id='username', value=USERNAME) - else: - sys.exit() - - global PASSWORD - if PASSWORD == '': - dialog = xbmcgui.Dialog() - PASSWORD = dialog.input(LOCAL_STRING(30203), type=xbmcgui.INPUT_ALPHANUM, - option=xbmcgui.ALPHANUM_HIDE_INPUT) - if PASSWORD != '': - ADDON.setSetting(id='password', value=PASSWORD) - else: - sys.exit() - - if USERNAME != '' and PASSWORD != '': - url = 'https://auth.api.sonyentertainmentnetwork.com/2.0/ssocookie' - headers = {"Accept": "*/*", - "Content-type": "application/x-www-form-urlencoded", - "Origin": "https://id.sonyentertainmentnetwork.com", - "Accept-Language": "en-US,en;q=0.8", - "Accept-Encoding": "deflate", - "User-Agent": UA_ANDROID, - "Connection": "Keep-Alive" - } - - payload = 'authentication_type=password&username='+urllib.quote_plus(USERNAME)+'&password='+urllib.quote_plus(PASSWORD)+'&client_id='+LOGIN_CLIENT_ID - r = requests.post(url, headers=headers, cookies=load_cookies(), data=payload, verify=VERIFY) - json_source = r.json() - save_cookies(r.cookies) - - if 'npsso' in json_source: - npsso = json_source['npsso'] - ADDON.setSetting(id='npsso', value=npsso) - elif 'authentication_type' in json_source: - if json_source['authentication_type'] == 'two_step': - ticket_uuid = json_source['ticket_uuid'] - two_step_verification(ticket_uuid) - elif 'error_description' in json_source: - msg = json_source['error_description'] - dialog = xbmcgui.Dialog() - ok = dialog.ok(LOCAL_STRING(30200), msg) - sys.exit() - else: - # Something went wrong during login - dialog = xbmcgui.Dialog() - ok = dialog.ok(LOCAL_STRING(30200), LOCAL_STRING(30201)) - sys.exit() - - -def two_step_verification(ticket_uuid): - dialog = xbmcgui.Dialog() - code = dialog.input(LOCAL_STRING(30204), type=xbmcgui.INPUT_ALPHANUM) - if code == '': sys.exit() - - url = 'https://auth.api.sonyentertainmentnetwork.com/2.0/ssocookie' - headers = { - "Accept": "*/*", - "Content-type": "application/x-www-form-urlencoded", - "Origin": "https://id.sonyentertainmentnetwork.com", - "Accept-Language": "en-US,en;q=0.8", - "Accept-Encoding": "deflate", - "User-Agent": UA_ANDROID, - "Connection": "Keep-Alive", - "Referer": "https://id.sonyentertainmentnetwork.com/signin/?service_entity=urn:service-entity:psn&ui=pr&service_logo=ps&response_type=code&scope=psn:s2s&client_id="+REQ_CLIENT_ID+"&request_locale=en_US&redirect_uri=https://io.playstation.com/playstation/psn/acceptLogin&error=login_required&error_code=4165&error_description=User+is+not+authenticated" - } - - payload = 'authentication_type=two_step&ticket_uuid='+ticket_uuid+'&code='+code+'&client_id='+LOGIN_CLIENT_ID - r = requests.post(url, headers=headers, cookies=load_cookies(), data=payload, verify=VERIFY) - json_source = r.json() - save_cookies(r.cookies) - - if 'npsso' in json_source: - npsso = json_source['npsso'] - ADDON.setSetting(id='npsso', value=npsso) - elif 'error_description' in json_source: - msg = json_source['error_description'] - dialog = xbmcgui.Dialog() - ok = dialog.ok(LOCAL_STRING(30200), msg) - sys.exit() - else: - # Something went wrong during login - dialog = xbmcgui.Dialog() - ok = dialog.ok(LOCAL_STRING(30200), LOCAL_STRING(30201)) - sys.exit() - - -def logout(): - url = 'https://auth.api.sonyentertainmentnetwork.com/2.0/ssocookie' - headers = { - "User-Agent": "com.sony.snei.np.android.sso.share.oauth.versa.USER_AGENT", - "Content-Type": "application/x-www-form-urlencoded", - "Connection": "Keep-Alive", - "Accept-Encoding": "gzip" - } - - r = requests.delete(url, headers=headers, cookies=load_cookies(), verify=VERIFY) - # Clear addon settings - ADDON.setSetting(id='reqPayload', value='') - ADDON.setSetting(id='npsso', value='') - ADDON.setSetting(id='default_profile', value='') - - -def get_reqpayload(): - url = 'https://auth.api.sonyentertainmentnetwork.com/2.0/oauth/authorize' - url += '?state=209189737' - url += '&duid=' + DEVICE_ID - url += '&ui=pr' - url += '&animation=enable' - url += '&client_id=' + REQ_CLIENT_ID - url += '&device_base_font_size=10' - url += '&device_profile=tablet' - url += '&hidePageElements=noAccountSection' - url += '&redirect_uri=https%3A%2F%2Fthemis.dl.playstation.net%2Fthemis%2Fzartan%2Fredirect.html' - url += '&response_type=code' - url += '&scope=psn%3As2s' - url += '&service_entity=urn%3Aservice-entity%3Anp' - url += '&service_logo=ps' - url += '&smcid=android%3Apsvue' - url += '&support_scheme=sneiprls' - - headers = {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "X-Requested-With": "com.snei.vue.android", - "Accept-Language": "en-US", - "Accept-Encoding": "deflate", - "User-Agent": UA_ANDROID, - "Connection": "Keep-Alive", - "Upgrade-Insecure-Requests": "1", - "Referer": "https://id.sonyentertainmentnetwork.com/signin/?hidePageElements=noAccountSection&smcid=android%3Apsvue&client_id=" + REQ_CLIENT_ID + "&response_type=code&scope=psn%3As2s&redirect_uri=https%3A%2F%2Fthemis.dl.playstation.net%2Fthemis%2Fzartan%2Fredirect.html&state=209189737&service_entity=urn%3Aservice-entity%3Anp&duid=" + DEVICE_ID + "&ui=pr&support_scheme=sneiprls&device_profile=tablet&device_base_font_size=10&animation=enable&service_logo=ps&error=login_required&error_code=4165&error_description=User+is+not+authenticated" - } - - r = requests.get(url, headers=headers, cookies=load_cookies(), verify=VERIFY) - last_url = r.url - parsed = urlparse.urlparse(last_url) - code = urlparse.parse_qs(parsed.query)['code'][0] - - # Get reqPayload - url = 'https://sentv-user-auth.totsuko.tv/sentv_user_auth/ws/oauth2/token' - url += '?device_type_id=android_tablet' - url += '&device_id=' + DEVICE_ID - url += '&code=' + code - url += '&redirect_uri=https%3A%2F%2Fthemis.dl.playstation.net%2Fthemis%2Fzartan%2Fredirect.html' - - headers = {"Accept": "*/*", - "Origin": "https://themis.dl.playstation.net", - "Connection": "Keep-Alive", - "Accept-Encoding": "gzip" - } - - r = requests.get(url, headers=headers, verify=VERIFY) - if 'reqPayload' in r.headers: - req_payload = str(r.headers['reqPayload']) - ADDON.setSetting(id='reqPayload', value=req_payload) - else: - sys.exit() - - def get_json(url): headers = {'Accept': '*/*', 'reqPayload': ADDON.getSetting(id='reqPayload'), @@ -488,92 +280,6 @@ def get_json(url): return r.json() -def check_reqpayload(): - check_login() - - if ADDON.getSetting(id='reqPayload') == '': - get_reqpayload() - - -def check_login(): - expired_cookies = True - - try: - cj = cookielib.LWPCookieJar() - cj.load(os.path.join(ADDON_PATH_PROFILE, 'cookies.lwp'), ignore_discard=True) - if ADDON.getSetting(id='npsso') != '': - for cookie in cj: - if cookie.name == 'npsso': - xbmc.log(str(cookie.name)) - xbmc.log(str(cookie.expires)) - xbmc.log(str(cookie.is_expired())) - expired_cookies = cookie.is_expired() - except: - pass - - if expired_cookies: - login() - - -def get_profiles(): - url = 'https://sentv-user-ext.totsuko.tv/sentv_user_ext/ws/v2/profile/ids' - headers = { - 'User-Agent': UA_ANDROID, - 'reqPayload': ADDON.getSetting(id='reqPayload'), - 'Accept': '*/*', - 'Origin': 'https://themis.dl.playstation.net', - 'Host': 'sentv-user-ext.totsuko.tv', - 'Connection': 'Keep-Alive', - 'Accept-Encoding': 'gzip' - } - - r = requests.get(url, headers=headers, verify=VERIFY) - profiles = r.json()['body']['profiles'] - prof_dict = {} - prof_list = [] - for profile in profiles: - xbmc.log(str(profile['profile_id']) + ' ' + str(profile['profile_name'])) - prof_dict[str(profile['profile_name'])] = str(profile['profile_id']) - prof_list.append(str(profile['profile_name'])) - - dialog = xbmcgui.Dialog() - ret = dialog.select('Choose Profile', prof_list) - if ret >= 0: - set_profile(prof_dict[prof_list[ret]]) - else: - sys.exit() - - -def set_profile(profile_id): - url = 'https://sentv-user-ext.totsuko.tv/sentv_user_ext/ws/v2/profile/' + profile_id - headers = { - 'User-Agent': UA_ANDROID, - 'reqPayload': ADDON.getSetting(id='reqPayload'), - 'Accept': '*/*', - 'Origin': 'https://themis.dl.playstation.net', - 'Host': 'sentv-user-ext.totsuko.tv', - 'Connection': 'Keep-Alive', - 'Accept-Encoding': 'gzip' - } - - r = requests.get(url, headers=headers, verify=VERIFY) - req_payload = str(r.headers['reqPayload']) - ADDON.setSetting(id='reqPayload', value=req_payload) - ADDON.setSetting(id='default_profile', value=profile_id) - - -def save_cookies(cookiejar): - filename = os.path.join(ADDON_PATH_PROFILE, 'cookies.lwp') - lwp_cookiejar = cookielib.LWPCookieJar() - for c in cookiejar: - args = dict(vars(c).items()) - args['rest'] = args['_rest'] - del args['_rest'] - c = cookielib.Cookie(**args) - lwp_cookiejar.set_cookie(c) - lwp_cookiejar.save(filename, ignore_discard=True) - - def load_cookies(): filename = os.path.join(ADDON_PATH_PROFILE, 'cookies.lwp') lwp_cookiejar = cookielib.LWPCookieJar() @@ -597,8 +303,8 @@ def stringToDate(string, date_format): def create_device_id(): android_id = ''.join(random.choice('0123456789abcdef') for i in range(16)) android_id = android_id.rjust(30, '0') - manufacturer = 'Amazon' - model = 'AFTS' # fire tv gen 2 + manufacturer = 'ASUS' + model = 'Nexus 7' manf_model = ":%s:%s" % (manufacturer.rjust(10, ' '), model.rjust(10, ' ')) manf_model = manf_model.encode("hex") zero = '0' @@ -681,16 +387,17 @@ FANART = os.path.join(ROOTDIR, "resources", "fanart.jpg") ICON = os.path.join(ROOTDIR, "resources", "icon.png") ADDON_PATH_PROFILE = xbmc.translatePath(ADDON.getAddonInfo('profile')) -USERNAME = ADDON.getSetting(id='username') -PASSWORD = ADDON.getSetting(id='password') DEVICE_ID = ADDON.getSetting(id='deviceId') + +amazon_device = 'Amazon' +amazon_device = amazon_device.encode("hex") +if amazon_device in DEVICE_ID: DEVICE_ID = '' + if DEVICE_ID == '': create_device_id() DEVICE_ID = ADDON.getSetting(id='deviceId') UA_ANDROID = 'Mozilla/5.0 (Linux; Android 6.0.1; Build/MOB31H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/44.0.2403.119 Safari/537.36' -LOGIN_CLIENT_ID = '71a7beb8-f21a-47d9-a604-2e71bee24fe0' -REQ_CLIENT_ID = 'dee6a88d-c3be-4e17-aec5-1018514cee40' CHANNEL_URL = 'https://media-framework.totsuko.tv/media-framework/media/v2.1/stream/channel' EPG_URL = 'https://epg-service.totsuko.tv/epg_service_sony/service/v2' VERIFY = False diff --git a/plugin.video.psvue/resources/lib/sony.py b/plugin.video.psvue/resources/lib/sony.py new file mode 100644 index 0000000..968169f --- /dev/null +++ b/plugin.video.psvue/resources/lib/sony.py @@ -0,0 +1,334 @@ +import os, xbmc, xbmcaddon, xbmcgui +import cookielib, requests, urllib +from datetime import datetime + + +class SONY(): + addon = xbmcaddon.Addon() + api_url = 'https://auth.api.sonyentertainmentnetwork.com/2.0' + device_id = '' + localized = addon.getLocalizedString + login_client_id = '71a7beb8-f21a-47d9-a604-2e71bee24fe0' + npsso = '' + password = '' + req_client_id = 'dee6a88d-c3be-4e17-aec5-1018514cee40' + ua_android = 'Mozilla/5.0 (Linux; Android 6.0.1; Build/MOB31H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/44.0.2403.119 Safari/537.36' + ua_sony = 'com.sony.snei.np.android.sso.share.oauth.versa.USER_AGENT' + username = '' + verify = False + + + def __init__(self): + self.device_id = self.addon.getSetting('deviceId') + self.npsso = self.addon.getSetting('npsso') + self.password = self.addon.getSetting('password') + self.username = self.addon.getSetting('username') + + + def check_auth(self): + self.check_login() + self.authorize_device() + + + def check_login(self): + expired_cookies = True + addon_profile_path = xbmc.translatePath(self.addon.getAddonInfo('profile')) + try: + cj = cookielib.LWPCookieJar() + cj.load(os.path.join(addon_profile_path, 'cookies.lwp'), ignore_discard=True) + if self.npsso != '': + for cookie in cj: + if cookie.name == 'npsso': + expired_cookies = cookie.is_expired() + break + except: + pass + + if expired_cookies: + self.login() + + + def login(self): + if self.username == '': + dialog = xbmcgui.Dialog() + self.username = dialog.input(self.localized(30202), type=xbmcgui.INPUT_ALPHANUM) + if self.username != '': + self.addon.setSetting(id='username', value=self.username) + else: + sys.exit() + + if self.password == '': + dialog = xbmcgui.Dialog() + self.password = dialog.input(self.localized(30203), type=xbmcgui.INPUT_ALPHANUM, option=xbmcgui.ALPHANUM_HIDE_INPUT) + if self.password != '': + self.addon.setSetting(id='password', value=self.password) + else: + sys.exit() + + if self.username != '' and self.password != '': + url = self.api_url + '/ssocookie' + headers = {"Accept": "*/*", + "Content-type": "application/x-www-form-urlencoded", + "Origin": "https://id.sonyentertainmentnetwork.com", + "Accept-Language": "en-US,en;q=0.8", + "Accept-Encoding": "deflate", + "User-Agent": self.ua_android, + "Connection": "Keep-Alive" + } + + payload = 'authentication_type=password&username='+urllib.quote_plus(self.username)+'&password='+urllib.quote_plus(self.password)+'&client_id='+self.login_client_id + r = requests.post(url, headers=headers, cookies=self.load_cookies(), data=payload, verify=self.verify) + json_source = r.json() + self.save_cookies(r.cookies) + + if 'npsso' in json_source: + npsso = json_source['npsso'] + self.addon.setSetting(id='npsso', value=npsso) + elif 'authentication_type' in json_source: + if json_source['authentication_type'] == 'two_step': + ticket_uuid = json_source['ticket_uuid'] + self.two_step_verification(ticket_uuid) + elif 'error_description' in json_source: + msg = json_source['error_description'] + self.error_msg(self.localized(30200), msg) + sys.exit() + else: + # Something went wrong during login + self.error_msg(self.localized(30200), self.localized(30201)) + sys.exit() + + + def two_step_verification(self, ticket_uuid): + dialog = xbmcgui.Dialog() + code = dialog.input(self.localized(30204), type=xbmcgui.INPUT_ALPHANUM) + if code == '': sys.exit() + + url = self.api_url + '/ssocookie' + headers = { + "Accept": "*/*", + "Content-type": "application/x-www-form-urlencoded", + "Origin": "https://id.sonyentertainmentnetwork.com", + "Accept-Language": "en-US,en;q=0.8", + "Accept-Encoding": "deflate", + "User-Agent": self.ua_android, + "Connection": "Keep-Alive", + "Referer": "https://id.sonyentertainmentnetwork.com/signin/?service_entity=urn:service-entity:psn&ui=pr&service_logo=ps&response_type=code&scope=psn:s2s&client_id="+self.req_client_id+"&request_locale=en_US&redirect_uri=https://io.playstation.com/playstation/psn/acceptLogin&error=login_required&error_code=4165&error_description=User+is+not+authenticated" + } + + payload = 'authentication_type=two_step&ticket_uuid='+ticket_uuid+'&code='+code+'&client_id='+self.login_client_id + r = requests.post(url, headers=headers, cookies=self.load_cookies(), data=payload, verify=self.verify) + json_source = r.json() + self.save_cookies(r.cookies) + + if 'npsso' in json_source: + npsso = json_source['npsso'] + self.addon.setSetting(id='npsso', value=npsso) + elif 'error_description' in json_source: + msg = json_source['error_description'] + self.error_msg(self.localized(30200), msg) + sys.exit() + else: + # Something went wrong during login + self.error_msg(self.localized(30200), self.localized(30201)) + sys.exit() + + + def logout(self): + url = self.api_url + '/ssocookie' + headers = { + "User-Agent": self.ua_sony, + "Content-Type": "application/x-www-form-urlencoded", + "Connection": "Keep-Alive", + "Accept-Encoding": "gzip" + } + + r = requests.delete(url, headers=headers, cookies=self.load_cookies(), verify=self.verify) + # Clear addon settings + self.addon.setSetting(id='reqPayload', value='') + self.addon.setSetting(id='last_auth', value='') + self.addon.setSetting(id='npsso', value='') + self.addon.setSetting(id='default_profile', value='') + + + def get_grant_code(self): + url = self.api_url + '/oauth/authorize' + url += '?response_type=code' + url += '&client_id=' + self.req_client_id + url += '&redirect_uri=https%3A%2F%2Fthemis.dl.playstation.net%2Fthemis%2Fzartan%2Fredirect.html' + url += '&scope=psn%3As2s' + url += '&signInOnly=true' + url += '&service_entity=urn%3Aservice-entity%3Anp' + url += '&prompt=none' + url += '&duid=' + self.device_id + + headers = { + "Accept-Encoding": "gzip", + "User-Agent": self.ua_sony, + "Connection": "Keep-Alive", + } + + code = '' + r = requests.get(url, headers=headers, allow_redirects=False, cookies=self.load_cookies(), verify=self.verify) + if 'X-NP-GRANT-CODE' in r.headers: + code = r.headers['X-NP-GRANT-CODE'] + else: + self.error_msg(self.localized(30207), self.localized(30208)) + sys.exit() + + return code + + + def authorize_device(self): + url = 'https://sentv-user-auth.totsuko.tv/sentv_user_auth/ws/oauth2/token' + url += '?device_type_id=android_tablet' + url += '&device_id=' + self.device_id + url += '&code=' + self.get_grant_code() + url += '&redirect_uri=https%3A%2F%2Fthemis.dl.playstation.net%2Fthemis%2Fzartan%2Fredirect.html' + + headers = { + 'Origin': 'https://themis.dl.playstation.net', + 'User-Agent': self.ua_android, + 'Accept': '*/*', + 'Connection': 'Keep-Alive', + 'Accept-Encoding': 'gzip' + } + if self.addon.getSetting(id='reqPayload') != '': + headers['reauth'] = '1' + headers['reqPayload'] = self.addon.getSetting(id='reqPayload') + + r = requests.get(url, headers=headers, verify=self.verify) + if 'reqPayload' in r.headers: + req_payload = str(r.headers['reqPayload']) + self.addon.setSetting(id='reqPayload', value=req_payload) + auth_time = r.json()['header']['time_stamp'] + self.addon.setSetting(id='last_auth', value=auth_time) + else: + self.error_msg(self.localized(30207), self.localized(30209)) + sys.exit() + + + def get_profiles(self): + url = 'https://sentv-user-ext.totsuko.tv/sentv_user_ext/ws/v2/profile/ids' + headers = { + 'User-Agent': self.ua_android, + 'reqPayload': self.addon.getSetting(id='reqPayload'), + 'Accept': '*/*', + 'Origin': 'https://themis.dl.playstation.net', + 'Connection': 'Keep-Alive', + 'Accept-Encoding': 'gzip' + } + + r = requests.get(url, headers=headers, verify=self.verify) + if 'body' in r.json() and 'profiles' in r.json()['body']: + profiles = r.json()['body']['profiles'] + prof_dict = {} + prof_list = [] + for profile in profiles: + xbmc.log(str(profile['profile_id']) + ' ' + str(profile['profile_name'])) + prof_dict[str(profile['profile_name'])] = str(profile['profile_id']) + prof_list.append(str(profile['profile_name'])) + + dialog = xbmcgui.Dialog() + ret = dialog.select(self.localized(30210), prof_list) + if ret >= 0: + self.set_profile(prof_dict[prof_list[ret]]) + else: + sys.exit() + else: + self.error_msg(self.localized(30205), self.localized(30206)) + sys.exit() + + + def set_profile(self, profile_id): + url = 'https://sentv-user-ext.totsuko.tv/sentv_user_ext/ws/v2/profile/' + profile_id + headers = { + 'User-Agent': self.ua_android, + 'reqPayload': self.addon.getSetting(id='reqPayload'), + 'Accept': '*/*', + 'Origin': 'https://themis.dl.playstation.net', + 'Host': 'sentv-user-ext.totsuko.tv', + 'Connection': 'Keep-Alive', + 'Accept-Encoding': 'gzip' + } + + r = requests.get(url, headers=headers, verify=self.verify) + req_payload = str(r.headers['reqPayload']) + self.addon.setSetting(id='reqPayload', value=req_payload) + auth_time = r.json()['header']['time_stamp'] + self.addon.setSetting(id='last_auth', value=auth_time) + self.addon.setSetting(id='default_profile', value=profile_id) + + + def put_resume_time(self): + """ + PUT https://sentv-user-action.totsuko.tv/sentv_user_action/ws/v2/watch_history HTTP/1.1 + Host: sentv-user-action.totsuko.tv + Connection: keep-alive + Content-Length: 247 + Accept: */* + reqPayload: redacted + User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; Build/MOB31H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/44.0.2403.119 Safari/537.36 + Origin: https://themis.dl.playstation.net + Content-Type: application/json + Referer: https://themis.dl.playstation.net/themis/zartan/2.2.2b/ + Accept-Encoding: gzip, deflate + Accept-Language: en-US + X-Requested-With: com.snei.vue.android + + {"series_id":redacted,"program_id":redacted,"channel_id":redacted,"tms_id":"EP005544655496","airing_id":redacted,"last_watch_date":"2017-04-28T00:40:43Z","last_timecode":"01:46:29","start_timecode":"00:00:00:00","fully_watched":false,"stream_type":"dvr"} + """ + url = 'https://sentv-user-action.totsuko.tv/sentv_user_action/ws/v2/watch_history' + headers = {"Accept": "*/*", + "Content-type": "application/json", + "Origin": "https://themis.dl.playstation.net", + "Accept-Language": "en-US", + "Referer": "https://themis.dl.playstation.net/themis/zartan/2.2.2b/", + "Accept-Encoding": "gzip, deflate", + "User-Agent": self.ua_android, + "Connection": "Keep-Alive", + 'reqPayload': self.addon.getSetting(id='reqPayload'), + 'X-Requested-With': 'com.snei.vue.android' + } + + payload = '{"series_id":redacted,' + payload += '"program_id":redacted,' + payload += '"channel_id":redacted,' + payload += '"tms_id":"redacted",' + payload += '"airing_id":redacted,' + payload += '"last_watch_date":"2017-04-28T00:40:43Z",' + payload += '"last_timecode":"01:46:29",' + payload += '"start_timecode":"00:00:00:00",' + payload += '"fully_watched":false,' + payload += '"stream_type":"dvr"}' + + r = requests.put(url, headers=headers, data=payload, verify=self.verify) + + + def save_cookies(self, cookiejar): + addon_profile_path = xbmc.translatePath(self.addon.getAddonInfo('profile')) + filename = os.path.join(addon_profile_path, 'cookies.lwp') + lwp_cookiejar = cookielib.LWPCookieJar() + for c in cookiejar: + args = dict(vars(c).items()) + args['rest'] = args['_rest'] + del args['_rest'] + c = cookielib.Cookie(**args) + lwp_cookiejar.set_cookie(c) + lwp_cookiejar.save(filename, ignore_discard=True) + + + def load_cookies(self): + addon_profile_path = xbmc.translatePath(self.addon.getAddonInfo('profile')) + filename = os.path.join(addon_profile_path, 'cookies.lwp') + lwp_cookiejar = cookielib.LWPCookieJar() + try: + lwp_cookiejar.load(filename, ignore_discard=True) + except: + pass + + return lwp_cookiejar + + + def error_msg(self, title, msg): + dialog = xbmcgui.Dialog() + dialog.notification(title, msg, xbmcgui.NOTIFICATION_INFO, 5000) diff --git a/plugin.video.psvue/resources/settings.xml b/plugin.video.psvue/resources/settings.xml index d54ae70..2628d20 100644 --- a/plugin.video.psvue/resources/settings.xml +++ b/plugin.video.psvue/resources/settings.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <settings> <!--Login--> - <category label="30000"> + <category label="30000"> <setting id="username" type="text" label="30001" default=""/> <setting id="password" type="text" label="30002" option="hidden" default=""/> <setting id="logout" type="action" label="30003" action="RunPlugin(plugin://plugin.video.psvue/?mode=999)" option="close" /> @@ -10,6 +10,7 @@ <!-- Hidden --> <setting id="reqPayload" type="text" default="" visible="false"/> + <setting id="last_auth" type="text" default="" visible="false"/> <setting id="deviceId" type="text" default="" visible="false"/> <setting id="npsso" type="text" default="" visible="false"/> <setting id="default_profile" type="text" default="" visible="false"/> |