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
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)
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))
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
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)
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_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)
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))