code time
This commit is contained in:
		
							parent
							
								
									789a5e0b02
								
							
						
					
					
						commit
						ae28da8d60
					
				
					 153 changed files with 56768 additions and 1 deletions
				
			
		
							
								
								
									
										4
									
								
								resources/lib/deps/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								resources/lib/deps/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| import os | ||||
| import sys | ||||
| 
 | ||||
| sys.path.insert(1, os.path.join(os.path.dirname(__file__))) | ||||
							
								
								
									
										4417
									
								
								resources/lib/deps/bottle.py
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										4417
									
								
								resources/lib/deps/bottle.py
									
										
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										307
									
								
								resources/lib/deps/simplecache.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										307
									
								
								resources/lib/deps/simplecache.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,307 @@ | |||
| #!/usr/bin/python | ||||
| # -*- coding: utf-8 -*- | ||||
| 
 | ||||
| '''provides a simple stateless caching system for Kodi addons and plugins''' | ||||
| 
 | ||||
| import sys | ||||
| import xbmcvfs | ||||
| import xbmcgui | ||||
| import xbmc | ||||
| import xbmcaddon | ||||
| import datetime | ||||
| import time | ||||
| import sqlite3 | ||||
| import json | ||||
| from functools import reduce | ||||
| 
 | ||||
| class SimpleCache(object): | ||||
|     '''simple stateless caching system for Kodi''' | ||||
|     enable_mem_cache = True | ||||
|     data_is_json = False | ||||
|     global_checksum = None | ||||
|     _exit = False | ||||
|     _auto_clean_interval = datetime.timedelta(hours=4) | ||||
|     _win = None | ||||
|     _busy_tasks = [] | ||||
|     _database = None | ||||
| 
 | ||||
|     def __init__(self, addon_id): | ||||
|         '''Initialize our caching class''' | ||||
|         self.addon_id = addon_id | ||||
|         self._win = xbmcgui.Window(10000) | ||||
|         self._monitor = xbmc.Monitor() | ||||
|         self.check_cleanup() | ||||
|         self._log_msg("Initialized") | ||||
| 
 | ||||
|     def close(self): | ||||
|         '''tell any tasks to stop immediately (as we can be called multithreaded) and cleanup objects''' | ||||
|         self._exit = True | ||||
|         # wait for all tasks to complete | ||||
|         while self._busy_tasks and not self._monitor.abortRequested(): | ||||
|             xbmc.sleep(25) | ||||
|         del self._win | ||||
|         del self._monitor | ||||
|         self._log_msg("Closed") | ||||
| 
 | ||||
|     def __del__(self): | ||||
|         '''make sure close is called''' | ||||
|         if not self._exit: | ||||
|             self.close() | ||||
| 
 | ||||
|     def get(self, endpoint, checksum="", json_data=False): | ||||
|         ''' | ||||
|             get object from cache and return the results | ||||
|             endpoint: the (unique) name of the cache object as reference | ||||
|             checkum: optional argument to check if the checksum in the cacheobject matches the checkum provided | ||||
|         ''' | ||||
|         checksum = self._get_checksum(checksum) | ||||
|         cur_time = self._get_timestamp(datetime.datetime.now()) | ||||
|         result = None | ||||
|         # 1: try memory cache first | ||||
|         if self.enable_mem_cache: | ||||
|             result = self._get_mem_cache(endpoint, checksum, cur_time, json_data) | ||||
| 
 | ||||
|         # 2: fallback to _database cache | ||||
|         if result is None: | ||||
|             result = self._get_db_cache(endpoint, checksum, cur_time, json_data) | ||||
| 
 | ||||
|         return result | ||||
| 
 | ||||
|     def set(self, endpoint, data, checksum="", expiration=datetime.timedelta(days=30), json_data=False): | ||||
|         ''' | ||||
|             set data in cache | ||||
|         ''' | ||||
|         task_name = "set.%s" % endpoint | ||||
|         self._busy_tasks.append(task_name) | ||||
|         checksum = self._get_checksum(checksum) | ||||
|         expires = self._get_timestamp(datetime.datetime.now() + expiration) | ||||
| 
 | ||||
