def precheck(user, villager): '''Precheck steps before processing requests.''' data_dict = utils.open_requestlog() # Precheck will fail when either one of these conditions is met: # - The user requested this villager before, so this is a duplicated check. # - The user has too many requests. See the default in REQUEST_LIMIT=1. message = '' villagers = [] last_rejected = [] for request_id, details in list(data_dict.items()): if details['name'] == str(user) and (details['villager'] == villager): message = 'You have requested a duplicated villager, check your application with ~status.' return message for k, v in list(details.items()): if (str(k) == 'name' and user in v and (details['status'] not in ( utils.Status.CLOSED.name, utils.Status.CANCEL.name, utils.Status.REJECTED.name))): villagers.append(str(details['villager'])) # if (str(k) == 'name' and user in v and (details['status'] # Use re.match to search pattern here. pattern = re.compile(str(villager).lower()) for v in villagers: if pattern.search(v): message = 'You requested **%s** before, please use *"~status"*' message += ' command to get previous application ID.' return message % villager if len(villagers) >= REQUEST_LIMIT: message = 'You hit the max application number (%d) per user.' message += '\nPlease work with the Villager Adoption Team to ' message += 'fulfill your current application first.' return message % REQUEST_LIMIT
async def cancel(self, ctx, req_id=None): '''Cancel an application.''' # Check if the requester matches, or a staff can cancel. data_dict = utils.open_requestlog() # Setup a Flow controller. flow = request.Flow(self.bot, ctx) found = False found_data = dict() # Since we only allow 1 application per user per time, do a quick search if # req_id = none and there is an open application of this user. if not req_id: req_id = self.auto_find(data_dict, ctx.message.author.name) for request_id, details in list(data_dict.items()): if str(request_id) == str(req_id): # user can only cancel their own request. for k, v in list(details.items()): if k == u'name' and ctx.message.author.name in v: message = 'You are about to cancel an application **%s**' % req_id em = utils.get_embed('red', message, title='Cancel An Application') await ctx.channel.send(embed=em) found = True details['status'] = utils.Status.CANCEL.name # mark a last_modified timestring tm = time.localtime(time.time()) timestring = time.strftime(TIME_FORMAT, tm) details['last_modified'] = timestring found_data[request_id] = details break if not found: message = 'Your application %s was not found.' % req_id em = utils.get_embed('red', message, title='Application Not Found') await ctx.channel.send(embed=em) return are_you_sure = await ctx.send( f":information_source: Please confirm YES/NO to cancel" f" this application **%s**" % request_id) reaction = await flow.get_yes_no_reaction_confirm(are_you_sure, 200) if reaction is None: return if not reaction: em = utils.get_embed('red', 'Aborted.') return await ctx.channel.send(embed=em) elif reaction: utils.flush_requestlog(data_dict) await ctx.send(f"You\'ve cancelled this application.") await self.send_logs_user('Cancelled application %s by %s' % (req_id, ctx.message.author.name)) await sheet.update_data(found_data) # Then hide the close row. await sheet.archive_column(req_id)
async def archive(self, ctx, req_id, *input_args): '''Archive and hide a row in the sheet by an application ID.''' data_dict = utils.open_requestlog() toggle = list(input_args) if not toggle: # always hide await sheet.archive_column(req_id) else: # unhide await sheet.archive_column(req_id, False) server_msg = 'DreamieBot archived a row of request_id %s in the sheet.' # send to log channel return await self.send_logs(server_msg % (req_id))
async def found(self, ctx, req_id): '''Indicates that you have found a requested villager.''' data_dict = utils.open_requestlog() user_id = 0 staff = ctx.message.author.name staff += '#' + ctx.message.author.discriminator staff_obj = self.bot.get_user(ctx.message.author.id) dreamie = '' found_data = dict() for request_id, details in list(data_dict.items()): if str(request_id) == str(req_id): message = 'A villager of this application **%s** was found by %s!' await ctx.send(message % (req_id, staff)) details['status'] = utils.Status.FOUND.name dreamie = details['villager'] # only get the name dreamie = dreamie.split(',')[0] # mark a last_modified timestring tm = time.localtime(time.time()) timestring = time.strftime(TIME_FORMAT, tm) details['last_modified'] = timestring details['staff'] = staff user_id = details['user_id'] found_data[request_id] = details utils.flush_requestlog(data_dict) time.sleep(1) await sheet.update_data(found_data) # send to log channel await self.send_logs('%s found a requested villager: %s' % (staff, dreamie)) # send a DM to note the user. if user_id: user = self.bot.get_user(user_id) dm_chan = user.dm_channel or await user.create_dm() message = ('%s your dreamie %s has been found.\nYou have 72hrs' ' to get an open plot ready and use the **~ready** ' 'command to notify us. You will be contacted by ' '%s to coordinate the following steps during your ' 'selected timeslot.\nRequest Id: %s\n\n' 'If you do not get ready in time, this application ' 'will expire automatically. Your villager will not ' 'be held for you and will be passed to the next ' 'applicant.\n') await dm_chan.send(message % (user.mention, dreamie, staff, req_id) )
async def list(self, ctx, req_id=None): '''List all applications, or a single one by its ID.''' # Retrieve data_dict from the request log file. data_dict = utils.open_requestlog() found = "" for request_id, details in list(data_dict.items()): if not req_id or str(request_id).lower() == str(req_id).lower(): # all requests. found += 'application_id: %s\n' % request_id found += utils.printadict(details) found += '\n' if found: time.sleep(1) pg_data = utils.paginate(found) embed = discord.Embed(title='List Applications') embed.color = utils.random_color() for message in pg_data: embed.description = ''.join(message) await ctx.send(embed=embed)
async def claim(self, ctx, req_id): '''Claim an application to move its status to PROCESSING.''' data_dict = utils.open_requestlog() user_id = 0 staff = ctx.message.author.name staff += '#' + ctx.message.author.discriminator staff_obj = self.bot.get_user(ctx.message.author.id) found_data = dict() for request_id, details in list(data_dict.items()): if str(request_id) == str(req_id): villager = details['villager'].split(',')[0] details['status'] = utils.Status.PROCESSING.name # mark a last_modified timestring tm = time.localtime(time.time()) timestring = time.strftime(TIME_FORMAT, tm) details['last_modified'] = timestring details['staff'] = staff found_data[request_id] = details user_id = details['user_id'] name = details['name'] break # Send a note back to the staff. staff_msg = ('You claimed an application: %s (%s looks for %s).') await ctx.send(staff_msg % (req_id, name, villager)) utils.flush_requestlog(data_dict) time.sleep(1) await sheet.update_data(found_data) # send to log channel await self.send_logs('%s claim an application: %s' % (staff, req_id)) # send a DM to note the user. if user_id: user = self.bot.get_user(user_id) dm_chan = user.dm_channel or await user.create_dm() user_msg = ( 'Thank you for applying to the Dream Villager Adoption Program.' ' We have approved your application and a staff member of our ' 'team, _%s_, has begun looking for your dream villager.\n' 'Please monitor your DMs for updates. You can use **~status** ' 'command to check the latest status.\n' 'If you have any questions, please check the FAQ in ' '_#villager-adoption-program, or ask questions in ' '#villager-trading_, or ping us **@adoptionteam**.') await dm_chan.send(user_msg % staff)
async def close(self, ctx, req_id): '''Close an application and pop some firework.''' data_dict = utils.open_requestlog() user_id = 0 staff = ctx.message.author.name staff += '#' + ctx.message.author.discriminator staff_obj = self.bot.get_user(ctx.message.author.id) villager = None found_data = dict() for request_id, details in list(data_dict.items()): if str(request_id) == str(req_id): message = 'Congrats! Application **%s** is now closed by %s!' villager = details['villager'].split(',')[0] await ctx.send(message % (req_id, staff)) details['status'] = utils.Status.CLOSED.name # mark a last_modified timestring tm = time.localtime(time.time()) timestring = time.strftime(TIME_FORMAT, tm) details['last_modified'] = timestring details['staff'] = staff user_id = details['user_id'] found_data[request_id] = details utils.flush_requestlog(data_dict) time.sleep(1) await sheet.update_data(found_data) # Then hide the close row. await sheet.archive_column(req_id) # send to log channel await self.send_logs('%s closed an application: %s' % (staff, req_id)) # send a DM to note the user. if user_id: user = self.bot.get_user(user_id) dm_chan = user.dm_channel or await user.create_dm() message = 'Congrats, %s! You have found your dreamie, %s.\n' message += 'Your application **%s** is closed by a staff (_%s_).\n' message += 'After you\'ve settled down, please consider showing ' message += 'appreciation in the #Player-Feedback channel!' await dm_chan.send(message % (user.mention, villager, req_id, staff))
async def status(self, ctx): '''Check the status of a user's application.''' # Retrieve data_dict from the request log file. data_dict = utils.open_requestlog() found = "" for request_id, details in list(data_dict.items()): for k, v in list(details.items()): if k == u'name' and ctx.message.author.name in v: # also hide closed requests with status == 'cancel', # 'rejected' or 'closed' if details['status'] not in (utils.Status.CLOSED.name, utils.Status.CANCEL.name, utils.Status.REJECTED.name): found += 'application_id: **%s**\n' % request_id found += utils.printadict(details, hide_self=True) found += '\n' if found: await ctx.send("Found your application:") color = utils.status_color(details) em = utils.get_embed(color, found) await ctx.channel.send(embed=em) else: em = utils.get_embed('red', "You don\'t have any application.") await ctx.channel.send(embed=em)
async def apply(self, ctx, villager=None): '''Apply to request a villager and get an <application ID> in return.''' # Setup a Flow controller. flow = request.Flow(self.bot, ctx) # Find is_bot_locked from Staff cog. staff_cog = self.bot.get_cog('Staff') if staff_cog.is_bot_locked: text = 'I am sorry but the application queue is now locked and ' text += 'cannot accept any application.\nPlease stay tuned in the ' text += '#villager-adoption-program channel for future updates.' em = utils.get_embed('gold', text) return await ctx.channel.send(embed=em) if not villager: text = 'Greetings! Which villager would you like to request?\n' text += 'Use `~apply <villager>` command to create an application.' em = utils.get_embed('gray', text) return await ctx.channel.send(embed=em) # Before starting anything, check if this applicant was rejected in # the last two weeks. data_dict = utils.open_requestlog() rejected = [] for request_id, details in list(data_dict.items()): for k, v in list(details.items()): if k == u'name' and ctx.message.author.name in v: # also hide requests with status == 'cancel' or 'closed' if details['status'] in (utils.Status.REJECTED.name): rejected.append(details) current = datetime.datetime.utcnow() for detail in rejected: # Fixed the period to 2 weeks. then_ts = time.strptime(detail['last_modified'], TIME_FORMAT) then_dt = datetime.datetime.fromtimestamp(time.mktime(then_ts)) period = datetime.timedelta(days=14) if (current - then_dt) < period: rejected_msg = ( 'Sorry, your previous application was closed at ' '%s within a two weeks cooldown.\nPlease come back' ' and reapply later.' % detail['last_modified']) em = utils.get_embed('red', rejected_msg, title='Still In Cooldown') await self.send_logs_user('%s attempted to re-apply a dreamie ' 'while in a 2 weeks cooldown.' % detail['name']) return await ctx.channel.send(embed=em) # Viallger name differeniation: some villagers have a space char # between its names: v = villager.lower() if v is not 'kidd' and v in ('kid', 'agent', 'big', 'wart'): if v == 'kid': villager = 'Kid Cat' villager_link = 'https://villagerdb.com/villager/kid-cat' if v == 'agent': villager = 'Agent S' villager_link = 'https://villagerdb.com/villager/agent-s' if v == 'big': villager = 'Big Top' villager_link = 'https://villagerdb.com/villager/big-top' if v == 'wart': villager = 'Wart Jr.' villager_link = 'https://villagerdb.com/villager/wart-jr' else: # Form a villager link villager_link = 'https://villagerdb.com/villager/{}'.format( villager.lower()) villager = villager.capitalize() # Validate the villager's name before everything. result = checks.validate_name(villager) if result: return await ctx.send(result) villager_data = "{}, {}".format(villager, villager_link) request_id = utils.generate_id(data_dict) # ctx.message.id name = ctx.message.author.name name += '#' + ctx.message.author.discriminator message = checks.precheck(name, villager) if message: em = utils.get_embed('red', message, title='Precheck Failed') return await ctx.channel.send(embed=em) tm = time.localtime(time.time()) timestring = time.strftime(TIME_FORMAT, tm) # init data and data_dict # data might be redundant because I use data_dict mostly. data = "" data_dict = dict() data = u"Request_Id: **{}**\n".format(request_id) details = dict() data += u"Name: {}\n".format(name) details['name'] = name details['user_id'] = ctx.message.author.id data += 'Villager: {}\n'.format(villager_data) details['villager'] = villager_data data += u"CreatedTime: {}\n".format(timestring) details['created_time'] = timestring em = utils.get_embed('gray', 'Your Application Details:') await ctx.channel.send(embed=em) await ctx.channel.send(utils.printadict(details, hide_self=True)) time.sleep(1) # default null for these two. Added after showing details to users, # so they won't know. details['last_modified'] = '' details['staff'] = '' # Flow control. tt_or_not = await ctx.send( f":information_source: Are you willing to do Time Travel in order to " f"make the process quicker?") tt_reaction = await flow.get_yes_no_reaction_confirm(tt_or_not, 200) if tt_reaction is None: return ctx.send( "Application cancelled. You may apply again at any time.") if not tt_reaction: details['can_time_travel'] = False # Only accept non-tter with an open plot in 72 hours to apply. within_72_hr = await ctx.send( f":question: Will you have an open plot with 72 hours? ") reaction = await flow.get_yes_no_reaction_confirm( within_72_hr, 200) if reaction is None: return ctx.send( "Application cancelled. You may apply again when an open plot is ready." ) if not reaction: rejected_text = ( "We’re sorry, your application cannot be accepted at this time.\n" "Please apply again when you are within a 72 hour window " "of an open plot.") em = utils.get_embed('red', rejected_text, title="Application Denied") return await ctx.channel.send(embed=em) else: details['can_time_travel'] = True # Available timeslot selection timeslot = await ctx.send( f":information_source: Which time slot is the best choice to contact you" " if we have to? \n" "NOTE: Please refer to https://www.thetimezoneconverter.com/ or https://time.is/UTC\n" " and select your most available timeslot in **UTC**.\n" ":sparkles: Slot 1: 00:00 - 03:59 UTC.\n:eight_spoked_asterisk: " "Slot 2: 04:00 - 07:59 UTC.\n" ":heart: Slot 3: 08:00 - 11:59 UTC.\n:rocket: Slot 4: 12:00 - 15:59 UTC.\n" ":crescent_moon: Slot 5: 16:00 - 19:59 UTC.\n:full_moon: Slot 6: " "20:00 - 23:59 UTC.\n") time.sleep(1) slot_reaction = await flow.get_timeslot_reaction_confirm(timeslot, 600) if slot_reaction is None: return ctx.send( "Application cancelled. You may apply again at any time.") if slot_reaction == 1: details['avail_time'] = '00:00-03:59 UTC' if slot_reaction == 2: details['avail_time'] = '04:00-07:59 UTC' if slot_reaction == 3: details['avail_time'] = '08:00-11:59 UTC' if slot_reaction == 4: details['avail_time'] = '12:00-15:59 UTC' if slot_reaction == 5: details['avail_time'] = '16:00-19:59 UTC' if slot_reaction == 6: details['avail_time'] = '20:00-23:59 UTC' # Final confirmation are_you_sure = await ctx.send( f":question: Please confirm YES/NO to create a new " f"application of finding **%s**." % villager) reaction = await flow.get_yes_no_reaction_confirm(are_you_sure, 200) if reaction is None: return ctx.send( "Application cancelled. You may apply again at any time.") if not reaction: return await ctx.send("Aborted your application.") elif reaction: user_obj = self.bot.get_user(ctx.message.author.id) await ctx.send("This application has been logged for review.") await ctx.send( "%s Please take a note of your application ID:\n**%s**" % (user_obj.mention, request_id)) time.sleep(1) # Add a default status. data += u"Status: {}".format(utils.Status.PENDING.name) details['status'] = utils.Status.PENDING.name # data_dict is keyed by request_id, and its value contains the rest details as a dictionary. data_dict[request_id] = details # Open request log file and append to the end. with open(RECORD_FILE, mode="a") as f: json.dump(data_dict, f, indent=None) f.write('\n') await sheet.update_data(data_dict) await self.send_logs_user('%s requested a dreamie (%s) at %s' % (name, villager.capitalize(), timestring) )
async def ready(self, ctx, req_id=None): '''Send a note to the staff team when you are ready to accept a new villager.''' # Check if the requester matches. data_dict = utils.open_requestlog() dreamie = None found = None found_data = dict() # Setup a Flow controller. flow = request.Flow(self.bot, ctx) # Since we only allow 1 application per user per time, do a quick search if # req_id = none and there is an open application of this user. if not req_id: req_id = self.auto_find(data_dict, ctx.message.author.name) for request_id, details in list(data_dict.items()): if str(request_id) == str(req_id): # user can only mark their own request. for k, v in list(details.items()): if k == u'name' and ctx.message.author.name in v: found = ( '%s has marked the application **%s** as ready to ' 'accept the dreamie!' % (ctx.message.author.name, req_id)) details['status'] = utils.Status.READY.name dreamie = details['villager'] dreamie = dreamie.split(',')[0] # mark a last_modified timestring tm = time.localtime(time.time()) timestring = time.strftime(TIME_FORMAT, tm) details['last_modified'] = timestring found_data[request_id] = details break if not found: em = utils.get_embed('red', 'Cannot found your application %s.' % req_id) return await ctx.channel.send(embed=em) are_you_sure = await ctx.send( f":information_source: Please confirm YES/NO to indicate" f" that you have an open plot ready to receive your dreamie.\n" f"Application ID:**%s**" % req_id) reaction = await flow.get_yes_no_reaction_confirm(are_you_sure, 200) if reaction is None: return if not reaction: em = utils.get_embed('red', 'Aborted.') return await ctx.channel.send(embed=em) elif reaction: color = utils.status_color(details) em = utils.get_embed(color, found) await ctx.channel.send(embed=em) utils.flush_requestlog(data_dict) user_obj = self.bot.get_user(found_data[req_id]['user_id']) await self.send_logs_user('%s is ready to accept a dreamie (%s).' % (found_data[req_id]['name'], dreamie)) await sheet.update_data(found_data) staff_lst = found_data[req_id]['staff'].split('#') staff_id = discord.utils.get(self.bot.get_all_members(), name=staff_lst[0], discriminator=staff_lst[1]).id staff_obj = self.bot.get_user(staff_id) staff_dm = staff_obj.dm_channel or await staff_obj.create_dm() staff_msg = '%s: %s is ready to accept a dreamie (%s).' % ( staff_obj.mention, user_obj.mention, dreamie) await staff_dm.send(staff_msg) """
async def review(self, ctx, req_id, denied=None): '''Review an application. The result is either approved or denied.''' data_dict = utils.open_requestlog() user_id = 0 staff = None found = False found_data = dict() rejected = dict() for request_id, details in list(data_dict.items()): if str(request_id) == str(req_id): found = True staff = ctx.message.author.name staff += '#' + ctx.message.author.discriminator staff_obj = self.bot.get_user(ctx.message.author.id) user_id = details['user_id'] if str(denied) == 'denied': # The message is sent back to a user. message = ':disappointed_relieved: Thank you for submitting an application for ' message += 'your dream villager! Unfortunately, your application has been ' message += 'rejected after being reviewed by the Beyond Stalks Villager Adoption Team.' message += '\n\nThe reason for your application\'s rejection is inactivity within ' message += 'the server community. Our adoption program is designed to be a reward ' message += 'for consistently active and positive contributers to the server. ' message += 'Please strive to meet these conditions and we will look forward to ' message += 'a follow-up application from you in due time. You may submit a new ' message += 'application in **2 weeks**.\n' message += '\nYour application has been closed.\n\n' message += 'Thanks!\nThe Villager Adoption Team' # A server message for reference. server_message = '%s denied an application: %s' % (staff, req_id) details['status'] = utils.Status.REJECTED.name # mark a last_modified timestring tm = time.localtime(time.time()) timestring = time.strftime(TIME_FORMAT, tm) details['last_modified'] = timestring details['staff'] = staff rejected[request_id] = details # send to log channel await self.send_logs(server_message) # send a DM to note the user. if user_id: user = self.bot.get_user(user_id) dm_chan = user.dm_channel or await user.create_dm() await dm_chan.send(message) else: message = 'Application **%s** is now approved by %s!' await ctx.send(message % (req_id, staff)) details['status'] = utils.Status.APPROVED.name # mark a last_modified timestring tm = time.localtime(time.time()) timestring = time.strftime(TIME_FORMAT, tm) details['last_modified'] = timestring details['staff'] = staff # send to log channel await self.send_logs('%s approved an application: %s' % (staff, req_id)) # send a DM to note the user. if user_id: user = self.bot.get_user(user_id) dm_chan = user.dm_channel or await user.create_dm() user_msg = ('Your application **{}** is approved by a ' 'staff (_{}_).\nPlease use the `~status` ' 'command to check current status.'.format( req_id, staff)) await dm_chan.send(user_msg) # Save changes to found_data found_data[request_id] = details if not found: message = 'Cannot find application **%s**' % req_id await ctx.send(message) # send to log channel return await self.send_logs(message) # Write changes back. utils.flush_requestlog(data_dict) time.sleep(1) await sheet.update_data(found_data) if rejected: for k, _ in rejected.items(): await sheet.archive_column(k)
async def search(self, ctx, *input_args): '''Search for a summary, status or name in all applications.''' data_dict = utils.open_requestlog() user_id = 0 # Since this function is shared with a background task. # When ctx = None, it will not send the result messages to the user, # instead, it returns a dictionary. all_args = [a.lower() for a in list(input_args)] all_status = [t.name for t in utils.Status] tmp_dict = dict() target = all_args[0] if target == 'summary': total = len(data_dict) # We don't care about closed, cancel or rejected applications. for status in all_status: # expected_status: tmp_dict[status] = 0 for _, details in list(data_dict.items()): if status == details['status']: tmp_dict[status] += 1 if tmp_dict[status]: ratio = '{} ({:.3f}{})'.format( tmp_dict[status], (tmp_dict[status] / total * 100), '%') tmp_dict[status] = ratio else: tmp_dict[status] = '0 (------)' if not ctx: return tmp_dict embed = discord.Embed() embed.title = 'Applications Summary:' embed.color = utils.random_color() embed.description = 'Total Applications: %d' % total for k, v in tmp_dict.items(): embed.add_field(name=k, value=v, inline=True) return await ctx.send(embed=embed) elif target.upper() in all_status: # Search by status target = target.upper() tmp_dict = dict() for request_id, details in list(data_dict.items()): if target == details['status']: villager = details['villager'].split(', ')[0] tmp_dict[request_id] = '**{}** looks for _{}_'.format( details['name'], villager) # Prepare for background reporting task. if not ctx and 'background' in all_args: return tmp_dict if not ctx and 'countdown' in all_args: # For coutndown background tasks, we need to return a raw # details dict. raw_dict = dict() for request_id, details in list(data_dict.items()): if target == details['status']: raw_dict[request_id] = details return raw_dict title = '_%s_ Application' % target.capitalize() if len(tmp_dict) > 1: title += 's: *%d*' % len(tmp_dict) else: title += ': *%d*' % len(tmp_dict) if tmp_dict: embed = discord.Embed(title=title) embed.color = utils.random_color() for k, v in tmp_dict.items(): embed.add_field(name=k, value=v, inline=True) return await ctx.send(embed=embed) else: return await ctx.send('Found nothing for status=%s' % target) else: # Search by user, could be multiple names. # fix the guild if nothing found. # 704875142496649256 is Beyond Stalks. # guild_id = ctx.message.guild if ctx.message.guild else '704875142496649256' tmp_list = [] for target in all_args: title = 'Search By Name: %s' % target found = dict() # user_id = discord.utils.get(client.get_all_members(), name=name_list[0], # discriminator=name_list[1]).id # pattern = re.search(target, details['name'], re.IGNORECASE) for request_id, details in list(data_dict.items()): if re.search(target, details['name'], re.IGNORECASE): villager = details['villager'].split(', ')[0] status = 'Status: {}'.format( details['status'].capitalize()) found[request_id] = '**{}**\n{}'.format( villager, status) if found: embed = discord.Embed(title=title) embed.color = utils.random_color() for k, v in found.items(): embed.add_field(name=k, value=v, inline=True) return await ctx.send(embed=embed) else: return await ctx.send('Found nothing for this name: %s' % target)
async def monitoring(self): '''Monitoring applications and report it back to #adoption-team's channel.''' # For testing, lets just spam foxfair. user = self.bot.get_user(self.bot.owner_id) dm_chan = user.dm_channel or await user.create_dm() # Send message to #bot-logs channel. # chan = self.bot.get_channel(auth_config.SEND_MSG_CHANNELS[0]) staff_cog = self.bot.get_cog('Staff') for status in ('pending', 'found', 'approved', 'ready', 'processing'): report = await staff_cog.search(None, status, 'background') title = '_%s_ Application' % status.capitalize() if len(report) > 1: title += 's: *%d*' % len(report) else: title += ': *%d*' % len(report) if report: embed = discord.Embed(title=title) embed.color = utils.random_color() for k, v in report.items(): embed.add_field(name=k, value=v, inline=True) # Save to self.last_status, and only report different/new info. # "if status not in self.last_status" means this is a new status. # "if report != self.last_status[status]" means data of this status # has changed. if status not in self.last_status or report != self.last_status[ status]: self.last_status[status] = report await dm_chan.send(embed=embed) # await chan.send(embed=embed) # To guarantee the first task will monitor this 'countdown' task. self.loop_counter += 1 if (self.loop_counter % 4) == 0: status = 'ready' report = await staff_cog.search(None, status, 'countdown') if report: for req_id, details in report.items(): # TODO: test only. # if 'foxfair' not in details['name']: # break current = datetime.datetime.utcnow() for exp_min in [360, 180, 60]: app_user = self.bot.get_user(details['user_id']) # Prepare to send a DM to remind the applicant reminder_chan = app_user.dm_channel or await app_user.create_dm( ) then_ts = time.strptime(details['last_modified'], TIME_FORMAT) then_dt = datetime.datetime.fromtimestamp( time.mktime(then_ts)) deadline = datetime.timedelta(hours=COUNTDOWN_HOURS) reminder_period = datetime.timedelta(minutes=exp_min) time_left = then_dt + deadline - current if (then_dt + deadline) <= current: user_msg = ('{} Your application ID: {}, was ' 'expired after 72 hours, and it was ' 'closed automatically.'.format( app_user.mention, req_id)) # Expired; closed by DreamieBot. data_dict = utils.open_requestlog() found_data = dict() for request_id, details in data_dict.items(): if request_id == req_id: details[ 'status'] = utils.Status.CLOSED.name # mark a last_modified timestring tm = time.localtime(time.time()) timestring = time.strftime(TIME_FORMAT, tm) details['last_modified'] = timestring details['staff'] = 'DreamieBot#1424' found_data[request_id] = details utils.flush_requestlog(data_dict) server_msg = ('DreamitBot closed an expired ' 'application {} at {}'.format( req_id, timestring)) await reminder_chan.send(user_msg) await staff_cog.send_logs(server_msg) time.sleep(1) await sheet.update_data(found_data) return await sheet.archive_column(req_id) elif time_left <= reminder_period: # remove microsecond, users dont care. time_left = utils.chop_microseconds(time_left) message = ( '{} Your application has been ready but ' 'the timer is approaching to the {} ' 'hours limit.\nRemaining time is {}.\n' 'Please use `~status` to see the details' 'and contact your staff, {}, to complete' 'your application ASAP.'.format( app_user.mention, COUNTDOWN_HOURS, time_left, details['staff'])) self.key = 'reminder-{}-{}'.format( exp_min, user.name) # Put into self.last_status and avoid spammy messages. if self.key not in self.last_status: self.last_status[self.key] = req_id await reminder_chan.send(message) # send logs to log channel log_msg = 'Send a reminder to {}: {}'.format( app_user.mention, self.key) await staff_cog.send_logs(log_msg) else: # debug print('current time: %s' % current) print('timer left: %s' % (then_dt + deadline - current))