Exemplo n.º 1
0
    async def timer_pause(self, ctx: commands.Context):
        """ Pauses the timer, if it's running. Keeps all settings and current
            period and time.
        """

        channel = self.bot.spoof(ctx.message.author, lib.get_channel(ctx))

        interface = self.bot.get_interface(channel)
        if len(interface.subbed) == 0:
            if interface.timer.stop():
                send = "No subs detected, timer will instead stop soon."
                await self.bot.say(send, delete_after=interface.timer.step)
            else:
                await self.bot.remove_messages(channel)

                send = "No subs detected, timer has stopped instead."
                await self.bot.say(send, tts=interface.tts)
            log = (
                "Attempted to pause the timer, but due to the lack of subs, "
                "it will be stopped instead")
        elif interface.timer.pause():
            log = "Timer will be paused soon."
            await self.bot.say(log, delete_after=interface.timer.step)

        else:
            log = "Could not pause timer, stopped or already running."
            await self.bot.say("I cannot stop something that isn't moving.",
                               delete_after=self.bot.ans_lifespan)

        lib.log(log, channel_id=channel.id)
Exemplo n.º 2
0
    async def timer_sub(self, ctx: commands.Context):
        """ Adds you to the list of people currently using the timer.
            If you're in this list, you will receive a private message if the
            timer's period changes or if it starts/pauses/stops.
        """

        author = ctx.message.author
        channel = self.bot.spoof(author, lib.get_channel(ctx))

        interface = self.bot.get_interface(channel)
        if author not in interface.subbed:
            interface.add_sub(author, datetime.utcnow())

            interface.restart_inactivity()

            log = (lib.get_author_name(ctx, True) +
                   " has subscribed to this timer.")
            send = "You've successfully subscribed to this timer, {}!" \
                .format(lib.get_author_name(ctx, True))
        else:
            log = (lib.get_author_name(ctx, True) + " tried to subscribe to " +
                   "this timer, but he was already added")
            send = ("You're already subscribed. " +
                    "I'll let you know of any changes!")

        lib.log(log, channel_id=channel.id)
        await self.bot.say(send, delete_after=self.bot.ans_lifespan)
Exemplo n.º 3
0
    async def attendance(self, ctx: commands.Context, name=None):
        """ Gives the last attendance (timer subscription) of the user
            to any timer, in the form of the UTC time in which it was
            registered.

        :param name: The username (Not the nick) of the user of whose
            attendance you want to know. Must use the name#discriminator format.
        :return:
        """
        author = ctx.message.author
        if name is None:
            name = str(author)

        if name == "all":
            result = '\n'.join("{}: {}".format(
                record.name.split('#')[0], "None found." if record.last_seen is
                None else record.last_seen.strftime("%m-%d-%y %H:%M"))
                               for record in db_manager.get_all_records())
        else:
            record = db_manager.get_user_attendance(name)
            result = "None found." if record is None else record\
                .strftime("%m-%d-%y %H:%M")

        log = "{} queried for {} attendance. Result was: {}"\
            .format(lib.get_name(author, True),
                    "their" if name == str(author) else (name + "'s"), result)

        lib.log(log, channel_id=lib.get_channel_id(ctx))

        await self.bot.say("```\n{}\n```".format(result),
                           delete_after=self.bot.ans_lifespan * 3)
Exemplo n.º 4
0
    async def admin_lock(self, ctx: commands.Context):
        """ Locks a channel's timer so no user can modify it.
            Unless they have permissions.
            This command either locks or unlocks, thus acting as a switch.
            Requires elevated permissions.
        """

        channel = lib.get_channel(ctx)
        interface = self.bot.get_interface(channel)

        if interface.spoofed is not None:
            channel = interface.spoofed

        if interface.locked:
            interface.locked = False

            await self.bot.say("Channel unlocked.",
                               delete_after=self.bot.ans_lifespan)
            lib.log(lib.get_author_name(ctx) + " unlocked the channel.",
                    channel_id=channel.id)
        else:
            interface.locked = True

            await self.bot.say("Channel locked.",
                               delete_after=self.bot.ans_lifespan)
            lib.log(lib.get_author_name(ctx) + " locked the channel.",
                    channel_id=channel.id)