|         # memory cache: write to window property | ||||
|         if self.enable_mem_cache and not self._exit: | ||||
|             self._set_mem_cache(endpoint, checksum, expires, data, json_data) | ||||
| 
 | ||||
|         # db cache | ||||
|         if not self._exit: | ||||
|             self._set_db_cache(endpoint, checksum, expires, data, json_data) | ||||
| 
 | ||||
|         # remove this task from list | ||||
|         self._busy_tasks.remove(task_name) | ||||
| 
 | ||||
|     def check_cleanup(self): | ||||
|         '''check if cleanup is needed - public method, may be called by calling addon''' | ||||
|         cur_time = datetime.datetime.now() | ||||
|         lastexecuted = self._win.getProperty("simplecache.clean.lastexecuted") | ||||
|         if not lastexecuted: | ||||
|             self._win.setProperty("simplecache.clean.lastexecuted", repr(cur_time)) | ||||
|         elif (eval(lastexecuted) + self._auto_clean_interval) < cur_time: | ||||
|             # cleanup needed... | ||||
|             self._do_cleanup() | ||||
| 
 | ||||
|     def _get_mem_cache(self, endpoint, checksum, cur_time, json_data): | ||||
|         ''' | ||||
|             get cache data from memory cache | ||||
|             we use window properties because we need to be stateless | ||||
|         ''' | ||||
|         result = None | ||||
|         cachedata = self._win.getProperty(endpoint) | ||||
| 
 | ||||
|         if cachedata: | ||||
|             if json_data or self.data_is_json: | ||||
|                 cachedata = json.loads(cachedata) | ||||
|             else: | ||||
|                 cachedata = eval(cachedata) | ||||
|             if cachedata[0] > cur_time: | ||||
|                 if not checksum or checksum == cachedata[2]: | ||||
|                     result = cachedata[1] | ||||
|         return result | ||||
| 
 | ||||
|     def _set_mem_cache(self, endpoint, checksum, expires, data, json_data): | ||||
|         ''' | ||||
|             window property cache as alternative for memory cache | ||||
|             usefull for (stateless) plugins | ||||
|         ''' | ||||
|         cachedata = (expires, data, checksum) | ||||
|         if json_data or self.data_is_json: | ||||
|             cachedata_str = json.dumps(cachedata) | ||||
|         else: | ||||
|             cachedata_str = repr(cachedata) | ||||
|         self._win.setProperty(endpoint, cachedata_str) | ||||
| 
 | ||||
| 
 | ||||
|     def _get_db_cache(self, endpoint, checksum, cur_time, json_data): | ||||
|         '''get cache data from sqllite _database''' | ||||
|         result = None | ||||
|         query = "SELECT expires, data, checksum FROM simplecache WHERE id = ?" | ||||
|         cache_data = self._execute_sql(query, (endpoint,)) | ||||
|         if cache_data: | ||||
|             cache_data = cache_data.fetchone() | ||||
|             if cache_data and cache_data[0] > cur_time: | ||||
|                 if not checksum or cache_data[2] == checksum: | ||||
|                     if json_data or self.data_is_json: | ||||
|                         result = json.loads(cache_data[1]) | ||||
|                     else: | ||||
|                         result = eval(cache_data[1]) | ||||
|                     # also set result in memory cache for further access | ||||
|                     if self.enable_mem_cache: | ||||
|                         self._set_mem_cache(endpoint, checksum, cache_data[0], result, json_data) | ||||
|         return result | ||||
| 
 | ||||
|     def _set_db_cache(self, endpoint, checksum, expires, data, json_data): | ||||
|         ''' store cache data in _database ''' | ||||
|         query = "INSERT OR REPLACE INTO simplecache( id, expires, data, checksum) VALUES (?, ?, ?, ?)" | ||||
|         if json_data or self.data_is_json: | ||||
|             data = json.dumps(data) | ||||
|         else: | ||||
|             data = repr(data) | ||||
|         self._execute_sql(query, (endpoint, expires, data, checksum)) | ||||
| 
 | ||||
