Example #1
0
    async def reload_clips(self, ctx):
        """Reloads the list of preset clips."""

        ## I don't really like having core modules intertwined with dynamic ones, maybe move the appropriate admin
        ## modules out into their dynamic module and exposing some admin auth function that they check in with before
        ## running the command?
        if (not self.clips_cog):
            await ctx.send(
                "Sorry <@{}>, but the clips cog isn't available.".format(
                    ctx.message.author.id))
            return False

        if (not self.is_admin(ctx.message.author)):
            await ctx.send("<@{}> isn't allowed to do that.".format(
                ctx.message.author.id))
            self.dynamo_db.put(
                dynamo_helper.DynamoItem(ctx, ctx.message.content,
                                         inspect.currentframe().f_code.co_name,
                                         False))
            return False

        count = self.clips_cog.reload_clips()

        loaded_clips_string = "Loaded {} clip{}.".format(
            count, "s" if count != 1 else "")
        await ctx.send(loaded_clips_string)

        self.dynamo_db.put(
            dynamo_helper.DynamoItem(ctx, ctx.message.content,
                                     inspect.currentframe().f_code.co_name,
                                     True))
        return (count >= 0)
Example #2
0
    async def skip(self, ctx):
        """Vote to skip the current speech."""

        state = self.get_speech_state(ctx.message.server)
        if(not state.is_speaking()):
            await self.bot.say("I'm not speaking at the moment.")
            self.dynamo_db.put(dynamo_helper.DynamoItem(ctx, ctx.message.content, inspect.currentframe().f_code.co_name, False))
            return False
        else:
            self.dynamo_db.put(dynamo_helper.DynamoItem(ctx, ctx.message.content, inspect.currentframe().f_code.co_name, True))

        voter = ctx.message.author
        if(voter == state.current_speech.requester):
            await self.bot.say("<@{}> skipped their own speech.".format(voter.id))
            await state.skip_speech(voter.voice_channel)
            ## Attempt to delete the command message
            await self.attempt_delete_command_message(ctx.message)
            return False
        elif(voter.id not in state.skip_votes):
            state.skip_votes.add(voter)

            total_votes = len([voter for voter in state.skip_votes if voter.voice_channel == state.voice_client.channel])
            total_members = len([member for member in await state.get_members() if not member.bot])
            vote_percentage = ceil((total_votes / total_members) * 100)

            if(total_votes >= self.skip_votes or vote_percentage >= self.skip_percentage):
                await self.bot.say("Skip vote passed by {}% of members. I'll skip the current speech.".format(vote_percentage))
                await state.skip_speech()
                return True
            else:
                raw = "Skip vote added, currently at {}/{} or {}%"
                await self.bot.say(raw.format(total_votes, total_members, vote_percentage))

        else:
            await self.bot.say("<@{}> has already voted!".format(voter.id))