Exemplo n.º 5
0
    async def timer_unsub(self, ctx: commands.Context):
        """ Removes you from the list of people currently using the timer.
        """

        channel = self.bot.spoof(ctx.message.author, lib.get_channel(ctx))
        author = ctx.message.author

        result = 0

        interface = self.bot.get_interface(channel)
        if author in interface.subbed:
            result = interface.remove_sub(author)

            log = (lib.get_author_name(ctx, True) +
                   " has un-subscribed to this timer.")
            send = "You've successfully un-subscribed to this timer, {}!" \
                .format(lib.get_author_name(ctx, True))
        else:
            log = (lib.get_author_name(ctx, True) + " tried to un-subscribe " +
                   "to this timer, but he was not in the list")
            send = "You're not subscribed to this timer... "

        lib.log(log, channel_id=channel.id)
        await self.bot.say(send, delete_after=self.bot.ans_lifespan)
        if result == -1:
            await self.bot.say("Timer now has no subs. Will stop after {} "
                               "minutes unless someone subscribes!".format(
                                   self.bot.timer_inactivity_allowed),
                               delete_after=self.bot.ans_lifespan)
        elif result == -2:
            await self.bot.remove_messages(channel)
            await self.bot.say("Timer has stopped since it was paused and "
                               "everyone un-subscribed")
Exemplo n.º 6
0
    async def timer_time(self, ctx: commands.Context):
        """ Gives the user the current period and time of the timer.
        """

        channel = self.bot.spoof(ctx.message.author, lib.get_channel(ctx))

        send = self.bot.get_interface(channel).timer.time(True)

        lib.log(send, channel_id=channel.id)
        await self.bot.say(send, delete_after=self.bot.ans_lifespan * 2)
Exemplo n.º 7
0
    async def admin_reloadcfg(self):
        """ Reloads the configuration.
            Requires elevated permissions.
        """

        self.bot.reload_config(config.get_config().reload())

        await self.bot.say("Successfully reloaded configuration.",
                           delete_after=self.bot.ans_lifespan)
        lib.log("Reloaded configuration.")
Exemplo n.º 8
0
    async def timer_status(self, ctx: commands.Context):
        """ Tells whether the timer is stopped, running or paused,
            if it's correctly set up and if it will soon stop or pause.
        """

        channel = self.bot.spoof(ctx.message.author, lib.get_channel(ctx))

        send = self.bot.get_interface(channel).timer.show_status()

        lib.log(send, channel_id=channel.id)
        await self.bot.say(send, delete_after=self.bot.ans_lifespan * 2)
Exemplo n.º 9
0
    def get_record(self, user: User):
        record = self._sql_session.query(TimerUser)\
            .filter_by(discord_id=user.id).first()
        if record is None:
            lib.log("DB queried for non-existent user {},"
                    " registry will be created.".format(str(user)))
            record = TimerUser(discord_id=user.id, name=str(user))
            self._sql_session.add(record)
            self._sql_session.commit()

        return record
Exemplo n.º 10
0
    async def last(self, ctx: commands.Context):
        """ Shows you how long your last session lasted.
        """
        time_str = printable_time(
            db_manager.get_user_last_session(ctx.message.author))
        if time_str is None:
            time_str = "None found."

        lib.log("{} queried for their last session time. Result: {}".format(
            lib.get_author_name(ctx, True), time_str))

        await self.bot.say("```{}```".format(time_str),
                           delete_after=self.bot.ans_lifespan * 3)
Exemplo n.º 11
0
    async def on_command_error(self, error, ctx: commands.Context):

        log = lib.get_author_name(ctx)
        send = None

        if isinstance(error, commands.CheckFailure):

            if str(error) == "timer not found":
                send = "No timer found for this channel."
                log += " tried to interact with a nonexistent timer."

            elif str(error) == "timer locked":
                send = "You are not allowed to modify this timer."
                log += " tried to modify a locked timer without permissions."

            elif str(error) == "no permissions" or str(error) == "not admin":
                send = "You do not have permission to do this!"
                log += (" tried to execute a command and failed, " +
                        "lacked permissions.")
            else:
                send = "Timers are not allowed in this channel."
                log += " tried to start a timer in a non-whitelisted channel."

        elif isinstance(error, commands.CommandNotFound):
            send = "Command not found: `" + ctx.invoked_with + "`."
            log += " tried to execute a nonexistent command: `{}`."\
                .format(ctx.invoked_with)

            alt = None
            for name, command in self.bot.commands.items():
                if ctx.invoked_with == name:
                    alt = name
                elif isinstance(command, commands.GroupMixin):
                    for sub_name, sub_command in command.commands.items():
                        if ctx.invoked_with == sub_name:
                            alt = name + " " + sub_name

            if alt is not None:
                send += " Did you mean `" + alt + "`?"

        elif isinstance(error, commands.CommandInvokeError):
            lib.log_cmd_stacktrace(error)
            return
        else:
            log = str(error)

        lib.log(log, channel_id=lib.get_channel_id(ctx), level=logging.WARN)
        await self.bot.safe_send(lib.get_channel(ctx),
                                 send,
                                 delete_after=self.bot.ans_lifespan)
