async def run_create(self, context, opts): """ Create an event on the server :param context: :param opts: :return: """ user = User(context.message.author.id, context.guild.id, context) # Do they have the permissions to create an event? self.check_permissions(context) # Get the title out of the arguments title = " ".join(opts[0:]) # Check if there is already an event running event = Event.get_by_guild(user.get_guild()) if event is not None: return await context.send( user.get_mention() + ', ' + lib.get_string('event:err:alreadyexists', user.get_guild())) # Make sure they specified a title. if len(title) == 0 or len(title) > 255: return await context.send( user.get_mention() + ', ' + lib.get_string('event:err:title', user.get_guild())) # Create the event Event.create(guild=user.get_guild(), channel=context.message.channel.id, title=title) return await context.send( user.get_mention() + ', ' + lib.get_string('event:created', user.get_guild()).format(title))
async def run_top(self, context): """ Get the leaderboard of words written for this event :param context: :return: """ user = User(context.message.author.id, context.guild.id, context) # First try and get the event as if its running event = Event.get_by_guild(user.get_guild()) # If that fails, get the last run one if event is None: event = Event.get_by_guild(user.get_guild(), include_ended=True) # If there is still not one, then just stop if event is None: return await context.send( user.get_mention() + ', ' + lib.get_string('event:err:noexists', user.get_guild())) event.set_context(context) event.set_guild_object(context.guild) return await context.send( embed=await event.get_leaderboard(Event.LEADERBOARD_LIMIT))
async def run_rename(self, context, opts): """ Rename the event :param context: :param opts: :return: """ user = User(context.message.author.id, context.guild.id, context) # Do they have the permissions to rename an event? self.check_permissions(context) # Check if there is an event running event = Event.get_by_guild(user.get_guild()) if event is None or not event.is_valid(): return await context.send( user.get_mention() + ', ' + lib.get_string('event:err:noexists', user.get_guild())) # Get the title out of the arguments title = " ".join(opts[0:]) # Make sure they specified a title. if len(title) == 0 or len(title) > 255: return await context.send( user.get_mention() + ', ' + lib.get_string('event:err:rename:title', user.get_guild())) # Create the event event.set_title(title) event.save() return await context.send( user.get_mention() + ', ' + lib.get_string('event:renamed', user.get_guild()).format(title))
async def run_unschedule(self, context): """ Unschedule the event :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())) # Unschedule the event event.set_startdate(None) event.set_enddate(None) event.save() # Remove any tasks we already had saved for this event. Task.cancel('event', event.get_id()) return await context.send(user.get_mention() + ', ' + lib.get_string( 'event:unscheduled', user.get_guild()).format(event.get_title()))
async def run_update(self, context, amount): """ Update the user's word count on the event :param context: :param amount: :return: """ user = User(context.message.author.id, context.guild.id, context) event = Event.get_by_guild(user.get_guild()) amount = lib.is_number(amount[0]) if amount is False or amount < 0: return await context.send( user.get_mention() + ', ' + lib.get_string('err:validamount', user.get_guild())) # Make sure the event is running if event is None or not event.is_running(): return await context.send( user.get_mention() + ', ' + lib.get_string('event:err:noexists', user.get_guild())) event.update_wordcount(user.get_id(), amount) return await context.send( user.get_mention() + ', ' + lib.get_string('event:updated', user.get_guild()).format( event.get_title(), amount))
async def run_set(self, context, type, opts): """ Set the value of something for the event, such as description or image url :param context: :param type: :param opts: :return: """ user = User(context.message.author.id, context.guild.id, context) # Do they have the permissions to set an event option? self.check_permissions(context) # Check if there is an event running event = Event.get_by_guild(user.get_guild()) if event is None or not event.is_valid(): return await context.send(user.get_mention() + ', ' + lib.get_string('event:err:noexists', user.get_guild())) value = " ".join(opts[0:]) if type == 'description': event.set_description(value) elif type == 'image': if len(value) > 255: return await context.send(user.get_mention() + ', ' + lib.get_string('event:err:img', user.get_guild())) event.set_image(value) elif type == 'colour': event.set_colour(value) # Save the changes event.save() return await context.send(user.get_mention() + ', ' + lib.get_string('event:set', user.get_guild()).format(type, value))
async def run_time(self, context): """ Check how much time is left in the event :param context: :return: """ user = User(context.message.author.id, context.guild.id, context) event = Event.get_by_guild(user.get_guild()) now = int(time.time()) # Make sure the event exists if event is None or not event.is_valid(): return await context.send(user.get_mention() + ', ' + lib.get_string('event:err:noexists', user.get_guild())) # Is the event scheduled to start, but has not yet started? if not event.is_running() and event.is_scheduled(): left = lib.secs_to_days(event.get_start_time() - now) return await context.send(user.get_mention() + ', ' + lib.get_string('event:timetostart', user.get_guild()).format(left)) # If the event is not running and it is NOT scheduled, then we don't know what time it will start. elif not event.is_running(): return await context.send(user.get_mention() + ', ' + lib.get_string('event:err:noexists', user.get_guild())) # At this point, the event must be running. If it is scheduled, then we can get the time left from the end time. Otherwise, we don't know. elif event.is_scheduled(): left = lib.secs_to_days(event.get_end_time() - now) return await context.send(user.get_mention() + ', ' + lib.get_string('event:timeleft', user.get_guild()).format(left)) else: return await context.send(user.get_mention() + ', ' + lib.get_string('event:noendtime', user.get_guild()))
async def run_me(self, context): """ Check your own word count for the event so far :param context: :return: """ user = User(context.message.author.id, context.guild.id, context) event = Event.get_by_guild(user.get_guild()) # Make sure the event is running if event is None or not event.is_running(): return await context.send(user.get_mention() + ', ' + lib.get_string('event:err:noexists', user.get_guild())) words = event.get_wordcount(user.get_id()) return await context.send(user.get_mention() + ', ' + lib.get_string('event:wordcount', user.get_guild()).format(event.get_title(), words))
async def run_delete(self, context): """ Delete the event on this server :param context: :return: """ user = User(context.message.author.id, context.guild.id, context) # Do they have the permissions to delete an event? self.check_permissions(context) # Check if there is an event running event = Event.get_by_guild(user.get_guild()) if event is None or not event.is_valid(): return await context.send( user.get_mention() + ', ' + lib.get_string('event:err:noexists', user.get_guild())) # Make a fake prompt to wait for confirmation. argument = { 'prompt': lib.get_string('event:deletesure', user.get_guild()), 'check': lambda resp: resp.lower() in ('y', 'yes', 'n', 'no') } response = await self.prompt(context, argument, True) if not response: return response = response.content # If they confirm, then delete the event. if response.lower() in ('y', 'yes'): event.delete() # Remove any tasks we already had saved for this event. Task.cancel('event', event.get_id()) output = lib.get_string('event:deleted', user.get_guild()).format(event.get_title()) else: # Otherwise, just print 'OK' output = 'OK' await context.send(context.message.author.mention + ', ' + output)
async def run_start(self, context): """ Start the event now :param context: :return: """ user = User(context.message.author.id, context.guild.id, context) # Do they have the permissions to start an event? self.check_permissions(context) event = Event.get_by_guild(user.get_guild()) if event is None or event.is_running(): return await context.send(user.get_mention() + ', ' + lib.get_string('event:err:cannotstart', user.get_guild())) event.set_context(context) return await event.start()
async def run_end(self, context): """ End the event now :param context: :return: """ user = User(context.message.author.id, context.guild.id, context) # Do they have the permissions to end an event? self.check_permissions(context) event = Event.get_by_guild(user.get_guild()) if event is None or not event.is_running(): return await context.send(user.get_mention() + ', ' + lib.get_string('event:err:noexists', user.get_guild())) event.set_context(context) event.set_guild_object(context.guild) # Remove any tasks we already had saved for this event. Task.cancel('event', event.get_id()) return await event.end()
async def complete(self, context=None, bot=None): """ Finish the sprint, calculate all the WPM and XP and display results :return: """ # Print the 'Results coming up shortly' message await self.say(lib.get_string('sprint:resultscomingsoon', self._guild), context, bot) # Create array to use for storing the results results = [] # If the sprint has already completed, stop. if self._completed != 0: return # Mark this sprint as complete so the cron doesn't pick it up and start processing it again self.set_complete() # Get all the users taking part users = self.get_users() # Loop through them and get their full sprint info for user_id in users: user = User(user_id, self._guild, context=context, bot=bot, channel=self.get_channel()) user_sprint = self.get_user_sprint(user_id) # If it's a non-word count sprint, we don't need to do anything with word counts. if user_sprint['sprint_type'] == Sprint.SPRINT_TYPE_NO_WORDCOUNT: # Just give them the completed sprint stat and XP. await user.add_xp(Experience.XP_COMPLETE_SPRINT) user.add_stat('sprints_completed', 1) # Push user to results results.append({ 'user': user, 'wordcount': 0, 'xp': Experience.XP_COMPLETE_SPRINT, 'type': user_sprint['sprint_type'] }) else: # If they didn't submit an ending word count, use their current one if user_sprint['ending_wc'] == 0: user_sprint['ending_wc'] = user_sprint['current_wc'] # Now we only process their result if they have declared something and it's different to their starting word count user_sprint['starting_wc'] = int(user_sprint['starting_wc']) user_sprint['current_wc'] = int(user_sprint['current_wc']) user_sprint['ending_wc'] = int(user_sprint['ending_wc']) user_sprint['timejoined'] = int(user_sprint['timejoined']) if user_sprint['ending_wc'] > 0 and user_sprint[ 'ending_wc'] != user_sprint['starting_wc']: wordcount = user_sprint['ending_wc'] - user_sprint[ 'starting_wc'] time_sprinted = self._end_reference - user_sprint[ 'timejoined'] # If for some reason the timejoined or sprint.end_reference are 0, then use the defined sprint length instead if user_sprint[ 'timejoined'] <= 0 or self._end_reference == 0: time_sprinted = self._length # Calculate the WPM from their time sprinted wpm = Sprint.calculate_wpm(wordcount, time_sprinted) # See if it's a new record for the user user_record = user.get_record('wpm') wpm_record = True if user_record is None or wpm > int( user_record) else False # If it is a record, update their record in the database if wpm_record: user.update_record('wpm', wpm) # Give them XP for finishing the sprint await user.add_xp(Experience.XP_COMPLETE_SPRINT) # Increment their stats user.add_stat('sprints_completed', 1) user.add_stat('sprints_words_written', wordcount) user.add_stat('total_words_written', wordcount) # Increment their words towards their goal await user.add_to_goals(wordcount) # If they were writing in a Project, update its word count. if user_sprint['project'] is not None: project = Project(user_sprint['project']) project.add_words(wordcount) # is there an event running on this server? event = Event.get_by_guild(self._guild) if event and event.is_running(): event.add_words(user.get_id(), wordcount) # Push user to results results.append({ 'user': user, 'wordcount': wordcount, 'wpm': wpm, 'wpm_record': wpm_record, 'xp': Experience.XP_COMPLETE_SPRINT, 'type': user_sprint['sprint_type'] }) # Sort the results results = sorted(results, key=itemgetter('wordcount'), reverse=True) # Now loop through them again and apply extra XP, depending on their position in the results position = 1 highest_word_count = 0 for result in results: if result['wordcount'] > highest_word_count: highest_word_count = result['wordcount'] # If the user finished in the top 5 and they weren't the only one sprinting, earn extra XP is_sprint_winner = result['wordcount'] == highest_word_count if position <= 5 and len(results) > 1: extra_xp = math.ceil( Experience.XP_WIN_SPRINT / (self.WINNING_POSITION if is_sprint_winner else position)) result['xp'] += extra_xp await result['user'].add_xp(extra_xp) # If they actually won the sprint, increase their stat by 1 # Since the results are in order, the highest word count will be set first # which means that any subsequent users with the same word count have tied for 1st place if position == 1 or result['wordcount'] == highest_word_count: result['user'].add_stat('sprints_won', 1) position += 1 # Post the final message with the results if len(results) > 0: position = 1 message = lib.get_string('sprint:results:header', self._guild) for result in results: if result['type'] == Sprint.SPRINT_TYPE_NO_WORDCOUNT: message = message + lib.get_string( 'sprint:results:row:nowc', self._guild).format( result['user'].get_mention(), result['xp']) else: message = message + lib.get_string( 'sprint:results:row', self._guild).format( position, result['user'].get_mention(), result['wordcount'], result['wpm'], result['xp']) # If it's a new PB, append that string as well if result['wpm_record'] is True: message = message + lib.get_string( 'sprint:results:pb', self._guild) message = message + '\n' position += 1 else: message = lib.get_string('sprint:nowordcounts', self._guild) # Send the message, either via the context or directly to the channel await self.say(message, context, bot)
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 wrote(self, context, amount=None, shortname=None): """ Adds to your total words written statistic. Examples: !wrote 250 - Adds 250 words to your total words written !wrote 200 sword - Adds 200 words to your Project with the shortname "sword". (See: Projects for more info). """ user = User(context.message.author.id, context.guild.id, context) # Check the arguments are valid args = await self.check_arguments(context, amount=amount, shortname=shortname) if not args: return amount = args['amount'] shortname = args['shortname'] message = None # If they were writing in a Project, update its word count. if shortname is not None: project = user.get_project(shortname.lower()) # Make sure the project exists. if not project: return await context.send( user.get_mention() + ', ' + lib.get_string('project:err:noexists', user.get_guild()).format(shortname)) project.add_words(amount) written_stat = user.get_stat('total_words_written') if written_stat is None: written_stat = 0 total = int(written_stat) + int(amount) message = lib.get_string('wrote:addedtoproject', user.get_guild()).format( str(amount), project.get_title(), project.get_words(), total) # # Is there an Event running? event = Event.get_by_guild(user.get_guild()) if event and event.is_running(): event.add_words(user.get_id(), amount) # Increment their words written statistic user.add_stat('total_words_written', amount) # Update their words towards their daily goal await user.add_to_goal('daily', amount) # Output message if message is None: total = user.get_stat('total_words_written') message = lib.get_string('wrote:added', user.get_guild()).format( str(amount), str(total)) await context.send(user.get_mention() + ', ' + message)
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))