Example #3
0
    async def say(self, ctx, *, message, ignore_char_limit=False, target_member=None):
        """Speaks your text aloud to your channel."""

        ## Todo: look into memoization of speech. Phrases.py's speech is a perfect candidate

        ## Verify that the target/requester is in a channel
        if (not target_member or not isinstance(target_member, Member)):
            target_member = ctx.message.author

        voice_channel = target_member.voice_channel
        if(voice_channel is None):
            await self.bot.say("<@{}> isn't in a voice channel.".format(target_member.id))
            self.dynamo_db.put(dynamo_helper.DynamoItem(ctx, ctx.message.content, inspect.currentframe().f_code.co_name, False))
            return False

        ## Make sure the message isn't too long
        if(not self.tts_controller.check_length(message) and not ignore_char_limit):
            await self.bot.say("Keep phrases less than {} characters.".format(self.tts_controller.char_limit))
            self.dynamo_db.put(dynamo_helper.DynamoItem(ctx, ctx.message.content, inspect.currentframe().f_code.co_name, False))
            return False

        state = self.get_speech_state(ctx.message.server)
        if(state.voice_client is None):
            ## Todo: Handle exception if unable to create a voice client
            await self.create_voice_client(voice_channel)

        ## Parse down the message before sending it to the TTS service
        message = self.message_parser.parse_message(message, ctx.message)

        try:
            ## Create a .wav file of the message
            wav_path = await self.save(message, ignore_char_limit)
            if(wav_path):
                ## Create a player for the .wav
                player = state.voice_client.create_ffmpeg_player(
                    wav_path,
                    before_options=self.ffmpeg_before_options,
                    options=self.ffmpeg_options,
                    after=state.next_speech
                )
            else:
                raise RuntimeError("Unable to save a proper .wav file.")
        except Exception as e:
            utilities.debug_print("Exception in say():", e, debug_level=0)
            await self.bot.say("Unable to say the last message. Sorry, <@{}>.".format(ctx.message.author.id))
            self.dynamo_db.put(dynamo_helper.DynamoItem(ctx, ctx.message.content, inspect.currentframe().f_code.co_name, False))
            return False
        else:
            ## On successful player creation, build a SpeechEntry and push it into the queue
            await state.speech_queue.put(SpeechEntry(ctx.message.author, voice_channel, player, wav_path))
            self.dynamo_db.put(dynamo_helper.DynamoItem(ctx, ctx.message.content, inspect.currentframe().f_code.co_name, True))

            ## Attempt to delete the command message
            await self.attempt_delete_command_message(ctx.message)

            ## Start a timeout to disconnect the bot if the bot hasn't spoken in a while
            await self.attempt_leave_channel(state)

            return True
Example #4
0
    async def disconnect(self, ctx):
        """ Disconnect from the current voice channel."""

        if(not self.is_admin(ctx.message.author)):
            await ctx.send("<@{}> isn't allowed to do that.".format(ctx.message.author.id))
            self.dynamo_db.put(dynamo_helper.DynamoItem(ctx, ctx.message.content, inspect.currentframe().f_code.co_name, False))
            return False

        state = self.audio_player_cog.get_server_state(ctx)
        await state.ctx.voice_client.disconnect()

        self.dynamo_db.put(dynamo_helper.DynamoItem(ctx, ctx.message.content, inspect.currentframe().f_code.co_name, True))
        return True
Example #5
0
        async def on_command_error(ctx, exception):
            '''Handles command errors. Attempts to find a similar command and suggests it, otherwise directs the user to the help prompt.'''

            logger.exception("Unable to process command.", exc_info=exception)
            self.dynamo_db.put(
                dynamo_helper.DynamoItem(ctx, ctx.message.content,
                                         inspect.currentframe().f_code.co_name,
                                         False, str(exception)))

            ## Attempt to find a command that's similar to the one they wanted. Otherwise just direct them to the help page
            most_similar_command = self.find_most_similar_command(
                ctx.message.content)

            if (most_similar_command[0] == ctx.invoked_with):
                ## Handle issues where the command is valid, but couldn't be completed for whatever reason.
                await ctx.send("I'm sorry <@{}>, I'm afraid I can't do that.\n" \
                    "Discord is having some issues that won't let me speak right now."
                    .format(ctx.message.author.id))
            else:
                help_text_chunks = [
                    "Sorry <@{}>, **{}{}** isn't a valid command.".format(
                        ctx.message.author.id, ctx.prefix, ctx.invoked_with)
                ]

                if (most_similar_command[1] >
                        self.invalid_command_minimum_similarity):
                    help_text_chunks.append("Did you mean **{}{}**?".format(
                        self.activation_string, most_similar_command[0]))
                else:
                    help_text_chunks.append("Try the **{}help** page.".format(
                        self.activation_string))

                ## Dump output to user
                await ctx.send(" ".join(help_text_chunks))
                return
Example #6
0
    async def reload_cogs(self, ctx):
        """Reloads the bot's cogs."""

        if(not self.is_admin(ctx.message.author)):
            await ctx.send("<@{}> isn't allowed to do that.".format(ctx.message.author.id))
            self.dynamo_db.put(dynamo_helper.DynamoItem(ctx, ctx.message.content, inspect.currentframe().f_code.co_name, False))
            return False

        count = self.hawking.module_manager.reload_all()
        total = len(self.hawking.module_manager.modules)

        loaded_cogs_string = "Loaded {} of {} cogs.".format(count, total)
        await ctx.send(loaded_cogs_string)

        self.dynamo_db.put(dynamo_helper.DynamoItem(ctx, ctx.message.content, inspect.currentframe().f_code.co_name, True))
        return (count >= 0)