Exemplo n.º 12
0
    async def admin_sub(self, ctx: commands.Context, user_id: str,
                        channel_id=None):
        """ Forcefully subscribes a member to a timer.

        :param user_id: The ID of the user to subscribe
        :type user_id: str

        :param channel_id: The ID of the channel to subscribe the user to. If
            it's None or not provided, defaults to the channel this command was
            sent to.
        :type channel_id: str
        """

        if channel_id is None:
            channel_id = lib.get_channel(ctx).id

        server = lib.get_server(ctx)
        user = server.get_member(user_id)

        interface = self.bot.get_interface(server.get_channel(channel_id),
                                           False)
        author_name = lib.get_author_name(ctx, True)
        member_name = lib.get_name(user, True)
        if interface is None:
            lib.log("{} tried to subscribe {} to {}, "
                    "but the channel was not found or had no interface"
                    .format(author_name, member_name,  "this channel" if
                            channel_id is None else "channel with id=" +
                                                    channel_id),
                    channel_id=channel_id)
            await self.bot.say("Channel not found or invalid.",
                               delete_after=self.bot.ans_lifespan)
        elif user is None:
            lib.log("{} tried to forcefully subscribe an invalid member"
                    .format(author_name), channel_id=channel_id)
            await self.bot.say("User is invalid. Are you sure its his ID?",
                               delete_after=self.bot.ans_lifespan)
        else:
            if user in interface.subbed:
                await self.bot.say("User is already subscribed!",
                                   delete_after=self.bot.ans_lifespan)
                lib.log("{} tried to subscribe {} to {}, but user is already "
                        "subscribed".format(author_name, member_name,
                                            "this channel" if channel_id is None
                                            else "channel with id=" + channel_id
                                            ), channel_id=channel_id)
                return

            interface.add_sub(user, datetime.now())
            lib.log("{} forcefully subscribed {} to {}."
                    .format(author_name, member_name, "this channel" if
                            channel_id is None else "channel with id=" +
                                                    channel_id),
                    channel_id=channel_id)
            await self.bot.say("Member subscribed!",
                               delete_after=self.bot.ans_lifespan)
            await self.bot.say("{}, {} has subscribed you to this channel's "
                               "timer!".format(member_name, author_name),
                               delete_after=self.bot.ans_lifespan)
Exemplo n.º 13
0
    async def leaderboard(self, ctx: commands.Context):
        """ Shows the highest recorded times
        """
        result = '\n' \
            .join("{} - {}".format(record.name.split('#')[0],
                                   "None found." if
                                   record.total_recorded is None else
                                   printable_time(record.total_recorded))
                  for record in db_manager.get_leaderboard())

        lib.log("{} queried for the leaderboard. Result: {}".format(
            lib.get_author_name(ctx, True), result))

        await self.bot.say("```\n{}\n```".format(result),
                           delete_after=self.bot.ans_lifespan * 3)
Exemplo n.º 14
0
    async def timer_resume(self, ctx: commands.Context):
        """ Resumes a paused timer.
        """

        channel = self.bot.spoof(ctx.message.author, lib.get_channel(ctx))

        if self.bot.get_interface(channel).timer.resume():
            await self.bot.run_timer(channel)
        else:
            lib.log("Unable to resume timer, stopped or already running.",
                    channel_id=channel.id)
            await self.bot.say("**grumble grumble.** The timer is " +
                               "stopped or already running, I can't " +
                               "resume that!",
                               delete_after=self.bot.ans_lifespan)
