def Queue(self): queue_type = __settings__.getSetting("queue_type") if queue_type == '0': fields = "media.episode_number,media.name,media.description,media.media_type,media.series_name,media.available,media.available_time,media.free_available,media.free_available_time,media.duration,media.playhead,media.url,media.screenshot_image,image.fwide_url,image.fwidestar_url,series.landscape_image,image.full_url" options = {'media_types':"anime|drama", 'fields':fields} request = self.makeAPIRequest('queue', options) if request['error'] is False: return self.list_media_items(request['data'], 'Queue', '1', 'queue', 'fanart') elif queue_type == '1': fields = "series.name,series.description,series.series_id,series.rating,series.media_count,series.url,series.publisher_name,series.year,series.portrait_image,image.large_url,series.landscape_image,image.full_url" options = {'media_types':"anime|drama", 'fields':fields} request = self.makeAPIRequest('queue', options) if request['error'] is False: for series in request['data']: series = series['series'] year = 'None' if series['year'] is None else series['year'] #only available on some series description = '' if series['description'] is None else series['description'].encode('utf-8') #Adding sinopses thumb = '' if (series['portrait_image'] is None or series['portrait_image']['large_url'] is None or 'portrait_image' not in series or 'large_url' not in series['portrait_image']) else series['portrait_image']['full_url'] #Becuase not all series have a thumbnail. art = '' if (series['landscape_image'] is None or series['landscape_image']['full_url'] is None or 'landscape_image' not in series or 'full_url' not in series['landscape_image']) else series['landscape_image']['full_url'] #Becuase not all series have art. rating = '0' if (series['rating'] == '' or 'rating' not in series) else series['rating'] #Because Crunchyroll seems to like passing series without ratings if ('media_count' in series and 'series_id' in series and 'name' in series and series['media_count'] > 0): #Because Crunchyroll seems to like passing series without these things crunchy_main.UI().addItem({'Title':series['name'].encode("utf8"),'mode':'list_coll', 'series_id':series['series_id'], 'Thumb':thumb, 'Fanart_Image':art, 'plot':description, 'year':year}, True) crunchy_main.UI().endofdirectory('none')
def list_collections(self, series_id, series_name, count, thumb, fanart): fields = "collection.collection_id,collection.season,collection.name,collection.description,collection.complete,collection.media_count" options = { 'series_id': series_id, 'fields': fields, 'sort': 'desc', 'limit': count } request = self.makeAPIRequest('list_collections', options) if request['error'] is False: if len(request['data']) <= 1: for collection in request['data']: complete = '1' if collection['complete'] else '0' return self.list_media(collection['collection_id'], series_name, count, complete, '1', fanart) else: for collection in request['data']: complete = '1' if collection['complete'] else '0' crunchy_main.UI().addItem( { 'Title': collection['name'].encode("utf8"), 'filterx': series_name, 'mode': 'list_media', 'count': str(count), 'id': collection['collection_id'], 'plot': collection['description'].encode("utf8"), 'complete': complete, 'season': str(collection['season']), 'series_id': series_id, 'Thumb': thumb, 'Fanart_Image': fanart }, True) crunchy_main.UI().endofdirectory('none')
def list_categories(self, title, media_type, filterx): options = {'media_type': media_type.lower()} request = self.makeAPIRequest('categories', options) if request['error'] is False: if filterx == 'genre': if 'genre' in request['data']: for genre in request['data']['genre']: crunchy_main.UI().addItem( { 'Title': genre['label'].encode("utf8"), 'mode': 'list_series', 'showtype': media_type, 'filterx': 'tag:' + genre['tag'] }, True) if filterx == 'season': if 'season' in request['data']: for season in request['data']['season']: crunchy_main.UI().addItem( { 'Title': season['label'].encode("utf8"), 'mode': 'list_series', 'showtype': media_type, 'filterx': 'tag:' + season['tag'] }, True) crunchy_main.UI().endofdirectory('none')
def list_media_items(self, request, series_name, season, mode, fanart): for media in request: #The following are items to help display Recently Watched and Queue items correctly season = media['collection']['season'] if mode == "history" else season series_name = media['series']['name'] if mode == "history" else series_name series_name = media['most_likely_media']['series_name'] if mode == "queue" else series_name fanart = media['series']['landscape_image']['fwide_url'] if (mode == "history" or mode == "queue") else fanart #On history/queue, the fanart is get directly from the json. media = media['media'] if mode == "history" else media #History media is one level deeper in the json string than normal media items. if mode == "queue" and 'most_likely_media' not in media: #Some queue items don't have most_likely_media so we have to skip them. continue media = media['most_likely_media'] if mode == "queue" else media #Queue media is one level deeper in the json string than normal media items. #Dates, times, and such current_datetime = datetime.datetime.now(dateutil.tz.tzutc()) available_datetime = dateutil.parser.parse(media['available_time']).astimezone(dateutil.tz.tzlocal()) available_date = available_datetime.date() available_delta = available_datetime - current_datetime available_in = str(available_delta.days)+" days." if available_delta.days > 0 else str(available_delta.seconds/60/60)+" hours." #Fix Crunchyroll inconsistencies & add details for upcoming or unreleased episodes media['episode_number'] = '0' if media['episode_number'] == '' else media['episode_number'] #PV episodes have no episode number so we set it to 0. media['episode_number'] = re.sub('\D', '', media['episode_number']) #Because CR puts letters into some rare episode numbers. if media['episode_number'] == '0': name = "NO NAME" if media['name'] == '' else media['name'] else: name = "Episode "+str(media['episode_number']) if media['name'] == '' else "Episode "+media['episode_number']+" - "+media['name'] #CR doesn't seem to include episode names for all media so we have to make one up. name = series_name + " " + name if (mode == "history" or mode == "queue") else name name = "* " + name if media['free_available'] is False else name soon = "Coming Soon - " + series_name + " Episode "+str(media['episode_number']) if mode == "queue" else "Coming Soon - Episode "+str(media['episode_number']) name = soon if media['available'] is False else name #Set the name for upcoming episode #season = '1' if season == '0' else season #There is a bug which prevents Season 0 from displaying correctly in PMC. This is to help fix that. Will break if a series has both season 0 and 1. thumb = "http://static.ak.crunchyroll.com/i/no_image_beta_full.jpg" if media['screenshot_image'] is None else media['screenshot_image']['fwide_url'] if media['free_available'] is True else media['screenshot_image']['fwidestar_url']#because not all shows have thumbnails. thumb = "http://static.ak.crunchyroll.com/i/coming_soon_beta_fwide.jpg" if media['available'] is False else thumb #Sets the thumbnail to coming soon if the episode isn't available yet. description = '' if media['description'] is None else media['description'].encode('utf-8') #Adding sinopses description = "This episode will be available in "+str(available_in) if media['available'] is False else description #Set the description for upcoming episodes. duration = "0" if media['available'] is False else str(media['duration']) playhead = "0" if media['available'] is False else str(media['playhead']) #current playback point year = 'None' if media['available_time'] is None else media['available_time'][:10] #Adding published date instead url = media['url'] media_id = url.split('-')[-1] crunchy_main.UI().addItem({'Title':name.encode("utf8"),'mode':'videoplay', 'id':media_id.encode("utf8"), 'Thumb':thumb.encode("utf8"), 'url':url.encode("utf8"), 'Fanart_Image':fanart, 'plot':description, 'year':year, 'playhead':playhead, 'duration':duration}, False) crunchy_main.UI().endofdirectory('none')
def list_series(self, title, media_type, filterx, offset): fields = "series.name,series.description,series.series_id,series.rating,series.media_count,series.url,series.publisher_name,series.year,series.portrait_image,image.large_url,series.landscape_image,image.full_url" options = {'media_type':media_type.lower(), 'filter':filterx, 'fields':fields, 'limit':'64', 'offset':int(offset)} request = self.makeAPIRequest('list_series', options) if request['error'] is False: counter = 0 for series in request['data']: counter = counter + 1 year = 'None' if series['year'] is None else series['year'] #only available on some series description = '' if series['description'] is None else series['description'].encode('utf-8') #Adding sinopses thumb = '' if (series['portrait_image'] is None or series['portrait_image']['large_url'] is None or 'portrait_image' not in series or 'large_url' not in series['portrait_image']) else series['portrait_image']['full_url'] #Becuase not all series have a thumbnail. art = '' if (series['landscape_image'] is None or series['landscape_image']['full_url'] is None or 'landscape_image' not in series or 'full_url' not in series['landscape_image']) else series['landscape_image']['full_url'] #Becuase not all series have art. rating = '0' if (series['rating'] == '' or 'rating' not in series) else series['rating'] #Because Crunchyroll seems to like passing series without ratings if ('media_count' in series and 'series_id' in series and 'name' in series and series['media_count'] > 0): #Because Crunchyroll seems to like passing series without these things crunchy_main.UI().addItem({'Title':series['name'].encode("utf8"),'mode':'list_coll', 'series_id':series['series_id'], 'count':str(series['media_count']), 'Thumb':thumb, 'Fanart_Image':art, 'plot':description, 'year':year}, True) if counter >= 64: offset = (int(offset) + counter) crunchy_main.UI().addItem({'Title':'...load more','mode':'list_series','showtype':media_type,'filterx':filterx, 'offset':str(offset)}) crunchy_main.UI().endofdirectory('none')
def loadShelf(self): try: self.base_path = xbmc.translatePath(__settings__.getAddonInfo('profile')).decode('utf-8') self.base_cache_path = os.path.join(self.base_path, "cache") if not os.path.exists(self.base_cache_path): os.makedirs(self.base_cache_path) shelf_path = os.path.join(self.base_path, "cruchyXBMC") #Load Persistan Vars userData = shelve.open(shelf_path,writeback=True) local_string = __settings__.getLocalizedString notice_msg = local_string(30200).encode("utf8") setup_msg = local_string(30203).encode("utf8") acc_type_error = local_string(30312).encode("utf8") change_language = __settings__.getSetting("change_language") if change_language == "0": userData.setdefault('API_LOCALE',"enUS") elif change_language == "1": userData['API_LOCALE'] = "enUS" elif change_language == "2": userData['API_LOCALE'] = "enGB" elif change_language == "3": userData['API_LOCALE'] = "jaJP" elif change_language == "4": userData['API_LOCALE'] = "frFR" elif change_language == "5": userData['API_LOCALE'] = "deDE" elif change_language == "6": userData['API_LOCALE'] = "ptBR" elif change_language == "7": userData['API_LOCALE'] = "ptPT" elif change_language == "8": userData['API_LOCALE'] = "esLA" elif change_language == "9": userData['API_LOCALE'] = "esES" userData['username'] = __settings__.getSetting("crunchy_username") userData['password'] = __settings__.getSetting("crunchy_password") if not userData.has_key('device_id'): char_set = string.ascii_letters + string.digits device_id = ''.join(random.sample(char_set,32)) userData["device_id"] = device_id xbmc.log("Crunchyroll;xbmc ----> New device_id created. New device_id is: "+ str(device_id)) userData['API_URL'] = "https://api.crunchyroll.com" userData['API_HEADERS'] = [('User-Agent',"Mozilla/5.0 (PLAYSTATION 3; 4.46)"), ('Host',"api.crunchyroll.com"), ('Accept-Encoding',"gzip, deflate"), ('Accept',"*/*"), ('Content-Type',"application/x-www-form-urlencoded")] userData['API_VERSION'] = "1.0.1" userData['API_ACCESS_TOKEN'] = "S7zg3vKx6tRZ0Sf" userData['API_DEVICE_TYPE'] = "com.crunchyroll.ps3" userData.setdefault('premium_type', 'UNKNOWN') current_datetime = datetime.datetime.now(dateutil.tz.tzutc()) userData.setdefault('lastreported', (current_datetime - dateutil.relativedelta.relativedelta( hours = +24 ))) self.userData = userData except: xbmc.log( "Unexpected error:", sys.exc_info()) userData['session_id'] = '' userData['auth_expires'] = current_datetime - dateutil.relativedelta.relativedelta( hours = +24 ) userData['lastreported'] = current_datetime - dateutil.relativedelta.relativedelta( hours = +24 ) userData['premium_type'] = 'UNKNOWN' userData['auth_token'] = '' userData['session_expires'] = current_datetime - dateutil.relativedelta.relativedelta( hours = +24 ) self.userData = userData userData.close() xbmc.log( "Crunchyroll -> Unable to Load shelve") return False #Check to see if a session_id doesn't exist or if the current auth token is invalid and if so start a new session and log it in. if (not userData.has_key('session_id')) or (not userData.has_key('auth_expires')) or current_datetime > userData['auth_expires']: #Start new session xbmc.log( "Crunchyroll;xbmc ----> Starting new session") opener = urllib2.build_opener() opener.addheaders = userData['API_HEADERS'] options = urllib.urlencode({'device_id':userData["device_id"], 'device_type':userData['API_DEVICE_TYPE'], 'access_token':userData['API_ACCESS_TOKEN'], 'version':userData['API_VERSION'], 'locale': userData['API_LOCALE']}) urllib2.install_opener(opener) url = userData['API_URL']+"/start_session.0.json" req = opener.open(url, options) json_data = req.read() if req.headers.get('content-encoding', None) == 'gzip': json_data = gzip.GzipFile(fileobj=StringIO.StringIO(json_data)).read().decode('utf-8','ignore') req.close() request = json.loads(json_data) if request['error'] is False: userData['session_id'] = request['data']['session_id'] userData['session_expires'] = (current_datetime + dateutil.relativedelta.relativedelta( hours = +4 )) userData['test_session'] = current_datetime xbmc.log( "Crunchyroll.bundle ----> New session created! Session ID is: "+ str(userData['session_id'])) elif request['error'] is True: xbmc.log( "Crunchyroll.bundle ----> Error starting new session. Error message is: "+ str(request['message'])) return False #Login the session we just started. if not userData['username'] or not userData['password']: xbmc.log("Crunchyroll.bundle ----> No Username or Password set") self.userData = userData userData.close() ex = 'XBMC.Notification("'+notice_msg+':","'+setup_msg+'.", 3000)' xbmc.executebuiltin(ex) xbmc.log( "crunchyroll-takeout -> NO CRUNCHYROLL ACCOUNT FOUND!") return False else: xbmc.log("Crunchyroll.bundle ----> Logging in the new session we just created.") opener = urllib2.build_opener() opener.addheaders = userData['API_HEADERS'] options = urllib.urlencode({'session_id':userData['session_id'], 'password':userData['password'], 'account':userData['username'], 'version':userData['API_VERSION'], 'locale': userData['API_LOCALE']}) url = userData['API_URL']+"/login.0.json" req = opener.open(url, options) json_data = req.read() if req.headers.get('content-encoding', None) == 'gzip': json_data = gzip.GzipFile(fileobj=StringIO.StringIO(json_data)).read().decode('utf-8','ignore') req.close() request = json.loads(json_data) if request['error'] is False: userData['auth_token'] = request['data']['auth'] userData['auth_expires'] = dateutil.parser.parse(request['data']['expires']) userData['premium_type'] = 'free' if request['data']['user']['premium'] == '' else request['data']['user']['premium'] xbmc.log("Crunchyroll.bundle ----> Login successful.") elif request['error'] is True: xbmc.log("Crunchyroll.bundle ----> Error logging in new session. Error message was: "+ str(request['message'])) self.userData = userData userData.close() return False #Call for Usage Reporting if current_datetime > userData['lastreported']: userData['lastreported'] = (current_datetime + dateutil.relativedelta.relativedelta( hours = +24 )) self.userData = userData self.usage_reporting() #Verify user is premium if userData['premium_type'] in 'anime|drama|manga': xbmc.log("Crunchyroll.bundle ----> User is a premium "+str(userData['premium_type'])+" member.") self.userData = userData userData.close() return True else: xbmc.log("Crunchyroll.bundle ----> User is not premium. ") xbmc.executebuiltin('Notification('+notice_msg+','+acc_type_error+',5000)') self.userData = userData = None userData.close() crunchy_main.UI().addItem({'Title':acc_type_error, 'mode':'Fail'}) crunchy_main.UI().endofdirectory('none') return False #Check to see if a valid session and auth token exist and if so reinitialize a new session using the auth token. elif userData.has_key("session_id") and userData.has_key("auth_expires") and current_datetime < userData['auth_expires'] and current_datetime > userData['session_expires']: #Re-start new session xbmc.log( "Crunchyroll.bundle ----> Valid auth token was detected. Restarting session.") opener = urllib2.build_opener() options = urllib.urlencode({'device_id':userData["device_id"], 'device_type':userData['API_DEVICE_TYPE'], 'access_token':userData['API_ACCESS_TOKEN'], 'version':userData['API_VERSION'], 'locale': userData['API_LOCALE'], 'auth':userData['auth_token']}) urllib2.install_opener(opener) url = userData['API_URL']+"/start_session.0.json" req = opener.open(url, options) json_data = req.read() if req.headers.get('content-encoding', None) == 'gzip': json_data = gzip.GzipFile(fileobj=StringIO.StringIO(json_data)).read().decode('utf-8','ignore') req.close() request = json.loads(json_data) try: if request['error'] is False: userData['session_id'] = request['data']['session_id'] userData['auth_expires'] = dateutil.parser.parse(request['data']['expires']) userData['premium_type'] = 'free' if request['data']['user']['premium'] == '' else request['data']['user']['premium'] userData['auth_token'] = request['data']['auth'] userData['session_expires'] = (current_datetime + dateutil.relativedelta.relativedelta( hours = +4 )) #4 hours is a guess. Might be +/- 4. userData['test_session'] = current_datetime xbmc.log("Crunchyroll.bundle ----> Session restart successful. New session_id is: "+ str(userData['session_id'])) #Call for Usage Reporting if current_datetime > userData['lastreported']: userData['lastreported'] = (current_datetime + dateutil.relativedelta.relativedelta( hours = +24 )) self.userData = userData self.usage_reporting() #Verify user is premium if userData['premium_type'] in 'anime|drama|manga': xbmc.log("Crunchyroll.bundle ----> User is a premium "+str(userData['premium_type'])+" member.") self.userData = userData userData.close() return True else: xbmc.log("Crunchyroll.bundle ----> User is not premium.") xbmc.executebuiltin('Notification('+notice_msg+','+acc_type_error+',5000)') self.userData = userData = None userData.close() crunchy_main.UI().addItem({'Title':acc_type_error, 'mode':'Fail'}) crunchy_main.UI().endofdirectory('none') return False elif request['error'] is True: #Remove userData so we start a new session next time around. del userData['session_id'] del userData['auth_expires'] del userData['premium_type'] del userData['auth_token'] del userData['session_expires'] xbmc.log("Crunchyroll.bundle ----> Error restarting session. Error message was: "+ str(request['message'])) self.userData = userData userData.Save() return False except: userData['session_id'] = '' userData['auth_expires'] = current_datetime - dateutil.relativedelta.relativedelta( hours = +24 ) userData['premium_type'] = 'unknown' userData['auth_token'] = '' userData['session_expires'] = current_datetime - dateutil.relativedelta.relativedelta( hours = +24 ) xbmc.log("Crunchyroll.bundle ----> Error restarting session. Error message was: "+ str(request['message'])) self.userData = userData userData.Save() return False #If we got to this point that means a session exists and it's still valid, we don't need to do anything. elif userData.has_key("session_id") and current_datetime < userData['session_expires']: #This secion below is Stupid Slow #return True if userData['test_session'] is None or current_datetime > userData['test_session']: userData['test_session'] = (current_datetime + dateutil.relativedelta.relativedelta( minutes = +10 )) #test once every 10 min #Test to make sure the session still works. (Sometimes sessions just stop working.) fields = "media.episode_number,media.name,media.description,media.media_type,media.series_name,media.available,media.available_time,media.free_available,media.free_available_time,media.duration,media.url,media.screenshot_image,image.fwide_url,image.fwidestar_url,series.landscape_image,image.full_url" options = {'media_types':"anime|drama", 'fields':fields} request = self.makeAPIRequest('queue', options) if request['error'] is False: xbmc.log("Crunchyroll.bundle ----> A valid session was detected. Using existing session_id of: "+ str(userData['session_id'])) #Call for Usage Reporting if current_datetime > userData['lastreported']: userData['lastreported'] = (current_datetime + dateutil.relativedelta.relativedelta( hours = +24 )) self.userData = userData self.usage_reporting() #Verify user is premium if userData['premium_type'] in 'anime|drama|manga': xbmc.log("Crunchyroll.bundle ----> User is a premium "+str(userData['premium_type'])+" member.") self.userData = userData userData.close() return True else: xbmc.log("Crunchyroll.bundle ----> User is not premium.") xbmc.executebuiltin('Notification('+notice_msg+','+acc_type_error+',5000)') self.userData = userData = None userData.close() crunchy_main.UI().addItem({'Title':acc_type_error, 'mode':'Fail'}) crunchy_main.UI().endofdirectory('none') return False elif request['error'] is True: xbmc.log("Crunchyroll.bundle ----> Something in the login process went wrong.") del userData['session_id'] del userData['auth_expires'] del userData['premium_type'] del userData['auth_token'] del userData['session_expires'] self.userData = userData userData.close() return False #This is here as a catch all in case something gets messed up along the way. Remove userData variables so we start a new session next time around. else: del userData['session_id'] del userData['auth_expires'] del userData['premium_type'] del userData['auth_token'] del userData['session_expires'] xbmc.log("Crunchyroll.bundle ----> Something in the login process went wrong.") self.userData = userData userData.close() return False