|     def _do_cleanup(self): | ||||
|         '''perform cleanup task''' | ||||
|         if self._exit or self._monitor.abortRequested(): | ||||
|             return | ||||
|         self._busy_tasks.append(__name__) | ||||
|         cur_time = datetime.datetime.now() | ||||
|         cur_timestamp = self._get_timestamp(cur_time) | ||||
|         self._log_msg("Running cleanup...") | ||||
|         if self._win.getProperty("simplecachecleanbusy"): | ||||
|             return | ||||
|         self._win.setProperty("simplecachecleanbusy", "busy") | ||||
| 
 | ||||
|         query = "SELECT id, expires FROM simplecache" | ||||
|         for cache_data in self._execute_sql(query).fetchall(): | ||||
|             cache_id = cache_data[0] | ||||
|             cache_expires = cache_data[1] | ||||
| 
 | ||||
|             if self._exit or self._monitor.abortRequested(): | ||||
|                 return | ||||
| 
 | ||||
|             # always cleanup all memory objects on each interval | ||||
|             self._win.clearProperty(cache_id) | ||||
| 
 | ||||
|             # clean up db cache object only if expired | ||||
|             if cache_expires < cur_timestamp: | ||||
|                 query = 'DELETE FROM simplecache WHERE id = ?' | ||||
|                 self._execute_sql(query, (cache_id,)) | ||||
|                 self._log_msg("delete from db %s" % cache_id) | ||||
| 
 | ||||
|         # compact db | ||||
|         self._execute_sql("VACUUM") | ||||
| 
 | ||||
|         # remove task from list | ||||
|         self._busy_tasks.remove(__name__) | ||||
|         self._win.setProperty("simplecache.clean.lastexecuted", repr(cur_time)) | ||||
|         self._win.clearProperty("simplecachecleanbusy") | ||||
|         self._log_msg("Auto cleanup done") | ||||
| 
 | ||||
|     def _get_database(self): | ||||
|         '''get reference to our sqllite _database - performs basic integrity check''' | ||||
|         addon = xbmcaddon.Addon(self.addon_id) | ||||
|         dbpath = addon.getAddonInfo('profile') | ||||
|         dbfile = xbmcvfs.translatePath("%s/simplecache.db" % dbpath) | ||||
| 
 | ||||
|         if not xbmcvfs.exists(dbpath): | ||||
|             xbmcvfs.mkdirs(dbpath) | ||||
|         del addon | ||||
|         try: | ||||
|             connection = sqlite3.connect(dbfile, timeout=30, isolation_level=None) | ||||
|             connection.execute('SELECT * FROM simplecache LIMIT 1') | ||||
|             return connection | ||||
|         except Exception as error: | ||||
|             # our _database is corrupt or doesn't exist yet, we simply try to recreate it | ||||
|             if xbmcvfs.exists(dbfile): | ||||
|                 xbmcvfs.delete(dbfile) | ||||
|             try: | ||||
|                 connection = sqlite3.connect(dbfile, timeout=30, isolation_level=None) | ||||
|                 connection.execute( | ||||
|                     """CREATE TABLE IF NOT EXISTS simplecache( | ||||
|                     id TEXT UNIQUE, expires INTEGER, data TEXT, checksum INTEGER)""") | ||||
|                 return connection | ||||
|             except Exception as error: | ||||
|                 self._log_msg("Exception while initializing _database: %s" % str(error), xbmc.LOGWARNING) | ||||
|                 self.close() | ||||
|                 return None | ||||
| 
 | ||||
|     def _execute_sql(self, query, data=None): | ||||
|         '''little wrapper around execute and executemany to just retry a db command if db is locked''' | ||||
|         retries = 0 | ||||
|         result = None | ||||
|         error = None | ||||
|         # always use new db object because we need to be sure that data is available for other simplecache instances | ||||
|         with self._get_database() as _database: | ||||
|             while not retries == 10 and not self._monitor.abortRequested(): | ||||
|                 if self._exit: | ||||
|                     return None | ||||
|                 try: | ||||
|                     if isinstance(data, list): | ||||
|                         result = _database.executemany(query, data) | ||||
|                     elif data: | ||||
|                         result = _database.execute(query, data) | ||||
|                     else: | ||||
|                         result = _database.execute(query) | ||||
|                     return result | ||||
|                 except sqlite3.OperationalError as error: | ||||
|                     if "_database is locked" in error: | ||||
|                         self._log_msg("retrying DB commit...") | ||||
|                         retries += 1 | ||||
|                         self._monitor.waitForAbort(0.5) | ||||
|                     else: | ||||
|                         break | ||||
|                 except Exception as error: | ||||
|                     break | ||||
|             self._log_msg("_database ERROR ! -- %s" % str(error), xbmc.LOGWARNING) | ||||
|         return None | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def _log_msg(msg, loglevel=xbmc.LOGDEBUG): | ||||
|         '''helper to send a message to the kodi log''' | ||||
|         xbmc.log("Skin Helper Simplecache --> %s" % msg, level=loglevel) | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def _get_timestamp(date_time): | ||||
|         '''Converts a datetime object to unix timestamp''' | ||||
|         return int(time.mktime(date_time.timetuple())) | ||||
| 
 | ||||