Example #7
0
    async def summon(self, ctx):
        """Summons the bot to join your voice channel."""

        ## Check that the requester is in a voice channel
        summoned_channel = ctx.message.author.voice_channel
        if(summoned_channel is None):
            await self.bot.say("{} isn't in a voice channel.".format(ctx.message.author))
            self.dynamo_db.put(dynamo_helper.DynamoItem(ctx, ctx.message.content, inspect.currentframe().f_code.co_name, False))
            return False
        else:
            self.dynamo_db.put(dynamo_helper.DynamoItem(ctx, ctx.message.content, inspect.currentframe().f_code.co_name, True))

        ## Attempt to delete the command message
        await self.attempt_delete_command_message(ctx.message)

        return await self.join_channel(summoned_channel)
Example #8
0
    async def _plot(self, ctx, message, map_name):
        """Plots your given plane's path on the game map."""

        ## Parse the user's command
        try:
            path_obj = self.path_parser.parse_message(message)
        except RuntimeError as e:
            ## Give them some feedback if the command isn't understandable
            await self.failed_command_feedback(e)

            ## Put some information about the failed query into the database
            self.dynamo_db.put(
                dynamo_helper.DynamoItem(ctx.message.author.id,
                                         ctx.message.timestamp.timestamp(),
                                         ctx.message.channel.name,
                                         ctx.message.server.name, map_name,
                                         message, None))
            return None
        else:
            ## Put some information about the successful query into the database
            self.dynamo_db.put(
                dynamo_helper.DynamoItem(ctx.message.author.id,
                                         ctx.message.timestamp.timestamp(),
                                         ctx.message.channel.name,
                                         ctx.message.server.name, map_name,
                                         message, str(path_obj)))

        ## Get the file path for the final map image, and generate a callback to delete the image
        plotted_map = self.plotter.plot_plane_path(map_name, path_obj)
        map_path = self.plotter.file_controller.save_map(plotted_map)
        delete_map_callback = self.plotter.file_controller.create_delete_map_callback(
            map_path)

        ## Upload the file to the user's channel in Discord.
        return await self.upload_file(
            map_path,
            ctx.message.channel,
            content="Here you go, <@{}>. Good luck!".format(
                ctx.message.author.id),
            callback=delete_map_callback)
Example #9
0
        async def on_command_error(ctx, exception):
            '''Handles command errors. Attempts to find a similar command and suggests it, otherwise directs the user to the help prompt.'''

            self.dynamo_db.put(
                dynamo_helper.DynamoItem(ctx, ctx.message.content,
                                         inspect.currentframe().f_code.co_name,
                                         False, str(exception)))

            ## Attempt to find a command that's similar to the one they wanted. Otherwise just direct them to the help page
            most_similar_command = self.find_most_similar_command(
                ctx.message.content)

            if (most_similar_command[0] == ctx.invoked_with):
                logger.exception(
                    "Unable to complete command, with content: {}, for author: {}, in channel {}, in server: {}"
                    .format(ctx.message.content, ctx.message.author.name,
                            ctx.guild.name),
                    exc_info=exception)
                ## Handle issues where the command is valid, but couldn't be completed for whatever reason.
                await ctx.send(
                    "I'm sorry <@{}>, I'm afraid I can't do that.\nSomething went wrong, and I couldn't complete the command."
                    .format(ctx.message.author.id))
            else:
                logger.exception(
                    "Received invalid command: '{0}{1}', suggested: '{0}{2}', for author: {3}, in server: {4}"
                    .format(self.activation_str, ctx.invoked_with,
                            most_similar_command[0], ctx.message.author.name,
                            ctx.guild.name),
                    exc_info=exception)

                help_text_chunks = [
                    "Sorry <@{}>, **{}{}** isn't a valid command.".format(
                        ctx.message.author.id, ctx.prefix, ctx.invoked_with)
                ]

                if (most_similar_command[1] >
                        self.invalid_command_minimum_similarity):
                    help_text_chunks.append("Did you mean **{}{}**?".format(
                        self.activation_str, most_similar_command[0]))
                else:
                    help_text_chunks.append("Try the **{}help** page.".format(
                        self.activation_str))

                ## Dump output to user
                await ctx.send(" ".join(help_text_chunks))
                return
