Massive refactor/update/UX improvement dump. Closes #6.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
import logging
|
||||
import re
|
||||
from typing import Tuple, Optional, Union
|
||||
|
||||
@@ -10,8 +10,6 @@ from pluralkit.errors import PluralKitError
|
||||
from pluralkit.member import Member
|
||||
from pluralkit.system import System
|
||||
|
||||
logger = logging.getLogger("pluralkit.bot.commands")
|
||||
|
||||
|
||||
def next_arg(arg_string: str) -> Tuple[str, Optional[str]]:
|
||||
# A basic quoted-arg parser
|
||||
@@ -68,6 +66,19 @@ class CommandContext:
|
||||
popped, self.args = next_arg(self.args)
|
||||
return popped
|
||||
|
||||
def peek_str(self) -> Optional[str]:
|
||||
if not self.args:
|
||||
return None
|
||||
popped, _ = next_arg(self.args)
|
||||
return popped
|
||||
|
||||
def match(self, next) -> bool:
|
||||
peeked = self.peek_str()
|
||||
if peeked and peeked.lower() == next.lower():
|
||||
self.pop_str()
|
||||
return True
|
||||
return False
|
||||
|
||||
async def pop_system(self, error: CommandError = None) -> System:
|
||||
name = self.pop_str(error)
|
||||
system = await utils.get_system_fuzzy(self.conn, self.client, name)
|
||||
@@ -103,6 +114,13 @@ class CommandContext:
|
||||
async def reply_warn(self, content=None, embed=None):
|
||||
return await self.reply(content="\u26a0 {}".format(content or ""), embed=embed)
|
||||
|
||||
async def reply_ok_dm(self, content: str):
|
||||
if isinstance(self.message.channel, discord.DMChannel):
|
||||
await self.reply_ok(content="\u2705 {}".format(content or ""))
|
||||
else:
|
||||
await self.message.author.send(content="\u2705 {}".format(content or ""))
|
||||
await self.reply_ok("DM'd!")
|
||||
|
||||
async def confirm_react(self, user: Union[discord.Member, discord.User], message: discord.Message):
|
||||
await message.add_reaction("\u2705") # Checkmark
|
||||
await message.add_reaction("\u274c") # Red X
|
||||
@@ -138,6 +156,35 @@ import pluralkit.bot.commands.switch_commands
|
||||
import pluralkit.bot.commands.system_commands
|
||||
|
||||
|
||||
async def command_root(ctx: CommandContext):
|
||||
if ctx.match("system"):
|
||||
await system_commands.system_root(ctx)
|
||||
elif ctx.match("member"):
|
||||
await member_commands.member_root(ctx)
|
||||
elif ctx.match("link"):
|
||||
await system_commands.account_link(ctx)
|
||||
elif ctx.match("unlink"):
|
||||
await system_commands.account_unlink(ctx)
|
||||
elif ctx.match("message"):
|
||||
await message_commands.message_info(ctx)
|
||||
elif ctx.match("log"):
|
||||
await mod_commands.set_log(ctx)
|
||||
elif ctx.match("invite"):
|
||||
await misc_commands.invite_link(ctx)
|
||||
elif ctx.match("export"):
|
||||
await misc_commands.export(ctx)
|
||||
elif ctx.match("switch"):
|
||||
await switch_commands.switch_root(ctx)
|
||||
elif ctx.match("token"):
|
||||
await api_commands.token_root(ctx)
|
||||
elif ctx.match("import"):
|
||||
await import_commands.import_root(ctx)
|
||||
elif ctx.match("help"):
|
||||
await misc_commands.help_root(ctx)
|
||||
else:
|
||||
raise CommandError("Unknown command {}. For a list of commands, type `pk;help commands`.".format(ctx.pop_str()))
|
||||
|
||||
|
||||
async def run_command(ctx: CommandContext, func):
|
||||
# lol nested try
|
||||
try:
|
||||
@@ -150,71 +197,20 @@ async def run_command(ctx: CommandContext, func):
|
||||
await ctx.reply(content=content, embed=embed)
|
||||
|
||||
|
||||
|
||||
async def command_dispatch(client: discord.Client, message: discord.Message, conn) -> bool:
|
||||
prefix = "^(pk(;|!)|<@{}> )".format(client.user.id)
|
||||
commands = [
|
||||
(r"system (new|register|create|init)", system_commands.new_system),
|
||||
(r"system set", system_commands.system_set),
|
||||
(r"system (name|rename)", system_commands.system_name),
|
||||
(r"system description", system_commands.system_description),
|
||||
(r"system avatar", system_commands.system_avatar),
|
||||
(r"system tag", system_commands.system_tag),
|
||||
(r"system link", system_commands.system_link),
|
||||
(r"system unlink", system_commands.system_unlink),
|
||||
(r"system (delete|remove|destroy|erase)", system_commands.system_delete),
|
||||
(r"system", system_commands.system_info),
|
||||
regex = re.compile(prefix, re.IGNORECASE)
|
||||
|
||||
(r"front", system_commands.system_fronter),
|
||||
(r"front history", system_commands.system_fronthistory),
|
||||
(r"front percent(age)?", system_commands.system_frontpercent),
|
||||
|
||||
(r"import tupperware", import_commands.import_tupperware),
|
||||
|
||||
(r"member (new|create|add|register)", member_commands.new_member),
|
||||
(r"member set", member_commands.member_set),
|
||||
(r"member (name|rename)", member_commands.member_name),
|
||||
(r"member description", member_commands.member_description),
|
||||
(r"member avatar", member_commands.member_avatar),
|
||||
(r"member color", member_commands.member_color),
|
||||
(r"member (pronouns|pronoun)", member_commands.member_pronouns),
|
||||
(r"member (birthday|birthdate)", member_commands.member_birthdate),
|
||||
(r"member proxy", member_commands.member_proxy),
|
||||
(r"member (delete|remove|destroy|erase)", member_commands.member_delete),
|
||||
(r"member", member_commands.member_info),
|
||||
|
||||
(r"message", message_commands.message_info),
|
||||
|
||||
(r"log", mod_commands.set_log),
|
||||
|
||||
(r"invite", misc_commands.invite_link),
|
||||
(r"export", misc_commands.export),
|
||||
|
||||
(r"help", misc_commands.show_help),
|
||||
|
||||
(r"switch move", switch_commands.switch_move),
|
||||
(r"switch out", switch_commands.switch_out),
|
||||
(r"switch", switch_commands.switch_member),
|
||||
|
||||
(r"token (refresh|expire|update)", api_commands.refresh_token),
|
||||
(r"token", api_commands.get_token)
|
||||
]
|
||||
|
||||
for pattern, func in commands:
|
||||
regex = re.compile(prefix + pattern, re.IGNORECASE)
|
||||
|
||||
cmd = message.content
|
||||
match = regex.match(cmd)
|
||||
if match:
|
||||
remaining_string = cmd[match.span()[1]:].strip()
|
||||
|
||||
ctx = CommandContext(
|
||||
client=client,
|
||||
message=message,
|
||||
conn=conn,
|
||||
args=remaining_string
|
||||
)
|
||||
|
||||
await run_command(ctx, func)
|
||||
return True
|
||||
cmd = message.content
|
||||
match = regex.match(cmd)
|
||||
if match:
|
||||
remaining_string = cmd[match.span()[1]:].strip()
|
||||
ctx = CommandContext(
|
||||
client=client,
|
||||
message=message,
|
||||
conn=conn,
|
||||
args=remaining_string
|
||||
)
|
||||
await run_command(ctx, command_root)
|
||||
return True
|
||||
return False
|
||||
|
@@ -1,18 +1,16 @@
|
||||
import logging
|
||||
from discord import DMChannel
|
||||
|
||||
from pluralkit.bot.commands import CommandContext
|
||||
|
||||
logger = logging.getLogger("pluralkit.commands")
|
||||
disclaimer = "Please note that this grants access to modify (and delete!) all your system data, so keep it safe and secure. If it leaks or you need a new one, you can invalidate this one with `pk;token refresh`."
|
||||
|
||||
async def reply_dm(ctx: CommandContext, message: str):
|
||||
await ctx.message.author.send(message)
|
||||
|
||||
if not isinstance(ctx.message.channel, DMChannel):
|
||||
await ctx.reply_ok("DM'd!")
|
||||
async def token_root(ctx: CommandContext):
|
||||
if ctx.match("refresh") or ctx.match("expire") or ctx.match("invalidate") or ctx.match("update"):
|
||||
await token_refresh(ctx)
|
||||
else:
|
||||
await token_get(ctx)
|
||||
|
||||
async def get_token(ctx: CommandContext):
|
||||
|
||||
async def token_get(ctx: CommandContext):
|
||||
system = await ctx.ensure_system()
|
||||
|
||||
if system.token:
|
||||
@@ -21,11 +19,13 @@ async def get_token(ctx: CommandContext):
|
||||
token = await system.refresh_token(ctx.conn)
|
||||
|
||||
token_message = "Here's your API token: \n**`{}`**\n{}".format(token, disclaimer)
|
||||
return await reply_dm(ctx, token_message)
|
||||
return await ctx.reply_ok_dm(token_message)
|
||||
|
||||
async def refresh_token(ctx: CommandContext):
|
||||
|
||||
async def token_refresh(ctx: CommandContext):
|
||||
system = await ctx.ensure_system()
|
||||
|
||||
token = await system.refresh_token(ctx.conn)
|
||||
token_message = "Your previous API token has been invalidated. You will need to change it anywhere it's currently used.\nHere's your new API token:\n**`{}`**\n{}".format(token, disclaimer)
|
||||
return await reply_dm(ctx, token_message)
|
||||
token_message = "Your previous API token has been invalidated. You will need to change it anywhere it's currently used.\nHere's your new API token:\n**`{}`**\n{}".format(
|
||||
token, disclaimer)
|
||||
return await ctx.reply_ok_dm(token_message)
|
||||
|
@@ -1,16 +1,17 @@
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
|
||||
import pluralkit.utils
|
||||
from pluralkit.bot.commands import *
|
||||
|
||||
logger = logging.getLogger("pluralkit.commands")
|
||||
|
||||
async def import_root(ctx: CommandContext):
|
||||
# Only one import method rn, so why not default to Tupperware?
|
||||
await import_tupperware(ctx)
|
||||
|
||||
|
||||
async def import_tupperware(ctx: CommandContext):
|
||||
tupperware_member = ctx.message.guild.get_member(431544605209788416)
|
||||
|
||||
# Check if there's a Tupperware bot on the server
|
||||
# Main instance of TW has that ID, at least
|
||||
tupperware_member = ctx.message.guild.get_member(431544605209788416)
|
||||
if not tupperware_member:
|
||||
raise CommandError("This command only works in a server where the Tupperware bot is also present.")
|
||||
|
||||
@@ -20,8 +21,7 @@ async def import_tupperware(ctx: CommandContext):
|
||||
# If it doesn't, throw error
|
||||
raise CommandError("This command only works in a channel where the Tupperware bot has read/send access.")
|
||||
|
||||
await ctx.reply(
|
||||
embed=embeds.status("Please reply to this message with `tul!list` (or the server equivalent)."))
|
||||
await ctx.reply("Please reply to this message with `tul!list` (or the server equivalent).")
|
||||
|
||||
# Check to make sure the message is sent by Tupperware, and that the Tupperware response actually belongs to the correct user
|
||||
def ensure_account(tw_msg):
|
||||
@@ -73,8 +73,9 @@ async def import_tupperware(ctx: CommandContext):
|
||||
# If this isn't the same page as last check, edit the status message
|
||||
if new_page != current_page:
|
||||
last_found_time = datetime.utcnow()
|
||||
await status_msg.edit(content="Multi-page member list found. Please manually scroll through all the pages. Read {}/{} pages.".format(
|
||||
len(pages_found), total_pages))
|
||||
await status_msg.edit(
|
||||
content="Multi-page member list found. Please manually scroll through all the pages. Read {}/{} pages.".format(
|
||||
len(pages_found), total_pages))
|
||||
current_page = new_page
|
||||
|
||||
# And sleep a bit to prevent spamming the CPU
|
||||
@@ -91,15 +92,10 @@ async def import_tupperware(ctx: CommandContext):
|
||||
# Also edit the status message to indicate we're now importing, and it may take a while because there's probably a lot of members
|
||||
await status_msg.edit(content="All pages read. Now importing...")
|
||||
|
||||
logger.debug("Importing from Tupperware...")
|
||||
|
||||
# Create new (nameless) system if there isn't any registered
|
||||
system = await ctx.get_system()
|
||||
if system is None:
|
||||
hid = pluralkit.utils.generate_hid()
|
||||
logger.debug("Creating new system (hid={})...".format(hid))
|
||||
system = await db.create_system(ctx.conn, system_name=None, system_hid=hid)
|
||||
await db.link_account(ctx.conn, system_id=system.id, account_id=ctx.message.author.id)
|
||||
system = await System.create_system(ctx.conn, ctx.message.author.id)
|
||||
|
||||
for embed in tupperware_page_embeds:
|
||||
for field in embed["fields"]:
|
||||
@@ -133,24 +129,16 @@ async def import_tupperware(ctx: CommandContext):
|
||||
member_description = line
|
||||
|
||||
# Read by name - TW doesn't allow name collisions so we're safe here (prevents dupes)
|
||||
existing_member = await db.get_member_by_name(ctx.conn, system_id=system.id, member_name=name)
|
||||
existing_member = await Member.get_member_by_name(ctx.conn, system.id, name)
|
||||
if not existing_member:
|
||||
# Or create a new member
|
||||
hid = pluralkit.utils.generate_hid()
|
||||
logger.debug("Creating new member {} (hid={})...".format(name, hid))
|
||||
existing_member = await db.create_member(ctx.conn, system_id=system.id, member_name=name,
|
||||
member_hid=hid)
|
||||
existing_member = await system.create_member(ctx.conn, name)
|
||||
|
||||
# Save the new stuff in the DB
|
||||
logger.debug("Updating fields...")
|
||||
await db.update_member_field(ctx.conn, member_id=existing_member.id, field="prefix", value=member_prefix)
|
||||
await db.update_member_field(ctx.conn, member_id=existing_member.id, field="suffix", value=member_suffix)
|
||||
await db.update_member_field(ctx.conn, member_id=existing_member.id, field="avatar_url",
|
||||
value=member_avatar)
|
||||
await db.update_member_field(ctx.conn, member_id=existing_member.id, field="birthday",
|
||||
value=member_birthdate)
|
||||
await db.update_member_field(ctx.conn, member_id=existing_member.id, field="description",
|
||||
value=member_description)
|
||||
await existing_member.set_proxy_tags(ctx.conn, member_prefix, member_suffix)
|
||||
await existing_member.set_avatar(ctx.conn, member_avatar)
|
||||
await existing_member.set_birthdate(ctx.conn, member_birthdate)
|
||||
await existing_member.set_description(ctx.conn, member_description)
|
||||
|
||||
await ctx.reply_ok(
|
||||
"System information imported. Try using `pk;system` now.\nYou should probably remove your members from Tupperware to avoid double-posting.")
|
||||
|
@@ -3,25 +3,74 @@ from pluralkit.bot import help
|
||||
from pluralkit.bot.commands import *
|
||||
from pluralkit.errors import PluralKitError
|
||||
|
||||
logger = logging.getLogger("pluralkit.commands")
|
||||
|
||||
async def member_root(ctx: CommandContext):
|
||||
if ctx.match("new") or ctx.match("create") or ctx.match("add") or ctx.match("register"):
|
||||
await new_member(ctx)
|
||||
elif ctx.match("help"):
|
||||
await ctx.reply(help.member_commands)
|
||||
elif ctx.match("set"):
|
||||
await member_set(ctx)
|
||||
# TODO "pk;member list"
|
||||
|
||||
if not ctx.has_next():
|
||||
raise CommandError("Must pass a subcommand. For a list of subcommands, type `pk;member help`.")
|
||||
|
||||
await specific_member_root(ctx)
|
||||
|
||||
|
||||
async def member_info(ctx: CommandContext):
|
||||
member = await ctx.pop_member(
|
||||
error=CommandError("You must pass a member name or ID.", help=help.lookup_member), system_only=False)
|
||||
async def specific_member_root(ctx: CommandContext):
|
||||
member = await ctx.pop_member(system_only=False)
|
||||
|
||||
if ctx.has_next():
|
||||
# Following commands operate on members only in the caller's own system
|
||||
# error if not, to make sure you can't destructively edit someone else's member
|
||||
system = await ctx.ensure_system()
|
||||
if not member.system == system.id:
|
||||
raise CommandError("Member must be in your own system.")
|
||||
|
||||
if ctx.match("name") or ctx.match("rename"):
|
||||
await member_name(ctx, member)
|
||||
elif ctx.match("description"):
|
||||
await member_description(ctx, member)
|
||||
elif ctx.match("avatar") or ctx.match("icon"):
|
||||
await member_avatar(ctx, member)
|
||||
elif ctx.match("proxy") or ctx.match("tags"):
|
||||
await member_proxy(ctx, member)
|
||||
elif ctx.match("pronouns") or ctx.match("pronoun"):
|
||||
await member_pronouns(ctx, member)
|
||||
elif ctx.match("color") or ctx.match("colour"):
|
||||
await member_color(ctx, member)
|
||||
elif ctx.match("birthday") or ctx.match("birthdate"):
|
||||
await member_birthdate(ctx, member)
|
||||
elif ctx.match("delete") or ctx.match("remove") or ctx.match("destroy") or ctx.match("erase"):
|
||||
await member_delete(ctx, member)
|
||||
elif ctx.match("help"):
|
||||
await ctx.reply(help.member_commands)
|
||||
else:
|
||||
raise CommandError(
|
||||
"Unknown subcommand {}. For a list of all commands, type `pk;member help`".format(ctx.pop_str()))
|
||||
else:
|
||||
# Basic lookup
|
||||
await member_info(ctx, member)
|
||||
|
||||
|
||||
async def member_info(ctx: CommandContext, member: Member):
|
||||
await ctx.reply(embed=await pluralkit.bot.embeds.member_card(ctx.conn, member))
|
||||
|
||||
|
||||
async def new_member(ctx: CommandContext):
|
||||
system = await ctx.ensure_system()
|
||||
if not ctx.has_next():
|
||||
raise CommandError("You must pass a name for the new member.", help=help.add_member)
|
||||
raise CommandError("You must pass a name for the new member.")
|
||||
|
||||
new_name = ctx.remaining()
|
||||
|
||||
existing_member = await Member.get_member_by_name(ctx.conn, system.id, new_name)
|
||||
if existing_member:
|
||||
msg = await ctx.reply_warn("There is already a member with this name, with the ID `{}`. Do you want to create a duplicate member anyway?".format(existing_member.hid))
|
||||
msg = await ctx.reply_warn(
|
||||
"There is already a member with this name, with the ID `{}`. Do you want to create a duplicate member anyway?".format(
|
||||
existing_member.hid))
|
||||
if not await ctx.confirm_react(ctx.message.author, msg):
|
||||
raise CommandError("Member creation cancelled.")
|
||||
|
||||
@@ -37,18 +86,19 @@ async def new_member(ctx: CommandContext):
|
||||
|
||||
async def member_set(ctx: CommandContext):
|
||||
raise CommandError(
|
||||
"`pk;member set` has been retired. Please use the new member modifying commands: `pk;member [name|description|avatar|color|pronouns|birthdate]`.")
|
||||
"`pk;member set` has been retired. Please use the new member modifying commands. Type `pk;member help` for a list.")
|
||||
|
||||
|
||||
async def member_name(ctx: CommandContext):
|
||||
async def member_name(ctx: CommandContext, member: Member):
|
||||
system = await ctx.ensure_system()
|
||||
member = await ctx.pop_member(CommandError("You must pass a member name.", help=help.edit_member))
|
||||
new_name = ctx.pop_str(CommandError("You must pass a new member name.", help=help.edit_member))
|
||||
new_name = ctx.pop_str(CommandError("You must pass a new member name."))
|
||||
|
||||
# Warn if there's a member by the same name already
|
||||
existing_member = await Member.get_member_by_name(ctx.conn, system.id, new_name)
|
||||
if existing_member:
|
||||
msg = await ctx.reply_warn("There is already a member with this name, with the ID `{}`. Do you want to rename this member anyway? This will result in two members with the same name.".format(existing_member.hid))
|
||||
if existing_member and existing_member.id != member.id:
|
||||
msg = await ctx.reply_warn(
|
||||
"There is already another member with this name, with the ID `{}`. Do you want to rename this member anyway? This will result in two members with the same name.".format(
|
||||
existing_member.hid))
|
||||
if not await ctx.confirm_react(ctx.message.author, msg):
|
||||
raise CommandError("Member renaming cancelled.")
|
||||
|
||||
@@ -56,27 +106,28 @@ async def member_name(ctx: CommandContext):
|
||||
await ctx.reply_ok("Member name updated.")
|
||||
|
||||
if len(new_name) < 2 and not system.tag:
|
||||
await ctx.reply_warn("This member's new name is under 2 characters, and thus cannot be proxied. To prevent this, use a longer member name, or add a system tag.")
|
||||
await ctx.reply_warn(
|
||||
"This member's new name is under 2 characters, and thus cannot be proxied. To prevent this, use a longer member name, or add a system tag.")
|
||||
elif len(new_name) > 32:
|
||||
exceeds_by = len(new_name) - 32
|
||||
await ctx.reply_warn("This member's new name is longer than 32 characters, and thus cannot be proxied. To prevent this, shorten the member name by {} characters.".format(exceeds_by))
|
||||
await ctx.reply_warn(
|
||||
"This member's new name is longer than 32 characters, and thus cannot be proxied. To prevent this, shorten the member name by {} characters.".format(
|
||||
exceeds_by))
|
||||
elif len(new_name) > system.get_member_name_limit():
|
||||
exceeds_by = len(new_name) - system.get_member_name_limit()
|
||||
await ctx.reply_warn("This member's new name, when combined with the system tag `{}`, is longer than 32 characters, and thus cannot be proxied. To prevent this, shorten the name or system tag by at least {} characters.".format(system.tag, exceeds_by))
|
||||
await ctx.reply_warn(
|
||||
"This member's new name, when combined with the system tag `{}`, is longer than 32 characters, and thus cannot be proxied. To prevent this, shorten the name or system tag by at least {} characters.".format(
|
||||
system.tag, exceeds_by))
|
||||
|
||||
|
||||
async def member_description(ctx: CommandContext):
|
||||
await ctx.ensure_system()
|
||||
member = await ctx.pop_member(CommandError("You must pass a member name.", help=help.edit_member))
|
||||
async def member_description(ctx: CommandContext, member: Member):
|
||||
new_description = ctx.remaining() or None
|
||||
|
||||
await member.set_description(ctx.conn, new_description)
|
||||
await ctx.reply_ok("Member description {}.".format("updated" if new_description else "cleared"))
|
||||
|
||||
|
||||
async def member_avatar(ctx: CommandContext):
|
||||
await ctx.ensure_system()
|
||||
member = await ctx.pop_member(CommandError("You must pass a member name.", help=help.edit_member))
|
||||
async def member_avatar(ctx: CommandContext, member: Member):
|
||||
new_avatar_url = ctx.remaining() or None
|
||||
|
||||
if new_avatar_url:
|
||||
@@ -88,53 +139,42 @@ async def member_avatar(ctx: CommandContext):
|
||||
await ctx.reply_ok("Member avatar {}.".format("updated" if new_avatar_url else "cleared"))
|
||||
|
||||
|
||||
async def member_color(ctx: CommandContext):
|
||||
await ctx.ensure_system()
|
||||
member = await ctx.pop_member(CommandError("You must pass a member name.", help=help.edit_member))
|
||||
async def member_color(ctx: CommandContext, member: Member):
|
||||
new_color = ctx.remaining() or None
|
||||
|
||||
await member.set_color(ctx.conn, new_color)
|
||||
await ctx.reply_ok("Member color {}.".format("updated" if new_color else "cleared"))
|
||||
|
||||
|
||||
async def member_pronouns(ctx: CommandContext):
|
||||
await ctx.ensure_system()
|
||||
member = await ctx.pop_member(CommandError("You must pass a member name.", help=help.edit_member))
|
||||
async def member_pronouns(ctx: CommandContext, member: Member):
|
||||
new_pronouns = ctx.remaining() or None
|
||||
|
||||
await member.set_pronouns(ctx.conn, new_pronouns)
|
||||
await ctx.reply_ok("Member pronouns {}.".format("updated" if new_pronouns else "cleared"))
|
||||
|
||||
|
||||
async def member_birthdate(ctx: CommandContext):
|
||||
await ctx.ensure_system()
|
||||
member = await ctx.pop_member(CommandError("You must pass a member name.", help=help.edit_member))
|
||||
async def member_birthdate(ctx: CommandContext, member: Member):
|
||||
new_birthdate = ctx.remaining() or None
|
||||
|
||||
await member.set_birthdate(ctx.conn, new_birthdate)
|
||||
await ctx.reply_ok("Member birthdate {}.".format("updated" if new_birthdate else "cleared"))
|
||||
|
||||
|
||||
async def member_proxy(ctx: CommandContext):
|
||||
await ctx.ensure_system()
|
||||
member = await ctx.pop_member(CommandError("You must pass a member name.", help=help.member_proxy))
|
||||
|
||||
async def member_proxy(ctx: CommandContext, member: Member):
|
||||
if not ctx.has_next():
|
||||
prefix, suffix = None, None
|
||||
else:
|
||||
# Sanity checking
|
||||
example = ctx.remaining()
|
||||
if "text" not in example:
|
||||
raise CommandError("Example proxy message must contain the string 'text'.", help=help.member_proxy)
|
||||
raise CommandError("Example proxy message must contain the string 'text'. For help, type `pk;help proxy`.")
|
||||
|
||||
if example.count("text") != 1:
|
||||
raise CommandError("Example proxy message must contain the string 'text' exactly once.",
|
||||
help=help.member_proxy)
|
||||
raise CommandError("Example proxy message must contain the string 'text' exactly once. For help, type `pk;help proxy`.")
|
||||
|
||||
# Extract prefix and suffix
|
||||
prefix = example[:example.index("text")].strip()
|
||||
suffix = example[example.index("text") + 4:].strip()
|
||||
logger.debug("Matched prefix '{}' and suffix '{}'".format(prefix, suffix))
|
||||
|
||||
# DB stores empty strings as None, make that work
|
||||
if not prefix:
|
||||
@@ -144,13 +184,11 @@ async def member_proxy(ctx: CommandContext):
|
||||
|
||||
async with ctx.conn.transaction():
|
||||
await member.set_proxy_tags(ctx.conn, prefix, suffix)
|
||||
await ctx.reply_ok("Proxy settings updated." if prefix or suffix else "Proxy settings cleared.")
|
||||
await ctx.reply_ok(
|
||||
"Proxy settings updated." if prefix or suffix else "Proxy settings cleared. If you meant to set your proxy tags, type `pk;help proxy` for help.")
|
||||
|
||||
|
||||
async def member_delete(ctx: CommandContext):
|
||||
await ctx.ensure_system()
|
||||
member = await ctx.pop_member(CommandError("You must pass a member name.", help=help.remove_member))
|
||||
|
||||
async def member_delete(ctx: CommandContext, member: Member):
|
||||
delete_confirm_msg = "Are you sure you want to delete {}? If so, reply to this message with the member's ID (`{}`).".format(
|
||||
member.name, member.hid)
|
||||
if not await ctx.confirm_text(ctx.message.author, ctx.message.channel, member.hid, delete_confirm_msg):
|
||||
|
@@ -1,62 +1,18 @@
|
||||
from pluralkit.bot import help
|
||||
from pluralkit.bot.commands import *
|
||||
|
||||
logger = logging.getLogger("pluralkit.commands")
|
||||
|
||||
|
||||
async def get_message_contents(client: discord.Client, channel_id: int, message_id: int):
|
||||
channel = client.get_channel(channel_id)
|
||||
if channel:
|
||||
try:
|
||||
original_message = await channel.get_message(message_id)
|
||||
return original_message.content or None
|
||||
except (discord.errors.Forbidden, discord.errors.NotFound):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
async def message_info(ctx: CommandContext):
|
||||
mid_str = ctx.pop_str(CommandError("You must pass a message ID.", help=help.message_lookup))
|
||||
mid_str = ctx.pop_str(CommandError("You must pass a message ID."))
|
||||
|
||||
try:
|
||||
mid = int(mid_str)
|
||||
except ValueError:
|
||||
raise CommandError("You must pass a valid number as a message ID.", help=help.message_lookup)
|
||||
raise CommandError("You must pass a valid number as a message ID.")
|
||||
|
||||
# Find the message in the DB
|
||||
message = await db.get_message(ctx.conn, mid)
|
||||
if not message:
|
||||
raise CommandError("Message with ID '{}' not found.".format(mid))
|
||||
raise CommandError(
|
||||
"Message with ID '{}' not found. Are you sure it's a message proxied by PluralKit?".format(mid))
|
||||
|
||||
# Get the original sender of the messages
|
||||
try:
|
||||
original_sender = await ctx.client.get_user_info(message.sender)
|
||||
except discord.NotFound:
|
||||
# Account was since deleted - rare but we're handling it anyway
|
||||
original_sender = None
|
||||
|
||||
embed = discord.Embed()
|
||||
embed.timestamp = discord.utils.snowflake_time(mid)
|
||||
embed.colour = discord.Colour.blue()
|
||||
|
||||
if message.system_name:
|
||||
system_value = "{} (`{}`)".format(message.system_name, message.system_hid)
|
||||
else:
|
||||
system_value = "`{}`".format(message.system_hid)
|
||||
embed.add_field(name="System", value=system_value)
|
||||
|
||||
embed.add_field(name="Member", value="{} (`{}`)".format(message.name, message.hid))
|
||||
|
||||
if original_sender:
|
||||
sender_name = "{}#{}".format(original_sender.name, original_sender.discriminator)
|
||||
else:
|
||||
sender_name = "(deleted account {})".format(message.sender)
|
||||
|
||||
embed.add_field(name="Sent by", value=sender_name)
|
||||
|
||||
message_content = await get_message_contents(ctx.client, message.channel, message.mid)
|
||||
embed.description = message_content or "(unknown, message deleted)"
|
||||
|
||||
embed.set_author(name=message.name, icon_url=message.avatar_url or discord.Embed.Empty)
|
||||
|
||||
await ctx.reply_ok(embed=embed)
|
||||
await ctx.reply_ok(embed=await embeds.message_card(ctx.client, message))
|
||||
|
@@ -1,61 +1,57 @@
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
from discord.utils import oauth_url
|
||||
|
||||
import pluralkit.utils
|
||||
from pluralkit.bot import utils, embeds
|
||||
from pluralkit.bot import help
|
||||
from pluralkit.bot.commands import *
|
||||
|
||||
logger = logging.getLogger("pluralkit.commands")
|
||||
|
||||
|
||||
async def show_help(ctx: CommandContext):
|
||||
embed = embeds.status("")
|
||||
embed.title = "PluralKit Help"
|
||||
embed.set_footer(text="By Astrid (Ske#6201; pk;member qoxvy) | GitHub: https://github.com/xSke/PluralKit/")
|
||||
|
||||
category = ctx.pop_str() if ctx.has_next() else None
|
||||
|
||||
from pluralkit.bot.help import help_pages
|
||||
if category in help_pages:
|
||||
for name, text in help_pages[category]:
|
||||
if name:
|
||||
embed.add_field(name=name, value=text)
|
||||
else:
|
||||
embed.description = text
|
||||
async def help_root(ctx: CommandContext):
|
||||
if ctx.match("commands"):
|
||||
await ctx.reply(help.all_commands)
|
||||
elif ctx.match("proxy"):
|
||||
await ctx.reply(help.proxy_guide)
|
||||
else:
|
||||
raise CommandError("Unknown help page '{}'.".format(category))
|
||||
|
||||
await ctx.reply(embed=embed)
|
||||
await ctx.reply(help.root)
|
||||
|
||||
|
||||
async def invite_link(ctx: CommandContext):
|
||||
client_id = os.environ["CLIENT_ID"]
|
||||
|
||||
permissions = discord.Permissions()
|
||||
|
||||
# So the bot can actually add the webhooks it needs to do the proxy functionality
|
||||
permissions.manage_webhooks = True
|
||||
|
||||
# So the bot can respond with status, error, and success messages
|
||||
permissions.send_messages = True
|
||||
|
||||
# So the bot can delete channels
|
||||
permissions.manage_messages = True
|
||||
|
||||
# So the bot can respond with extended embeds, ex. member cards
|
||||
permissions.embed_links = True
|
||||
|
||||
# So the bot can send images too
|
||||
permissions.attach_files = True
|
||||
|
||||
# (unsure if it needs this, actually, might be necessary for message lookup)
|
||||
permissions.read_message_history = True
|
||||
|
||||
# So the bot can add reactions for confirm/deny prompts
|
||||
permissions.add_reactions = True
|
||||
|
||||
url = oauth_url(client_id, permissions)
|
||||
logger.debug("Sending invite URL: {}".format(url))
|
||||
await ctx.reply_ok("Use this link to add PluralKit to your server: {}".format(url))
|
||||
|
||||
|
||||
async def export(ctx: CommandContext):
|
||||
system = await ctx.ensure_system()
|
||||
|
||||
members = await db.get_all_members(ctx.conn, system.id)
|
||||
accounts = await db.get_linked_accounts(ctx.conn, system.id)
|
||||
switches = await pluralkit.utils.get_front_history(ctx.conn, system.id, 999999)
|
||||
members = await system.get_members(ctx.conn)
|
||||
accounts = await system.get_linked_account_ids(ctx.conn)
|
||||
switches = await system.get_switches(ctx.conn, 999999)
|
||||
|
||||
data = {
|
||||
"name": system.name,
|
||||
@@ -81,10 +77,10 @@ async def export(ctx: CommandContext):
|
||||
"accounts": [str(uid) for uid in accounts],
|
||||
"switches": [
|
||||
{
|
||||
"timestamp": timestamp.isoformat(),
|
||||
"members": [member.hid for member in members]
|
||||
} for timestamp, members in switches
|
||||
]
|
||||
"timestamp": switch.timestamp.isoformat(),
|
||||
"members": [member.hid for member in await switch.fetch_members(ctx.conn)]
|
||||
} for switch in switches
|
||||
] # TODO: messages
|
||||
}
|
||||
|
||||
f = io.BytesIO(json.dumps(data).encode("utf-8"))
|
||||
|
@@ -1,7 +1,5 @@
|
||||
from pluralkit.bot.commands import *
|
||||
|
||||
logger = logging.getLogger("pluralkit.commands")
|
||||
|
||||
|
||||
async def set_log(ctx: CommandContext):
|
||||
if not ctx.message.author.guild_permissions.administrator:
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import dateparser
|
||||
import humanize
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
@@ -7,24 +6,34 @@ import pluralkit.utils
|
||||
from pluralkit.bot import help
|
||||
from pluralkit.bot.commands import *
|
||||
from pluralkit.member import Member
|
||||
from pluralkit.utils import display_relative
|
||||
|
||||
logger = logging.getLogger("pluralkit.commands")
|
||||
|
||||
async def switch_root(ctx: CommandContext):
|
||||
if not ctx.has_next():
|
||||
raise CommandError("You must use a subcommand. For a list of subcommands, type `pk;switch help`.")
|
||||
|
||||
if ctx.match("out"):
|
||||
await switch_out(ctx)
|
||||
elif ctx.match("move"):
|
||||
await switch_move(ctx)
|
||||
elif ctx.match("delete") or ctx.match("remove") or ctx.match("erase") or ctx.match("cancel"):
|
||||
await switch_delete(ctx)
|
||||
elif ctx.match("help"):
|
||||
await ctx.reply(help.member_commands)
|
||||
else:
|
||||
await switch_member(ctx)
|
||||
|
||||
|
||||
async def switch_member(ctx: CommandContext):
|
||||
system = await ctx.ensure_system()
|
||||
|
||||
if not ctx.has_next():
|
||||
raise CommandError("You must pass at least one member name or ID to register a switch to.",
|
||||
help=help.switch_register)
|
||||
raise CommandError("You must pass at least one member name or ID to register a switch to.")
|
||||
|
||||
members: List[Member] = []
|
||||
for member_name in ctx.remaining().split(" "):
|
||||
# Find the member
|
||||
member = await utils.get_member_fuzzy(ctx.conn, system.id, member_name)
|
||||
if not member:
|
||||
raise CommandError("Couldn't find member \"{}\".".format(member_name))
|
||||
members.append(member)
|
||||
while ctx.has_next():
|
||||
members.append(await ctx.pop_member())
|
||||
|
||||
# Compare requested switch IDs and existing fronter IDs to check for existing switches
|
||||
# Lists, because order matters, it makes sense to just swap fronters
|
||||
@@ -40,10 +49,7 @@ async def switch_member(ctx: CommandContext):
|
||||
raise CommandError("Duplicate members in member list.")
|
||||
|
||||
# Log the switch
|
||||
async with ctx.conn.transaction():
|
||||
switch_id = await db.add_switch(ctx.conn, system_id=system.id)
|
||||
for member in members:
|
||||
await db.add_switch_member(ctx.conn, switch_id=switch_id, member_id=member.id)
|
||||
await system.add_switch(ctx.conn, members)
|
||||
|
||||
if len(members) == 1:
|
||||
await ctx.reply_ok("Switch registered. Current fronter is now {}.".format(members[0].name))
|
||||
@@ -55,20 +61,52 @@ async def switch_member(ctx: CommandContext):
|
||||
async def switch_out(ctx: CommandContext):
|
||||
system = await ctx.ensure_system()
|
||||
|
||||
# Get current fronters
|
||||
fronters, _ = await pluralkit.utils.get_fronter_ids(ctx.conn, system_id=system.id)
|
||||
if not fronters:
|
||||
switch = await system.get_latest_switch(ctx.conn)
|
||||
if switch and not switch.members:
|
||||
raise CommandError("There's already no one in front.")
|
||||
|
||||
# Log it, and don't log any members
|
||||
await db.add_switch(ctx.conn, system_id=system.id)
|
||||
await system.add_switch(ctx.conn, [])
|
||||
await ctx.reply_ok("Switch-out registered.")
|
||||
|
||||
|
||||
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 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.", help=help.switch_move)
|
||||
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={
|
||||
@@ -76,11 +114,11 @@ async def switch_move(ctx: CommandContext):
|
||||
"RETURN_AS_TIMEZONE_AWARE": False
|
||||
})
|
||||
if not new_time:
|
||||
raise CommandError("'{}' can't be parsed as a valid time.".format(ctx.remaining()), help=help.switch_move)
|
||||
raise CommandError("'{}' can't be parsed as a valid time.".format(ctx.remaining()))
|
||||
|
||||
# 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.", help=help.switch_move)
|
||||
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():
|
||||
@@ -94,19 +132,25 @@ async def switch_move(ctx: CommandContext):
|
||||
second_last_timestamp, _ = last_two_switches[1]
|
||||
|
||||
if new_time < second_last_timestamp:
|
||||
time_str = humanize.naturaltime(pluralkit.utils.fix_time(second_last_timestamp))
|
||||
time_str = display_relative(second_last_timestamp)
|
||||
raise CommandError(
|
||||
"Can't move switch to before last switch time ({}), as it would cause conflicts.".format(time_str))
|
||||
"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
|
||||
members = ", ".join([member.name for member in last_fronters]) or "nobody"
|
||||
last_absolute = last_timestamp.isoformat(sep=" ", timespec="seconds")
|
||||
last_relative = humanize.naturaltime(pluralkit.utils.fix_time(last_timestamp))
|
||||
last_relative = display_relative(last_timestamp)
|
||||
new_absolute = new_time.isoformat(sep=" ", timespec="seconds")
|
||||
new_relative = humanize.naturaltime(pluralkit.utils.fix_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 {} ({}) to {} ({}). Is this OK?".format(members, last_absolute, last_relative, new_absolute, new_relative))
|
||||
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.")
|
||||
|
||||
@@ -116,6 +160,3 @@ async def switch_move(ctx: CommandContext):
|
||||
# Change the switch in the DB
|
||||
await db.move_last_switch(ctx.conn, system.id, switch_id, new_time)
|
||||
await ctx.reply_ok("Switch moved.")
|
||||
|
||||
|
||||
|
||||
|
@@ -3,28 +3,73 @@ import humanize
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pluralkit.bot.embeds
|
||||
import pluralkit.utils
|
||||
from pluralkit.bot import help
|
||||
from pluralkit.bot.commands import *
|
||||
from pluralkit.errors import ExistingSystemError, UnlinkingLastAccountError, PluralKitError, AccountAlreadyLinkedError
|
||||
|
||||
logger = logging.getLogger("pluralkit.commands")
|
||||
from pluralkit.errors import ExistingSystemError, UnlinkingLastAccountError, AccountAlreadyLinkedError
|
||||
from pluralkit.utils import display_relative
|
||||
|
||||
|
||||
async def system_info(ctx: CommandContext):
|
||||
if ctx.has_next():
|
||||
system = await ctx.pop_system()
|
||||
async def system_root(ctx: CommandContext):
|
||||
# Commands that operate without a specified system (usually defaults to the executor's own system)
|
||||
if ctx.match("name") or ctx.match("rename"):
|
||||
await system_name(ctx)
|
||||
elif ctx.match("description"):
|
||||
await system_description(ctx)
|
||||
elif ctx.match("avatar") or ctx.match("icon"):
|
||||
await system_avatar(ctx)
|
||||
elif ctx.match("tag"):
|
||||
await system_tag(ctx)
|
||||
elif ctx.match("new") or ctx.match("register") or ctx.match("create") or ctx.match("init"):
|
||||
await system_new(ctx)
|
||||
elif ctx.match("delete") or ctx.match("delete") or ctx.match("erase"):
|
||||
await system_delete(ctx)
|
||||
elif ctx.match("front") or ctx.match("fronter") or ctx.match("fronters"):
|
||||
await system_fronter(ctx, await ctx.ensure_system())
|
||||
elif ctx.match("fronthistory"):
|
||||
await system_fronthistory(ctx, await ctx.ensure_system())
|
||||
elif ctx.match("frontpercent") or ctx.match("frontbreakdown") or ctx.match("frontpercentage"):
|
||||
await system_frontpercent(ctx, await ctx.ensure_system())
|
||||
elif ctx.match("help"):
|
||||
await ctx.reply(help.system_commands)
|
||||
elif ctx.match("set"):
|
||||
await system_set(ctx)
|
||||
elif not ctx.has_next():
|
||||
# (no argument, command ends here, default to showing own system)
|
||||
await system_info(ctx, await ctx.ensure_system())
|
||||
else:
|
||||
system = await ctx.ensure_system()
|
||||
# If nothing matches, the next argument is likely a system name/ID, so delegate
|
||||
# to the specific system root
|
||||
await specified_system_root(ctx)
|
||||
|
||||
|
||||
async def specified_system_root(ctx: CommandContext):
|
||||
# Commands that operate on a specified system (ie. not necessarily the command executor's)
|
||||
system_name = ctx.pop_str()
|
||||
system = await utils.get_system_fuzzy(ctx.conn, ctx.client, system_name)
|
||||
if not system:
|
||||
raise CommandError(
|
||||
"Unable to find system `{}`. If you meant to run a command, type `pk;system help` for a list of system commands.".format(
|
||||
system_name))
|
||||
|
||||
if ctx.match("front") or ctx.match("fronter"):
|
||||
await system_fronter(ctx, system)
|
||||
elif ctx.match("fronthistory"):
|
||||
await system_fronthistory(ctx, system)
|
||||
elif ctx.match("frontpercent") or ctx.match("frontbreakdown") or ctx.match("frontpercentage"):
|
||||
await system_frontpercent(ctx, system)
|
||||
else:
|
||||
await system_info(ctx, system)
|
||||
|
||||
|
||||
async def system_info(ctx: CommandContext, system: System):
|
||||
await ctx.reply(embed=await pluralkit.bot.embeds.system_card(ctx.conn, ctx.client, system))
|
||||
|
||||
|
||||
async def new_system(ctx: CommandContext):
|
||||
system_name = ctx.remaining() or None
|
||||
async def system_new(ctx: CommandContext):
|
||||
new_name = ctx.remaining() or None
|
||||
|
||||
try:
|
||||
await System.create_system(ctx.conn, ctx.message.author.id, system_name)
|
||||
await System.create_system(ctx.conn, ctx.message.author.id, new_name)
|
||||
except ExistingSystemError as e:
|
||||
raise CommandError(e.message)
|
||||
|
||||
@@ -95,9 +140,10 @@ async def system_avatar(ctx: CommandContext):
|
||||
await ctx.reply_ok("System avatar {}.".format("updated" if new_avatar_url else "cleared"))
|
||||
|
||||
|
||||
async def system_link(ctx: CommandContext):
|
||||
async def account_link(ctx: CommandContext):
|
||||
system = await ctx.ensure_system()
|
||||
account_name = ctx.pop_str(CommandError("You must pass an account to link this system to.", help=help.link_account))
|
||||
account_name = ctx.pop_str(CommandError(
|
||||
"You must pass an account to link this system to. You can either use a \\@mention, or a raw account ID."))
|
||||
|
||||
# Do the sanity checking here too (despite it being done in System.link_account)
|
||||
# Because we want it to be done before the confirmation dialog is shown
|
||||
@@ -105,14 +151,15 @@ async def system_link(ctx: CommandContext):
|
||||
# Find account to link
|
||||
linkee = await utils.parse_mention(ctx.client, account_name)
|
||||
if not linkee:
|
||||
raise CommandError("Account not found.")
|
||||
raise CommandError("Account `{}` not found.".format(account_name))
|
||||
|
||||
# Make sure account doesn't already have a system
|
||||
account_system = await System.get_by_account(ctx.conn, linkee.id)
|
||||
if account_system:
|
||||
raise CommandError(AccountAlreadyLinkedError(account_system).message)
|
||||
|
||||
msg = await ctx.reply("{}, please confirm the link by clicking the \u2705 reaction on this message.".format(linkee.mention))
|
||||
msg = await ctx.reply(
|
||||
"{}, please confirm the link by clicking the \u2705 reaction on this message.".format(linkee.mention))
|
||||
if not await ctx.confirm_react(linkee, msg):
|
||||
raise CommandError("Account link cancelled.")
|
||||
|
||||
@@ -120,7 +167,7 @@ async def system_link(ctx: CommandContext):
|
||||
await ctx.reply_ok("Account linked to system.")
|
||||
|
||||
|
||||
async def system_unlink(ctx: CommandContext):
|
||||
async def account_unlink(ctx: CommandContext):
|
||||
system = await ctx.ensure_system()
|
||||
|
||||
try:
|
||||
@@ -131,24 +178,18 @@ async def system_unlink(ctx: CommandContext):
|
||||
await ctx.reply_ok("Account unlinked.")
|
||||
|
||||
|
||||
async def system_fronter(ctx: CommandContext):
|
||||
if ctx.has_next():
|
||||
system = await ctx.pop_system()
|
||||
else:
|
||||
system = await ctx.ensure_system()
|
||||
|
||||
async def system_fronter(ctx: CommandContext, system: System):
|
||||
embed = await embeds.front_status(await system.get_latest_switch(ctx.conn), ctx.conn)
|
||||
await ctx.reply(embed=embed)
|
||||
|
||||
|
||||
async def system_fronthistory(ctx: CommandContext):
|
||||
if ctx.has_next():
|
||||
system = await ctx.pop_system()
|
||||
else:
|
||||
system = await ctx.ensure_system()
|
||||
|
||||
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:
|
||||
@@ -158,13 +199,13 @@ async def system_fronthistory(ctx: CommandContext):
|
||||
|
||||
# Make proper date string
|
||||
time_text = timestamp.isoformat(sep=" ", timespec="seconds")
|
||||
rel_text = humanize.naturaltime(pluralkit.utils.fix_time(timestamp))
|
||||
rel_text = display_relative(timestamp)
|
||||
|
||||
delta_text = ""
|
||||
if i > 0:
|
||||
last_switch_time = front_history[i - 1][0]
|
||||
delta_text = ", for {}".format(humanize.naturaldelta(timestamp - last_switch_time))
|
||||
lines.append("**{}** ({}, {}{})".format(name, time_text, rel_text, delta_text))
|
||||
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"
|
||||
@@ -183,9 +224,7 @@ async def system_delete(ctx: CommandContext):
|
||||
await ctx.reply_ok("System deleted.")
|
||||
|
||||
|
||||
async def system_frontpercent(ctx: CommandContext):
|
||||
system = await ctx.ensure_system()
|
||||
|
||||
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={
|
||||
@@ -265,6 +304,6 @@ async def system_frontpercent(ctx: CommandContext):
|
||||
embed.add_field(name=member.name if member else "(no fronter)",
|
||||
value="{}% ({})".format(percent, humanize.naturaldelta(front_time)))
|
||||
|
||||
embed.set_footer(text="Since {} ({})".format(span_start.isoformat(sep=" ", timespec="seconds"),
|
||||
humanize.naturaltime(pluralkit.utils.fix_time(span_start))))
|
||||
embed.set_footer(text="Since {} ({} ago)".format(span_start.isoformat(sep=" ", timespec="seconds"),
|
||||
display_relative(span_start)))
|
||||
await ctx.reply(embed=embed)
|
||||
|
Reference in New Issue
Block a user