|     def _get_checksum(self, stringinput): | ||||
|         '''get int checksum from string''' | ||||
|         if not stringinput and not self.global_checksum: | ||||
|             return 0 | ||||
|         if self.global_checksum: | ||||
|             stringinput = "%s-%s" %(self.global_checksum, stringinput) | ||||
|         else: | ||||
|             stringinput = str(stringinput) | ||||
|         return reduce(lambda x, y: x + y, map(ord, stringinput)) | ||||
| 
 | ||||
| 
 | ||||
| def use_cache(cache_days=14): | ||||
|     ''' | ||||
|         wrapper around our simple cache to use as decorator | ||||
|         Usage: define an instance of SimpleCache with name "cache" (self.cache) in your class | ||||
|         Any method that needs caching just add @use_cache as decorator | ||||
|         NOTE: use unnamed arguments for calling the method and named arguments for optional settings | ||||
|     ''' | ||||
|     def decorator(func): | ||||
|         '''our decorator''' | ||||
|         def decorated(*args, **kwargs): | ||||
|             '''process the original method and apply caching of the results''' | ||||
|             method_class = args[0] | ||||
|             method_class_name = method_class.__class__.__name__ | ||||
|             cache_str = "%s.%s" % (method_class_name, func.__name__) | ||||
|             # cache identifier is based on positional args only | ||||
|             # named args are considered optional and ignored | ||||
|             for item in args[1:]: | ||||
|                 cache_str += u".%s" % item | ||||
|             cache_str = cache_str.lower() | ||||
|             cachedata = method_class.cache.get(cache_str) | ||||
|             global_cache_ignore = False | ||||
|             try: | ||||
|                 global_cache_ignore = method_class.ignore_cache | ||||
|             except Exception: | ||||
|                 pass | ||||
|             if cachedata is not None and not kwargs.get("ignore_cache", False) and not global_cache_ignore: | ||||
|                 return cachedata | ||||
|             else: | ||||
|                 result = func(*args, **kwargs) | ||||
|                 method_class.cache.set(cache_str, result, expiration=datetime.timedelta(days=cache_days)) | ||||
|                 return result | ||||
|         return decorated | ||||
|     return decorator | ||||
							
								
								
									
										5
									
								
								resources/lib/deps/spotipy/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								resources/lib/deps/spotipy/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| from .cache_handler import *  # noqa | ||||
| from .client import *  # noqa | ||||
| from .exceptions import *  # noqa | ||||
| from .oauth2 import *  # noqa | ||||
| from .util import *  # noqa | ||||
							
								
								
									
										173
									
								
								resources/lib/deps/spotipy/cache_handler.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								resources/lib/deps/spotipy/cache_handler.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,173 @@ | |||
| __all__ = [ | ||||
|     'CacheHandler', | ||||
|     'CacheFileHandler', | ||||
|     'DjangoSessionCacheHandler', | ||||
|     'FlaskSessionCacheHandler', | ||||
|     'MemoryCacheHandler'] | ||||
| 
 | ||||
| import errno | ||||
| import json | ||||
| import logging | ||||
| import os | ||||
| from spotipy.util import CLIENT_CREDS_ENV_VARS | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class CacheHandler(): | ||||
|     """ | ||||
|     An abstraction layer for handling the caching and retrieval of | ||||
|     authorization tokens. | ||||
| 
 | ||||
|     Custom extensions of this class must implement get_cached_token | ||||
|     and save_token_to_cache methods with the same input and output | ||||
|     structure as the CacheHandler class. | ||||
|     """ | ||||
| 
 | ||||