Example #10
0
        async def on_command_error(exception, ctx):
            # discord.py uses reflection to set the destination chat channel for whatever reason (sans command ctx)
            _internal_channel = ctx.message.channel

            utilities.debug_print(exception, debug_level=2)

            self.dynamo_db.put(dynamo_helper.DynamoItem(
                ctx, ctx.message.content, inspect.currentframe().f_code.co_name, False, str(exception)))

            ## Handy for debugging
            # import traceback
            # print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr)
            # traceback.print_exception(type(exception), exception, exception.__traceback__, file=sys.stderr)

            ## Permissions error?
            if (isinstance(exception, CommandInvokeError) and isinstance(exception.original, TimeoutError)):
                await self.bot.say("Sorry <@{}>, I'm not able to join the voice channel right now. Discord might be having issues, or I might not have permission to join."
                    .format(ctx.message.author.id))
                return

            ## Poorly handled (for now, until I can get more concrete examples in my database) error messages for users
            if ("code =" in str(exception)):
                await self.bot.say("Sorry <@{}>, Discord is having some issues that won't let me speak right now."
                    .format(ctx.message.author.id))
                return

            ## Attempt to find a command that's similar to the one they wanted. Otherwise just direct them to the help page
            else:
                most_similar_command = self.find_most_similar_command(ctx.message.content)

                if (most_similar_command[0] == ctx.invoked_with):
                    ## Handle issues where the command is valid, but couldn't be completed for whatever reason.
                    await self.bot.say("Sorry <@{}>, I can't talk right now. Try again in a little bit.".format(ctx.message.author.id))
                else:
                    ## Otherwise, handle other issues involving invalid commands
                    help_text_chunks = [
                        "Sorry <@{}>, **{}{}** isn't a valid command.".format(ctx.message.author.id, ctx.prefix, ctx.invoked_with)
                    ]

                    ## Build the output to give to the user
                    if (most_similar_command[1] > self.invalid_command_minimum_similarity):
                        help_text_chunks.append("Did you mean **{}{}**?".format(self.activation_str, most_similar_command[0]))
                    else:
                        help_text_chunks.append("Try the **{}help** page.".format(self.activation_str))

                    await self.bot.say(" ".join(help_text_chunks))
Example #11
0
    async def play_audio(self,
                         ctx,
                         file_path: str,
                         target_member=None,
                         callback: Callable = None):
        '''Plays the given audio file aloud to your channel'''

        ## Verify that the target/requester is in a channel
        if (not target_member or not isinstance(target_member, Member)):
            target_member = ctx.message.author

        voice_channel = None
        if (target_member.voice):  ## Handle users not in a voice channel
            voice_channel = target_member.voice.channel
        if (voice_channel is None):
            await ctx.send("<@{}> isn't in a voice channel.".format(
                target_member.id))
            return False

        ## Make sure file_path points to an actual file
        if (not os.path.isfile(file_path)):
            logger.error(
                "Unable to play file at: {}, file doesn't exist or isn't a file."
                .format(file_path))
            await ctx.send("Sorry, <@{}>, that couldn't be played.".format(
                ctx.message.author.id))
            return False

        ## Get/Build a state for this audio, build the player, and add it to the state
        state = self.get_server_state(ctx)
        player = self.build_player(file_path)
        await state.add_play_request(
            AudioPlayRequest(ctx.message.author, voice_channel, player,
                             file_path, callback))

        self.dynamo_db.put(
            dynamo_helper.DynamoItem(ctx, ctx.message.content,
                                     inspect.currentframe().f_code.co_name,
                                     True))

        return True