Exemple #1
0
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)
Exemple #2
0
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
Exemple #3
0
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.")
Exemple #4
0
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)
Exemple #5
0
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.")