|     def get_cached_token(self): | ||||
|         """ | ||||
|         Get and return a token_info dictionary object. | ||||
|         """ | ||||
|         # return token_info | ||||
|         raise NotImplementedError() | ||||
| 
 | ||||
|     def save_token_to_cache(self, token_info): | ||||
|         """ | ||||
|         Save a token_info dictionary object to the cache and return None. | ||||
|         """ | ||||
|         raise NotImplementedError() | ||||
|         return None | ||||
| 
 | ||||
| 
 | ||||
| class CacheFileHandler(CacheHandler): | ||||
|     """ | ||||
|     Handles reading and writing cached Spotify authorization tokens | ||||
|     as json files on disk. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, | ||||
|                  cache_path=None, | ||||
|                  username=None, | ||||
|                  encoder_cls=None): | ||||
|         """ | ||||
|         Parameters: | ||||
|              * cache_path: May be supplied, will otherwise be generated | ||||
|                            (takes precedence over `username`) | ||||
|              * username: May be supplied or set as environment variable | ||||
|                          (will set `cache_path` to `.cache-{username}`) | ||||
|              * encoder_cls: May be supplied as a means of overwriting the | ||||
|                         default serializer used for writing tokens to disk | ||||
|         """ | ||||
|         self.encoder_cls = encoder_cls | ||||
|         if cache_path: | ||||
|             self.cache_path = cache_path | ||||
|         else: | ||||
|             cache_path = ".cache" | ||||
|             username = (username or os.getenv(CLIENT_CREDS_ENV_VARS["client_username"])) | ||||
|             if username: | ||||
|                 cache_path += "-" + str(username) | ||||
|             self.cache_path = cache_path | ||||
| 
 | ||||
|     def get_cached_token(self): | ||||
|         token_info = None | ||||
| 
 | ||||
|         try: | ||||
|             f = open(self.cache_path) | ||||
|             token_info_string = f.read() | ||||
|             f.close() | ||||
|             token_info = json.loads(token_info_string) | ||||
| 
 | ||||
|         except IOError as error: | ||||
|             if error.errno == errno.ENOENT: | ||||
|                 logger.debug("cache does not exist at: %s", self.cache_path) | ||||
|             else: | ||||
|                 logger.warning("Couldn't read cache at: %s", self.cache_path) | ||||
| 
 | ||||
|         return token_info | ||||
| 
 | ||||
|     def save_token_to_cache(self, token_info): | ||||
|         try: | ||||
|             f = open(self.cache_path, "w") | ||||
|             f.write(json.dumps(token_info, cls=self.encoder_cls)) | ||||
|             f.close() | ||||
|         except IOError: | ||||
|             logger.warning('Couldn\'t write token to cache at: %s', | ||||
|                            self.cache_path) | ||||
| 
 | ||||
| 
 | ||||
| class MemoryCacheHandler(CacheHandler): | ||||
|     """ | ||||
|     A cache handler that simply stores the token info in memory as an | ||||
|     instance attribute of this class. The token info will be lost when this | ||||
|     instance is freed. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, token_info=None): | ||||
|         """ | ||||
|         Parameters: | ||||
|             * token_info: The token info to store in memory. Can be None. | ||||
|         """ | ||||
|         self.token_info = token_info | ||||
| 
 | ||||
|     def get_cached_token(self): | ||||
|         return self.token_info | ||||
| 
 | ||||
|     def save_token_to_cache(self, token_info): | ||||
|         self.token_info = token_info | ||||
| 
 | ||||
| 
 | ||||
