async def system_fronthistory(ctx: CommandContext, system: System): lines = [] front_history = await pluralkit.utils.get_front_history(ctx.conn, system.id, count=10) if not front_history: raise CommandError("You have no logged switches. Use `pk;switch´ to start logging.") for i, (timestamp, members) in enumerate(front_history): # Special case when no one's fronting if len(members) == 0: name = "(no fronter)" else: name = ", ".join([member.name for member in members]) # Make proper date string time_text = ctx.format_time(timestamp) rel_text = display_relative(timestamp) delta_text = "" if i > 0: last_switch_time = front_history[i - 1][0] delta_text = ", for {}".format(display_relative(timestamp - last_switch_time)) lines.append("**{}** ({}, {} ago{})".format(name, time_text, rel_text, delta_text)) embed = embeds.status("\n".join(lines) or "(none)") embed.title = "Past switches" await ctx.reply(embed=embed)
async def front_status(ctx: "CommandContext", switch: Switch) -> discord.Embed: if switch: embed = status("") fronter_names = [ member.name for member in await switch.fetch_members(ctx.conn) ] if len(fronter_names) == 0: embed.add_field(name="Current fronter", value="(no fronter)") elif len(fronter_names) == 1: embed.add_field(name="Current fronter", value=truncate_field_body(fronter_names[0])) else: embed.add_field(name="Current fronters", value=truncate_field_body( ", ".join(fronter_names))) if switch.timestamp: embed.add_field(name="Since", value="{} ({})".format( ctx.format_time(switch.timestamp), display_relative(switch.timestamp))) else: embed = error("No switches logged.") return embed
async def switch_delete(ctx: CommandContext): system = await ctx.ensure_system() last_two_switches = await system.get_switches(ctx.conn, 2) if not last_two_switches: raise CommandError("You do not have a logged switch to delete.") last_switch = last_two_switches[0] next_last_switch = last_two_switches[1] if len( last_two_switches) > 1 else None last_switch_members = ", ".join( [member.name for member in await last_switch.fetch_members(ctx.conn)]) last_switch_time = display_relative(last_switch.timestamp) if next_last_switch: next_last_switch_members = ", ".join([ member.name for member in await next_last_switch.fetch_members(ctx.conn) ]) next_last_switch_time = display_relative(next_last_switch.timestamp) msg = await ctx.reply_warn( "This will delete the latest switch ({}, {} ago). The next latest switch is {} ({} ago). Is this okay?" .format(last_switch_members, last_switch_time, next_last_switch_members, next_last_switch_time)) else: msg = await ctx.reply_warn( "This will delete the latest switch ({}, {} ago). You have no other switches logged. Is this okay?" .format(last_switch_members, last_switch_time)) if not await ctx.confirm_react(ctx.message.author, msg): raise CommandError("Switch deletion cancelled.") await last_switch.delete(ctx.conn) if next_last_switch: # lol block scope amirite # but yeah this is fine await ctx.reply_ok( "Switch deleted. Next latest switch is now {} ({} ago).".format( next_last_switch_members, next_last_switch_time)) else: await ctx.reply_ok("Switch deleted. You now have no logged switches.")
async def system_frontpercent(ctx: CommandContext, system: System): # Parse the time limit (will go this far back) if ctx.remaining(): before = dateparser.parse(ctx.remaining(), languages=["en"], settings={ "TO_TIMEZONE": "UTC", "RETURN_AS_TIMEZONE_AWARE": False }) if not before: raise CommandError("Could not parse '{}' as a valid time.".format(ctx.remaining())) # If time is in the future, just kinda discard if before and before > datetime.utcnow(): before = None else: before = datetime.utcnow() - timedelta(days=30) # Fetch list of switches all_switches = await pluralkit.utils.get_front_history(ctx.conn, system.id, 99999) if not all_switches: raise CommandError("No switches registered to this system.") # Cull the switches *ending* before the limit, if given # We'll need to find the first switch starting before the limit, then cut off every switch *before* that if before: for last_stamp, _ in all_switches: if last_stamp < before: break all_switches = [(stamp, members) for stamp, members in all_switches if stamp >= last_stamp] start_times = [stamp for stamp, _ in all_switches] end_times = [datetime.utcnow()] + start_times switch_members = [members for _, members in all_switches] # Gonna save a list of members by ID for future lookup too members_by_id = {} # Using the ID as a key here because it's a simple number that can be hashed and used as a key member_times = {} for start_time, end_time, members in zip(start_times, end_times, switch_members): # Cut off parts of the switch that occurs before the time limit (will only happen if this is the last switch) if before and start_time < before: start_time = before # Calculate length of the switch switch_length = end_time - start_time def add_switch(id, length): if id not in member_times: member_times[id] = length else: member_times[id] += length for member in members: # Add the switch length to the currently registered time for that member add_switch(member.id, switch_length) # Also save the member in the ID map for future reference members_by_id[member.id] = member # Also register a no-fronter switch with the key None if not members: add_switch(None, switch_length) # Find the total timespan of the range span_start = max(start_times[-1], before) if before else start_times[-1] total_time = datetime.utcnow() - span_start embed = embeds.status("") for member_id, front_time in sorted(member_times.items(), key=lambda x: x[1], reverse=True): member = members_by_id[member_id] if member_id else None # Calculate percent fraction = front_time / total_time percent = round(fraction * 100) embed.add_field(name=member.name if member else "(no fronter)", value="{}% ({})".format(percent, humanize.naturaldelta(front_time))) embed.set_footer(text="Since {} ({} ago)".format(ctx.format_time(span_start), display_relative(span_start))) await ctx.reply(embed=embed)
async def switch_move(ctx: CommandContext): system = await ctx.ensure_system() if not ctx.has_next(): raise CommandError("You must pass a time to move the switch to.") # Parse the time to move to new_time = dateparser.parse( ctx.remaining(), languages=["en"], settings={ # Tell it to default to the system's given time zone # If no time zone was given *explicitly in the string* it'll return as naive "TIMEZONE": system.ui_tz }) if not new_time: raise CommandError("'{}' can't be parsed as a valid time.".format( ctx.remaining())) tz = pytz.timezone(system.ui_tz) # So we default to putting the system's time zone in the tzinfo if not new_time.tzinfo: new_time = tz.localize(new_time) # Now that we have a system-time datetime, convert this to UTC and make it naive since that's what we deal with new_time = pytz.utc.normalize(new_time).replace(tzinfo=None) # Make sure the time isn't in the future if new_time > datetime.utcnow(): raise CommandError("Can't move switch to a time in the future.") # Make sure it all runs in a big transaction for atomicity async with ctx.conn.transaction(): # Get the last two switches to make sure the switch to move isn't before the second-last switch last_two_switches = await system.get_switches(ctx.conn, 2) if len(last_two_switches) == 0: raise CommandError( "There are no registered switches for this system.") last_switch = last_two_switches[0] if len(last_two_switches) > 1: second_last_switch = last_two_switches[1] if new_time < second_last_switch.timestamp: time_str = display_relative(second_last_switch.timestamp) raise CommandError( "Can't move switch to before last switch time ({} ago), as it would cause conflicts." .format(time_str)) # Display the confirmation message w/ humanized times last_fronters = await last_switch.fetch_members(ctx.conn) members = ", ".join([member.name for member in last_fronters]) or "nobody" last_absolute = ctx.format_time(last_switch.timestamp) last_relative = display_relative(last_switch.timestamp) new_absolute = ctx.format_time(new_time) new_relative = display_relative(new_time) # Confirm with user switch_confirm_message = await ctx.reply( "This will move the latest switch ({}) from {} ({} ago) to {} ({} ago). Is this OK?" .format(members, last_absolute, last_relative, new_absolute, new_relative)) if not await ctx.confirm_react(ctx.message.author, switch_confirm_message): raise CommandError("Switch move cancelled.") # Actually move the switch await last_switch.move(ctx.conn, new_time) await ctx.reply_ok("Switch moved.")