Exemplo n.º 15
0
    async def check_last(self, ctx: commands.Context, name):
        """ Shows you how long other users' last session lasted.

            :param name: The name (not the nick) of the person to check.
            Must use the name#discriminator format.
        """
        time_str = printable_time(db_manager.get_user_last_session(name))
        if time_str is None:
            time_str = "None found."

        lib.log("{} queried for {}'s last session time. Result: {}".format(
            lib.get_author_name(ctx, True),
            "their" if name == str(ctx.message.author) else (name + "'s"),
            time_str))

        await self.bot.say("```{}```".format(time_str),
                           delete_after=self.bot.ans_lifespan * 3)
Exemplo n.º 16
0
    async def admin_debug(self):
        """ Makes the logger show debug-level information on the log.
            This command is administrator-only.
        """

        if lib.is_logger_debug():
            lib.debug(False)
            level = "info"
            state = "off"
        else:
            lib.debug(True)
            level = "debug"
            state = "on"

        lib.log("Switching to {}-level logging".format(level))
        await self.bot.say("Debug mode {}.".format(state),
                           delete_after=self.bot.ans_lifespan)
Exemplo n.º 17
0
    async def _translate_keyword(self, keyword: str, server_id: str,
                                 channel_id: str):
        if keyword == "help":
            example_periods = ', '.join(
                str(period.time)
                for period in PomodoroTimer.parse_format(SAFE_DEFAULT_FMT))
            await self.bot.say(
                ("**Example:**\n\t {}setup {}\n\t"
                 "_This will give you a sequence of {}_").format(
                     self.bot.command_prefix, SAFE_DEFAULT_FMT,
                     example_periods),
                delete_after=self.bot.ans_lifespan * 2)

            return None

        if keyword == 'default':
            # fetch default setup string from config,
            # or fallback to "Safe Default"
            translation = config.get_config().get_str(
                'timer.channel_whitelist.' + server_id + '.' + channel_id)
            if translation is None:
                lib.log("No setup configured for this channel. Using the " +
                        "safe default option",
                        channel_id=channel_id)
                translation = SAFE_DEFAULT_FMT

            return translation
        if keyword == 'blank':
            pass

        keys = keyword.split(':', 1)
        if len(keys) < 2:
            return keyword
        if keys[0] == 'typical':
            durations = keys[1].split(',', 2)
            return "(2xStudy/Work:{x},Break:{y}),Study/Work:{x},Long_Break:{z}"\
                .format(x=durations[0], y=durations[1], z=durations[2])

        if keys[0] == 'saved':
            translation = config.get_config().get_str('timer.saved_formats.' +
                                                      keys[1])
            return translation
        return keyword
Exemplo n.º 18
0
    async def timer_stop(self, ctx: commands.Context):
        """ Stops the timer, if it's running.
            Resets the current period and time, but keeps the setup.
        """

        channel = self.bot.spoof(ctx.message.author, lib.get_channel(ctx))

        interface = self.bot.get_interface(channel)
        if interface.timer.stop():
            send = "Timer will stop soon."
            await self.bot.say(send, delete_after=interface.timer.step)

        else:
            await self.bot.remove_messages(channel)

            send = "Timer has stopped."
            await self.bot.say(send, tts=interface.tts)

        lib.log(send, channel_id=channel.id)
Exemplo n.º 19
0
    async def admin_spoof(self, ctx: commands.Context, spoofed_id=None):
        """ Enables spoof-mode on a channel.
            Spoof mode allows users with permissions to modify another specified
            channel's timer from the one in which this command
            was executed.

            For example, if channel #session_1 has ID '249719010319532064'
            and someone executes '!spoof 249719010319532064' from #admin_area,
            all timer-related commands (except for setup) executed from
            #admin_area by members with permissions will either affect or give
            information of the timer in #session_1 instead.

        :param spoofed_id: The ID of the channel that instructions will be
            sent to.
        :type spoofed_id: str
        """

        channel = lib.get_channel(ctx)

        if channel.id == spoofed_id:
            await self.bot.say("How about no. " + spoofed_id,
                               delete_after=self.bot.ans_lifespan)
            return

        spoofed_channel = lib.get_server(ctx).get_channel(spoofed_id)

        if spoofed_id is not None:
            self.bot.get_interface(channel).spoofed = spoofed_channel

            send = "Now acting in channel " + spoofed_channel.name
            log = "Now acting as if in " + spoofed_channel.name

        elif self.bot.get_interface(channel).spoofed is not None:
            self.bot.get_interface(channel).spoofed = None

            send = "Now acting in current channel"
            log = "Spoofing now off"
        else:
            raise commands.MissingRequiredArgument

        await self.bot.say(send, delete_after=self.bot.ans_lifespan)
        lib.log(log, channel_id=channel.id)