| class DjangoSessionCacheHandler(CacheHandler): | ||||
|     """ | ||||
|     A cache handler that stores the token info in the session framework | ||||
|     provided by Django. | ||||
| 
 | ||||
|     Read more at https://docs.djangoproject.com/en/3.2/topics/http/sessions/ | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, request): | ||||
|         """ | ||||
|         Parameters: | ||||
|             * request: HttpRequest object provided by Django for every | ||||
|             incoming request | ||||
|         """ | ||||
|         self.request = request | ||||
| 
 | ||||
|     def get_cached_token(self): | ||||
|         token_info = None | ||||
|         try: | ||||
|             token_info = self.request.session['token_info'] | ||||
|         except KeyError: | ||||
|             logger.debug("Token not found in the session") | ||||
| 
 | ||||
|         return token_info | ||||
| 
 | ||||
|     def save_token_to_cache(self, token_info): | ||||
|         try: | ||||
|             self.request.session['token_info'] = token_info | ||||
|         except Exception as e: | ||||
|             logger.warning("Error saving token to cache: " + str(e)) | ||||
| 
 | ||||
| 
 | ||||
| class FlaskSessionCacheHandler(CacheHandler): | ||||
|     """ | ||||
|     A cache handler that stores the token info in the session framework | ||||
|     provided by flask. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, session): | ||||
|         self.session = session | ||||
| 
 | ||||
|     def get_cached_token(self): | ||||
|         token_info = None | ||||
|         try: | ||||
|             token_info = self.session["token_info"] | ||||
|         except KeyError: | ||||
|             logger.debug("Token not found in the session") | ||||
| 
 | ||||
|         return token_info | ||||
| 
 | ||||
|     def save_token_to_cache(self, token_info): | ||||
|         try: | ||||
|             self.session["token_info"] = token_info | ||||
|         except Exception as e: | ||||
|             logger.warning("Error saving token to cache: " + str(e)) | ||||
							
								
								
									
										2035
									
								
								resources/lib/deps/spotipy/client.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2035
									
								
								resources/lib/deps/spotipy/client.py
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										16
									
								
								resources/lib/deps/spotipy/exceptions.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								resources/lib/deps/spotipy/exceptions.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| class SpotifyException(Exception): | ||||
| 
 | ||||
|     def __init__(self, http_status, code, msg, reason=None, headers=None): | ||||
|         self.http_status = http_status | ||||
|         self.code = code | ||||
|         self.msg = msg | ||||
|         self.reason = reason | ||||
|         # `headers` is used to support `Retry-After` in the event of a | ||||
|         # 429 status code. | ||||
|         if headers is None: | ||||
|             headers = {} | ||||
|         self.headers = headers | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         return 'http status: {0}, code:{1} - {2}, reason: {3}'.format( | ||||
|             self.http_status, self.code, self.msg, self.reason) | ||||
							
								
								
									
										1308
									
								
								resources/lib/deps/spotipy/oauth2.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1308
									
								
								resources/lib/deps/spotipy/oauth2.py
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										135
									
								
								resources/lib/deps/spotipy/util.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								resources/lib/deps/spotipy/util.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,135 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| 
 | ||||
| """ Shows a user's playlists (need to be authenticated via oauth) """ | ||||
| 
 | ||||
| __all__ = ["CLIENT_CREDS_ENV_VARS", "prompt_for_user_token"] | ||||
| 
 | ||||
| import logging | ||||
| import os | ||||
| import warnings | ||||
| 
 | ||||
| import spotipy | ||||
| 
 | ||||
| LOGGER = logging.getLogger(__name__) | ||||
| 
 | ||||
