Ejemplo n.º 1
0
    async def task_reset(self, bot):
        """
        The scheduled task to reset user goals at midnight
        :param bot:
        :return:
        """
        # Find all the user_goal records which are due a reset
        now = int(time.time())

        records = self.__db.get_all_sql(
            'SELECT * FROM user_goals WHERE reset <= %s', [now])
        for record in records:

            # Calculate the next reset time for the goal, depending on its type.
            user = User(record['user'], 0)
            try:
                next = user.calculate_user_reset_time(record['type'])
                lib.debug('Setting next ' + record['type'] +
                          ' goal reset time for ' + str(record['user']) +
                          ' to: ' + str(next))
                self.__db.update('user_goals', {
                    'completed': 0,
                    'current': 0,
                    'reset': next
                }, {'id': record['id']})
            except pytz.exceptions.UnknownTimeZoneError:
                lib.out('[ERROR] Invalid timezone (' +
                        user.get_setting('timezone') + ') for user ' +
                        str(record['user']))

        return True
Ejemplo n.º 2
0
    async def remind(self, context, *opts):
        """
        Set or configure a reminder
        @param opts:
        @param context:
        @return:
        """
        if not Guild(context.guild).is_command_enabled('remind'):
            return await context.send(lib.get_string('err:disabled', context.guild.id))

        user = User(context.message.author.id, context.guild.id, context)

        # Does the user have a timezone setup? If not, can't do anything.
        if not lib.is_valid_timezone(user.get_setting('timezone')):
            return await context.send(user.get_mention() + ', ' + lib.get_string('err:notimezone', user.get_guild()))

        # Convert the natural language of the command into variables.
        cmd = ' '.join(opts)

        # Check what we are trying to do with reminders.
        if cmd.lower() == 'list':
            return await self.run_list(context, user)
        elif cmd.lower() == 'delete':
            return await self.run_delete(context, user)
        else:
            return await self.run_remind(context, user, cmd)
Ejemplo n.º 3
0
    async def run_set(self, context, type, amount):

        user = User(context.message.author.id, context.guild.id, context)

        # Check if we can convert the amount to an int
        amount = lib.is_number(amount)
        if not amount:
            return await context.send(user.get_mention() + ', ' + lib.get_string('err:validamount', user.get_guild()))

        # Set the user's goal
        user.set_goal(type, amount)
        timezone = user.get_setting('timezone') or 'UTC'

        reset_every = lib.get_string('goal:set:'+type, user.get_guild())
        return await context.send(user.get_mention() + ', ' + lib.get_string('goal:set', user.get_guild()).format(type, amount, reset_every, timezone))
Ejemplo n.º 4
0
    async def task_reset(self, bot):
        """
        The scheduled task to reset user goals at midnight
        :param bot:
        :return:
        """
        # Find all the user_goal records which are due a reset
        now = int(time.time())

        records = self.__db.get_all_sql(
            'SELECT * FROM user_goals WHERE reset <= %s', [now])
        for record in records:

            # Calculate the next reset time for the goal, depending on its type.
            user = User(record['user'], 0)
            try:
                user.reset_goal(record)
            except pytz.exceptions.UnknownTimeZoneError:
                lib.out('[ERROR] Invalid timezone (' +
                        user.get_setting('timezone') + ') for user ' +
                        str(record['user']))

        return True
Ejemplo n.º 5
0
    async def sprint(self, context, cmd=None, opt1=None, opt2=None, opt3=None):
        """
        Write with your friends and see who can write the most in the time limit!
        When choosing a length and start delay, there are maximums of 60 minutes length of sprint, and 24 hours delay until sprint begins.
        NOTE: The bot checks for sprint changes every 30 seconds, so your start/end times might be off by +-30 seconds or so.

        Run `help sprint` for more extra information, including any custom server settings related to sprints.

        Examples:
            `sprint start` - Quickstart a sprint with the default settings.
            `sprint for 20 in 3` - Schedules a sprint for 20 minutes, to start in 3 minutes.
            `sprint cancel` - Cancels the current sprint. This can only be done by the person who created the sprint, or any users with the MANAGE_MESSAGES permission.
            `sprint join` - Joins the current sprint.
            `sprint join 100` - Joins the current sprint, with a starting word count of 100.
            `sprint join 100 sword` - Joins the current sprint, with a starting word count of 100 and sets your sprint to count towards your Project with the shortname "sword" (See: Projects for more info).
            `sprint join same` - Use the keyword `same` to join the sprint using the same Project and Final Word Count from your most recent sprint.
            `sprint leave` - Leaves the current sprint.
            `sprint project sword` - Sets your sprint to count towards your Project with the shortname "sword" (See: Projects for more info).
            `sprint wc 250` - Declares your final word count at 250.
            `sprint time` - Displays the time left in the current sprint.
            `sprint pb` - Displays your personal best wpm from sprints on this server. Run sprint pb reset to reset your personal best to 0 on the current server.
            `sprint notify` - You will be notified when someone starts a new sprint.
            `sprint forget` - You will no longer be notified when someone starts a new sprint.
            `sprint status` - Shows you your current word count on the sprint.

        **Sprint Tips**
        If you join the sprint with a starting word count, remember to declare your total word count at the end, not just the amount of words you wrote in the sprint.
        e.g. If you joined with 1000 words, and during the sprint you wrote another 500 words, the final word count you should declare would be 1500
        """
        user = User(context.message.author.id, context.guild.id, context)

        # Check the arguments are valid
        args = await self.check_arguments(context,
                                          cmd=cmd,
                                          opt1=opt1,
                                          opt2=opt2,
                                          opt3=opt3)
        if not args:
            return

        # Convert cmd to lowercase
        cmd = args['cmd'].lower()

        # Start a sprint
        if cmd == 'start':
            return await self.run_start(context)

        elif cmd == 'for':

            length = opt1

            # If the second option is invalid, display an error message
            if opt2 is not None and opt2.lower() not in ['now', 'in', 'at']:
                return await context.send(
                    user.get_mention() + ', ' +
                    lib.get_string('sprint:err:for:unknown', user.get_guild()))

            # If they left off the last argument and just said `sprint for 20` then assume they mean now.
            if opt2 is None:
                opt2 = 'now'

            # Get the delay they want before starting the sprint.
            if opt2.lower() == 'now':
                delay = 0
            elif opt2.lower() == 'in':
                delay = opt3
            elif opt2.lower() == 'at':

                # Make sure the user has set their timezone, otherwise we can't calculate it.
                timezone = user.get_setting('timezone')
                user_timezone = lib.get_timezone(timezone)
                if not user_timezone:
                    return await context.send(
                        user.get_mention() + ', ' +
                        lib.get_string('err:notimezone', user.get_guild()))

                # If they are doing `sprint for 20 at :15` for example, then opt3 must be set in the format '.00'.
                start = int(opt3[1:]) if (isinstance(opt3, str)
                                          and opt3.startswith('.')
                                          and len(opt3) == 3) else opt3
                if not isinstance(start, int) or start < 0 or start >= 60:
                    return await context.send(
                        user.get_mention() + ', ' +
                        lib.get_string('sprint:err:for:at', user.get_guild()))

                # Now using their timezone and the minute they requested, calculate when that should be.
                user_timezone = lib.get_timezone(timezone)
                delay = (60 + start - datetime.now(user_timezone).minute) % 60

            return await self.run_start(context, length, delay)

        elif cmd == "in":

            delay = opt1

            # If the second option is invalid, display an error message
            if opt2 is not None and opt2.lower() != "for":
                return await context.send(
                    user.get_mention() + ", " +
                    lib.get_string("sprint:err:in:unknown", user.get_guild()))

            # Get the length they want before starting the sprint.
            # If they left off the last argument and just said `sprint for 20` then assume they mean now.
            if opt2 is None:
                length = self.DEFAULT_LENGTH
            elif opt2.lower() == "for":
                length = opt3

            return await self.run_start(context, length, delay)

        elif cmd == 'cancel':
            return await self.run_cancel(context)

        elif cmd == 'notify':
            return await self.run_notify(context)

        elif cmd == 'forget':
            return await self.run_forget(context)

        elif cmd == 'time':
            return await self.run_time(context)

        elif cmd == 'leave':
            return await self.run_leave(context)

        elif cmd == 'join':
            return await self.run_join(context, opt1, opt2)

        elif cmd == 'pb':
            return await self.run_pb(context)

        elif cmd == 'status':
            return await self.run_status(context)

        elif cmd == 'wc' or cmd == 'declare':
            return await self.run_declare(context, opt1)

        elif cmd == 'end':
            return await self.run_end(context)

        elif cmd == 'project':
            return await self.run_project(context, opt1)

        elif cmd == 'purge':
            return await self.run_purge(context)
Ejemplo n.º 6
0
    async def run_declare(self, context, amount=None):
        """
        Declare user's current word count for the sprint
        :param context:
        :param amount:
        :return:
        """
        user = User(context.message.author.id, context.guild.id, context)
        sprint = Sprint(user.get_guild())

        # If there is no active sprint, then just display an error
        if not sprint.exists():
            return await context.send(
                user.get_mention() + ', ' +
                lib.get_string('sprint:err:noexists', user.get_guild()))

        # If the user is not sprinting, then again, just display that error
        if not sprint.is_user_sprinting(user.get_id()):
            return await context.send(
                user.get_mention() + ', ' +
                lib.get_string('sprint:err:notjoined', user.get_guild()))

        # If the sprint hasn't started yet, display error
        if not sprint.has_started():
            return await context.send(
                user.get_mention() + ', ' +
                lib.get_string('sprint:err:notstarted', user.get_guild()))

        # Get the user's sprint info
        user_sprint = sprint.get_user_sprint(user.get_id())

        # If they joined without a word count, they can't add one.
        if user_sprint['sprint_type'] == Sprint.SPRINT_TYPE_NO_WORDCOUNT:
            return await context.send(
                user.get_mention() + ', ' +
                lib.get_string('sprint:err:nonwordcount', user.get_guild()))

        # Did they enter something for the amount?
        if amount is None:
            return await context.send(
                user.get_mention() + ', ' +
                lib.get_string('sprint:err:amount', user.get_guild()))

        # Are they trying to do a calculation instead of declaring a number?
        if amount[0] == '+' or amount[0] == '-':

            # Set the calculation variable to True so we know later on that it was not a proper declaration
            calculation = True

            # Convert the amount string to an int
            amount = int(amount)

            # Add that to the current word count, to get the new value
            new_amount = user_sprint['current_wc'] + amount

        else:
            calculation = False
            new_amount = amount

        # Make sure the amount is now a valid number
        if not lib.is_number(new_amount):
            return await context.send(
                user.get_mention() + ', ' +
                lib.get_string('sprint:err:amount', user.get_guild()))

        # Just make sure the new_amount is defintely an int
        new_amount = int(new_amount)

        # If the declared value is less than they started with and it is not a calculation, then that is an error.
        if new_amount < int(user_sprint['starting_wc']) and not calculation:
            diff = user_sprint['current_wc'] - new_amount
            return await context.send(
                user.get_mention() + ', ' +
                lib.get_string('sprint:err:wclessthanstart', user.get_guild(
                )).format(new_amount, user_sprint['starting_wc'], diff))

        # Is the sprint finished? If so this will be an ending_wc declaration, not a current_wc one.
        col = 'ending' if sprint.is_finished() else 'current'

        # Before we actually update it, if the WPM is huge and most likely an error, just check with them if they meant to put that many words.
        written = new_amount - int(user_sprint['starting_wc'])
        seconds = int(sprint.get_end_reference()) - user_sprint['timejoined']
        wpm = Sprint.calculate_wpm(written, seconds)

        # Does the user have a configured setting for max wpm to check?
        max_wpm = user.get_setting('maxwpm')
        if not max_wpm:
            max_wpm = self.WPM_CHECK

        if wpm > int(max_wpm):

            # Make a fake prompt to wait for confirmation.
            argument = {
                'prompt':
                lib.get_string('sprint:wpm:sure',
                               user.get_guild()).format(written, wpm),
                'check':
                lambda resp: resp.lower() in ('y', 'yes', 'n', 'no')
            }

            response = await self.adhoc_prompt(context, argument, True)

            # If they confirm, then delete the event.
            if response is False or response.content.lower() in ('n', 'no'):
                return await context.send(
                    user.get_mention() + ', ' +
                    lib.get_string('sprint:declareagain', user.get_guild()))

        # Update the user's sprint record
        arg = {col: new_amount}
        sprint.update_user(user.get_id(), **arg)

        # Reload the user sprint info
        user_sprint = sprint.get_user_sprint(user.get_id())

        # Which value are we displaying?
        wordcount = user_sprint['ending_wc'] if sprint.is_finished(
        ) else user_sprint['current_wc']
        written = int(wordcount) - int(user_sprint['starting_wc'])

        await context.send(user.get_mention() + ', ' + lib.get_string(
            'sprint:declared', user.get_guild()).format(wordcount, written))

        # Is the sprint now over and has everyone declared?
        if sprint.is_finished() and sprint.is_declaration_finished():
            Task.cancel('sprint', sprint.get_id())
            await sprint.complete(context)
Ejemplo n.º 7
0
    async def run_info(self, context):
        """
        Get the event information embedded message
        :param context:
        :return:
        """
        user = User(context.message.author.id, context.guild.id, context)
        event = Event.get_by_guild(user.get_guild())
        config = lib.get('./settings.json')

        # Make sure there is an event
        if event is None:
            return await context.send(
                user.get_mention() + ', ' +
                lib.get_string('event:err:noexists', user.get_guild()))

        # Work out which timezone to use when displaying the start and end dates.
        start_date = lib.get_string('na', user.get_guild())
        end_date = lib.get_string('na', user.get_guild())
        user_timezone = user.get_setting('timezone')
        if not user_timezone:
            user_timezone = 'UTC'

        timezone = pytz.timezone(user_timezone)

        # Is it scheduled with start and end dates?
        if event.is_scheduled():
            start = datetime.fromtimestamp(event.get_start_time())
            end = datetime.fromtimestamp(event.get_end_time())
            start_date = start.astimezone(timezone).strftime(
                '%d-%m-%Y %H:%M:%S') + ' (' + user_timezone + ')'
            end_date = end.astimezone(timezone).strftime(
                '%d-%m-%Y %H:%M:%S') + ' (' + user_timezone + ')'

        # Get the running status
        if event.is_running():
            status = lib.get_string('event:started', user.get_guild())
        else:
            status = lib.get_string('event:notyetstarted', user.get_guild())

        # Get the number of users in the event and how many words they have written in it so far
        writers = len(event.get_users())
        words = event.get_total_wordcount()

        # Get the description of the event and add to the end of the status, or just display the status if the description is empty
        description = event.get_description()
        if description and len(description) > 0:
            description = status + '\n\n' + description
        else:
            description = status

        # Get the thumbnail image to use
        image = event.get_image()
        if not image or len(image) == 0:
            image = config.avatar

        # Build the embedded message.
        embed = discord.Embed(title=event.get_title(),
                              color=event.get_colour(),
                              description=description)
        embed.set_thumbnail(url=image)
        embed.add_field(name=lib.get_string('event:startdate',
                                            user.get_guild()),
                        value=start_date,
                        inline=False)
        embed.add_field(name=lib.get_string('event:enddate', user.get_guild()),
                        value=end_date,
                        inline=False)
        embed.add_field(name=lib.get_string('event:numwriters',
                                            user.get_guild()),
                        value=str(writers),
                        inline=True)
        embed.add_field(name=lib.get_string('event:numwords',
                                            user.get_guild()),
                        value=str(words),
                        inline=True)

        # Send the message
        return await context.send(embed=embed)
Ejemplo n.º 8
0
    async def run_schedule(self, context):
        """
        Schedule the event to start and end at a specific datetime
        :param context:
        :return:
        """
        user = User(context.message.author.id, context.guild.id, context)
        event = Event.get_by_guild(user.get_guild())

        # Do they have the permissions to rename an event?
        self.check_permissions(context)

        # Make sure the event is running
        if event is None:
            return await context.send(
                user.get_mention() + ', ' +
                lib.get_string('event:err:noexists', user.get_guild()))

        # Make sure the event is running
        if event.is_running():
            return await context.send(
                user.get_mention() + ', ' +
                lib.get_string('event:err:alreadyrunning', user.get_guild()))

        # Do they have a timezone set in their user settings?
        user_timezone = user.get_setting('timezone')
        if user_timezone is None:
            return await context.send(
                user.get_mention() + ', ' +
                lib.get_string('event:err:timezonenotset', user.get_guild()))

        timezone = pytz.timezone(user_timezone)
        time = datetime.now(timezone).strftime('%H:%M:%S')
        offset = datetime.now(timezone).strftime('%z')

        # Print the pre-schedule information to check their timezone is correct.
        await context.send(user.get_mention() + ', ' +
                           lib.get_string('event:preschedule', user.get_guild(
                           )).format(user_timezone, time, offset))

        # We now have various stages to go through, so we loop through the stages, ask the question and store the user input as the answer.
        answers = []

        # Stage 1 - Start date
        answer = await self.run_stage_one(context)
        if not answer:
            return
        answers.append({'stage': 1, 'answer': answer})

        # Stage 2 - Start time
        answer = await self.run_stage_two(context)
        if not answer:
            return
        answers.append({'stage': 2, 'answer': answer})

        # Stage 3 - End date
        answer = await self.run_stage_three(context)
        if not answer:
            return
        answers.append({'stage': 3, 'answer': answer})

        # Stage 4 - End time
        answer = await self.run_stage_four(context)
        if not answer:
            return
        answers.append({'stage': 4, 'answer': answer})

        # Stage 5 - Confirmation
        answer = await self.run_stage_five(context, answers)
        if not answer:
            return
        answers.append({'stage': 5, 'answer': answer})

        # Now run the checks to make sure the end date is later than start date, etc...
        check = await self.check_answers(context, answers)
        if not check:
            return

        # Now convert those start and end times to UTC timestamps
        start = datetime.strptime(
            lib.find(answers, 'stage', 1)['answer'] + ' ' +
            lib.find(answers, 'stage', 2)['answer'], '%d-%m-%Y %H:%M')
        end = datetime.strptime(
            lib.find(answers, 'stage', 3)['answer'] + ' ' +
            lib.find(answers, 'stage', 4)['answer'], '%d-%m-%Y %H:%M')

        adjusted_start = int(timezone.localize(start).timestamp())
        adjusted_end = int(timezone.localize(end).timestamp())

        # Schedule the event with those timestamps
        event.set_startdate(adjusted_start)
        event.set_enddate(adjusted_end)
        event.set_channel(context.message.channel.id)
        event.save()

        # Remove any tasks we already had saved for this event.
        Task.cancel('event', event.get_id())

        # Schedule the tasks to run at those times.
        Task.schedule(Event.TASKS['start'], event.get_start_time(), 'event',
                      event.get_id())
        Task.schedule(Event.TASKS['end'], event.get_end_time(), 'event',
                      event.get_id())

        return await context.send(
            user.get_mention() + ', ' +
            lib.get_string('event:scheduled', user.get_guild()).format(
                event.get_title(), start, end))