async def run_end(self, context): """ Manually force the sprint to end (if the cron hasn't posted the message) and ask for final word counts :param context: :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 they do not have permission to cancel this sprint, display an error if int(sprint.get_createdby()) != user.get_id() and context.message.author.permissions_in(context.message.channel).manage_messages is not True: return await context.send(user.get_mention() + ', ' + lib.get_string('sprint:err:cannotend', user.get_guild())) # If the sprint hasn't started yet, it can't be ended. if not sprint.has_started(): return await context.send(user.get_mention() + ', ' + lib.get_string('sprint:err:notstarted', user.get_guild())) # Change the end reference to now, otherwise wpm calculations will be off, as it will use the time in the future when it was supposed to end. sprint.update_end_reference(int(time.time())) # Since we are forcing the end, we should cancel any pending tasks for this sprint Task.cancel('sprint', sprint.get_id()) # We need to set the bot into the sprint object, as we will need it when trying to get the guild object sprint.set_bot(self.bot) return await sprint.end(context)
async def run_cancel(self, context): """ Cancel a running sprint on the server :param context: :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 they do not have permission to cancel this sprint, display an error if int(sprint.get_createdby()) != user.get_id( ) and context.message.author.permissions_in( context.message.channel).manage_messages is not True: return await context.send( user.get_mention() + ', ' + lib.get_string('sprint:err:cannotcancel', user.get_guild())) # Get the users sprinting and create an array of mentions users = sprint.get_users() notify = sprint.get_notifications(users) # Cancel the sprint sprint.cancel(context) # Display the cancellation message message = lib.get_string('sprint:cancelled', user.get_guild()) message = message + ', '.join(notify) return await context.send(message)
async def run_time(self, context): """ Get how long is left in the sprint :param context: :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())) now = int(time.time()) # If the sprint has not yet started, display the time until it starts if not sprint.has_started(): left = lib.secs_to_mins(sprint.get_start() - now) return await context.send(user.get_mention() + ', ' + lib.get_string('sprint:startsin', user.get_guild()).format(left['m'], left['s'])) # If it's currently still running, display how long is left elif not sprint.is_finished(): left = lib.secs_to_mins(sprint.get_end() - now) return await context.send(user.get_mention() + ', ' + lib.get_string('sprint:timeleft', user.get_guild()).format(left['m'], left['s'])) # If it's finished but not yet marked as completed, we must be waiting for word counts elif sprint.is_finished(): return await context.send(user.get_mention() + ', ' + lib.get_string('sprint:waitingforwc', user.get_guild()))
async def run_status(self, context): """ Get the user's status in this sprint :param context: :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())) # If they are sprinting, then display their current status. user_sprint = sprint.get_user_sprint(user.get_id()) # Build the variables to be passed into the status string now = int(time.time()) current = user_sprint['current_wc'] written = current - user_sprint['starting_wc'] seconds = now - user_sprint['timejoined'] elapsed = round(seconds / 60, 1) wpm = Sprint.calculate_wpm(written, seconds) left = round((sprint.get_end() - now) / 60, 1) return await context.send(user.get_mention() + ', ' + lib.get_string('sprint:status', user.get_guild()).format(current, written, elapsed, wpm, left))
async def run_join(self, context, starting_wc=None, shortname=None): """ Join the sprint, with an optional starting word count and project shortname :param starting_wc: :param shortname: Shortname of Project :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())) # Convert starting_wc to int if we can starting_wc = lib.is_number(starting_wc) if starting_wc is False: starting_wc = 0 # If the user is already sprinting, then just update their starting wordcount if sprint.is_user_sprinting(user.get_id()): # Update the sprint_users record. We set their current_wc to the same as starting_wc here, otherwise if they join with, say 50 and their current remains 0 # then if they run a status, or in the ending calculations, it will say they wrote -50. sprint.update_user(user.get_id(), start=starting_wc, current=starting_wc) # Send message back to channel letting them know their starting word count was updated await context.send(user.get_mention() + ', ' + lib.get_string('sprint:join:update', user.get_guild()).format(starting_wc)) else: # Join the sprint sprint.join(user.get_id(), starting_wc) # Send message back to channel letting them know their starting word count was updated await context.send(user.get_mention() + ', ' + lib.get_string('sprint:join', user.get_guild()).format(starting_wc)) # If a project shortname is supplied, try to set that as what the user is sprinting for. if shortname is not None: # Convert to lowercase for searching. shortname = shortname.lower() # Make sure the project exists. project = user.get_project(shortname) if not project: return await context.send(user.get_mention() + ', ' + lib.get_string('project:err:noexists', user.get_guild()).format(shortname)) sprint.set_project(project.get_id(), user.get_id()) return await context.send(user.get_mention() + ', ' + lib.get_string('sprint:project', user.get_guild()).format(project.get_title()))
async def run_project(self, context, shortname): """ Set the project the user wants to sprint in. :param context: :param shortname: :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())) # Make sure the project exists. shortname = shortname.lower() if shortname is not None else None project = user.get_project(shortname) if not project: return await context.send( user.get_mention() + ', ' + lib.get_string('project:err:noexists', user.get_guild()).format(shortname)) sprint.set_project(project.get_id(), user.get_id()) return await context.send(user.get_mention() + ', ' + lib.get_string( 'sprint:project', user.get_guild()).format(project.get_title()))
async def run(self, bot): """ Run this task TODO: In the future, make this better. For now it will do. :return: bool """ # If the task is already processing, don't go any further. if self.is_processing(): return True # Mark the task as processing so other shards don't pick it up. self.start_processing(1) # Build a variable to store the method name to run method = 'task_' + str(self.type) # Start off with a False and see if we can successfully run and turn that to True result = False # Sprint tasks. if self.object == 'sprint': from structures.sprint import Sprint sprint = Sprint.get(self.object_id) if sprint.is_valid(): result = await getattr(sprint, method)(bot) else: # If the sprint doesn't exist, then we can just delete this task. result = True elif self.object == 'goal': from structures.goal import Goal goal = Goal() result = await getattr(goal, method)(bot) elif self.object == 'event': from structures.event import Event event = Event(self.object_id) if event.is_valid(): result = await getattr(event, method)(bot) else: # If the event doesn't exist, then we can just delete this task. return True else: # Invalid task object. May as well just delete this task. lib.out('Invalid task object: ' + self.object) result = True # If we finished the task, and it's not a recurring one, delete it. if result is True and not self.is_recurring(): self.delete() else: self.start_processing(0) # If it's a recurring task, set its next run time. if self.is_recurring(): self.set_recur() return result
async def run_start(self, context, length=None, start=None): """ Try to start a sprint on the server :param context :param length: Length of time (in minutes) the sprint should last :param start: Time in minutes from now, that the sprint should start :return: """ user = User(context.message.author.id, context.guild.id, context) sprint = Sprint(user.get_guild()) # Check if sprint is finished but not marked as completed, in which case we can mark it as complete if sprint.is_finished(): # Mark the sprint as complete await sprint.complete() # Reload the sprint object, as now there shouldn't be a pending one sprint = Sprint(user.get_guild()) # If a sprint is currently running, we cannot start a new one if sprint.exists(): return await context.send( user.get_mention() + ', ' + lib.get_string('sprint:err:alreadyexists', user.get_guild())) # Must be okay to continue # # If the length argument is not valid, use the default if length is None or lib.is_number(length) is False or lib.is_number( length) <= 0 or lib.is_number(length) > self.MAX_LENGTH: length = self.DEFAULT_LENGTH # Same goes for the start argument if start is None or lib.is_number(start) is False or lib.is_number( start) < 0 or lib.is_number(start) > self.MAX_DELAY: start = self.DEFAULT_DELAY # Make sure we are using ints and not floats passed through in the command length = int(length) start = int(start) # Calculate the start and end times based on the current timestamp now = int(time.time()) start_time = now + (start * 60) end_time = start_time + (length * 60) # Create the sprint sprint = Sprint.create(guild=user.get_guild(), channel=context.message.channel.id, start=start_time, end=end_time, end_reference=end_time, length=length, createdby=user.get_id(), created=now) # Join the sprint sprint.join(user.get_id()) # Increment the user's stat for sprints created user.add_stat('sprints_started', 1) # Are we starting immediately or after a delay? if start == 0: # Immediately. That means we need to schedule the end task. Task.schedule(sprint.TASKS['end'], end_time, 'sprint', sprint.get_id()) return await sprint.post_start(context) else: # Delay. That means we need to schedule the start task, which will in turn schedule the end task once it's run. Task.schedule(sprint.TASKS['start'], start_time, 'sprint', sprint.get_id()) return await sprint.post_delayed_start(context)
async def run_join(self, context, arg1=None, arg2=None): """ Join the sprint, with an optional starting word count and project shortname :param opt1: Argument 1 of the join command :param opt2: Argument 2 of the join command :return: """ user = User(context.message.author.id, context.guild.id, context) sprint = Sprint(user.get_guild()) project_id = None starting_wc = None sprint_type = None # 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())) # Are we using the `same` keyword? if arg1 == "same": # Okay, check for their most recent sprint record most_recent = user.get_most_recent_sprint(sprint) if most_recent is not None: starting_wc = most_recent['ending_wc'] project_id = most_recent['project'] sprint_type = most_recent['sprint_type'] # Can't use the second argument in this case. arg2 = None # Are we doing a no wordcount sprint? E.g. we are sprinting or using the functionality for something else. elif arg1 in ["edit", "non-wc"]: sprint_type = Sprint.SPRINT_TYPE_NO_WORDCOUNT # Can't use the second argument in this case. arg2 = None else: # Convert starting_wc to int if we can starting_wc = lib.is_number(arg1) # If the starting_wc is still False at this point, just set it to 0 if not starting_wc: starting_wc = 0 # If the user is already sprinting, then just update their starting wordcount if sprint.is_user_sprinting(user.get_id()): # Update the sprint_users record. We set their current_wc to the same as starting_wc here, otherwise if they join with, say 50 and their current remains 0 # then if they run a status, or in the ending calculations, it will say they wrote -50. sprint.update_user(user.get_id(), start=starting_wc, current=starting_wc, sprint_type=sprint_type) # If it's a non-wordcount sprint, send a different message. if sprint_type == Sprint.SPRINT_TYPE_NO_WORDCOUNT: await context.send(user.get_mention() + ', ' + lib.get_string( 'sprint:join:update:no_wordcount', user.get_guild())) else: # Send message back to channel letting them know their starting word count was updated await context.send(user.get_mention() + ', ' + lib.get_string( 'sprint:join:update', user.get_guild()).format(starting_wc) ) else: # Join the sprint sprint.join(user.get_id(), starting_wc=starting_wc, sprint_type=sprint_type) if sprint_type == Sprint.SPRINT_TYPE_NO_WORDCOUNT: await context.send(user.get_mention() + ', ' + lib.get_string( 'sprint:join:update:no_wordcount', user.get_guild())) else: # Send message back to channel letting them know their starting word count was updated await context.send(user.get_mention() + ', ' + lib.get_string( 'sprint:join', user.get_guild()).format(starting_wc)) # Currently this is the only usage of argument 2. shortname = arg2 # If a project shortname is supplied, try to set that as what the user is sprinting for. if shortname is not None or project_id is not None: # Did we supply the project by name? if shortname is not None: # Convert to lowercase for searching. shortname = shortname.lower() # Make sure the project exists. project = user.get_project(shortname) # Or do we already have the ID from using 'same' and getting from previous sprint? elif project_id is not None: project = Project(project_id) # If that did not yield a valid project, send an error message. if not project: return await context.send( user.get_mention() + ', ' + lib.get_string('project:err:noexists', user.get_guild()).format(shortname)) sprint.set_project(project.get_id(), user.get_id()) return await context.send( user.get_mention() + ', ' + lib.get_string('sprint:project', user.get_guild()).format( project.get_title()))
async def run_leave(self, context): """ Leave the sprint :param context: :return: """ user = User(context.message.author.id, context.guild.id, context) sprint = Sprint(user.get_guild()) # If there is no active sprint or the user is not joined to it, display an error if not sprint.exists() or not sprint.is_user_sprinting(user.get_id()): return await context.send( user.get_mention() + ', ' + lib.get_string('sprint:err:notjoined', user.get_guild())) # Remove the user from the sprint sprint.leave(user.get_id()) await context.send(user.get_mention() + ', ' + lib.get_string('sprint:leave', user.get_guild())) # If there are now no users left, cancel the whole sprint if len(sprint.get_users()) == 0: # Cancel the sprint sprint.cancel(context) # Decrement sprints_started stat for whoever started this one creator = User(sprint.get_createdby(), sprint.get_guild()) creator.add_stat('sprints_started', -1) # Display a message letting users know return await context.send( lib.get_string('sprint:leave:cancelled', user.get_guild()))
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)
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())) # 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())) # Get the user's sprint info user_sprint = sprint.get_user_sprint(user.get_id()) # 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' arg = {col: new_amount} # Update the user's sprint record 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)