| CLIENT_CREDS_ENV_VARS = { | ||||
|     "client_id": "SPOTIPY_CLIENT_ID", | ||||
|     "client_secret": "SPOTIPY_CLIENT_SECRET", | ||||
|     "client_username": "SPOTIPY_CLIENT_USERNAME", | ||||
|     "redirect_uri": "SPOTIPY_REDIRECT_URI", | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| def prompt_for_user_token( | ||||
|     username=None, | ||||
|     scope=None, | ||||
|     client_id=None, | ||||
|     client_secret=None, | ||||
|     redirect_uri=None, | ||||
|     cache_path=None, | ||||
|     oauth_manager=None, | ||||
|     show_dialog=False | ||||
| ): | ||||
|     warnings.warn( | ||||
|         "'prompt_for_user_token' is deprecated." | ||||
|         "Use the following instead: " | ||||
|         "    auth_manager=SpotifyOAuth(scope=scope)" | ||||
|         "    spotipy.Spotify(auth_manager=auth_manager)", | ||||
|         DeprecationWarning | ||||
|     ) | ||||
|     """ prompts the user to login if necessary and returns | ||||
|         the user token suitable for use with the spotipy.Spotify | ||||
|         constructor | ||||
| 
 | ||||
|         Parameters: | ||||
| 
 | ||||
|          - username - the Spotify username (optional) | ||||
|          - scope - the desired scope of the request (optional) | ||||
|          - client_id - the client id of your app (required) | ||||
|          - client_secret - the client secret of your app (required) | ||||
|          - redirect_uri - the redirect URI of your app (required) | ||||
|          - cache_path - path to location to save tokens (optional) | ||||
|          - oauth_manager - Oauth manager object (optional) | ||||
|          - show_dialog - If true, a login prompt always shows (optional, defaults to False) | ||||
| 
 | ||||
|     """ | ||||
|     if not oauth_manager: | ||||
|         if not client_id: | ||||
|             client_id = os.getenv("SPOTIPY_CLIENT_ID") | ||||
| 
 | ||||
|         if not client_secret: | ||||
|             client_secret = os.getenv("SPOTIPY_CLIENT_SECRET") | ||||
| 
 | ||||
|         if not redirect_uri: | ||||
|             redirect_uri = os.getenv("SPOTIPY_REDIRECT_URI") | ||||
| 
 | ||||
|         if not client_id: | ||||
|             LOGGER.warning( | ||||
|                 """ | ||||
|                 You need to set your Spotify API credentials. | ||||
|                 You can do this by setting environment variables like so: | ||||
| 
 | ||||
|                 export SPOTIPY_CLIENT_ID='your-spotify-client-id' | ||||
|                 export SPOTIPY_CLIENT_SECRET='your-spotify-client-secret' | ||||
|                 export SPOTIPY_REDIRECT_URI='your-app-redirect-url' | ||||
| 
 | ||||
|                 Get your credentials at | ||||
|                     https://developer.spotify.com/my-applications | ||||
|             """ | ||||
|             ) | ||||
|             raise spotipy.SpotifyException(550, -1, "no credentials set") | ||||
| 
 | ||||
|     sp_oauth = oauth_manager or spotipy.SpotifyOAuth( | ||||
|         client_id, | ||||
|         client_secret, | ||||
|         redirect_uri, | ||||
|         scope=scope, | ||||
|         cache_path=cache_path, | ||||
|         username=username, | ||||
|         show_dialog=show_dialog | ||||
|     ) | ||||
| 
 | ||||
|     # try to get a valid token for this user, from the cache, | ||||
|     # if not in the cache, then create a new (this will send | ||||
|     # the user to a web page where they can authorize this app) | ||||
| 
 | ||||
|     token_info = sp_oauth.validate_token(sp_oauth.cache_handler.get_cached_token()) | ||||
| 
 | ||||
|     if not token_info: | ||||
|         code = sp_oauth.get_auth_response() | ||||
|         token = sp_oauth.get_access_token(code, as_dict=False) | ||||
|     else: | ||||
|         return token_info["access_token"] | ||||
| 
 | ||||
|     # Auth'ed API request | ||||
|     if token: | ||||
|         return token | ||||
|     else: | ||||
|         return None | ||||
| 
 | ||||
| 
 | ||||
| def get_host_port(netloc): | ||||
|     if ":" in netloc: | ||||
|         host, port = netloc.split(":", 1) | ||||
|         port = int(port) | ||||
|     else: | ||||
|         host = netloc | ||||
|         port = None | ||||
| 
 | ||||
|     return host, port | ||||
| 
 | ||||
| 
 | ||||
| def normalize_scope(scope): | ||||
|     if scope: | ||||
|         if isinstance(scope, str): | ||||
|             scopes = scope.split(',') | ||||
|         elif isinstance(scope, list) or isinstance(scope, tuple): | ||||
|             scopes = scope | ||||
|         else: | ||||
|             raise Exception( | ||||
|                 "Unsupported scope value, please either provide a list of scopes, " | ||||
|                 "or a string of scopes separated by commas" | ||||
|             ) | ||||
|         return " ".join(sorted(scopes)) | ||||
|     else: | ||||
|         return None | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue