def test_preserve_last_key_case(self): cid = CaseInsensitiveDict({"Accept": "application/json", "user-Agent": "requests"}) cid.update({"ACCEPT": "application/json"}) cid["USER-AGENT"] = "requests" keyset = frozenset(["ACCEPT", "USER-AGENT"]) assert frozenset(i[0] for i in cid.items()) == keyset assert frozenset(cid.keys()) == keyset assert frozenset(cid) == keyset
def test_preserve_key_case(self): cid = CaseInsensitiveDict({ 'Accept': 'application/json', 'user-Agent': 'requests', }) keyset = frozenset(['Accept', 'user-Agent']) assert frozenset(i[0] for i in cid.items()) == keyset assert frozenset(cid.keys()) == keyset assert frozenset(cid) == keyset
def test_preserve_key_case(self): cid = CaseInsensitiveDict({ 'Accept': 'application/json', 'user-Agent': 'requests', }) keyset = frozenset(['Accept', 'user-Agent']) assert frozenset(i[0] for i in cid.items()) == keyset assert frozenset(cid.keys()) == keyset assert frozenset(cid) == keyset
def test_fixes_649(self): """__setitem__ should behave case-insensitively.""" cid = CaseInsensitiveDict() cid['spam'] = 'oneval' cid['Spam'] = 'twoval' cid['sPAM'] = 'redval' cid['SPAM'] = 'blueval' assert cid['spam'] == 'blueval' assert cid['SPAM'] == 'blueval' assert list(cid.keys()) == ['SPAM']
def test_fixes_649(self): """__setitem__ should behave case-insensitively.""" cid = CaseInsensitiveDict() cid["spam"] = "oneval" cid["Spam"] = "twoval" cid["sPAM"] = "redval" cid["SPAM"] = "blueval" assert cid["spam"] == "blueval" assert cid["SPAM"] == "blueval" assert list(cid.keys()) == ["SPAM"]
def test_fixes_649(self): """__setitem__ should behave case-insensitively.""" cid = CaseInsensitiveDict() cid['spam'] = 'oneval' cid['Spam'] = 'twoval' cid['sPAM'] = 'redval' cid['SPAM'] = 'blueval' assert cid['spam'] == 'blueval' assert cid['SPAM'] == 'blueval' assert list(cid.keys()) == ['SPAM']
def __init__(self, response=None, status=None, headers=None, mimetype=None, content_type=None, direct_passthrough=False): headers = CaseInsensitiveDict(headers) if headers is not None else None if response is not None and isinstance( response, BaseResponse) and response.headers is not None: headers = CaseInsensitiveDict(response.headers) if headers is None: headers = CaseInsensitiveDict() h = headers h['Access-Control-Allow-Origin'] = headers.get( 'Access-Control-Allow-Origin', '*') h['Access-Control-Allow-Methods'] = headers.get( 'Access-Control-Allow-Methods', "GET, PUT, POST, HEAD, OPTIONS, DELETE") h['Access-Control-Max-Age'] = headers.get('Access-Control-Max-Age', "21600") h['Cache-Control'] = headers.get( 'Cache-Control', "no-cache, must-revalidate, no-store") if 'Access-Control-Allow-Headers' not in headers and len( headers.keys()) > 0: h['Access-Control-Allow-Headers'] = ', '.join(iterkeys(headers)) data = None if response is not None and isinstance(response, string_types): data = response response = None if response is not None and isinstance(response, BaseResponse): new_response_headers = CaseInsensitiveDict( response.headers if response.headers is not None else {}) new_response_headers.update(h) response.headers = new_response_headers headers = None data = response.get_data() else: headers.update(h) headers = dict(headers) super(IppResponse, self).__init__(response=response, status=status, headers=headers, mimetype=mimetype, content_type=content_type, direct_passthrough=direct_passthrough) if data is not None: self.set_data(data)
def test_preserve_last_key_case(self): cid = CaseInsensitiveDict({ 'Accept': 'application/json', 'user-Agent': 'requests', }) cid.update({'ACCEPT': 'application/json'}) cid['USER-AGENT'] = 'requests' keyset = frozenset(['ACCEPT', 'USER-AGENT']) assert frozenset(i[0] for i in cid.items()) == keyset assert frozenset(cid.keys()) == keyset assert frozenset(cid) == keyset
def test_preserve_last_key_case(self): cid = CaseInsensitiveDict({ 'Accept': 'application/json', 'user-Agent': 'requests', }) cid.update({'ACCEPT': 'application/json'}) cid['USER-AGENT'] = 'requests' keyset = frozenset(['ACCEPT', 'USER-AGENT']) assert frozenset(i[0] for i in cid.items()) == keyset assert frozenset(cid.keys()) == keyset assert frozenset(cid) == keyset
def test_inject_reported_fields_matches_carrier_fields(self): carrier = CaseInsensitiveDict() AwsXRayPropagatorTest.XRAY_PROPAGATOR.inject( carrier, build_test_current_context(), ) injected_keys = set(carrier.keys()) self.assertEqual(injected_keys, AwsXRayPropagatorTest.XRAY_PROPAGATOR.fields)
def dict_subset(input_list, dic_data: CaseInsensitiveDict) -> CaseInsensitiveDict: for name in input_list: if name not in [key.strip().lower() for key in dic_data.keys()]: logging.warning( f"provided entry with the name - {name} is not " f"valid The program will disregard this input") input_list.remove(name) sub_data = { key: value for key, value in dic_data.items() if key.strip().lower() in [item.strip().lower() for item in input_list] } return CaseInsensitiveDict(data=sub_data)
def set_next_safe_time(self, headers: CaseInsensitiveDict) -> None: # type: ignore """ Parse returned headers to get weight. The binance API has weights on endpoints and that value differs for different users. This function parses headers and gets the weight value. :param headers: API response headers. """ weight_regex = re.compile( r"X-SAPI-USED-IP-WEIGHT-(?P<num>\d+)(?P<period>[SMHD])", ) period_mapping = {"S": "seconds", "M": "minutes", "H": "hours", "D": "days"} for header_name in headers.keys(): match = weight_regex.match(header_name) if match: delta_param = { period_mapping[match.group("period")]: int(match.group("num")), } self._next_query_time = datetime.now() + timedelta(**delta_param) return
class StateMachine(object): """ Helper class that tracks the state of different entities. """ def __init__(self, bus): self._states = CaseInsensitiveDict() self._bus = bus self._lock = threading.Lock() def entity_ids(self, domain_filter=None): """ List of entity ids that are being tracked. """ if domain_filter is not None: domain_filter = domain_filter.lower() return [ state.entity_id for key, state in self._states.lower_items() if util.split_entity_id(key)[0] == domain_filter ] else: return list(self._states.keys()) def all(self): """ Returns a list of all states. """ return [state.copy() for state in self._states.values()] def get(self, entity_id): """ Returns the state of the specified entity. """ state = self._states.get(entity_id) # Make a copy so people won't mutate the state return state.copy() if state else None def get_since(self, point_in_time): """ Returns all states that have been changed since point_in_time. """ point_in_time = util.strip_microseconds(point_in_time) with self._lock: return [ state for state in self._states.values() if state.last_updated >= point_in_time ] def is_state(self, entity_id, state): """ Returns True if entity exists and is specified state. """ return (entity_id in self._states and self._states[entity_id].state == state) def remove(self, entity_id): """ Removes an entity from the state machine. Returns boolean to indicate if an entity was removed. """ with self._lock: return self._states.pop(entity_id, None) is not None def set(self, entity_id, new_state, attributes=None): """ Set the state of an entity, add entity if it does not exist. Attributes is an optional dict to specify attributes of this state. If you just update the attributes and not the state, last changed will not be affected. """ new_state = str(new_state) attributes = attributes or {} with self._lock: old_state = self._states.get(entity_id) is_existing = old_state is not None same_state = is_existing and old_state.state == new_state same_attr = is_existing and old_state.attributes == attributes # If state did not exist or is different, set it if not (same_state and same_attr): last_changed = old_state.last_changed if same_state else None state = self._states[entity_id] = \ State(entity_id, new_state, attributes, last_changed) event_data = {'entity_id': entity_id, 'new_state': state} if old_state: event_data['old_state'] = old_state self._bus.fire(EVENT_STATE_CHANGED, event_data) def track_change(self, entity_ids, action, from_state=None, to_state=None): """ Track specific state changes. entity_ids, from_state and to_state can be string or list. Use list to match multiple. Returns the listener that listens on the bus for EVENT_STATE_CHANGED. Pass the return value into hass.bus.remove_listener to remove it. """ from_state = _process_match_param(from_state) to_state = _process_match_param(to_state) # Ensure it is a lowercase list with entity ids we want to match on if isinstance(entity_ids, str): entity_ids = (entity_ids.lower(), ) else: entity_ids = tuple(entity_id.lower() for entity_id in entity_ids) @ft.wraps(action) def state_listener(event): """ The listener that listens for specific state changes. """ if event.data['entity_id'].lower() in entity_ids and \ 'old_state' in event.data and \ _matcher(event.data['old_state'].state, from_state) and \ _matcher(event.data['new_state'].state, to_state): action(event.data['entity_id'], event.data['old_state'], event.data['new_state']) self._bus.listen(EVENT_STATE_CHANGED, state_listener) return state_listener
def reload_config(self, config): self._superuser = config['authentication'].get('superuser', {}) server_parameters = self.get_server_parameters(config) conf_changed = hba_changed = ident_changed = local_connection_address_changed = pending_restart = False if self._postgresql.state == 'running': changes = CaseInsensitiveDict({p: v for p, v in server_parameters.items() if '.' not in p}) changes.update({p: None for p in self._server_parameters.keys() if not ('.' in p or p in changes)}) if changes: # XXX: query can raise an exception for r in self._postgresql.query(('SELECT name, setting, unit, vartype, context ' + 'FROM pg_catalog.pg_settings ' + ' WHERE pg_catalog.lower(name) IN (' + ', '.join(['%s'] * len(changes)) + ')'), *(k.lower() for k in changes.keys())): if r[4] != 'internal' and r[0] in changes: new_value = changes.pop(r[0]) if new_value is None or not compare_values(r[3], r[2], r[1], new_value): if r[4] == 'postmaster': pending_restart = True logger.info('Changed %s from %s to %s (restart required)', r[0], r[1], new_value) if config.get('use_unix_socket') and r[0] == 'unix_socket_directories'\ or r[0] in ('listen_addresses', 'port'): local_connection_address_changed = True else: logger.info('Changed %s from %s to %s', r[0], r[1], new_value) conf_changed = True for param in changes: if param in server_parameters: logger.warning('Removing invalid parameter `%s` from postgresql.parameters', param) server_parameters.pop(param) # Check that user-defined-paramters have changed (parameters with period in name) if not conf_changed: for p, v in server_parameters.items(): if '.' in p and (p not in self._server_parameters or str(v) != str(self._server_parameters[p])): logger.info('Changed %s from %s to %s', p, self._server_parameters.get(p), v) conf_changed = True break if not conf_changed: for p, v in self._server_parameters.items(): if '.' in p and (p not in server_parameters or str(v) != str(server_parameters[p])): logger.info('Changed %s from %s to %s', p, v, server_parameters.get(p)) conf_changed = True break if not server_parameters.get('hba_file') and config.get('pg_hba'): hba_changed = self._config.get('pg_hba', []) != config['pg_hba'] if not server_parameters.get('ident_file') and config.get('pg_ident'): ident_changed = self._config.get('pg_ident', []) != config['pg_ident'] self._config = config self._postgresql.set_pending_restart(pending_restart) self._server_parameters = server_parameters self._adjust_recovery_parameters() self._connect_address = config.get('connect_address') self._krbsrvname = config.get('krbsrvname') # for not so obvious connection attempts that may happen outside of pyscopg2 if self._krbsrvname: os.environ['PGKRBSRVNAME'] = self._krbsrvname if not local_connection_address_changed: self.resolve_connection_addresses() if conf_changed: self.write_postgresql_conf() if hba_changed: self.replace_pg_hba() if ident_changed: self.replace_pg_ident() if conf_changed or hba_changed or ident_changed: logger.info('PostgreSQL configuration items changed, reloading configuration.') self._postgresql.reload() elif not pending_restart: logger.info('No PostgreSQL configuration items changed, nothing to reload.')
def make_request(self, method, bucket, key=None, params=None, data=None, headers=None): # Remove params that are set to None if isinstance(params, dict): for k, v in params.copy().items(): if v is None: params.pop(k) # Construct target url url = 'http://{}.{}'.format(bucket, self.hostname) url += '/{}'.format(key) if key is not None else '/' if isinstance(params, dict) and len(params) > 0: url += '?{}'.format(urllib.urlencode(params)) elif isinstance(params, basestring): url += '?{}'.format(params) # Make headers case insensitive if headers is None: headers = {} headers = CaseInsensitiveDict(headers) headers['Host'] = '{}.{}'.format(bucket, self.hostname) if data is not None: try: raw_md5 = utils.f_md5(data) except: m = hashlib.md5() m.update(data) raw_md5 = m.digest() md5 = b64encode(raw_md5) headers['Content-MD5'] = md5 else: md5 = '' try: content_type = headers['Content-Type'] except KeyError: content_type = '' date = formatdate(timeval=None, localtime=False, usegmt=True) headers['x-amz-date'] = date # Construct canonicalized amz headers string canonicalized_amz_headers = '' amz_keys = [k for k in list(headers.keys()) if k.startswith('x-amz-')] for k in sorted(amz_keys): v = headers[k].strip() canonicalized_amz_headers += '{}:{}\n'.format(k.lower(), v) # Construct canonicalized resource string canonicalized_resource = '/' + bucket canonicalized_resource += '/' if key is None else '/{}'.format(key) if isinstance(params, basestring): canonicalized_resource += '?{}'.format(params) elif isinstance(params, dict) and len(params) > 0: canonicalized_resource += '?{}'.format(urllib.urlencode(params)) # Construct string to sign string_to_sign = method.upper() + '\n' string_to_sign += md5 + '\n' string_to_sign += content_type + '\n' string_to_sign += '\n' # date is always set through x-amz-date string_to_sign += canonicalized_amz_headers + canonicalized_resource # Create signature h = hmac.new(self.secret_access_key, string_to_sign, hashlib.sha1) signature = b64encode(h.digest()) # Set authorization header auth_head = 'AWS {}:{}'.format(self.access_key_id, signature) headers['Authorization'] = auth_head # Prepare Request req = Request(method, url, data=data, headers=headers).prepare() # Log request data. # Prepare request beforehand so requests-altered headers show. # Combine into a single message so we don't have to bother with # locking to make lines appear together. log_message = '{} {}\n'.format(method, url) log_message += 'headers:' for k in sorted(req.headers.keys()): log_message += '\n {}: {}'.format(k, req.headers[k]) log.debug(log_message) # Send request resp = Session().send(req) # Update stats, log response data. self.stats[method.upper()] += 1 log.debug('response: {} ({} {})'.format(resp.status_code, method, url)) # Handle errors if resp.status_code/100 != 2: soup = BeautifulSoup(resp.text) error = soup.find('error') log_message = "S3 replied with non 2xx response code!!!!\n" log_message += ' request: {} {}\n'.format(method, url) for c in error.children: error_name = c.name error_message = c.text.encode('unicode_escape') log_message += ' {}: {}\n'.format(error_name, error_message) log.debug(log_message) code = error.find('code').text message = error.find('message').text raise S3ResponseError(code, message, resp) return resp
class BlinkSyncModule(): """Class to initialize sync module.""" def __init__(self, blink, network_name, network_id, camera_list): """ Initialize Blink sync module. :param blink: Blink class instantiation """ self.blink = blink self._auth_header = blink.auth_header self.network_id = network_id self.region = blink.region self.region_id = blink.region_id self.name = network_name self.serial = None self.status = None self.sync_id = None self.host = None self.summary = None self.network_info = None self.events = [] self.cameras = CaseInsensitiveDict({}) self.motion_interval = blink.motion_interval self.motion = {} self.last_record = {} self.camera_list = camera_list @property def attributes(self): """Return sync attributes.""" attr = { 'name': self.name, 'id': self.sync_id, 'network_id': self.network_id, 'serial': self.serial, 'status': self.status, 'region': self.region, 'region_id': self.region_id, } return attr @property def urls(self): """Return device urls.""" return self.blink.urls @property def online(self): """Return boolean system online status.""" return ONLINE[self.status] @property def arm(self): """Return status of sync module: armed/disarmed.""" try: return self.network_info['network']['armed'] except (KeyError, TypeError): return None @arm.setter def arm(self, value): """Arm or disarm system.""" if value: return api.request_system_arm(self.blink, self.network_id) return api.request_system_disarm(self.blink, self.network_id) def start(self): """Initialize the system.""" response = api.request_syncmodule(self.blink, self.network_id) try: self.summary = response['syncmodule'] self.network_id = self.summary['network_id'] except (TypeError, KeyError): _LOGGER.error(("Could not retrieve sync module information " "with response: %s"), response, exc_info=True) return False try: self.sync_id = self.summary['id'] self.serial = self.summary['serial'] self.status = self.summary['status'] except KeyError: _LOGGER.error("Could not extract some sync module info: %s", response, exc_info=True) self.network_info = api.request_network_status(self.blink, self.network_id) self.check_new_videos() try: for camera_config in self.camera_list: if 'name' not in camera_config: break name = camera_config['name'] self.cameras[name] = BlinkCamera(self) self.motion[name] = False camera_info = self.get_camera_info(camera_config['id']) self.cameras[name].update(camera_info, force_cache=True, force=True) except KeyError: _LOGGER.error("Could not create cameras instances for %s", self.name, exc_info=True) return False return True def get_events(self, **kwargs): """Retrieve events from server.""" force = kwargs.pop('force', False) response = api.request_sync_events(self.blink, self.network_id, force=force) try: return response['event'] except (TypeError, KeyError): _LOGGER.error("Could not extract events: %s", response, exc_info=True) return False def get_camera_info(self, camera_id): """Retrieve camera information.""" response = api.request_camera_info(self.blink, self.network_id, camera_id) try: return response['camera'][0] except (TypeError, KeyError): _LOGGER.error("Could not extract camera info: %s", response, exc_info=True) return [] def refresh(self, force_cache=False): """Get all blink cameras and pulls their most recent status.""" self.network_info = api.request_network_status(self.blink, self.network_id) self.check_new_videos() for camera_name in self.cameras.keys(): camera_id = self.cameras[camera_name].camera_id camera_info = self.get_camera_info(camera_id) self.cameras[camera_name].update(camera_info, force_cache=force_cache) def check_new_videos(self): """Check if new videos since last refresh.""" try: interval = self.blink.last_refresh - self.motion_interval*60 except TypeError: # This is the first start, so refresh hasn't happened yet. # No need to check for motion. return False resp = api.request_videos(self.blink, time=interval, page=1) for camera in self.cameras.keys(): self.motion[camera] = False try: info = resp['media'] except (KeyError, TypeError): _LOGGER.warning("Could not check for motion. Response: %s", resp) return False for entry in info: try: name = entry['device_name'] clip = entry['media'] timestamp = entry['created_at'] self.motion[name] = True self.last_record[name] = {'clip': clip, 'time': timestamp} except KeyError: _LOGGER.debug("No new videos since last refresh.") return True
def test_preserve_key_case(self): cid = CaseInsensitiveDict({"Accept": "application/json", "user-Agent": "requests"}) keyset = frozenset(["Accept", "user-Agent"]) assert frozenset(i[0] for i in cid.items()) == keyset assert frozenset(cid.keys()) == keyset assert frozenset(cid) == keyset
class tizgmusicproxy(object): """A class for logging into a Google Play Music account and retrieving song URLs. """ all_songs_album_title = "All Songs" thumbs_up_playlist_name = "Thumbs Up" def __init__(self, email, password, device_id): self.__gmusic = Mobileclient() self.__email = email self.__device_id = device_id self.logged_in = False self.queue = list() self.queue_index = -1 self.play_queue_order = list() self.play_modes = TizEnumeration(["NORMAL", "SHUFFLE"]) self.current_play_mode = self.play_modes.NORMAL self.now_playing_song = None userdir = os.path.expanduser('~') tizconfig = os.path.join(userdir, ".config/tizonia/." + email + ".auth_token") auth_token = "" if os.path.isfile(tizconfig): with open(tizconfig, "r") as f: auth_token = pickle.load(f) if auth_token: # 'Keep track of the auth token' workaround. See: # https://github.com/diraimondo/gmusicproxy/issues/34#issuecomment-147359198 print_msg("[Google Play Music] [Authenticating] : " \ "'with cached auth token'") self.__gmusic.android_id = device_id self.__gmusic.session._authtoken = auth_token self.__gmusic.session.is_authenticated = True try: self.__gmusic.get_registered_devices() except CallFailure: # The token has expired. Reset the client object print_wrn("[Google Play Music] [Authenticating] : " \ "'auth token expired'") self.__gmusic = Mobileclient() auth_token = "" if not auth_token: attempts = 0 print_nfo("[Google Play Music] [Authenticating] : " \ "'with user credentials'") while not self.logged_in and attempts < 3: self.logged_in = self.__gmusic.login(email, password, device_id) attempts += 1 with open(tizconfig, "a+") as f: f.truncate() pickle.dump(self.__gmusic.session._authtoken, f) self.library = CaseInsensitiveDict() self.song_map = CaseInsensitiveDict() self.playlists = CaseInsensitiveDict() self.stations = CaseInsensitiveDict() def logout(self): """ Reset the session to an unauthenticated, default state. """ self.__gmusic.logout() def set_play_mode(self, mode): """ Set the playback mode. :param mode: curren tvalid values are "NORMAL" and "SHUFFLE" """ self.current_play_mode = getattr(self.play_modes, mode) self.__update_play_queue_order() def current_song_title_and_artist(self): """ Retrieve the current track's title and artist name. """ logging.info("current_song_title_and_artist") song = self.now_playing_song if song: title = to_ascii(self.now_playing_song.get('title')) artist = to_ascii(self.now_playing_song.get('artist')) logging.info("Now playing %s by %s", title, artist) return artist, title else: return '', '' def current_song_album_and_duration(self): """ Retrieve the current track's album and duration. """ logging.info("current_song_album_and_duration") song = self.now_playing_song if song: album = to_ascii(self.now_playing_song.get('album')) duration = to_ascii \ (self.now_playing_song.get('durationMillis')) logging.info("album %s duration %s", album, duration) return album, int(duration) else: return '', 0 def current_track_and_album_total(self): """Return the current track number and the total number of tracks in the album, if known. """ logging.info("current_track_and_album_total") song = self.now_playing_song track = 0 total = 0 if song: try: track = self.now_playing_song['trackNumber'] total = self.now_playing_song['totalTrackCount'] logging.info("track number %s total tracks %s", track, total) except KeyError: logging.info("trackNumber or totalTrackCount : not found") else: logging.info("current_song_track_number_" "and_total_tracks : not found") return track, total def current_song_year(self): """ Return the current track's year of publication. """ logging.info("current_song_year") song = self.now_playing_song year = 0 if song: try: year = song['year'] logging.info("track year %s", year) except KeyError: logging.info("year : not found") else: logging.info("current_song_year : not found") return year def clear_queue(self): """ Clears the playback queue. """ self.queue = list() self.queue_index = -1 def enqueue_artist(self, arg): """ Search the user's library for tracks from the given artist and adds them to the playback queue. :param arg: an artist """ try: self.__update_local_library() artist = None if arg not in self.library.keys(): for name, art in self.library.iteritems(): if arg.lower() in name.lower(): artist = art print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ name.encode('utf-8'))) break if not artist: # Play some random artist from the library random.seed() artist = random.choice(self.library.keys()) artist = self.library[artist] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) else: artist = self.library[arg] tracks_added = 0 for album in artist: tracks_added += self.__enqueue_tracks(artist[album]) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(artist))) self.__update_play_queue_order() except KeyError: raise KeyError("Artist not found : {0}".format(arg)) def enqueue_album(self, arg): """ Search the user's library for albums with a given name and adds them to the playback queue. """ try: self.__update_local_library() album = None artist = None tentative_album = None tentative_artist = None for library_artist in self.library: for artist_album in self.library[library_artist]: print_nfo("[Google Play Music] [Album] '{0}'." \ .format(to_ascii(artist_album))) if not album: if arg.lower() == artist_album.lower(): album = artist_album artist = library_artist break if not tentative_album: if arg.lower() in artist_album.lower(): tentative_album = artist_album tentative_artist = library_artist if album: break if not album and tentative_album: album = tentative_album artist = tentative_artist print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ album.encode('utf-8'))) if not album: # Play some random album from the library random.seed() artist = random.choice(self.library.keys()) album = random.choice(self.library[artist].keys()) print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) if not album: raise KeyError("Album not found : {0}".format(arg)) self.__enqueue_tracks(self.library[artist][album]) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(album))) self.__update_play_queue_order() except KeyError: raise KeyError("Album not found : {0}".format(arg)) def enqueue_playlist(self, arg): """Search the user's library for playlists with a given name and adds the tracks of the first match to the playback queue. Requires Unlimited subscription. """ try: self.__update_local_library() self.__update_playlists() self.__update_playlists_unlimited() playlist = None playlist_name = None for name, plist in self.playlists.items(): print_nfo("[Google Play Music] [Playlist] '{0}'." \ .format(to_ascii(name))) if arg not in self.playlists.keys(): for name, plist in self.playlists.iteritems(): if arg.lower() in name.lower(): playlist = plist playlist_name = name print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ to_ascii(name))) break if not playlist: # Play some random playlist from the library random.seed() playlist_name = random.choice(self.playlists.keys()) playlist = self.playlists[playlist_name] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) else: playlist_name = arg playlist = self.playlists[arg] self.__enqueue_tracks(playlist) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(playlist_name))) self.__update_play_queue_order() except KeyError: raise KeyError("Playlist not found : {0}".format(arg)) def enqueue_station_unlimited(self, arg): """Search the user's library for a station with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ try: # First try to find a suitable station in the user's library self.__enqueue_user_station_unlimited(arg) if not len(self.queue): # If no suitable station is found in the user's library, then # search google play unlimited for a potential match. self.__enqueue_station_unlimited(arg) if not len(self.queue): raise KeyError except KeyError: raise KeyError("Station not found : {0}".format(arg)) def enqueue_genre_unlimited(self, arg): """Search Unlimited for a genre with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving genres] : '{0}'. " \ .format(self.__email)) try: all_genres = list() root_genres = self.__gmusic.get_genres() second_tier_genres = list() for root_genre in root_genres: second_tier_genres += self.__gmusic.get_genres(root_genre['id']) all_genres += root_genres all_genres += second_tier_genres for genre in all_genres: print_nfo("[Google Play Music] [Genre] '{0}'." \ .format(to_ascii(genre['name']))) genre = dict() if arg not in all_genres: genre = next((g for g in all_genres \ if arg.lower() in to_ascii(g['name']).lower()), \ None) tracks_added = 0 while not tracks_added: if not genre and len(all_genres): # Play some random genre from the search results random.seed() genre = random.choice(all_genres) print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) genre_name = genre['name'] genre_id = genre['id'] station_id = self.__gmusic.create_station(genre_name, \ None, None, None, genre_id) num_tracks = 200 tracks = self.__gmusic.get_station_tracks(station_id, num_tracks) tracks_added = self.__enqueue_tracks(tracks) logging.info("Added %d tracks from %s to queue", tracks_added, genre_name) if not tracks_added: # This will produce another iteration in the loop genre = None print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(genre['name']))) self.__update_play_queue_order() except KeyError: raise KeyError("Genre not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_situation_unlimited(self, arg): """Search Unlimited for a situation with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving situations] : '{0}'. " \ .format(self.__email)) try: self.__enqueue_situation_unlimited(arg) if not len(self.queue): raise KeyError logging.info("Added %d tracks from %s to queue", \ len(self.queue), arg) except KeyError: raise KeyError("Situation not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_artist_unlimited(self, arg): """Search Unlimited for an artist and adds the artist's 200 top tracks to the playback queue. Requires Unlimited subscription. """ try: artist = self.__gmusic_search(arg, 'artist') include_albums = False max_top_tracks = 200 max_rel_artist = 0 artist_tracks = dict() if artist: artist_tracks = self.__gmusic.get_artist_info \ (artist['artist']['artistId'], include_albums, max_top_tracks, max_rel_artist)['topTracks'] if not artist_tracks: raise KeyError tracks_added = self.__enqueue_tracks(artist_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Artist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_album_unlimited(self, arg): """Search Unlimited for an album and add its tracks to the playback queue. Requires Unlimited subscription. """ try: album = self.__gmusic_search(arg, 'album') album_tracks = dict() if album: album_tracks = self.__gmusic.get_album_info \ (album['album']['albumId'])['tracks'] if not album_tracks: raise KeyError print_wrn("[Google Play Music] Playing '{0}'." \ .format((album['album']['name']).encode('utf-8'))) tracks_added = self.__enqueue_tracks(album_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Album not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_tracks_unlimited(self, arg): """ Search Unlimited for a track name and adds all the matching tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \ .format(self.__email)) try: max_results = 200 track_hits = self.__gmusic.search(arg, max_results)['song_hits'] if not len(track_hits): # Do another search with an empty string track_hits = self.__gmusic.search("", max_results)['song_hits'] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) tracks = list() for hit in track_hits: tracks.append(hit['track']) tracks_added = self.__enqueue_tracks(tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Playlist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_promoted_tracks_unlimited(self): """ Retrieve the url of the next track in the playback queue. """ try: tracks = self.__gmusic.get_promoted_songs() count = 0 for track in tracks: store_track = self.__gmusic.get_track_info(track['storeId']) if u'id' not in store_track.keys(): store_track[u'id'] = store_track['nid'] self.queue.append(store_track) count += 1 if count == 0: print_wrn("[Google Play Music] Operation requires " \ "an Unlimited subscription.") logging.info("Added %d Unlimited promoted tracks to queue", \ count) self.__update_play_queue_order() except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def next_url(self): """ Retrieve the url of the next track in the playback queue. """ if len(self.queue): self.queue_index += 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): next_song = self.queue[self.play_queue_order[self.queue_index]] return self.__retrieve_track_url(next_song) else: self.queue_index = -1 return self.next_url() else: return '' def prev_url(self): """ Retrieve the url of the previous track in the playback queue. """ if len(self.queue): self.queue_index -= 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): prev_song = self.queue[self.play_queue_order[self.queue_index]] return self.__retrieve_track_url(prev_song) else: self.queue_index = len(self.queue) return self.prev_url() else: return '' def __update_play_queue_order(self): """ Update the queue playback order. A sequential order is applied if the current play mode is "NORMAL" or a random order if current play mode is "SHUFFLE" """ total_tracks = len(self.queue) if total_tracks: if not len(self.play_queue_order): # Create a sequential play order, if empty self.play_queue_order = range(total_tracks) if self.current_play_mode == self.play_modes.SHUFFLE: random.shuffle(self.play_queue_order) print_nfo("[Google Play Music] [Tracks in queue] '{0}'." \ .format(total_tracks)) def __retrieve_track_url(self, song): """ Retrieve a song url """ song_url = self.__gmusic.get_stream_url(song['id'], self.__device_id) try: self.now_playing_song = song return song_url except AttributeError: logging.info("Could not retrieve the song url!") raise def __update_local_library(self): """ Retrieve the songs and albums from the user's library """ print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \ .format(self.__email)) songs = self.__gmusic.get_all_songs() self.playlists[self.thumbs_up_playlist_name] = list() # Retrieve the user's song library for song in songs: if "rating" in song and song['rating'] == "5": self.playlists[self.thumbs_up_playlist_name].append(song) song_id = song['id'] song_artist = song['artist'] song_album = song['album'] self.song_map[song_id] = song if song_artist == "": song_artist = "Unknown Artist" if song_album == "": song_album = "Unknown Album" if song_artist not in self.library: self.library[song_artist] = CaseInsensitiveDict() self.library[song_artist][self.all_songs_album_title] = list() if song_album not in self.library[song_artist]: self.library[song_artist][song_album] = list() self.library[song_artist][song_album].append(song) self.library[song_artist][self.all_songs_album_title].append(song) # Sort albums by track number for artist in self.library.keys(): logging.info("Artist : %s", to_ascii(artist)) for album in self.library[artist].keys(): logging.info(" Album : %s", to_ascii(album)) if album == self.all_songs_album_title: sorted_album = sorted(self.library[artist][album], key=lambda k: k['title']) else: sorted_album = sorted(self.library[artist][album], key=lambda k: k.get('trackNumber', 0)) self.library[artist][album] = sorted_album def __update_stations_unlimited(self): """ Retrieve stations (Unlimited) """ self.stations.clear() stations = self.__gmusic.get_all_stations() self.stations[u"I'm Feeling Lucky"] = 'IFL' for station in stations: station_name = station['name'] logging.info("station name : %s", to_ascii(station_name)) self.stations[station_name] = station['id'] def __enqueue_user_station_unlimited(self, arg): """ Enqueue a user station (Unlimited) """ print_msg("[Google Play Music] [Station search "\ "in user's library] : '{0}'. " \ .format(self.__email)) self.__update_stations_unlimited() station_name = arg station_id = None for name, st_id in self.stations.iteritems(): print_nfo("[Google Play Music] [Station] '{0}'." \ .format(to_ascii(name))) if arg not in self.stations.keys(): for name, st_id in self.stations.iteritems(): if arg.lower() in name.lower(): station_id = st_id station_name = name break else: station_id = self.stations[arg] num_tracks = 200 tracks = list() if station_id: try: tracks = self.__gmusic.get_station_tracks(station_id, \ num_tracks) except KeyError: raise RuntimeError("Operation requires an " "Unlimited subscription.") tracks_added = self.__enqueue_tracks(tracks) if tracks_added: if arg != station_name: print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), name.encode('utf-8'))) logging.info("Added %d tracks from %s to queue", tracks_added, arg) self.__update_play_queue_order() else: print_wrn("[Google Play Music] '{0}' has no tracks. " \ .format(station_name)) if not len(self.queue): print_wrn("[Google Play Music] '{0}' " \ "not found in the user's library. " \ .format(arg.encode('utf-8'))) def __enqueue_station_unlimited(self, arg, max_results=200, quiet=False): """Search for a station and enqueue all of its tracks (Unlimited) """ if not quiet: print_msg("[Google Play Music] [Station search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: station_name = arg station_id = None station = self.__gmusic_search(arg, 'station', max_results, quiet) if station: station = station['station'] station_name = station['name'] seed = station['seed'] seed_type = seed['seedType'] track_id = seed['trackId'] if seed_type == u'2' else None artist_id = seed['artistId'] if seed_type == u'3' else None album_id = seed['albumId'] if seed_type == u'4' else None genre_id = seed['genreId'] if seed_type == u'5' else None playlist_token = seed['playlistShareToken'] if seed_type == u'8' else None curated_station_id = seed['curatedStationId'] if seed_type == u'9' else None num_tracks = max_results tracks = list() try: station_id \ = self.__gmusic.create_station(station_name, \ track_id, \ artist_id, \ album_id, \ genre_id, \ playlist_token, \ curated_station_id) tracks \ = self.__gmusic.get_station_tracks(station_id, \ num_tracks) except KeyError: raise RuntimeError("Operation requires an " "Unlimited subscription.") tracks_added = self.__enqueue_tracks(tracks) if tracks_added: if not quiet: print_wrn("[Google Play Music] [Station] : '{0}'." \ .format(station_name.encode('utf-8'))) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg.encode('utf-8')) self.__update_play_queue_order() except KeyError: raise KeyError("Station not found : {0}".format(arg)) def __enqueue_situation_unlimited(self, arg): """Search for a situation and enqueue all of its tracks (Unlimited) """ print_msg("[Google Play Music] [Situation search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: situation_hits = self.__gmusic.search(arg)['situation_hits'] if not len(situation_hits): # Do another search with an empty string situation_hits = self.__gmusic.search("")['situation_hits'] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) situation = next((hit for hit in situation_hits \ if 'best_result' in hit.keys()), None) num_tracks = 200 if not situation and len(situation_hits): max_results = num_tracks / len(situation_hits) for hit in situation_hits: situation = hit['situation'] print_nfo("[Google Play Music] [Situation] '{0} : {1}'." \ .format((hit['situation']['title']).encode('utf-8'), (hit['situation']['description']).encode('utf-8'))) self.__enqueue_station_unlimited(situation['title'], max_results, True) if not situation: raise KeyError except KeyError: raise KeyError("Situation not found : {0}".format(arg)) def __enqueue_tracks(self, tracks): """ Add tracks to the playback queue """ count = 0 for track in tracks: if u'id' not in track.keys(): track[u'id'] = track['nid'] self.queue.append(track) count += 1 return count def __update_playlists(self): """ Retrieve the user's playlists """ plists = self.__gmusic.get_all_user_playlist_contents() for plist in plists: plist_name = plist['name'] logging.info("playlist name : %s", to_ascii(plist_name)) tracks = plist['tracks'] tracks.sort(key=itemgetter('creationTimestamp')) self.playlists[plist_name] = list() for track in tracks: try: song = self.song_map[track['trackId']] self.playlists[plist_name].append(song) except IndexError: pass def __update_playlists_unlimited(self): """ Retrieve shared playlists (Unlimited) """ plists_subscribed_to = [p for p in self.__gmusic.get_all_playlists() \ if p.get('type') == 'SHARED'] for plist in plists_subscribed_to: share_tok = plist['shareToken'] playlist_items \ = self.__gmusic.get_shared_playlist_contents(share_tok) plist_name = plist['name'] logging.info("shared playlist name : %s", to_ascii(plist_name)) self.playlists[plist_name] = list() for item in playlist_items: try: song = item['track'] song['id'] = item['trackId'] self.playlists[plist_name].append(song) except IndexError: pass def __gmusic_search(self, query, query_type, max_results=200, quiet=False): """ Search Google Play (Unlimited) """ search_results = self.__gmusic.search(query, max_results)[query_type + '_hits'] result = next((hit for hit in search_results \ if 'best_result' in hit.keys()), None) if not result and len(search_results): secondary_hit = None for hit in search_results: if not quiet: print_nfo("[Google Play Music] [{0}] '{1}'." \ .format(query_type.capitalize(), (hit[query_type]['name']).encode('utf-8'))) if query.lower() == \ to_ascii(hit[query_type]['name']).lower(): result = hit break if query.lower() in \ to_ascii(hit[query_type]['name']).lower(): secondary_hit = hit if not result and secondary_hit: result = secondary_hit if not result and not len(search_results): # Do another search with an empty string search_results = self.__gmusic.search("")[query_type + '_hits'] if not result and len(search_results): # Play some random result from the search results random.seed() result = random.choice(search_results) if not quiet: print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(query.encode('utf-8'))) return result
class tizgmusicproxy(object): """A class for accessing a Google Music account to retrieve song URLs. """ all_songs_album_title = "All Songs" thumbs_up_playlist_name = "Thumbs Up" def __init__(self, email, password, device_id): self.__api = Mobileclient() self.logged_in = False self.__device_id = device_id self.queue = list() self.queue_index = -1 self.play_mode = 0 self.now_playing_song = None attempts = 0 while not self.logged_in and attempts < 3: self.logged_in = self.__api.login(email, password) attempts += 1 self.playlists = CaseInsensitiveDict() self.library = CaseInsensitiveDict() def logout(self): self.__api.logout() def update_local_lib(self): songs = self.__api.get_all_songs() self.playlists[self.thumbs_up_playlist_name] = list() # Get main library song_map = dict() for song in songs: if "rating" in song and song["rating"] == "5": self.playlists[self.thumbs_up_playlist_name].append(song) song_id = song["id"] song_artist = song["artist"] song_album = song["album"] song_map[song_id] = song if song_artist == "": song_artist = "Unknown Artist" if song_album == "": song_album = "Unknown Album" if not (song_artist in self.library): self.library[song_artist] = dict() self.library[song_artist][self.all_songs_album_title] = list() if not (song_album in self.library[song_artist]): self.library[song_artist][song_album] = list() self.library[song_artist][song_album].append(song) self.library[song_artist][self.all_songs_album_title].append(song) # Sort albums by track number for artist in self.library.keys(): logging.info("Artist : {0}".format(artist.encode("utf-8"))) for album in self.library[artist].keys(): logging.info(" Album : {0}".format(album.encode("utf-8"))) if album == self.all_songs_album_title: sorted_album = sorted(self.library[artist][album], key=lambda k: k["title"]) else: sorted_album = sorted(self.library[artist][album], key=lambda k: k.get("trackNumber", 0)) self.library[artist][album] = sorted_album # Get all playlists plists = self.__api.get_all_user_playlist_contents() for plist in plists: plist_name = plist["name"] self.playlists[plist_name] = list() for track in plist["tracks"]: try: song = song_map[track["trackId"]] self.playlists[plist_name].append(song) except IndexError: pass def current_song_title_and_artist(self): logging.info("current_song_title_and_artist") song = self.now_playing_song if song is not None: title = self.now_playing_song["title"] artist = self.now_playing_song["artist"] logging.info("Now playing {0} by {1}".format(title.encode("utf-8"), artist.encode("utf-8"))) return artist.encode("utf-8"), title.encode("utf-8") else: return "", "" def current_song_album_and_duration(self): logging.info("current_song_album_and_duration") song = self.now_playing_song if song is not None: album = self.now_playing_song["album"] duration = self.now_playing_song["durationMillis"] logging.info("album {0} duration {1}".format(album.encode("utf-8"), duration.encode("utf-8"))) return album.encode("utf-8"), int(duration) else: return "", 0 def current_song_track_number_and_total_tracks(self): logging.info("current_song_track_number_and_total_tracks") song = self.now_playing_song if song is not None: track = self.now_playing_song["trackNumber"] total = self.now_playing_song["totalTrackCount"] logging.info("track number {0} total tracks {1}".format(track, total)) return track, total else: logging.info("current_song_track_number_and_total_tracks : not found") return 0, 0 def clear_queue(self): self.queue = list() self.queue_index = -1 def enqueue_artist(self, arg): try: artist = self.library[arg] count = 0 for album in artist: for song in artist[album]: self.queue.append(song) count += 1 logging.info("Added {0} tracks by {1} to queue".format(count, arg)) except KeyError: logging.info("Cannot find {0}".format(arg)) raise def enqueue_album(self, arg): try: for artist in self.library: for album in self.library[artist]: logging.info("enqueue album : {0} | {1}".format(artist.encode("utf-8"), album.encode("utf-8"))) if album.lower() == arg.lower(): count = 0 for song in self.library[artist][album]: self.queue.append(song) count += 1 logging.info( "Added {0} tracks from {1} by " "{2} to queue".format(count, album.encode("utf-8"), artist.encode("utf-8")) ) except KeyError: logging.info("Cannot find {0}".format(arg)) raise def enqueue_playlist(self, arg): try: playlist = self.playlists[arg] count = 0 for song in playlist: self.queue.append(song) count += 1 logging.info("Added {0} tracks from {1} to queue".format(count, arg)) except KeyError: logging.info("Cannot find {0}".format(arg)) raise def next_url(self): logging.info("next_url") if len(self.queue): self.queue_index += 1 if (self.queue_index < len(self.queue)) and (self.queue_index >= 0): next_song = self.queue[self.queue_index] return self.__get_song_url(next_song) else: self.queue_index = -1 return self.next_url() else: return "" def prev_url(self): if len(self.queue): self.queue_index -= 1 if (self.queue_index < len(self.queue)) and (self.queue_index >= 0): prev_song = self.queue[self.queue_index] return self.__get_song_url(prev_song) else: self.queue_index = len(self.queue) return self.prev_url() else: return "" def __get_song_url(self, song): song_url = self.__api.get_stream_url(song["id"], self.__device_id) try: self.now_playing_song = song return song_url except AttributeError: logging.info("Could not retrieve song url!") raise
class BlinkSyncModule: """Class to initialize sync module.""" def __init__(self, blink, network_name, network_id, camera_list): """ Initialize Blink sync module. :param blink: Blink class instantiation """ self.blink = blink self.network_id = network_id self.region_id = blink.auth.region_id self.name = network_name self.serial = None self.status = "offline" self.sync_id = None self.host = None self.summary = None self.network_info = None self.events = [] self.cameras = CaseInsensitiveDict({}) self.motion_interval = blink.motion_interval self.motion = {} self.last_record = {} self.camera_list = camera_list self.available = False @property def attributes(self): """Return sync attributes.""" attr = { "name": self.name, "id": self.sync_id, "network_id": self.network_id, "serial": self.serial, "status": self.status, "region_id": self.region_id, } return attr @property def urls(self): """Return device urls.""" return self.blink.urls @property def online(self): """Return boolean system online status.""" try: return ONLINE[self.status] except KeyError: _LOGGER.error("Unknown sync module status %s", self.status) self.available = False return False @property def arm(self): """Return status of sync module: armed/disarmed.""" try: return self.network_info["network"]["armed"] except (KeyError, TypeError): self.available = False return None @arm.setter def arm(self, value): """Arm or disarm camera.""" if value: return api.request_system_arm(self.blink, self.network_id) return api.request_system_disarm(self.blink, self.network_id) def start(self): """Initialize the system.""" response = self.sync_initialize() if not response: return False try: self.sync_id = self.summary["id"] self.serial = self.summary["serial"] self.status = self.summary["status"] except KeyError: _LOGGER.error("Could not extract some sync module info: %s", response) is_ok = self.get_network_info() self.check_new_videos() if not is_ok or not self.update_cameras(): return False self.available = True return True def sync_initialize(self): """Initialize a sync module.""" response = api.request_syncmodule(self.blink, self.network_id) try: self.summary = response["syncmodule"] self.network_id = self.summary["network_id"] except (TypeError, KeyError): _LOGGER.error( "Could not retrieve sync module information with response: %s", response) return False return response def update_cameras(self, camera_type=BlinkCamera): """Update cameras from server.""" try: for camera_config in self.camera_list: if "name" not in camera_config: break blink_camera_type = camera_config.get("type", "") name = camera_config["name"] self.motion[name] = False owl_info = self.get_owl_info(name) if blink_camera_type == "mini": camera_type = BlinkCameraMini self.cameras[name] = camera_type(self) camera_info = self.get_camera_info(camera_config["id"], owl_info=owl_info) self.cameras[name].update(camera_info, force_cache=True, force=True) except KeyError: _LOGGER.error("Could not create camera instances for %s", self.name) return False return True def get_owl_info(self, name): """Extract owl information.""" try: for owl in self.blink.homescreen["owls"]: if owl["name"] == name: return owl except KeyError: pass return None def get_events(self, **kwargs): """Retrieve events from server.""" force = kwargs.pop("force", False) response = api.request_sync_events(self.blink, self.network_id, force=force) try: return response["event"] except (TypeError, KeyError): _LOGGER.error("Could not extract events: %s", response) return False def get_camera_info(self, camera_id, **kwargs): """Retrieve camera information.""" owl = kwargs.get("owl_info", None) if owl is not None: return owl response = api.request_camera_info(self.blink, self.network_id, camera_id) try: return response["camera"][0] except (TypeError, KeyError): _LOGGER.error("Could not extract camera info: %s", response) return {} def get_network_info(self): """Retrieve network status.""" self.network_info = api.request_network_update(self.blink, self.network_id) try: if self.network_info["network"]["sync_module_error"]: raise KeyError except (TypeError, KeyError): self.available = False return False return True def refresh(self, force_cache=False): """Get all blink cameras and pulls their most recent status.""" if not self.get_network_info(): return self.check_new_videos() for camera_name in self.cameras.keys(): camera_id = self.cameras[camera_name].camera_id camera_info = self.get_camera_info( camera_id, owl_info=self.get_owl_info(camera_name)) self.cameras[camera_name].update(camera_info, force_cache=force_cache) self.available = True def check_new_videos(self): """Check if new videos since last refresh.""" try: interval = self.blink.last_refresh - self.motion_interval * 60 except TypeError: # This is the first start, so refresh hasn't happened yet. # No need to check for motion. return False resp = api.request_videos(self.blink, time=interval, page=1) for camera in self.cameras.keys(): self.motion[camera] = False try: info = resp["media"] except (KeyError, TypeError): _LOGGER.warning("Could not check for motion. Response: %s", resp) return False for entry in info: try: name = entry["device_name"] clip = entry["media"] timestamp = entry["created_at"] if self.check_new_video_time(timestamp): self.motion[name] = True and self.arm self.last_record[name] = {"clip": clip, "time": timestamp} except KeyError: _LOGGER.debug("No new videos since last refresh.") return True def check_new_video_time(self, timestamp): """Check if video has timestamp since last refresh.""" return time_to_seconds(timestamp) > self.blink.last_refresh
def _recommendation(self, venues_data: CaseInsensitiveDict, user_pref: Dict) -> Dict: logging.info( f"Generating a recommendation report to advise which venues should the team members go." ) places_to_avoid = list() for bad_food, _users in user_pref['bad_food_user_mappings'].items(): for u in _users: for venue, available_food in venues_data.items(): # Assumption: if the venue offers anything else but what the user won't eat, then # it is assumed that the user is comfortable in visiting that place. However, # if the venue only offers the very food that the user won't eat then it is assumed that # that venue should be avoided... list_of_foods_offered_by_venue = list( set(x.strip().lower() for x in available_food['food'])) if ([bad_food] == list_of_foods_offered_by_venue) or ( len(list_of_foods_offered_by_venue) == 0): # [bad_food] == list_of_foods_offered_by_venue will return true only when the # venue offers one type of dish and that happens to be the one that the user won't eat. # len(list_of_foods_offered_by_venue) == 0 means that venue doesn't offer anything at all, # hence it should be avoided. places_to_avoid.append({ venue: f'There is nothing for {u.split()[0]} to eat.' }) for _user, drinks in user_pref['user_drinks_mappings'].items(): for _v_name, available_drinks in venues_data.items(): set_of_drinks_offered_by_venue = set( x.strip().lower() for x in available_drinks['drinks']) if len( set(drinks).intersection( set_of_drinks_offered_by_venue)) == 0: # len(set(drinks).intersection(set_of_drinks_offered_by_venue)) == 0 will return True if # the venue offers none of the drinks that are desired by the users; In either case # this suggests that the team cannot go to that venue. places_to_avoid.append({ _v_name: f'There is nothing for {_user.split()[0]} to drink.' }) avoid_dict = self._merge_dicts(places_to_avoid, default_type=list) return { 'places_to_visit': [ v for v in venues_data.keys() if v not in [key for key in avoid_dict.keys()] ], 'places_to_avoid': [{ 'name': key, 'reason': value } for key, value in avoid_dict.items()] }
def make_request(self, method, bucket, key=None, subresource=None, params=None, data=None, headers=None): # Remove params that are set to None if params is None: params = {} for k, v in params.copy().items(): if v is None: params.pop(k) # Construct target url url = 'http://{}/{}'.format(self.hostname, bucket) url += '/{}'.format(key) if key is not None else '/' if subresource is not None: url += '?{}'.format(subresource) elif len(params) > 0: url += '?{}'.format(urllib.urlencode(params)) # Make headers case insensitive if headers is None: headers = {} headers = CaseInsensitiveDict(headers) headers['Host'] = self.hostname if self.temporary_security_token is not None: headers['x-amz-security-token'] = self.temporary_security_token if data is not None: try: raw_md5 = utils.f_md5(data) except: m = hashlib.md5() m.update(data) raw_md5 = m.digest() md5 = b64encode(raw_md5) headers['Content-MD5'] = md5 else: md5 = '' try: content_type = headers['Content-Type'] except KeyError: content_type = '' date = formatdate(timeval=None, localtime=False, usegmt=True) headers['x-amz-date'] = date # Construct canonicalized amz headers string canonicalized_amz_headers = '' amz_keys = [k for k in list(headers.keys()) if k.startswith('x-amz-')] for k in sorted(amz_keys): v = headers[k].strip() k = k.lower() canonicalized_amz_headers += '{}:{}\n'.format(k, v) # Construct canonicalized resource string canonicalized_resource = '/' + bucket canonicalized_resource += '/' if key is None else '/{}'.format(key) if subresource is not None: canonicalized_resource += '?{}'.format(subresource) elif len(params) > 0: canonicalized_resource += '?{}'.format(urllib.urlencode(params)) # Construct string to sign string_to_sign = method.upper() + '\n' string_to_sign += md5 + '\n' string_to_sign += content_type + '\n' string_to_sign += '\n' # date is always set through x-amz-date string_to_sign += canonicalized_amz_headers + canonicalized_resource # Create signature h = hmac.new(self.secret_access_key, string_to_sign, hashlib.sha1) signature = b64encode(h.digest()) # Set authorization header auth_head = 'AWS {}:{}'.format(self.access_key_id, signature) headers['Authorization'] = auth_head # Prepare Request req = Request(method, url, data=data, headers=headers).prepare() # Log request data. # Prepare request beforehand so requests-altered headers show. # Combine into a single message so we don't have to bother with # locking to make lines appear together. log_message = '{} {}\n'.format(method, url) log_message += 'string to sign: {}\n'.format(repr(string_to_sign)) log_message += 'headers:' for k in sorted(req.headers.keys()): log_message += '\n {}: {}'.format(k, req.headers[k]) log.debug(log_message) # Send request resp = Session().send(req) # Update stats, log response data. self.stats[method.upper()] += 1 log.debug('response: {} ({} {})'.format(resp.status_code, method, url)) # Handle errors if resp.status_code / 100 != 2: soup = BeautifulSoup(resp.text) error = soup.find('error') log_message = "S3 replied with non 2xx response code!!!!\n" log_message += ' request: {} {}\n'.format(method, url) for c in error.children: error_name = c.name error_message = c.text.encode('unicode_escape') log_message += ' {}: {}\n'.format(error_name, error_message) log.debug(log_message) code = error.find('code').text message = error.find('message').text raise S3ResponseError(code, message, resp) return resp
class ReminderManager(): def __init__(self, bot, google_creds, relay_map): self.bot = bot self.google_creds = google_creds self.relay_map = CaseInsensitiveDict(relay_map) self.tasks = [] async def initialize(self): logging.info("Initializing google calendar reminders...") # For each google calendar, create a new async task that will poll for upcoming events for calendar_label in self.relay_map.keys(): config = self.relay_map[calendar_label] calendar_id = config['calendar_id'] channel_ids = config['channels'] # This is a list of integers representing how many minutes prior to the event do we want a reminder when_to_notify = config['when'] ping = config['ping'] channels = [] for channel_id in channel_ids: channel = discord.utils.get(self.bot.get_all_channels(), id=int(channel_id)) if not channel: logging.warning( f"\tBot does not have access to channel with ID {channel_id}!" ) else: channels.append(channel) logging.info( f"\tWatching for events from Google Calendar {calendar_label} with id {calendar_id}..." ) self.tasks.append( asyncio.create_task( self.poll_calendar_events(calendar_id, channels, when_to_notify, ping))) logging.info("Done.") async def auth(self): # Grabs an authenticated endpoint for pulling calendar data credentials = service_account.Credentials.from_service_account_file( self.google_creds, scopes=SCOPES) return build('calendar', 'v3', credentials=credentials, cache_discovery=False) async def poll_calendar_events(self, calendar_id, channels, when_to_notify, ping): # This cache is local to only this running coroutine # We only want to notify an event for a particular "minutes until event" trigger one time cache = {} logging.info('Spawned a poll watcher. Trump would be proud.') for look_ahead in when_to_notify: cache[look_ahead] = set() while True: # Grab a timezone-aware timestamp for "now", in UTC time right_now = datetime.now(timezone.utc) for look_ahead in when_to_notify: # Based on the "minutes until event" value, create a "look ahead" window of a minute # Ex: imagine a value of "2" for "2 minutes before event, notify" # # now look ahead window # (12:00:30) |---------------| # |--------------|---------------|--------------|--------------| # 12:00 12:01 12:02 12:03 12:04 # # TODO: This isn't ideal. This window of one minute means that we're notifying # up to a minute too soon, depending on where "now" falls on the seconds clock. start_after = right_now + timedelta(minutes=look_ahead) start_before = start_after + timedelta(minutes=1) for channel in channels: newline = "\n" await self.get_events_in_window( calendar_id, start_after, start_before, channel, look_ahead, cache[look_ahead], f'📅 🐱 💬 {"@here " if ping else ""}' + f'Events are starting {"in " + str(look_ahead) + " minutes" if look_ahead > 0 else "now"}!' ) await asyncio.sleep(CALENDAR_POLL_INTERVAL) async def get_events_in_window(self, calendar_id, start_after, start_before, channel, look_ahead, cache, prompt): try: # Construct the query to Google Calendar. We're only able to provide a cutoff for starting after a date. # So we'll validate the "starting before" the other end of the window later. service = await self.auth() result = service.events().list( calendarId=calendar_id, singleEvents=True, orderBy='startTime', timeMin=f'{start_after.isoformat(timespec="seconds")}', maxResults=5).execute() # Edge case - no data comes back if 'items' not in result: return # Filter out anything that's not an event future_events = [ item for item in result['items'] if 'kind' in item and item['kind'] == 'calendar#event' ] # Filter out anything that's already in the cache uncached_future_events = [ item for item in future_events if item['id'] not in cache ] if not uncached_future_events: return # Some events may only have a date. This just converts those dates to datetime objects (midnight on date). await self._change_events_start_date_to_datetime( uncached_future_events) # Filter out anything that's not actually in the "window" to_notify = [] for future_event in uncached_future_events: start = future_event['start'] when = datetime.fromisoformat( future_event['start']['dateTime']) if when >= start_after and when < start_before: to_notify.append(future_event) if not to_notify: return # Construct the message for the notification. msg = f"{prompt}\n" for future_event in to_notify: if future_event['id'] in cache: continue cache.add(future_event['id']) event_as_str = await self._render_event(future_event) msg += f'```{event_as_str}```' + "\n" logging.info( f" Event {future_event['id']} reminder being sent from calendar {calendar_id} to channel {channel.id}." ) await channel.send(msg) except Exception as e: logging.exception( f'Exception thrown while attempting to check events on calendar {calendar_id} for channel {channel.id}' ) async def get_upcoming_events(self, channel, calendar_name=None): if not channel: return available_calendars = '\n'.join(self.relay_map.keys()) if not calendar_name: return await channel.send( f'😾 Which one? Try using `!event <calendar>` with one of these.\n```{available_calendars}```' ) if calendar_name not in self.relay_map: return await channel.send( f'🙀 I only know of these calendars.\n```{available_calendars}```' ) calendar_id = self.relay_map[calendar_name]['calendar_id'] right_now = right_now = datetime.now(timezone.utc) # Calculate the number of days to add to get to the last day of next month. # There's probably a better way to do this, because this really stinks. next_month_year = right_now.year next_month = right_now.month + 1 if next_month == 13: next_month_year += 1 next_month = 1 days_in_next_month = monthrange(next_month_year, next_month)[1] days_in_this_month = monthrange(right_now.year, right_now.month)[1] total_days_add = (days_in_this_month - right_now.day) + days_in_next_month last_day_of_next_month = right_now + timedelta(days=+total_days_add) # Okay, now we have SOME time ON the last day. Let's cut that time off, and # force it to be the last second of the day. last_day_of_next_month.strftime('%Y-%m-%d') + 'T23:59:59Z' service = await self.auth() result = service.events().list( calendarId=calendar_id, singleEvents=True, orderBy='startTime', timeMin=f'{right_now.isoformat(timespec="seconds")}', timeMax=f'{last_day_of_next_month.strftime("%Y-%m-%d")}T23:59:59Z', maxResults=20).execute() if not 'items' in result: return future_events = [ item for item in result['items'] if 'kind' in item and item['kind'] == 'calendar#event' ] if not future_events: return # Convert all events with only dates to have datetimes starting at midnight await self._change_events_start_date_to_datetime(future_events) future_events = sorted( future_events, key=lambda item: datetime.fromisoformat(item['start']['dateTime'])) msg = "📅 🐱 💬 There are some meetings and events coming up...\n" for future_event in future_events: event_as_str = await self._render_event(future_event) if len(msg + event_as_str) > 1900: break msg += f'```{event_as_str}```' + "\n" await channel.send(msg) async def _change_events_start_date_to_datetime(self, future_events): for future_event in future_events: start = future_event['start'] if 'date' in start: new_start = {'dateTime': start['date'] + 'T00:00:00-00:00'} future_event['start'] = new_start async def _render_event(self, future_event): start = future_event['start'] when = datetime.fromisoformat( future_event['start']['dateTime']).astimezone(EASTERN_TIMEZONE) when_str = when.strftime("%A, %d. %B %Y %I:%M%p %Z").replace( '12:00AM EST', '') location = future_event[ 'location'] if 'location' in future_event else '' description = future_event[ 'description'] if 'description' in future_event else '' # sanitize html nonsense from the description if BeautifulSoup(description, 'html.parser').find(): description = description.replace('<br>', '\n').replace( '<wbr>', '').replace(' ', '') description = BeautifulSoup(description, "lxml").text newline = "\n" return f'{future_event["summary"]} {when_str}{newline}'\ f'{newline + location if location else ""}' \ f'{newline + description if description else ""}'.strip()
def reload_config(self, config, sighup=False): self._superuser = config['authentication'].get('superuser', {}) server_parameters = self.get_server_parameters(config) conf_changed = hba_changed = ident_changed = local_connection_address_changed = pending_restart = False if self._postgresql.state == 'running': changes = CaseInsensitiveDict( {p: v for p, v in server_parameters.items() if '.' not in p}) changes.update({ p: None for p in self._server_parameters.keys() if not ('.' in p or p in changes) }) if changes: if 'wal_buffers' in changes: # we need to calculate the default value of wal_buffers undef = [ p for p in ('shared_buffers', 'wal_segment_size', 'wal_block_size') if p not in changes ] changes.update({p: None for p in undef}) # XXX: query can raise an exception old_values = { r[0]: r for r in self._postgresql.query(( 'SELECT name, setting, unit, vartype, context ' + 'FROM pg_catalog.pg_settings ' + ' WHERE pg_catalog.lower(name) = ANY(%s)' ), [k.lower() for k in changes.keys()]) } if 'wal_buffers' in changes: self._handle_wal_buffers(old_values, changes) for p in undef: del changes[p] for r in old_values.values(): if r[4] != 'internal' and r[0] in changes: new_value = changes.pop(r[0]) if new_value is None or not compare_values( r[3], r[2], r[1], new_value): conf_changed = True if r[4] == 'postmaster': pending_restart = True logger.info( 'Changed %s from %s to %s (restart might be required)', r[0], r[1], new_value) if config.get('use_unix_socket') and r[0] == 'unix_socket_directories'\ or r[0] in ('listen_addresses', 'port'): local_connection_address_changed = True else: logger.info('Changed %s from %s to %s', r[0], r[1], new_value) for param in changes: if param in server_parameters: logger.warning( 'Removing invalid parameter `%s` from postgresql.parameters', param) server_parameters.pop(param) # Check that user-defined-paramters have changed (parameters with period in name) for p, v in server_parameters.items(): if '.' in p and (p not in self._server_parameters or str(v) != str(self._server_parameters[p])): logger.info('Changed %s from %s to %s', p, self._server_parameters.get(p), v) conf_changed = True for p, v in self._server_parameters.items(): if '.' in p and (p not in server_parameters or str(v) != str(server_parameters[p])): logger.info('Changed %s from %s to %s', p, v, server_parameters.get(p)) conf_changed = True if not server_parameters.get('hba_file') and config.get('pg_hba'): hba_changed = self._config.get('pg_hba', []) != config['pg_hba'] if not server_parameters.get('ident_file') and config.get( 'pg_ident'): ident_changed = self._config.get('pg_ident', []) != config['pg_ident'] self._config = config self._postgresql.set_pending_restart(pending_restart) self._server_parameters = server_parameters self._adjust_recovery_parameters() self._krbsrvname = config.get('krbsrvname') # for not so obvious connection attempts that may happen outside of pyscopg2 if self._krbsrvname: os.environ['PGKRBSRVNAME'] = self._krbsrvname if not local_connection_address_changed: self.resolve_connection_addresses() if conf_changed: self.write_postgresql_conf() if hba_changed: self.replace_pg_hba() if ident_changed: self.replace_pg_ident() if sighup or conf_changed or hba_changed or ident_changed: logger.info('Reloading PostgreSQL configuration.') self._postgresql.reload() if self._postgresql.major_version >= 90500: time.sleep(1) try: pending_restart = self._postgresql.query( 'SELECT COUNT(*) FROM pg_catalog.pg_settings' ' WHERE pending_restart').fetchone()[0] > 0 self._postgresql.set_pending_restart(pending_restart) except Exception as e: logger.warning('Exception %r when running query', e) else: logger.info( 'No PostgreSQL configuration items changed, nothing to reload.' )
def run(self, tmp=None, task_vars=None): ''' handler for template operations ''' self.nsbl_env = os.environ.get("NSBL_ENVIRONMENT", False) == "true" if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) format = { "child_marker": "packages", "default_leaf": "vars", "default_leaf_key": "name", "key_move_map": { '*': "vars" } } chain = [frkl.FrklProcessor(format)] frkl_obj = frkl.Frkl(self._task.args["packages"], chain) package = frkl_obj.process() if len(package) == 0: raise Exception("No packages provided for package: {}".format( self._task.args["packages"])) if len(package) != 1: raise Exception( "For some reason more than one package provided, this shouldn't happen: {}" .format(package)) package = package[0] if "pkg_mgr" not in package[VARS_KEY].keys(): pkg_mgr = self._task.args.get('pkg_mgr', 'auto') else: pkg_mgr = package[VARS_KEY]["pkg_mgr"] if pkg_mgr == 'auto': try: if self._task.delegate_to: pkg_mgr = self._templar.template( "{{hostvars['%s']['ansible_facts']['ansible_pkg_mgr']}}" % self._task.delegate_to) else: pkg_mgr = self._templar.template( '{{ansible_facts["ansible_pkg_mgr"]}}') except Exception as e: pass # could not get it from template! auto = pkg_mgr == 'auto' facts = self._execute_module(module_name='setup', module_args=dict(gather_subset='!all'), task_vars=task_vars) if auto: pkg_mgr = facts['ansible_facts'].get('ansible_pkg_mgr', None) os_family = facts['ansible_facts'].get('ansible_os_family', None) distribution = facts['ansible_facts'].get('ansible_distribution', None) distribution_major_version = facts['ansible_facts'].get( 'ansible_distribution_major_version', None) distribution_version = facts['ansible_facts'].get( 'ansible_distribution_version', None) distribution_release = facts['ansible_facts'].get( 'ansible_distribution_release', None) # figure out actual package name if distribution_version: full_version_string = "{}-{}".format(distribution, distribution_version).lower() else: full_version_string = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" if distribution_release: full_release_string = "{}-{}".format(distribution, distribution_release).lower() else: full_release_string = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" if distribution_major_version: distribution_major_string = "{}-{}".format( distribution, distribution_major_version).lower() else: distribution_major_string = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" distribution_string = distribution.lower() os_string = os_family.lower() if pkg_mgr == 'unknown' and os_family == "Darwin": pkg_mgr = "homebrew" if pkg_mgr in ['auto', 'unknown']: result['failed'] = True result[ 'msg'] = 'Could not detect which package manager to use. Try gathering facts or setting the "use" option.' return result if pkg_mgr not in self._shared_loader_obj.module_loader: result['failed'] = True result[ 'msg'] = "Could not find an ansible module for package manager '{}'.".format( pkg_mgr) return result # calculate package name, just in case pkg_dict = CaseInsensitiveDict(package[VARS_KEY].get("pkgs")) if pkg_mgr.lower() in (name.lower() for name in pkg_dict.keys()): calculated_package_pkg_mgr = pkg_dict[pkg_mgr.lower()] elif 'other' in (name.lower() for name in pkg_dict.keys()): calculated_package_pkg_mgr = pkg_dict['other'] else: calculated_package_pkg_mgr = None if full_version_string in (name.lower() for name in pkg_dict.keys()): calculated_package_platform = pkg_dict[full_version_string] elif full_release_string in (name.lower() for name in pkg_dict.keys()): calculated_package_platform = pkg_dict[full_release_string] elif distribution_major_string in (name.lower() for name in pkg_dict.keys()): calculated_package_platform = pkg_dict[distribution_major_string] elif distribution_string in (name.lower() for name in pkg_dict.keys()): calculated_package_platform = pkg_dict[distribution_string] elif os_string in (name.lower() for name in pkg_dict.keys()): calculated_package_platform = pkg_dict[os_string] elif 'other' in (name.lower() for name in pkg_dict.keys()): calculated_package_platform = pkg_dict['other'] else: calculated_package_platform = None # if calculated_package_platform in ['ignore', 'omit'] or calculated_package_pkg_mgr in ['ignore', 'omit']: # result['msg'] = "Ignoring package {}".format(package[VARS_KEY]["name"]) # result['skipped'] = True # return result if not auto or not calculated_package_platform: calculated_package = calculated_package_pkg_mgr else: calculated_package = calculated_package_platform if calculated_package in ['ignore', 'omit']: result['msg'] = "Ignoring package {}".format( package[VARS_KEY]["name"]) result['skipped'] = True return result module_result = self.execute_package_module(package, calculated_package, auto, pkg_mgr, task_vars, result) if module_result: result.update(module_result) return result
class tizgmusicproxy(object): """A class for logging into a Google Play Music account and retrieving song URLs. """ all_songs_album_title = "All Songs" thumbs_up_playlist_name = "Thumbs Up" # pylint: disable=too-many-instance-attributes,too-many-public-methods def __init__(self, email, password, device_id): self.__gmusic = Mobileclient() self.__email = email self.__device_id = device_id self.logged_in = False self.queue = list() self.queue_index = -1 self.play_queue_order = list() self.play_modes = TizEnumeration(["NORMAL", "SHUFFLE"]) self.current_play_mode = self.play_modes.NORMAL self.now_playing_song = None userdir = os.path.expanduser('~') tizconfig = os.path.join(userdir, ".config/tizonia/." + email + ".auth_token") auth_token = "" if os.path.isfile(tizconfig): with open(tizconfig, "r") as f: auth_token = pickle.load(f) if auth_token: # 'Keep track of the auth token' workaround. See: # https://github.com/diraimondo/gmusicproxy/issues/34#issuecomment-147359198 print_msg("[Google Play Music] [Authenticating] : " \ "'with cached auth token'") self.__gmusic.android_id = device_id self.__gmusic.session._authtoken = auth_token self.__gmusic.session.is_authenticated = True try: self.__gmusic.get_registered_devices() except CallFailure: # The token has expired. Reset the client object print_wrn("[Google Play Music] [Authenticating] : " \ "'auth token expired'") self.__gmusic = Mobileclient() auth_token = "" if not auth_token: attempts = 0 print_nfo("[Google Play Music] [Authenticating] : " \ "'with user credentials'") while not self.logged_in and attempts < 3: self.logged_in = self.__gmusic.login(email, password, device_id) attempts += 1 with open(tizconfig, "a+") as f: f.truncate() pickle.dump(self.__gmusic.session._authtoken, f) self.library = CaseInsensitiveDict() self.song_map = CaseInsensitiveDict() self.playlists = CaseInsensitiveDict() self.stations = CaseInsensitiveDict() def logout(self): """ Reset the session to an unauthenticated, default state. """ self.__gmusic.logout() def set_play_mode(self, mode): """ Set the playback mode. :param mode: curren tvalid values are "NORMAL" and "SHUFFLE" """ self.current_play_mode = getattr(self.play_modes, mode) self.__update_play_queue_order() def current_song_title_and_artist(self): """ Retrieve the current track's title and artist name. """ logging.info("current_song_title_and_artist") song = self.now_playing_song if song: title = to_ascii(self.now_playing_song.get('title')) artist = to_ascii(self.now_playing_song.get('artist')) logging.info("Now playing %s by %s", title, artist) return artist, title else: return '', '' def current_song_album_and_duration(self): """ Retrieve the current track's album and duration. """ logging.info("current_song_album_and_duration") song = self.now_playing_song if song: album = to_ascii(self.now_playing_song.get('album')) duration = to_ascii \ (self.now_playing_song.get('durationMillis')) logging.info("album %s duration %s", album, duration) return album, int(duration) else: return '', 0 def current_track_and_album_total(self): """Return the current track number and the total number of tracks in the album, if known. """ logging.info("current_track_and_album_total") song = self.now_playing_song track = 0 total = 0 if song: try: track = self.now_playing_song['trackNumber'] total = self.now_playing_song['totalTrackCount'] logging.info("track number %s total tracks %s", track, total) except KeyError: logging.info("trackNumber or totalTrackCount : not found") else: logging.info("current_song_track_number_" "and_total_tracks : not found") return track, total def current_song_year(self): """ Return the current track's year of publication. """ logging.info("current_song_year") song = self.now_playing_song year = 0 if song: try: year = song['year'] logging.info("track year %s", year) except KeyError: logging.info("year : not found") else: logging.info("current_song_year : not found") return year def clear_queue(self): """ Clears the playback queue. """ self.queue = list() self.queue_index = -1 def enqueue_tracks(self, arg): """ Search the user's library for tracks and add them to the playback queue. :param arg: a track search term """ try: songs = self.__gmusic.get_all_songs() track_hits = list() for song in songs: song_title = song['title'] if arg.lower() in song_title.lower(): track_hits.append(song) print_nfo("[Google Play Music] [Track] '{0}'." \ .format(to_ascii(song_title))) if not len(track_hits): print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) random.seed() track_hits = random.sample(songs, MAX_TRACKS) for hit in track_hits: song_title = hit['title'] print_nfo("[Google Play Music] [Track] '{0}'." \ .format(to_ascii(song_title))) if not len(track_hits): raise KeyError tracks_added = self.__enqueue_tracks(track_hits) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Track not found : {0}".format(arg)) def enqueue_artist(self, arg): """ Search the user's library for tracks from the given artist and add them to the playback queue. :param arg: an artist """ try: self.__update_local_library() artist = None artist_dict = None if arg not in self.library.keys(): for name, art in self.library.iteritems(): if arg.lower() in name.lower(): artist = name artist_dict = art if arg.lower() != name.lower(): print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ name.encode('utf-8'))) break if not artist: # Play some random artist from the library random.seed() artist = random.choice(self.library.keys()) artist_dict = self.library[artist] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) else: artist = arg artist_dict = self.library[arg] tracks_added = 0 for album in artist_dict: tracks_added += self.__enqueue_tracks(artist_dict[album]) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(artist))) self.__update_play_queue_order() except KeyError: raise KeyError("Artist not found : {0}".format(arg)) def enqueue_album(self, arg): """ Search the user's library for albums with a given name and add them to the playback queue. """ try: self.__update_local_library() album = None artist = None tentative_album = None tentative_artist = None for library_artist in self.library: for artist_album in self.library[library_artist]: print_nfo("[Google Play Music] [Album] '{0}'." \ .format(to_ascii(artist_album))) if not album: if arg.lower() == artist_album.lower(): album = artist_album artist = library_artist break if not tentative_album: if arg.lower() in artist_album.lower(): tentative_album = artist_album tentative_artist = library_artist if album: break if not album and tentative_album: album = tentative_album artist = tentative_artist print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ album.encode('utf-8'))) if not album: # Play some random album from the library random.seed() artist = random.choice(self.library.keys()) album = random.choice(self.library[artist].keys()) print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) if not album: raise KeyError("Album not found : {0}".format(arg)) self.__enqueue_tracks(self.library[artist][album]) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(album))) self.__update_play_queue_order() except KeyError: raise KeyError("Album not found : {0}".format(arg)) def enqueue_playlist(self, arg): """Search the user's library for playlists with a given name and add the tracks of the first match to the playback queue. Requires Unlimited subscription. """ try: self.__update_local_library() self.__update_playlists() self.__update_playlists_unlimited() playlist = None playlist_name = None for name, plist in self.playlists.items(): print_nfo("[Google Play Music] [Playlist] '{0}'." \ .format(to_ascii(name))) if arg not in self.playlists.keys(): for name, plist in self.playlists.iteritems(): if arg.lower() in name.lower(): playlist = plist playlist_name = name if arg.lower() != name.lower(): print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ to_ascii(name))) break else: playlist_name = arg playlist = self.playlists[arg] random.seed() x = 0 while (not playlist or not len(playlist)) and x < 3: x += 1 # Play some random playlist from the library playlist_name = random.choice(self.playlists.keys()) playlist = self.playlists[playlist_name] print_wrn("[Google Play Music] '{0}' not found or found empty. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) if not len(playlist): raise KeyError self.__enqueue_tracks(playlist) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(playlist_name))) self.__update_play_queue_order() except KeyError: raise KeyError( "Playlist not found or found empty : {0}".format(arg)) def enqueue_podcast(self, arg): """Search Google Play Music for a podcast series and add its tracks to the playback queue (). Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving podcasts] : '{0}'. " \ .format(self.__email)) try: self.__enqueue_podcast(arg) if not len(self.queue): raise KeyError logging.info("Added %d episodes from '%s' to queue", \ len(self.queue), arg) self.__update_play_queue_order() except KeyError: raise KeyError("Podcast not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_station_unlimited(self, arg): """Search the user's library for a station with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ try: # First try to find a suitable station in the user's library self.__enqueue_user_station_unlimited(arg) if not len(self.queue): # If no suitable station is found in the user's library, then # search google play unlimited for a potential match. self.__enqueue_station_unlimited(arg) if not len(self.queue): raise KeyError except KeyError: raise KeyError("Station not found : {0}".format(arg)) def enqueue_genre_unlimited(self, arg): """Search Unlimited for a genre with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving genres] : '{0}'. " \ .format(self.__email)) try: all_genres = list() root_genres = self.__gmusic.get_genres() second_tier_genres = list() for root_genre in root_genres: second_tier_genres += self.__gmusic.get_genres( root_genre['id']) all_genres += root_genres all_genres += second_tier_genres for genre in all_genres: print_nfo("[Google Play Music] [Genre] '{0}'." \ .format(to_ascii(genre['name']))) genre = dict() if arg not in all_genres: genre = next((g for g in all_genres \ if arg.lower() in to_ascii(g['name']).lower()), \ None) tracks_added = 0 while not tracks_added: if not genre and len(all_genres): # Play some random genre from the search results random.seed() genre = random.choice(all_genres) print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) genre_name = genre['name'] genre_id = genre['id'] station_id = self.__gmusic.create_station(genre_name, \ None, None, None, genre_id) num_tracks = MAX_TRACKS tracks = self.__gmusic.get_station_tracks( station_id, num_tracks) tracks_added = self.__enqueue_tracks(tracks) logging.info("Added %d tracks from %s to queue", tracks_added, genre_name) if not tracks_added: # This will produce another iteration in the loop genre = None print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(genre['name']))) self.__update_play_queue_order() except KeyError: raise KeyError("Genre not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_situation_unlimited(self, arg): """Search Unlimited for a situation with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving situations] : '{0}'. " \ .format(self.__email)) try: self.__enqueue_situation_unlimited(arg) if not len(self.queue): raise KeyError logging.info("Added %d tracks from %s to queue", \ len(self.queue), arg) except KeyError: raise KeyError("Situation not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_artist_unlimited(self, arg): """Search Unlimited for an artist and add the artist's 200 top tracks to the playback queue. Requires Unlimited subscription. """ try: artist = self.__gmusic_search(arg, 'artist') include_albums = False max_top_tracks = MAX_TRACKS max_rel_artist = 0 artist_tracks = dict() if artist: artist_tracks = self.__gmusic.get_artist_info \ (artist['artist']['artistId'], include_albums, max_top_tracks, max_rel_artist)['topTracks'] if not artist_tracks: raise KeyError for track in artist_tracks: song_title = track['title'] print_nfo("[Google Play Music] [Track] '{0}'." \ .format(to_ascii(song_title))) tracks_added = self.__enqueue_tracks(artist_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Artist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_album_unlimited(self, arg): """Search Unlimited for an album and add its tracks to the playback queue. Requires Unlimited subscription. """ try: album = self.__gmusic_search(arg, 'album') album_tracks = dict() if album: album_tracks = self.__gmusic.get_album_info \ (album['album']['albumId'])['tracks'] if not album_tracks: raise KeyError print_wrn("[Google Play Music] Playing '{0}'." \ .format((album['album']['name']).encode('utf-8'))) tracks_added = self.__enqueue_tracks(album_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Album not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_tracks_unlimited(self, arg): """ Search Unlimited for a track name and add all the matching tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \ .format(self.__email)) try: max_results = MAX_TRACKS track_hits = self.__gmusic.search(arg, max_results)['song_hits'] if not len(track_hits): # Do another search with an empty string track_hits = self.__gmusic.search("", max_results)['song_hits'] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) tracks = list() for hit in track_hits: tracks.append(hit['track']) tracks_added = self.__enqueue_tracks(tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Playlist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_playlist_unlimited(self, arg): """Search Unlimited for a playlist name and add all its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving playlists] : '{0}'. " \ .format(self.__email)) try: playlist_tracks = list() playlist_hits = self.__gmusic_search(arg, 'playlist') if playlist_hits: playlist = playlist_hits['playlist'] playlist_contents = self.__gmusic.get_shared_playlist_contents( playlist['shareToken']) else: raise KeyError print_nfo("[Google Play Music] [Playlist] '{}'." \ .format(playlist['name']).encode('utf-8')) for item in playlist_contents: print_nfo("[Google Play Music] [Playlist Track] '{} by {} (Album: {}, {})'." \ .format((item['track']['title']).encode('utf-8'), (item['track']['artist']).encode('utf-8'), (item['track']['album']).encode('utf-8'), (item['track']['year']))) track = item['track'] playlist_tracks.append(track) if not playlist_tracks: raise KeyError tracks_added = self.__enqueue_tracks(playlist_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Playlist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_promoted_tracks_unlimited(self): """ Retrieve the url of the next track in the playback queue. """ try: tracks = self.__gmusic.get_promoted_songs() count = 0 for track in tracks: store_track = self.__gmusic.get_track_info(track['storeId']) if u'id' not in store_track.keys(): store_track[u'id'] = store_track['storeId'] self.queue.append(store_track) count += 1 if count == 0: print_wrn("[Google Play Music] Operation requires " \ "an Unlimited subscription.") logging.info("Added %d Unlimited promoted tracks to queue", \ count) self.__update_play_queue_order() except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def next_url(self): """ Retrieve the url of the next track in the playback queue. """ if len(self.queue): self.queue_index += 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): next_song = self.queue[self.play_queue_order[self.queue_index]] return self.__retrieve_track_url(next_song) else: self.queue_index = -1 return self.next_url() else: return '' def prev_url(self): """ Retrieve the url of the previous track in the playback queue. """ if len(self.queue): self.queue_index -= 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): prev_song = self.queue[self.play_queue_order[self.queue_index]] return self.__retrieve_track_url(prev_song) else: self.queue_index = len(self.queue) return self.prev_url() else: return '' def __update_play_queue_order(self): """ Update the queue playback order. A sequential order is applied if the current play mode is "NORMAL" or a random order if current play mode is "SHUFFLE" """ total_tracks = len(self.queue) if total_tracks: if not len(self.play_queue_order): # Create a sequential play order, if empty self.play_queue_order = range(total_tracks) if self.current_play_mode == self.play_modes.SHUFFLE: random.shuffle(self.play_queue_order) print_nfo("[Google Play Music] [Tracks in queue] '{0}'." \ .format(total_tracks)) def __retrieve_track_url(self, song): """ Retrieve a song url """ if song.get('episodeId'): song_url = self.__gmusic.get_podcast_episode_stream_url( song['episodeId'], self.__device_id) else: song_url = self.__gmusic.get_stream_url(song['id'], self.__device_id) try: self.now_playing_song = song return song_url except AttributeError: logging.info("Could not retrieve the song url!") raise def __update_local_library(self): """ Retrieve the songs and albums from the user's library """ print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \ .format(self.__email)) songs = self.__gmusic.get_all_songs() self.playlists[self.thumbs_up_playlist_name] = list() # Retrieve the user's song library for song in songs: if "rating" in song and song['rating'] == "5": self.playlists[self.thumbs_up_playlist_name].append(song) song_id = song['id'] song_artist = song['artist'] song_album = song['album'] self.song_map[song_id] = song if song_artist == "": song_artist = "Unknown Artist" if song_album == "": song_album = "Unknown Album" if song_artist not in self.library: self.library[song_artist] = CaseInsensitiveDict() self.library[song_artist][self.all_songs_album_title] = list() if song_album not in self.library[song_artist]: self.library[song_artist][song_album] = list() self.library[song_artist][song_album].append(song) self.library[song_artist][self.all_songs_album_title].append(song) # Sort albums by track number for artist in self.library.keys(): logging.info("Artist : %s", to_ascii(artist)) for album in self.library[artist].keys(): logging.info(" Album : %s", to_ascii(album)) if album == self.all_songs_album_title: sorted_album = sorted(self.library[artist][album], key=lambda k: k['title']) else: sorted_album = sorted( self.library[artist][album], key=lambda k: k.get('trackNumber', 0)) self.library[artist][album] = sorted_album def __update_stations_unlimited(self): """ Retrieve stations (Unlimited) """ self.stations.clear() stations = self.__gmusic.get_all_stations() self.stations[u"I'm Feeling Lucky"] = 'IFL' for station in stations: station_name = station['name'] logging.info("station name : %s", to_ascii(station_name)) self.stations[station_name] = station['id'] def __enqueue_user_station_unlimited(self, arg): """ Enqueue a user station (Unlimited) """ print_msg("[Google Play Music] [Station search "\ "in user's library] : '{0}'. " \ .format(self.__email)) self.__update_stations_unlimited() station_name = arg station_id = None for name, st_id in self.stations.iteritems(): print_nfo("[Google Play Music] [Station] '{0}'." \ .format(to_ascii(name))) if arg not in self.stations.keys(): for name, st_id in self.stations.iteritems(): if arg.lower() in name.lower(): station_id = st_id station_name = name break else: station_id = self.stations[arg] num_tracks = MAX_TRACKS tracks = list() if station_id: try: tracks = self.__gmusic.get_station_tracks(station_id, \ num_tracks) except KeyError: raise RuntimeError("Operation requires an " "Unlimited subscription.") tracks_added = self.__enqueue_tracks(tracks) if tracks_added: if arg.lower() != station_name.lower(): print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), name.encode('utf-8'))) logging.info("Added %d tracks from %s to queue", tracks_added, arg) self.__update_play_queue_order() else: print_wrn("[Google Play Music] '{0}' has no tracks. " \ .format(station_name)) if not len(self.queue): print_wrn("[Google Play Music] '{0}' " \ "not found in the user's library. " \ .format(arg.encode('utf-8'))) def __enqueue_station_unlimited(self, arg, max_results=MAX_TRACKS, quiet=False): """Search for a station and enqueue all of its tracks (Unlimited) """ if not quiet: print_msg("[Google Play Music] [Station search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: station_name = arg station_id = None station = self.__gmusic_search(arg, 'station', max_results, quiet) if station: station = station['station'] station_name = station['name'] seed = station['seed'] seed_type = seed['seedType'] track_id = seed['trackId'] if seed_type == u'2' else None artist_id = seed['artistId'] if seed_type == u'3' else None album_id = seed['albumId'] if seed_type == u'4' else None genre_id = seed['genreId'] if seed_type == u'5' else None playlist_token = seed[ 'playlistShareToken'] if seed_type == u'8' else None curated_station_id = seed[ 'curatedStationId'] if seed_type == u'9' else None num_tracks = max_results tracks = list() try: station_id \ = self.__gmusic.create_station(station_name, \ track_id, \ artist_id, \ album_id, \ genre_id, \ playlist_token, \ curated_station_id) tracks \ = self.__gmusic.get_station_tracks(station_id, \ num_tracks) except KeyError: raise RuntimeError("Operation requires an " "Unlimited subscription.") tracks_added = self.__enqueue_tracks(tracks) if tracks_added: if not quiet: print_wrn("[Google Play Music] [Station] : '{0}'." \ .format(station_name.encode('utf-8'))) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg.encode('utf-8')) self.__update_play_queue_order() except KeyError: raise KeyError("Station not found : {0}".format(arg)) def __enqueue_situation_unlimited(self, arg): """Search for a situation and enqueue all of its tracks (Unlimited) """ print_msg("[Google Play Music] [Situation search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: situation_hits = self.__gmusic.search(arg)['situation_hits'] # If the search didn't return results, just do another search with # an empty string if not len(situation_hits): situation_hits = self.__gmusic.search("")['situation_hits'] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) # Try to find a "best result", if one exists situation = next((hit for hit in situation_hits \ if 'best_result' in hit.keys() \ and hit['best_result'] == True), None) num_tracks = MAX_TRACKS # If there is no best result, then get a selection of tracks from # each situation. At least we'll play some music. if not situation and len(situation_hits): max_results = num_tracks / len(situation_hits) for hit in situation_hits: situation = hit['situation'] print_nfo("[Google Play Music] [Situation] '{0} : {1}'." \ .format((hit['situation']['title']).encode('utf-8'), (hit['situation']['description']).encode('utf-8'))) self.__enqueue_station_unlimited(situation['title'], max_results, True) elif situation: # There is at list one sitution, enqueue its tracks. situation = situation['situation'] max_results = num_tracks self.__enqueue_station_unlimited(situation['title'], max_results, True) if not situation: raise KeyError except KeyError: raise KeyError("Situation not found : {0}".format(arg)) def __enqueue_podcast(self, arg): """Search for a podcast series and enqueue all of its tracks. """ print_msg("[Google Play Music] [Podcast search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: podcast_hits = self.__gmusic_search(arg, 'podcast', 10, quiet=False) if not podcast_hits: print_wrn( "[Google Play Music] [Podcast] 'Search returned zero results'." ) print_wrn( "[Google Play Music] [Podcast] 'Are you in a supported region " "(currently only US and Canada) ?'") # Use the first podcast retrieved. At least we'll play something. podcast = dict() if podcast_hits and len(podcast_hits): podcast = podcast_hits['series'] episodes_added = 0 if podcast: # There is a podcast, enqueue its episodes. print_nfo("[Google Play Music] [Podcast] 'Playing '{0}' by {1}'." \ .format((podcast['title']).encode('utf-8'), (podcast['author']).encode('utf-8'))) print_nfo("[Google Play Music] [Podcast] '{0}'." \ .format((podcast['description'][0:150]).encode('utf-8'))) series = self.__gmusic.get_podcast_series_info( podcast['seriesId']) episodes = series['episodes'] for episode in episodes: print_nfo("[Google Play Music] [Podcast Episode] '{0} : {1}'." \ .format((episode['title']).encode('utf-8'), (episode['description'][0:80]).encode('utf-8'))) episodes_added = self.__enqueue_tracks(episodes) if not podcast or not episodes_added: raise KeyError except KeyError: raise KeyError( "Podcast not found or no episodes found: {0}".format(arg)) def __enqueue_tracks(self, tracks): """ Add tracks to the playback queue """ count = 0 for track in tracks: if u'id' not in track.keys() and track.get('storeId'): track[u'id'] = track['storeId'] self.queue.append(track) count += 1 return count def __update_playlists(self): """ Retrieve the user's playlists """ plists = self.__gmusic.get_all_user_playlist_contents() for plist in plists: plist_name = plist.get('name') tracks = plist.get('tracks') if plist_name and tracks: logging.info("playlist name : %s", to_ascii(plist_name)) tracks.sort(key=itemgetter('creationTimestamp')) self.playlists[plist_name] = list() for track in tracks: song_id = track.get('trackId') if song_id: song = self.song_map.get(song_id) if song: self.playlists[plist_name].append(song) def __update_playlists_unlimited(self): """ Retrieve shared playlists (Unlimited) """ plists_subscribed_to = [p for p in self.__gmusic.get_all_playlists() \ if p.get('type') == 'SHARED'] for plist in plists_subscribed_to: share_tok = plist['shareToken'] playlist_items \ = self.__gmusic.get_shared_playlist_contents(share_tok) plist_name = plist['name'] logging.info("shared playlist name : %s", to_ascii(plist_name)) self.playlists[plist_name] = list() for item in playlist_items: try: song = item['track'] song['id'] = item['trackId'] self.playlists[plist_name].append(song) except IndexError: pass def __gmusic_search(self, query, query_type, max_results=MAX_TRACKS, quiet=False): """ Search Google Play (Unlimited) """ search_results = self.__gmusic.search(query, max_results)[query_type + '_hits'] # This is a workaround. Some podcast results come without these two # keys in the dictionary if query_type == "podcast" and len(search_results) \ and not search_results[0].get('navigational_result'): for res in search_results: res[u'best_result'] = False res[u'navigational_result'] = False res[query_type] = res['series'] result = '' if query_type != "playlist": result = next((hit for hit in search_results \ if 'best_result' in hit.keys() \ and hit['best_result'] == True), None) if not result and len(search_results): secondary_hit = None for hit in search_results: name = '' if hit[query_type].get('name'): name = hit[query_type].get('name') elif hit[query_type].get('title'): name = hit[query_type].get('title') if not quiet: print_nfo("[Google Play Music] [{0}] '{1}'." \ .format(query_type.capitalize(), (name).encode('utf-8'))) if query.lower() == \ to_ascii(name).lower(): result = hit break if query.lower() in \ to_ascii(name).lower(): secondary_hit = hit if not result and secondary_hit: result = secondary_hit if not result and not len(search_results): # Do another search with an empty string search_results = self.__gmusic.search("")[query_type + '_hits'] if not result and len(search_results): # Play some random result from the search results random.seed() result = random.choice(search_results) if not quiet: print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(query.encode('utf-8'))) return result