Exemplo n.º 20
0
    async def admin_shutdown(self, ctx: commands.Context):
        """ Exits the program. Administrator only!
        """

        lib.log("Shutting down...")
        await self.bot.say("Hope I did well, bye!")

        for channel, timer in self.bot.valid_timers().items():
            if timer.get_state() != State.STOPPED:
                timer.stop()
                if lib.get_channel(ctx) != channel:
                    await self.bot.safe_send(
                        channel,
                        "I'm sorry, I have to go. See you later!"
                    )

                    await self.bot.remove_messages(channel)
        self.bot.unsub_all()

        await self.bot.logout()
Exemplo n.º 21
0
    async def timer_superreset(self, ctx: commands.Context):
        """ Ignores all conditions and resets the channel's timer.
            Requires elevated permissions.
        """

        channel = self.bot.spoof(ctx.message.author, lib.get_channel(ctx))

        interface = self.bot.get_interface(channel)
        if interface.timer.get_state() == State.RUNNING:
            self.bot.timers_running -= 1
            await self.bot.update_status()

        await self.bot.remove_messages(channel)

        interface.timer = None

        lib.log("Successfully forced a reset on this channel's timer.",
                channel_id=channel.id)
        await self.bot.say("Timer has been force-reset",
                           delete_after=self.bot.ans_lifespan)
Exemplo n.º 22
0
    async def timer(self, ctx):
        """ Controls the channel's timer. Do '!help timer' for sub-commands.
            None of the sub-commands will really work without using `setup`
            first.
        """

        if ctx.invoked_subcommand is None:
            sect = ctx.message.content.split(' ')
            if len(sect) < 2 or sect[1] is None:
                log = "{} invoked an incomplete timer command."

                send = "Timers are allowed here. Now what?"
            else:
                log = "{} invoked an invalid timer command."
                send = "Invalid timer sub-command."
        else:
            return

        lib.log(log.format(lib.get_author_name(ctx)),
                channel_id=lib.get_channel_id(ctx))
        await self.bot.say(send, delete_after=self.bot.ans_lifespan)
Exemplo n.º 23
0
    async def total(self, ctx: commands.Context, name=None):
        """ Shows you the total time a user has used the timer for.

            :param name: The name (not the nick) of the person to check.
            Must use the name#discriminator format. If none is provided, it will
            check your own record.
        """
        if name is None:
            name = ctx.message.author

        time_str = printable_time(db_manager.get_user_total(name))
        if time_str is None:
            time_str = "None found."

        name = str(name)
        lib.log("{} queried for {}'s last session time. Result: {}".format(
            lib.get_author_name(ctx, True),
            "their" if name == str(ctx.message.author) else (name + "'s"),
            time_str))

        await self.bot.say("```{}```".format(time_str),
                           delete_after=self.bot.ans_lifespan * 3)
Exemplo n.º 24
0
    async def timer_goto(self, ctx: commands.Context, period_idx):
        """ Skips to the n-th period, assuming the periods' indexes go
            from 1 to the amount of them.

        :param period_idx: The index of the period to start from, from 1 to n.
        :type period_idx: 'next' or int such that 1 <= period_idx <= n,
            n being the amount of periods set.
        """

        channel = self.bot.spoof(ctx.message.author, lib.get_channel(ctx))

        interface = self.bot.get_interface(channel)

        if period_idx == "next":
            idx = interface.timer.get_period(True) + 1
        else:
            try:
                idx = int(period_idx)
            except TypeError:
                raise commands.BadArgument

        label = interface.timer.goto(idx)

        if label is not None:
            log = send = "Moved to period number {!s} ({})".format(idx, label)

            if interface.timer.get_state() != State.STOPPED:
                await self.bot.edit_message(interface.list_message,
                                            interface.timer.list_periods())

                if interface.timer.get_state() == State.PAUSED:
                    await self.bot.edit_message(interface.time_message,
                                                interface.timer.time())
        else:
            log = "Invalid period number entered when trying goto command."
            send = "Invalid period number."

        lib.log(log, channel_id=channel.id)
        await self.bot.say(send, delete_after=self.bot.ans_lifespan)
Exemplo n.º 25
0
    async def timer_reset(self, ctx: commands.Context):
        """ Resets the timer setup.
        """

        channel = self.bot.spoof(ctx.message.author, lib.get_channel(ctx))

        interface = self.bot.get_interface(channel)
        if interface.timer.get_state() == State.STOPPED:
            interface.timer.set_state(None)

            interface.timer = None
            interface.time_message = None
            interface.list_message = None

            log = lib.get_author_name(ctx) + " reset the timer."
            send = "Successfully reset session configuration."
        else:
            log = (lib.get_author_name(ctx) + " tried resetting a timer that "
                   "was running or paused.")
            send = "Cannot do that while the timer is not stopped."

        lib.log(log, channel_id=channel.id)
        await self.bot.say(send, delete_after=self.bot.ans_lifespan)
Exemplo n.º 26
0
    async def timer_tts(self, ctx: commands.Context, toggle: str = None):
        """ Sets the TTS option on or off for the channel.

        :param toggle: Whether to turn on or off the TTS option. If no option
            is provided, it will toggle it
        :type toggle: str
        """

        channel = self.bot.spoof(ctx.message.author, lib.get_channel(ctx))
        interface = self.bot.get_interface(channel)

        log = send = None

        if toggle is None:
            interface.tts = not interface.tts
            toggle = "ok"
        else:
            try:
                interface.tts = lib.to_boolean(toggle)

            except TypeError:
                toggle = None
                log = "TTS command failed, bad argument."
                send = ("I could not understand if you wanted to " +
                        "turn TTS on or off.")

        if toggle is not None:
            status = ("on" if interface.tts else "off")
            log = "TTS now " + status + " for this channel."
            send = "Text-to-speech now " + status + " for this channel."

        lib.log(log,
                channel_id=channel.id,
                level=logging.WARN if toggle is None else logging.INFO)
        await self.bot.say(send,
                           tts=interface.tts and toggle is not None,
                           delete_after=self.bot.ans_lifespan)
Exemplo n.º 27
0
    async def timer_start(self, ctx: commands.Context, period_idx=1):
        """ Starts the timer with the recorded setup. The timer must be
            correctly set up and not running for it to work.

        :param period_idx: The index of the period to start from, from 1 to n.
        :type period_idx: int; 1 <= period_idx <= amount of periods
        """

        channel = self.bot.spoof(ctx.message.author, lib.get_channel(ctx))

        interface = self.bot.get_interface(channel)
        timer = interface.timer
        if timer.start():
            if not 0 < period_idx <= len(timer.periods):
                period_idx = 1

            try:
                if interface.restart_inactivity():
                    await self.bot.say(
                        "Timer has no subs. Will stop after"
                        " {} minutes unless someone subscribes!".format(
                            self.bot.timer_inactivity_allowed),
                        delete_after=self.bot.ans_lifespan)

                await self.bot.run_timer(channel, period_idx - 1)
            except discord.errors.HTTPException:
                await self.bot.say("@here\n"
                                   "Connection interrupted, please resume! (1)"
                                   )
                timer.pause()
        else:
            lib.log(lib.get_author_name(ctx) +
                    " tried to start a timer that was already running.",
                    channel_id=channel.id)
            await self.bot.say("This channel's timer is already running",
                               delete_after=self.bot.ans_lifespan)
Exemplo n.º 28
0
    async def on_ready(self):
        """ A listener for the event in which the bot is ready to work.
        """

        lib.log("")
        lib.log("Using discord.py version: " + discord.__version__)
        lib.log("Logged in as :")
        lib.log("\t" + self.bot.user.name)
        lib.log("\t" + self.bot.user.id)
        lib.log("")

        await self.bot.update_status()

        message = "**[{}]** {}"\
            .format(config.get_config().get_str('version'),
                    config.get_config().get_str('startup_msg'))
        for server in self.bot.servers:
            await self.bot.send_message(server, message)
Exemplo n.º 29
0
    async def run_timer(self, channel: discord.Channel, start_idx=0):
        """ Makes a timer run.

        :param channel: The channel where the timer that is being ran is.
        :type channel: discord.Channel

        :param start_idx: The index of the period from which the timer should
            start from. Defaults to 0, or is 0 if it's outside the valid range.
        :type start_idx: int; 0 < start_idx <= len(timer.periods)
        """

        interface = self.get_interface(channel)
        timer = interface.timer
        if timer is None:
            lib.log("Tried to start a timer, but none found.",
                    channel_id=channel.id)
            return

        await self.wait_until_ready()

        self.timers_running += 1
        await self.update_status()

        while not self.is_closed:
            iter_start = datetime.now()
            start_micro = iter_start.second * 1000000 + iter_start.microsecond

            if timer.get_state() == State.RUNNING and \
                    timer.curr_time >= timer.periods[timer.get_period()]\
                    .time * 60:

                say = "'{}' period over!" \
                    .format(timer.periods[timer.get_period()].name)

                timer.curr_time = 0

                if timer.get_period() + 1 >= len(timer.periods) and \
                        not timer.repeat:
                    say += "\nI have ran out of periods, and looping is off."
                    lib.log(say, channel_id=channel.id)
                    await self.safe_send(channel, say, tts=interface.tts)

                    break

                timer.set_period((timer.get_period() + 1) % len(timer.periods))

                if timer.action == Action.NONE:
                    say += " '{}' period now starting ({})." \
                        .format(timer.periods[timer.get_period()].name,
                                lib.pluralize(
                                    timer.periods[timer.get_period()].time,
                                    "minute", append="s"))

                lib.log(say, channel_id=channel.id)
                try:
                    await self.safe_send(channel, say, tts=interface.tts)

                    await self.edit_message(interface.list_message,
                                            timer.list_periods())
                except d_err.HTTPException:
                    lib.log("Skipped updating periods due to HTTPException",
                            channel_id=channel.id,
                            level=logging.WARN)

            if timer.action == Action.STOP:
                timer.action = Action.NONE

                lib.log("Timer has stopped.", channel_id=channel.id)
                await self.safe_send(channel, "Timer has stopped.")

                break

            elif timer.action == Action.PAUSE:
                timer.action = Action.NONE
                timer.set_state(State.PAUSED)

                lib.log("Timer has paused.", channel_id=channel.id)
                await self.safe_send(channel, "Timer has paused.")

            elif timer.action == Action.RUN:
                timer.action = Action.NONE

                prev_state = timer.get_state()
                timer.set_state(State.RUNNING)

                if prev_state == State.STOPPED:
                    timer.set_period(start_idx)
                    say_action = "Starting"
                else:
                    say_action = "Resuming"

                if start_idx != 0:
                    say_action += " (from period n." + str(start_idx + 1) + ")"

                lib.log(say_action, channel_id=channel.id)
                await self.safe_send(channel, say_action)

                if interface.time_message is None:
                    try:
                        await self._generate_messages(channel)
                    except discord.Forbidden:
                        lib.log("No permission to pin.", channel_id=channel.id)
                        kitty = ("I tried to pin a message and failed." +
                                 " Can I haz permission to pin messages?" +
                                 " https://goo.gl/tYYD7s")
                        await self.safe_send(channel, kitty)

            try:
                if interface.time_message is not None:
                    await self.edit_message(interface.time_message,
                                            timer.time())
            except d_err.NotFound:
                pass
            except d_err.HTTPException:
                lib.log(
                    "Skipped editing the time message due to HTTPException",
                    channel_id=channel.id,
                    level=logging.WARN)

            if timer.get_state() == State.RUNNING:
                iter_end = datetime.now()
                end_micro = iter_end.second * 1000000 + iter_end.microsecond

                end_micro -= start_micro
                end_micro %= 1000000.0
                sleep_time = ((timer.step * 1000000.0) - end_micro)

                await asyncio.sleep(sleep_time / 1000000.0)
                timer.curr_time += timer.step

                inactive = interface.check_inactivity(
                    self.timer_inactivity_allowed,
                    self.user_inactivity_allowed)

                if isinstance(inactive, bool) and inactive:
                    send = "Timer will stop due to inactivity."
                    lib.log(send, channel_id=channel.id, level=logging.INFO)
                    await self.safe_send(channel,
                                         send,
                                         delete_after=self.ans_lifespan)
                elif isinstance(inactive, list):
                    for user in inactive:
                        notice = ("ou have been un-subscribed due to"
                                  " inactivity!")
                        # Yes, there's a typo, but there's also a hacky solution
                        await self.safe_send(channel,
                                             "{}, y{}".format(
                                                 user.mention, notice),
                                             delete_after=self.ans_lifespan)
                        await self.safe_send(user, "Y" + notice)
                        if interface.restart_inactivity():
                            send = ("Timer has no subs. Will stop after {} "
                                    "minutes unless someone subscribes!")\
                                .format(self.timer_inactivity_allowed)
                            await self.safe_send(
                                channel, send, delete_after=self.ans_lifespan)
                interface.add_sub_time(timer.step)
            else:
                break

        if timer.get_state() != State.PAUSED:
            timer.curr_time = 0
            timer.set_period(-1)
            timer.set_state(State.STOPPED)

            if len(timer.periods) == 0:
                timer.set_state(None)
                interface.timer = None
                await self.safe_send(channel,
                                     "Timer has been reset.",
                                     delete_after=self.bot.ans_lifespan)

            await self.remove_messages(channel)

        self.timers_running -= 1
        await self.update_status()
