def __init__(self, redis_instace=None, spotify_acess_point=None): if redis_instace is not None: self.redis_instance = redis_instace else: self.redis_instance = RedisAcess() if spotify_acess_point is not None: self.spotify_endpoint_acess = spotify_acess_point else: self.spotify_endpoint_acess = SpotifyEndpointAcess( self.redis_instance)
def __init__(self, redis_instace=None, spotify_acess_point=None): if redis_instace is not None: self.redis_instance = redis_instace else: self.redis_instance = RedisAcess() if spotify_acess_point is not None: self.spotify_endpoint_acess = spotify_acess_point else: self.spotify_endpoint_acess = SpotifyEndpointAcess( self.redis_instance) with open('config.yaml', 'r') as f: self.config_file = yaml.safe_load(f)
class BotGeneralCallbacks: def __init__(self, redis_instace=None, spotify_acess_point=None): if redis_instace is not None: self.redis_instance = redis_instace else: self.redis_instance = RedisAcess() if spotify_acess_point is not None: self.spotify_endpoint_acess = spotify_acess_point else: self.spotify_endpoint_acess = SpotifyEndpointAcess( self.redis_instance) with open('config.yaml', 'r') as f: self.config_file = yaml.safe_load(f) def start(self, update, context): """ '/start' command """ if len(context.args) == 0: message = """ Welcome to the SpotSurveyBot, a bot that let you generate a Spotify playlist based on your preferences. To start, you must first login to your Spotify account by giving the command '/login'. Use '/help' for more information about the available commands and standart procedures. """ context.bot.send_message(chat_id=update.effective_chat.id, text=message) else: chat_id = update.message.chat_id # Serves as a flag too (for error that will prevent from proceding from login to creating playlist) error_message = "" try: self.spotify_endpoint_acess.register(chat_id, context.args[0]) context.bot.send_message(chat_id=chat_id, text="Login was sucessful!") # This first exception is not a critical error, so even if it happens, continue to playlist creation except AlreadyLoggedInException: context.bot.send_message( chat_id=chat_id, text= "Could not complete registration process: User already logged in" ) except RedisError: error_message = "Error: Internal database error during authentication!" except ValueError: error_message = "Error: Invalid start command parameter. Do not enter value on start parameter" except TokenRequestException: error_message = "Error: Could not retrieve user tokens from Spotify API during authentication" # If there was an error during login process if error_message: context.bot.send_message(chat_id=chat_id, text=error_message) return # If user is logged in, try to create playlist self.create_playlist(update, context) def create_playlist(self, update, context): chat_id = update.message.chat_id # If some error occurs during checking, assume there is no playlist registered on bot try: playlist_already_registered = self.spotify_endpoint_acess.playlist_already_registered( chat_id) except: LOGGER.exception('') context.bot.send_message( chat_id=chat_id, text= '''_Warning_: Could not check if previous playlist was registered on this bot before''', parse_mode='MarkdownV2') playlist_already_registered = False if playlist_already_registered: context.bot.send_message( chat_id=chat_id, text= '''_Note_: there is already a playlist registered on our internal database\. Skipping playlist generation\.\.\. ''', parse_mode='MarkdownV2') else: playlist_name = self.config_file['spotify']['playlistName'] playlist_description = self.config_file['spotify'][ 'playlistDescription'] time.sleep(1) context.bot.send_message( chat_id=chat_id, text=''' Creating playlist named '{playlist_name}'...'''. format(playlist_name=playlist_name)) # Create playlist. If no error, link it to Telegram user try: self.spotify_endpoint_acess.create_playlist( chat_id, playlist_name, playlist_description) except: LOGGER.exception('') context.bot.send_message( chat_id=chat_id, text= '''Error: Could not create playlist '{playlist_name}' '''. format(playlist_name=playlist_name)) else: time.sleep(2) context.bot.send_message(chat_id=chat_id, text=''' Playlist created! ''') def get_setup(self, update: Update, context: CallbackContext): chat_id = update.effective_chat.id message = """ *Here is your current setup:*\n """ message += """__Artists__\n\n""" user_artists = self.redis_instance.get_user_artists(chat_id) if len(user_artists) == 0: message += """_None_\n""" else: for artist in user_artists: message += """{mic_emoji} {artist_name} \([link]({link})\)\n""".format( mic_emoji=emojize(":microphone:", use_aliases=True), artist_name=escape_markdown(artist.get('name', ''), version=2), link=escape_markdown(artist.get('link', ''), version=2, entity_type='TEXT_LINKS')) for genre_name in artist.get('genres', []): message += """ _{genre_name}_\n""".format( genre_name=escape_markdown(genre_name, version=2)) message += """\n""" message += """\n""" message += """__Tracks__\n\n""" user_tracks = self.redis_instance.get_user_tracks(chat_id) if len(user_tracks) == 0: message += """_None_\n""" else: for track in user_tracks: message += """{music_emoji} {track_name} \([link]({link})\)\n""".format( music_emoji=emojize(":musical_note:", use_aliases=True), track_name=escape_markdown(track.get('name', ''), version=2), link=escape_markdown(track.get('link', ''), version=2, entity_type='TEXT_LINKS')) for artist_name in track.get('artists', []): message += """ _{artist_name}_\n""".format( artist_name=escape_markdown(artist_name, version=2)) message += """\n""" message += """\n""" message += """__Music Attributes__\n\n""" attributes = self.redis_instance.get_all_survey_attributes(chat_id) for attribute, value in attributes.items(): if value: message += """{attribute}: {value}\n""".format( attribute=escape_markdown(attribute, version=2), value=escape_markdown(value.get('text', ''), version=2)) message += """\n""" context.bot.send_message(chat_id=chat_id, text=message, parse_mode='MarkdownV2', disable_web_page_preview=True) def help_callback(self, update: Update, context: CallbackContext): help_message = """ Available commands: * /start: Gives welcome message. * /login: Generates a link to Spotify authentication page. You must accept it to use this bot. When loggin in, a Spotify playlist named "SpotSurveyBot's Playlist" will be created. All your information regarding Spotify and future attributes and seeds selected will be stored in an internbal database, all associated with our Telegram chat ID. * /setup_seed: Open conversation to allow users to select which tracks and/or artists they want to use as seeds for the generated recommendation. * /setup_attributes: Star survey (a series of Telegram Polls where the next questionary appers after the previous one has been filled) to select music attributes to orient what kind of music the recommendation generator should use. * /get_setup: See seeds and attributes that will be used on command '/generate_playlist' * /generate_playlist: Using the Spotify API and the seeds and attributes set and linked to our Telegram chat, populated the Spotify playlist associated with our Telegram chat with musics recommended to you (first removing all musics from it). * /logout: Remove all stored informations about you and your connection to Spotify from this bot. Optionally, you can delete the associated Spotify playlist from your Spotify account. """ context.bot.send_message(chat_id=update.effective_chat.id, text=help_message) # ! Test function! def unknown_command(self, update, context): context.bot.send_message( chat_id=update.effective_chat.id, text="""Unknow command. Try '/help' for list of commands""") def login(self, update, context): """'/login' command. Makes user authentication from Spotify""" login_url = self.spotify_endpoint_acess.authorization_link() message = """ Please, click on [this link]({login_url}) to login with our Spotify account\. After being redirected to Telegram on this chat, press 'Start button'\.""".format( login_url=login_url) context.bot.send_message(chat_id=update.effective_chat.id, text=message, parse_mode='MarkdownV2')
class BotSeedCallbacks: def __init__(self, redis_instace=None, spotify_acess_point=None): if redis_instace is not None: self.redis_instance = redis_instace else: self.redis_instance = RedisAcess() if spotify_acess_point is not None: self.spotify_endpoint_acess = spotify_acess_point else: self.spotify_endpoint_acess = SpotifyEndpointAcess( self.redis_instance) # ========================================= SETUP CONVERSATION ============================================ # def setup(self, update: Update, context: CallbackContext): if not self.redis_instance.is_user_logged_in(update.effective_chat.id): update.message.reply_text( """ Cannot perform operation: User not logged in with a Spotify account! """ ) return ConversationHandler.END context.chat_data['max_num_items'] = 5 initial_message = """ *Starting Setup* You're about to start setting up some information associated with this Bot\. More specifically, you will choose a set of up to {max_num_item} artists and tracks combined that will serve as seeds for the recommendation algorithm\. In other to conclude this step, you must choose a minimum of 1 item \(between both artists and tracks\)\. To select items \(artists or tracks\), just input the number that appears at the side of the item on the chat\. If you want to include an artist, for example, you need to type their associated number \(or multiple numbers separeted by commas, if selecting multiple artists\) while the selection message is showing the artists \(if the message is showing tracks, for example, the tracks with these numbers will be selected, not the artists\)\. """.format(max_num_item=context.chat_data['max_num_items']) update.message.reply_text(initial_message, parse_mode='MarkdownV2') keyboard = InlineKeyboardMarkup( [[InlineKeyboardButton(text='Start', callback_data='Start')]]) update.message.reply_text( "Press Start button to start setting up your musical preferences", reply_markup=keyboard) # Set how many entries are goind to be shown at a given time context.chat_data['page_lenght'] = 10 context.chat_data['total_artists'] = 30 context.chat_data['total_tracks'] = 50 context.chat_data['current_message_id'] = None context.chat_data['artists_list_page'], context.chat_data[ 'tracks_list_page'] = 0, 0 context.chat_data['selected_artists_index'], context.chat_data[ 'selected_tracks_index'] = set(), set() context.chat_data[ 'artists_list'] = self.spotify_endpoint_acess.get_user_top_artists( update.effective_chat.id, amount=context.chat_data['total_artists'], is_all_info=True) context.chat_data[ 'tracks_list'] = self.spotify_endpoint_acess.get_user_top_tracks( update.effective_chat.id, amount=context.chat_data['total_tracks'], is_all_info=True) return SELECT_ARTISTS def _select_items(self, update: Update, context: CallbackContext, item_type: str): # If switching between selection states (selecting artists or tracks), this could already have been called try: # If the type of Update is not a callback query (like when selecting items via text), don't try it. if update.callback_query is not None: update.callback_query.answer() except telegramBadRequest: pass # Store current message (the one that can go 'previous', 'next' and to other kind of item) if context.chat_data.get(item_type + '_list', None) is None: context.chat_data[ 'current_message_id'] = update.callback_query.message.message_id # If coming from the same state (acessing other page), set current page number # Process should not enter this block if this functions wasn't called from a Callback update (as in called # throught the selection of the items numbers by the user) elif update.callback_query: button_pressed = update.callback_query.data if button_pressed == 'Next': context.chat_data[item_type + '_list_page'] += 1 elif button_pressed == 'Previous': context.chat_data[item_type + '_list_page'] -= 1 # Little hack: since wee need to change states when pressing the 'Select Tracks' or 'Select Artists' # button, and to avoid having a middle function for making this transition, just call the method which presents # the other set of options and return their value. At the end, this should chnage the state (allowing for using # the 'Previous' and 'Next' buttons and for sending the selected numbers on chat) elif button_pressed == 'Tracks': # this needs to be done in order to avoid infinite loops (when mantaining the callback # query text, code will re-call function infinitely) update.callback_query.data = '' return self._select_items(update, context, 'tracks') elif button_pressed == 'Artists': update.callback_query.data = '' return self._select_items(update, context, 'artists') if (context.chat_data[item_type + '_list_page'] < 0 or context.chat_data[item_type + '_list_page'] > math.ceil( len(context.chat_data[item_type + '_list']) / context.chat_data['page_lenght']) - 1): raise ValueError(""" Page out of bounds! """) current_page = context.chat_data[item_type + '_list_page'] page_lenght = context.chat_data['page_lenght'] total_items = context.chat_data['total_' + item_type] # Artists on this page page_range = (page_lenght * current_page, min((page_lenght * (current_page + 1) - 1), total_items - 1)) page_items = context.chat_data[item_type + '_list'][page_range[0]:(page_range[1] + 1)] page_items_rank = list(range(page_range[0] + 1, page_range[1] + 2)) # 'Previous' and Next buttons to switch which page is shown buttons_list = [] if context.chat_data[item_type + '_list_page'] > 0: buttons_list.append( InlineKeyboardButton(text='Previous', callback_data='Previous')) # TODO: CHECK IF THIS BREAKS ANYTHING if context.chat_data[item_type + '_list_page'] < math.ceil( len(context.chat_data[item_type + '_list']) / context.chat_data['page_lenght']) - 1: buttons_list.append( InlineKeyboardButton(text='Next', callback_data='Next')) selection_option = '' if item_type == 'artists': selection_option = 'Tracks' elif item_type == 'tracks': selection_option = 'Artists' buttons = [ buttons_list, [ InlineKeyboardButton(text='Select ' + selection_option, callback_data=selection_option) ], [ InlineKeyboardButton(text='Done', callback_data='Done'), InlineKeyboardButton(text='Cancel', callback_data='Cancel') ] ] keyboard = InlineKeyboardMarkup(buttons) page_text = self._assemble_message(context, page_items, page_items_rank, item_type) # This part will be called if the current update is a message (came from 'selected_tracks') if context.chat_data['current_message_id'] is None: new_message = context.bot.send_message( chat_id=update.effective_chat.id, text=page_text, reply_markup=keyboard, parse_mode='MarkdownV2', disable_web_page_preview=True) context.chat_data['current_message_id'] = new_message.message_id # And this should be called when entrying this functions with an Update of CallbackQuery else: update.callback_query.edit_message_text( text=page_text, reply_markup=keyboard, parse_mode='MarkdownV2', disable_web_page_preview=True) if item_type == 'artists': return SELECT_ARTISTS elif item_type == 'tracks': return SELECT_TRACKS return END_STATE def _assemble_message(self, context: CallbackContext, page_items, page_items_rank, item_type: str): # Setting up message to be shown page_text = """__Select up to {n} {item_type}__\n\n""".format( n=context.chat_data['max_num_items'], item_type=item_type) for i, item in enumerate(page_items): # If item was already selected, strikethrough the item name item_name = escape_markdown(item.get('name', ''), version=2) if (page_items_rank[i] - 1) in context.chat_data['selected_' + item_type + '_index']: item_name = '~' + item_name + '~' if item_type == 'artists': page_text += """{mic_emoji} *{position}\.* {artist_name} \([link]({link})\)\n""".format( mic_emoji=emojize(":microphone:", use_aliases=True), position=page_items_rank[i], artist_name=item_name, link=escape_markdown(item.get('link', ''), version=2, entity_type='TEXT_LINKS')) for genre in item.get('genres', []): page_text += """ _{genre_name}_\n""".format( genre_name=escape_markdown(genre, version=2)) elif item_type == 'tracks': page_text += """{music_emoji} *{position}\.* {track_name} \([link]({link})\)\n""".format( music_emoji=emojize(":musical_note:", use_aliases=True), position=page_items_rank[i], track_name=item_name, link=escape_markdown(item.get('link', ''), version=2, entity_type='TEXT_LINKS')) for artist_name in item.get('artists', []): page_text += """ _{artist_name}_\n""".format( artist_name=escape_markdown(artist_name, version=2)) page_text += """\n""" page_text += """__Select up to {n} {item_type}__\n\n""".format( n=context.chat_data['max_num_items'], item_type=item_type) return page_text def select_artists(self, update: Update, context: CallbackContext): return self._select_items(update, context, 'artists') def select_tracks(self, update: Update, context: CallbackContext): return self._select_items(update, context, 'tracks') def _selected_items(self, update: Update, context: CallbackContext, item_type: str): #Serve as to indicate if all went well and we can set appropiate values is_ok = True if context.chat_data['max_num_items'] == 0: update.message.reply_text( """You cannot add any more items. Press Done or Cancel buttons on item selection""" ) is_ok = False selected_ranks = set( map(int, (update.message.text).replace(" ", "").split(","))) previously_selected_ranks = set([ index + 1 for index in context.chat_data['selected_' + item_type + '_index'] ]) already_selected_numbers = selected_ranks.intersection( previously_selected_ranks) if len(already_selected_numbers) != 0: update.message.reply_text( """You already added one of these selected items ({items})! Try again without them.""" .format(items=", ".join(already_selected_numbers))) is_ok = False if len(selected_ranks) > context.chat_data[ 'max_num_items'] and context.chat_data['max_num_items'] != 0: update.message.reply_text( """Cannot add this many items. Can only select {n} more items""" .format(n=context.chat_data['max_num_items'])) is_ok = False for rank in selected_ranks: if rank < 1 or rank > context.chat_data['total_' + item_type]: update.message.reply_text( """Item of type '{item_type}' of rank {rank} does not exist". Try again without it""" .format(item_type=item_type, rank=rank)) is_ok = False if is_ok: context.chat_data['selected_' + item_type + '_index'] = context.chat_data[ 'selected_' + item_type + '_index'].union( set([(val - 1) for val in selected_ranks])) context.chat_data['max_num_items'] -= len(selected_ranks) # Removing keyboard from the old message (to create a new one). this should be done even if the main operation was not sucessful context.bot.edit_message_reply_markup( chat_id=update.message.chat_id, message_id=context.chat_data['current_message_id']) context.chat_data['current_message_id'] = None # Create new message with right paging if item_type == 'artists': return self.select_artists(update, context) elif item_type == 'tracks': return self.select_tracks(update, context) return END_STATE def selected_artists(self, update: Update, context: CallbackContext): return self._selected_items(update, context, 'artists') def selected_tracks(self, update: Update, context: CallbackContext): return self._selected_items(update, context, 'tracks') def wrong_selection_input(self, update: Update, context: CallbackContext): context.bot.edit_message_reply_markup( chat_id=update.message.chat_id, message_id=context.chat_data['current_message_id']) context.chat_data['current_message_id'] = None update.message.reply_text( """Wrong input type: Must be a number or a series of numbers separated by comma""" ) return self.select_artists(update, context) def _delete_setup_context_variables(self, context: CallbackContext): context_variables_created = [ 'page_lenght', 'max_num_items', 'total_artists', 'total_tracks', 'current_message_id', 'artists_list_page', 'tracks_list_page', 'selected_artists_index', 'selected_tracks_index', 'artists_list', 'tracks_list' ] for var_name in context_variables_created: del context.chat_data[var_name] def ask_cancel(self, update: Update, context: CallbackContext): update.callback_query.answer() # Removing keyboard from the last page message. context.bot.edit_message_reply_markup( chat_id=update.callback_query.message.chat_id, message_id=context.chat_data['current_message_id']) keyboard = InlineKeyboardMarkup( [[InlineKeyboardButton(text='Yes', callback_data='Yes')], [InlineKeyboardButton(text='No', callback_data='No')]]) update.callback_query.message.reply_text( """ Do you whish to cancel selection process? """, reply_markup=keyboard) return CANCEL def cancel(self, update: Update, context: CallbackContext): update.callback_query.answer() context.bot.edit_message_reply_markup( chat_id=update.callback_query.message.chat_id, message_id=update.callback_query.message.message_id) self._delete_setup_context_variables(context) update.callback_query.message.reply_text( """ Seed selection process was canceled!""") return ConversationHandler.END def cancel_cancelation(self, update: Update, context: CallbackContext): update.callback_query.answer() context.bot.edit_message_reply_markup( chat_id=update.callback_query.message.chat_id, message_id=update.callback_query.message.message_id) context.chat_data['current_message_id'] = None update.callback_query.message.reply_text(""" Continuing process""") return self.select_artists(update, context) def setup_done(self, update: Update, context: CallbackContext): update.callback_query.answer() # Removing keyboard from the last page message. context.bot.edit_message_reply_markup( chat_id=update.callback_query.message.chat_id, message_id=context.chat_data['current_message_id']) # No item was selected. Must choose at least one if len(context.chat_data['selected_artists_index']) == 0 and len( context.chat_data['selected_tracks_index']) == 0: update.callback_query.message.reply_text( """ No item selected. you must select at least one item between artists and tracks""" ) context.chat_data['current_message_id'] = None return self.select_artists(update, context) keyboard = InlineKeyboardMarkup( [[InlineKeyboardButton(text='Yes', callback_data='Yes')], [InlineKeyboardButton(text='No', callback_data='No')]]) update.callback_query.message.reply_text( """The selected items will be associated with our chat. Do you whish to proceed? """, reply_markup=keyboard) return DONE def setup_confirm(self, update: Update, context: CallbackContext): update.callback_query.answer() context.bot.edit_message_reply_markup( chat_id=update.callback_query.message.chat_id, message_id=update.callback_query.message.message_id) selected_artists = [ artist for i, artist in enumerate(context.chat_data['artists_list']) if i in context.chat_data['selected_artists_index'] ] selected_tracks = [ track for i, track in enumerate(context.chat_data['tracks_list']) if i in context.chat_data['selected_tracks_index'] ] if update.callback_query.data == 'Yes': # Remove old configuration and put new on DB self.redis_instance.remove_user_artists( update.callback_query.message.chat_id) self.redis_instance.remove_user_tracks( update.callback_query.message.chat_id) self.redis_instance.register_user_artists( update.callback_query.message.chat_id, selected_artists) self.redis_instance.register_user_tracks( update.callback_query.message.chat_id, selected_tracks) self._delete_setup_context_variables(context) if update.callback_query.data == 'Yes': update.callback_query.message.reply_text( """ Seeds were sucessfuly registered to your user!""") elif update.callback_query.data == 'No': update.callback_query.message.reply_text( """ Selected seeds were not registered to your user """) return ConversationHandler.END def stop_setup(self, update: Update, context: CallbackContext): update.callback_query.answer() update.callback_query.message.reply_text(""" Setup was interrupted """) return ConversationHandler.END
class BotSurveyCallbacks: def __init__(self, redis_instace=None, spotify_acess_point=None): if redis_instace is not None: self.redis_instance = redis_instace else: self.redis_instance = RedisAcess() if spotify_acess_point is not None: self.spotify_endpoint_acess = spotify_acess_point else: self.spotify_endpoint_acess = SpotifyEndpointAcess( self.redis_instance) def __load_spotify_survey__(self): """ Loads the Spotify Survey from file 'spotify_survey.yaml' """ with open('spotify_survey.yaml', 'r') as f: spotify_survey_file = yaml.safe_load(f) questions_list = spotify_survey_file['questions'] options_list = spotify_survey_file['options'] return SurveyManager(questions_list, options_list) def start_survey(self, update, context): if not self.redis_instance.is_user_logged_in(update.effective_chat.id): update.message.reply_text( """ Cannot perform operation: User not logged in with a Spotify account! """ ) return message = """ You're about to start a survey that will select some of your preferences in the musics you want the bot to recommend. A Level parameter for an attribute will not exclude any musics, but only serve as a guide to choose musics that have attribute values closest to the specified Level. A Range parameter for an attribute will excludes all possible musics with that attribute's value outside of that range. Note: Your previous configuration will be deleted when the first poll is sent to you. Keep that in mind. """ # The survey, one for each chat if context.chat_data.get('spotify_survey') is not None: del context.chat_data['spotify_survey'] context.chat_data['spotify_survey'] = self.__load_spotify_survey__() update.message.reply_text(message) keyboard = InlineKeyboardMarkup( [[InlineKeyboardButton(text='Start', callback_data='Start')]]) update.message.reply_text("""Press 'Start' to start survey""", reply_markup=keyboard) return GENERATE_QUESTION def generate_poll(self, update: Update, context: CallbackContext): question, options = context.chat_data['spotify_survey'].get_poll_info() #keyboard = InlineKeyboardMarkup([[InlineKeyboardButton(text='Next', callback_data='Next')]]) text = """__{question}__\n\n""".format( question=escape_markdown(question, version=2)) i = 0 for option in options: text += """*{i}*: {option}\n""".format(i=(i + 1), option=escape_markdown( option, version=2)) i += 1 text += """\n""" # As this will only be called as a callback_query once, put past survey data deletion here if update.callback_query: update.callback_query.answer() self.redis_instance.remove_all_survey_attributes( update.effective_chat.id) update.callback_query.message.reply_text(text, parse_mode='MarkdownV2') else: update.message.reply_text(text, parse_mode='MarkdownV2') return RECEIVE_QUESTION def receive_poll(self, update: Update, context: CallbackContext): chat_id = update.effective_chat.id user_answer = int(update.message.text) - 1 _, options = context.chat_data['spotify_survey'].get_poll_info() if user_answer < 0 or user_answer >= len(options): context.bot.send_message( chat_id=chat_id, text="""{choice} is not an option. Try again""".format( choice=(user_answer + 1))) return self.generate_poll(update, context) # Getting selected attribute and its set of values selected by the use'r attribute, values = context.chat_data[ 'spotify_survey'].get_curr_attribute_values(user_answer) self.redis_instance.register_survey_attribute(chat_id, attribute, values) # if this was the last question of the survey, stop sending polls if context.chat_data['spotify_survey'].is_end(): context.bot.send_message(chat_id=chat_id, text=""" Survey has been completed! """) return ConversationHandler.END # Get the amount (if any) of questions to be skiped if some value was decided (on the current case, # options that are not skiping setting a value, like options 'Ignore Level' or 'Ignore Range', that # saves empty dicts on the DB) skip_amount = 0 if len(values) > 0: skip_amount = context.chat_data['spotify_survey'].get_skip_count() context.chat_data['spotify_survey'].go_next_poll(skip_amount) return self.generate_poll(update, context) def wrong_selection_input(self, update: Update, context: CallbackContext): message = """ Error: input must be a single 2-digit number corresponding to the current options. Please try again""" update.message.reply_text(message) return RECEIVE_QUESTION def cancel(self, update: Update, context: CallbackContext): update.message.reply_text( "Attributes setup canceled. Note that no attributes settings will be saved." ) if context.chat_data.get('spotify_survey') is not None: del context.chat_data['spotify_survey'] self.redis_instance.remove_all_survey_attributes( update.effective_chat.id) return ConversationHandler.END
class BotLogoutCallbacks: def __init__(self, redis_instace=None, spotify_acess_point=None): if redis_instace is not None: self.redis_instance = redis_instace else: self.redis_instance = RedisAcess() if spotify_acess_point is not None: self.spotify_endpoint_acess = spotify_acess_point else: self.spotify_endpoint_acess = SpotifyEndpointAcess( self.redis_instance) # ========================================= LOGOUT CONVERSATION ============================================ # def confirm_logout(self, update: Update, context: CallbackContext): if not self.redis_instance.is_user_logged_in(update.effective_chat.id): update.message.reply_text( """ Cannot perform operation: User not logged in with a Spotify account! """ ) return ConversationHandler.END confirmation_text = """ Are you sure you want to log out? All information stored within this bot will be deleted! """ buttons = [[ InlineKeyboardButton(text='Yes', callback_data='Yes'), InlineKeyboardButton(text='No', callback_data='No') ]] keyboard = InlineKeyboardMarkup(buttons) update.message.reply_text(confirmation_text, reply_markup=keyboard) return CONFIRM_LOGOUT def confirm_playlist_deletion(self, update: Update, context: CallbackContext): question_text = "Do you wish to remove the current SpotSurveyBot's playlist from our Spotify account?" buttons = [[ InlineKeyboardButton(text='Yes', callback_data='Yes'), InlineKeyboardButton(text='No', callback_data='No') ]] keyboard = InlineKeyboardMarkup(buttons) update.callback_query.answer() update.callback_query.edit_message_text(text=question_text, reply_markup=keyboard) return DELETE_USER def delete_playlist(self, update: Update, context: CallbackContext): update.callback_query.answer() self.spotify_endpoint_acess.delete_playlist(update.effective_chat.id) update.callback_query.edit_message_text( text=""" Deleting playlist... """) time.sleep(1) return self.delete_user(update, context) def delete_user(self, update: Update, context: CallbackContext): # Since it's possible that the callback_query was already answered (if this function was called from # 'confirm_playlist_deletion' function, check for it. If it has been answered, it will generate an error) try: update.callback_query.answer() except telegramBadRequest: pass try: self.redis_instance.delete_user(update.effective_chat.id) except RedisError: update.callback_query.message.reply_text( """Could not delete internal user info: Internal database error. Try again later...""" ) update.callback_query.edit_message_text( text=""" Sucessfuly logged out! """) return ConversationHandler.END def stop_logout(self, update: Update, context: CallbackContext): update.callback_query.answer() update.callback_query.edit_message_text( text="""Logout operation has been canceled.""") return ConversationHandler.END
telegram_bot_token = os.environ.get('TELEGRAM_TOKEN') # Prepare bot and its functions updater = Updater(token=telegram_bot_token) dispatcher = updater.dispatcher load_handlers(dispatcher) # Start Webhook and set it to a domain (https://[domain]/[token]) updater.start_webhook(listen='0.0.0.0', port=5001, url_path=telegram_bot_token) updater.bot.setWebhook(telegram_webhook_url + telegram_bot_token) # ! If bot not communication with Telegram, try commenting here! if __name__ == "__main__": logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) LOGGER = logging.getLogger(__name__) dotenv.load_dotenv() if check_config_vars() == 0: REDIS_INSTANCE = RedisAcess() SPOTIFY_ENDPOINTS_ACESS = SpotifyEndpointAcess(REDIS_INSTANCE) BOT_GENERAL_CALLBACKS = BotGeneralCallbacks(REDIS_INSTANCE, SPOTIFY_ENDPOINTS_ACESS) BOT_SEED_CALLBACKS = BotSeedCallbacks(REDIS_INSTANCE, SPOTIFY_ENDPOINTS_ACESS) BOT_SURVEY_CALLBACKS = BotSurveyCallbacks(REDIS_INSTANCE, SPOTIFY_ENDPOINTS_ACESS) BOT_PLAYLIST_CALLBACKS = BotPlaylistCallbacks(REDIS_INSTANCE, SPOTIFY_ENDPOINTS_ACESS) BOT_LOGOUT_CALLBACKS = BotLogoutCallbacks(REDIS_INSTANCE, SPOTIFY_ENDPOINTS_ACESS) start_bot()
class BotPlaylistCallbacks: def __init__(self, redis_instace=None, spotify_acess_point=None): if redis_instace is not None: self.redis_instance = redis_instace else: self.redis_instance = RedisAcess() if spotify_acess_point is not None: self.spotify_endpoint_acess = spotify_acess_point else: self.spotify_endpoint_acess = SpotifyEndpointAcess( self.redis_instance) def confirm_user_preferences(self, update: Update, context: CallbackContext): if not self.redis_instance.is_user_logged_in(update.effective_chat.id): update.message.reply_text( """ Cannot perform operation: User not logged in with a Spotify account! """ ) return ConversationHandler.END if not self.redis_instance.get_user_artists( update.effective_chat.id ) and not self.redis_instance.get_user_tracks( update.effective_chat.id): update.message.reply_text( """ You must have chosen your seeds with command '/setup_seed' before! """ ) return ConversationHandler.END buttons = [[ InlineKeyboardButton(text='Yes', callback_data='Yes'), InlineKeyboardButton(text='No', callback_data='No') ]] keyboard = InlineKeyboardMarkup(buttons) update.message.reply_text( "Do you wish to generate a playlist with your current user preferences (see '/get_setup command')?", reply_markup=keyboard) return GENERATE_PLAYLIST def generate_playlist(self, update: Update, context: CallbackContext): update.callback_query.answer() context.bot.edit_message_reply_markup( chat_id=update.callback_query.message.chat_id, message_id=update.callback_query.message.message_id) recommended_tracks = self.spotify_endpoint_acess.get_recommendations( update.effective_chat.id) if len(recommended_tracks) == 0: message = """ WARNING: Could not get any track with your current set of attributes selected during survey process. This usually happens because of either very restricted ranges to attributes and/or because there are lots of ranges set (setting ranges to an attribute cuts off any recomendations that has that attribute outside of user specified range). This could, too, be caused by a reduce number of items on selection pool of musics Spotify uses, which can be caused by selecting only too niche musics or artists """ update.callback_query.message.reply_text(message) return ConversationHandler.END self.spotify_endpoint_acess.delete_all_tracks(update.effective_chat.id) self.spotify_endpoint_acess.add_tracks(update.effective_chat.id, recommended_tracks) update.callback_query.message.reply_text( """ Playlist generated sucessfuly """) return ConversationHandler.END def end(self, update: Update, context: CallbackContext): update.message.reply_text("Playlist generation canceled") return ConversationHandler.END