diff --git a/bot/pluralkit/commands.py b/bot/pluralkit/commands.py index 1f2d5f4f..6b679efb 100644 --- a/bot/pluralkit/commands.py +++ b/bot/pluralkit/commands.py @@ -3,6 +3,7 @@ import re from urllib.parse import urlparse import discord +import humanize from pluralkit import db from pluralkit.bot import client, logger @@ -197,6 +198,63 @@ async def system_unlink(conn, message, args): await db.unlink_account(conn, system_id=system["id"], account_id=message.author.id) return True, "Account unlinked." +@command(cmd="pk;system", subcommand="fronter", usage="[system]", description="Gets the current fronter in the system.") +async def system_fronter(conn, message, args): + if len(args) == 0: + system = await db.get_system_by_account(conn, message.author.id) + + if system is None: + return False, "No system is registered to this account." + else: + system = await get_system_fuzzy(conn, args[0]) + + if system is None: + return False, "Can't find system \"{}\".".format(args[0]) + + current_fronter = await db.current_fronter(conn, system_id=system["id"]) + if not current_fronter: + return True, make_default_embed(None).add_field(name="Current fronter", value="*(nobody)*") + + fronter_name = "*(nobody)*" + if current_fronter["member"]: + member = await db.get_member(conn, member_id=current_fronter["member"]) + fronter_name = member["name"] + if current_fronter["member_del"]: + fronter_name = "*(deleted member)*" + + since = current_fronter["timestamp"] + + embed = make_default_embed(None) + embed.add_field(name="Current fronter", value=fronter_name) + embed.add_field(name="Since", value="{} ({})".format(since.isoformat(sep=" ", timespec="seconds"), humanize.naturaltime(since))) + return True, embed + +@command(cmd="pk;system", subcommand="fronthistory", usage="[system]", description="Shows the past 10 switches in the system.") +async def system_fronthistory(conn, message, args): + if len(args) == 0: + system = await db.get_system_by_account(conn, message.author.id) + + if system is None: + return False, "No system is registered to this account." + else: + system = await get_system_fuzzy(conn, args[0]) + + if system is None: + return False, "Can't find system \"{}\".".format(args[0]) + + switches = await db.past_fronters(conn, system_id=system["id"], amount=10) + + lines = [] + for switch in switches: + since = switch["timestamp"] + time_text = since.isoformat(sep=" ", timespec="seconds") + rel_text = humanize.naturaltime(since) + + lines.append("**{}** ({}, at {})".format(switch["name"], time_text, rel_text)) + + embed = make_default_embed("\n".join(lines)) + embed.title = "Past switches" + return True, embed @command(cmd="pk;member", subcommand="new", usage="", description="Adds a new member to your system.") async def new_member(conn, message, args): @@ -397,6 +455,45 @@ async def message_info(conn, message, args): await client.send_message(message.channel, embed=embed) return True +@command(cmd="pk;switch", subcommand=None, usage="", description="Registers a switch and changes the current fronter.") +async def switch_member(conn, message, args): + if len(args) == 0: + return False + + system = await db.get_system_by_account(conn, message.author.id) + + if system is None: + return False, "No system is registered to this account." + + # Find the member + member = await get_member_fuzzy(conn, system["id"], " ".join(args)) + if not member: + return False, "Couldn't find member \"{}\".".format(args[0]) + + # Get current fronter + current_fronter = await db.current_fronter(conn, system_id=system["id"]) + if current_fronter and current_fronter["member"] == member["id"]: + return False, "Member \"{}\" is already fronting.".format(member["name"]) + + # Log the switch + await db.add_switch(conn, system_id=system["id"], member_id=member["id"]) + return True, "Switch registered. Current fronter is now {}.".format(member["name"]) + +@command(cmd="pk;switch", subcommand="out", description="Registers a switch out, and leaves current fronter blank.") +async def switch_out(conn, message, args): + system = await db.get_system_by_account(conn, message.author.id) + + if system is None: + return False, "No system is registered to this account." + + # Get current fronter + current_fronter = await db.current_fronter(conn, system_id=system["id"]) + if not current_fronter or not current_fronter["member"]: + return False, "There's already no one in front." + + # Log it + await db.add_switch(conn, system_id=system["id"], member_id=None) + return True, "Switch-out registered." def make_help(cmds): embed = discord.Embed() diff --git a/bot/pluralkit/db.py b/bot/pluralkit/db.py index 9244d2d2..2c590991 100644 --- a/bot/pluralkit/db.py +++ b/bot/pluralkit/db.py @@ -20,7 +20,7 @@ def db_wrap(func): res = await func(*args, **kwargs) after = time.perf_counter() - logger.debug(" - DB took {:.2f} ms".format((after - before) * 1000)) + logger.debug(" - DB call {} took {:.2f} ms".format(func.__name__, (after - before) * 1000)) return res return inner @@ -177,6 +177,18 @@ async def delete_message(conn, message_id: str): logger.debug("Deleting message (id={})".format(message_id)) await conn.execute("delete from messages where mid = $1", int(message_id)) +@db_wrap +async def current_fronter(conn, system_id: int): + return await conn.fetchrow("select *, members.name from switches left outer join members on (members.id = switches.member) where switches.system = $1 order by timestamp desc", system_id) + +@db_wrap +async def past_fronters(conn, system_id: int, amount: int): + return await conn.fetch("select *, members.name from switches left outer join members on (members.id = switches.member) where switches.system = $1 order by timestamp desc limit $2", system_id, amount) + +@db_wrap +async def add_switch(conn, system_id: int, member_id: int): + logger.debug("Adding switch (system={}, member={})".format(system_id, member_id)) + return await conn.execute("insert into switches (system, member) values ($1, $2)", system_id, member_id) async def create_tables(conn): await conn.execute("""create table if not exists systems ( @@ -216,7 +228,7 @@ async def create_tables(conn): system serial not null references systems(id) on delete cascade, member serial references members(id) on delete restrict, timestamp timestamp not null default current_timestamp, - member_del bool default false + member_del bool not null default false )""") await conn.execute("""create table if not exists webhooks ( channel bigint primary key, diff --git a/bot/pluralkit/utils.py b/bot/pluralkit/utils.py index f2918738..32664a79 100644 --- a/bot/pluralkit/utils.py +++ b/bot/pluralkit/utils.py @@ -1,11 +1,11 @@ import random import re import string -import time import asyncio import asyncpg import discord +import humanize from pluralkit import db from pluralkit.bot import client, logger @@ -82,10 +82,7 @@ command_map = {} def command(cmd, subcommand, usage=None, description=None): def wrap(func): async def wrapper(conn, message, args): - before = time.perf_counter() res = await func(conn, message, args) - after = time.perf_counter() - time_ms = (after - before) * 1000 if res is not None: if not isinstance(res, tuple): @@ -162,6 +159,11 @@ async def generate_system_info_card(conn, system: asyncpg.Record) -> discord.Emb if system["tag"]: card.add_field(name="Tag", value=system["tag"]) + current_fronter = await db.current_fronter(conn, system_id=system["id"]) + if current_fronter and current_fronter["member"]: + fronter_val = "{} (for {})".format(current_fronter["name"], humanize.naturaldelta(current_fronter["timestamp"])) + card.add_field(name="Current fronter", value=fronter_val) + # Get names of all linked accounts async def get_name(account_id): account = await client.get_user_info(account_id) diff --git a/bot/requirements.txt b/bot/requirements.txt index cb088c81..70260281 100644 --- a/bot/requirements.txt +++ b/bot/requirements.txt @@ -1,4 +1,5 @@ -discord.py -asyncpg aiohttp -aioinflux \ No newline at end of file +aioinflux +asyncpg +discord.py +humanize \ No newline at end of file