Exemplo n.º 30
0
    async def setup(self,
                    ctx: commands.Context,
                    timer_format="default",
                    repeat=None,
                    count_back=None):
        """ Sets up a timer for the channel in which this command was executed.
            Only allows timers in white-listed channels (Specified in the
            configuration).

            :param timer_format: The string containing the periods and
                their names, in a format similar to that of a dictionary.
                Ex.: PeriodA:10,PeriodB:5,PeriodC:15
                     This will create 3 periods of 10, 5 and 15 minutes.

                It also accepts segments with the format (nxName1:t1,Name2:t2),
                which creates n iterations of Name1:t1,Name2:t2 periods (Where
                Name1 and Name2 are the period names and t1, t2 the respective
                times).
                Ex.: (3xPeriodA:10,PeriodB:5),PeriodC:15
                    This will create 7 periods of times 10,5,10,5,10,5 and 15.
            :type timer_format: str

            :param repeat: (boolean) Whether the timer should go back to
                period 1 after going through the complete list (True)
                or not (False). Defaults to True.

            :param count_back: (boolean) Whether the timer should show
                remaining (True) or elapsed (False) time. Defaults to True.
        """

        channel = self.bot.spoof(ctx.message.author, lib.get_channel(ctx))

        timer_format = await self._translate_keyword(timer_format,
                                                     lib.get_server_id(ctx),
                                                     channel.id)
        if timer_format is None:
            return

        # Parse the countdown and looping arguments with the custom function.
        try:
            loop = config.get_config().get_boolean('timer.looping_default') if \
                repeat is None else lib.to_boolean(repeat)

            countdown = config.get_config() \
                .get_boolean('timer.countdown_default') if count_back is None \
                else lib.to_boolean(count_back)

        except TypeError:
            lib.log(
                "Could not parse boolean arguments '{!s}' and '{!s}'".format(
                    repeat, count_back),
                channel_id=channel.id)
            await self.bot.say("Invalid arguments received, please try again.",
                               delete_after=self.bot.ans_lifespan)
            return

        interface = self.bot.get_interface(channel)
        if interface.timer is None:
            interface.timer = PomodoroTimer(interface)
            times = interface.timer.setup(timer_format, loop, countdown)

            if times is not None:
                log = ("Correctly set up timer config: {}."
                       "\nLooping is **{}**\nCountdown is **{}**") \
                    .format(times, "ON" if loop else "OFF",
                            "ON" if countdown else "OFF")
                send = log
            else:
                interface.timer = None
                log = ("Could not set the periods correctly, "
                       "command 'setup' failed.")
                send = ("I did not understand what you wanted, "
                        "please try again!")

        else:  # channel_id is in p_timers.keys()
            log = ("Rejecting setup command, there is a period set already "
                   "established.")
            send = ("I'm already set and ready to go, please use the reset "
                    "command if you want to change the timer configuration.")

        lib.log(log, channel_id=channel.id)
        await self.bot.say(send, delete_after=self.bot.ans_lifespan)