コード例 #1
0
    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)
コード例 #2
0
    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)
コード例 #3
0
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')
コード例 #4
0
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
コード例 #5
0
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
コード例 #6
0
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
コード例 #7
0
    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()
コード例 #8
0
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