diff --git a/.vscode/launch.json b/.vscode/launch.json index 0e8a6a3f..841353a4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,8 +9,8 @@ "type": "python", "request": "launch", "program": "${workspaceRoot}/src/bot_main.py", - "envFile": "${workspaceFolder}/.env", - "console": "integratedTerminal" + "args": ["${workspaceRoot}/pluralkit.conf"], + "console": "integratedTerminal", } ] } \ No newline at end of file diff --git a/src/pluralkit/bot/commands/__init__.py b/src/pluralkit/bot/commands/__init__.py index b00a2312..724be2ca 100644 --- a/src/pluralkit/bot/commands/__init__.py +++ b/src/pluralkit/bot/commands/__init__.py @@ -42,6 +42,9 @@ class CommandError(Exception): class CommandContext: + client: discord.Client + message: discord.Message + def __init__(self, client: discord.Client, message: discord.Message, conn, args: str, system: Optional[System]): self.client = client self.message = message diff --git a/src/pluralkit/bot/commands/system_commands.py b/src/pluralkit/bot/commands/system_commands.py index 589b24f8..2e86ac59 100644 --- a/src/pluralkit/bot/commands/system_commands.py +++ b/src/pluralkit/bot/commands/system_commands.py @@ -39,6 +39,8 @@ async def system_root(ctx: CommandContext): await system_timezone(ctx) elif ctx.match("set"): await system_set(ctx) + elif ctx.match("list") or ctx.match("members"): + await system_list(ctx, await ctx.ensure_system()) elif not ctx.has_next(): # (no argument, command ends here, default to showing own system) await system_info(ctx, await ctx.ensure_system()) @@ -63,12 +65,15 @@ async def specified_system_root(ctx: CommandContext): await system_fronthistory(ctx, system) elif ctx.match("frontpercent") or ctx.match("frontbreakdown") or ctx.match("frontpercentage"): await system_frontpercent(ctx, system) + elif ctx.match("list") or ctx.match("members"): + await system_list(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)) + this_system = await ctx.get_system() + await ctx.reply(embed=await pluralkit.bot.embeds.system_card(ctx.conn, ctx.client, system, this_system and this_system.id == system.id)) async def system_new(ctx: CommandContext): @@ -124,6 +129,10 @@ async def system_timezone(ctx: CommandContext): # Take the lat/long given by Overpass and put it into timezonefinder lat, lng = (float(data[0]["lat"]), float(data[0]["lon"])) timezone_name = tzf.timezone_at(lng=lng, lat=lat) + + # Also delete the original searching message + await msg.delete() + if not timezone_name: raise CommandError("Time zone for city '{}' not found. This should never happen.".format(data[0]["display_name"])) @@ -132,6 +141,7 @@ async def system_timezone(ctx: CommandContext): tz = await system.set_time_zone(ctx.conn, timezone_name) offset = tz.utcoffset(datetime.utcnow()) offset_str = "UTC{:+02d}:{:02d}".format(int(offset.total_seconds() // 3600), int(offset.total_seconds() // 60 % 60)) + await ctx.reply_ok("System time zone set to {} ({}, {}).\n*Data from OpenStreetMap, queried using Nominatim.*".format(tz.tzname(datetime.utcnow()), offset_str, tz.zone)) @@ -349,3 +359,41 @@ async def system_frontpercent(ctx: CommandContext, system: System): embed.set_footer(text="Since {} ({} ago)".format(ctx.format_time(span_start), display_relative(span_start))) await ctx.reply(embed=embed) + +async def system_list(ctx: CommandContext, system: System): + all_members = sorted(await system.get_members(ctx.conn), key=lambda m: m.name) + page_size = 10 + if len(all_members) <= page_size: + # If we have less than 10 members, don't bother paginating + await ctx.reply(embed=embeds.member_list(await ctx.get_system(), all_members, 0, page_size = page_size)) + else: + current_page = 0 + msg: discord.Message = None + while True: + page_count = len(all_members) // page_size + embed = embeds.member_list(await ctx.get_system(), all_members, current_page) + + # Add reactions for moving back and forth + if not msg: + msg = await ctx.reply(embed=embed) + await msg.add_reaction("\u2B05") + await msg.add_reaction("\u27A1") + else: + await msg.edit(embed=embed) + + def check(reaction, user): + return user.id == ctx.message.author.id and reaction.emoji in ["\u2B05", "\u27A1"] + + try: + reaction, _ = await ctx.client.wait_for("reaction_add", timeout=5*60, check=check) + except asyncio.TimeoutError: + return + + if reaction.emoji == "\u2B05": + current_page = (current_page - 1) % page_count + elif reaction.emoji == "\u27A1": + current_page = (current_page + 1) % page_count + + # If we can, remove the original reaction from the member + if ctx.message.channel.permissions_for(ctx.message.guild.get_member(ctx.client.user.id)).manage_messages: + await reaction.remove(ctx.message.author) \ No newline at end of file diff --git a/src/pluralkit/bot/embeds.py b/src/pluralkit/bot/embeds.py index 7e77917a..6eb8f290 100644 --- a/src/pluralkit/bot/embeds.py +++ b/src/pluralkit/bot/embeds.py @@ -1,6 +1,6 @@ import discord import humanize -from typing import Tuple +from typing import Tuple, List from pluralkit import db from pluralkit.bot.utils import escape @@ -66,7 +66,7 @@ def exception_log(message_content, author_name, author_discriminator, author_id, return embed -async def system_card(conn, client: discord.Client, system: System) -> discord.Embed: +async def system_card(conn, client: discord.Client, system: System, is_own_system: bool = True) -> discord.Embed: card = discord.Embed() card.colour = discord.Colour.blue() @@ -97,33 +97,7 @@ async def system_card(conn, client: discord.Client, system: System) -> discord.E card.add_field(name="Description", value=truncate_field_body(system.description), inline=False) - # Get names of all members - all_members = await system.get_members(conn) - if all_members: - member_texts = [] - for member in all_members: - member_texts.append("{} (`{}`)".format(escape(member.name), member.hid)) - - # Interim solution for pagination of large systems - # Previously a lot of systems would hit the 1024 character limit and thus break the message - # This splits large system lists into multiple embed fields - # The 6000 character total limit will still apply here but this sort of pushes the problem until I find a better fix - pages = [""] - for member in member_texts: - last_page = pages[-1] - new_page = last_page + "\n" + member if last_page else member - - if len(new_page) >= 1024: - pages.append(member) - else: - pages[-1] = new_page - - for index, page in enumerate(pages): - field_name = "Members" - if index >= 1: - field_name = "Members (part {})".format(index + 1) - card.add_field(name=truncate_field_name(field_name), value=truncate_field_body(page), inline=False) - + card.add_field(name="Members", value="*See `pk;system {} list`".format(system.hid) if not is_own_system else "*See `pk;system list`*") card.set_footer(text="System ID: {}".format(system.hid)) return card @@ -243,3 +217,21 @@ def help_footer_embed() -> discord.Embed: embed = discord.Embed() embed.set_footer(text="By @Ske#6201 | GitHub: https://github.com/xSke/PluralKit/") return embed + +def member_list(system: System, all_members: List[Member], current_page: int = 0, page_size: int = 10): + page_count = len(all_members) // page_size + + title = "" + if len(all_members) > page_size: + title += "[{}/{}] ".format(current_page + 1, page_count) + + if system.name: + title += "Members of {} (`{}`)".format(system.name, system.hid) + else: + title += "Members of `{}`".format(system.hid) + + embed = discord.Embed() + embed.title = title + for member in all_members[current_page*page_size:current_page*page_size+page_size]: + embed.add_field(name=member.name, value=(member.description or "") + "\n*ID: `{}`*".format(member.hid), inline=False) + return embed \ No newline at end of file diff --git a/src/pluralkit/bot/help.py b/src/pluralkit/bot/help.py index ce29c975..ecb6fb61 100644 --- a/src/pluralkit/bot/help.py +++ b/src/pluralkit/bot/help.py @@ -13,6 +13,7 @@ pk;system delete pk;system [system] fronter pk;system [system] fronthistory pk;system [system] frontpercent +pk;system [system] list pk;link pk;unlink ``` diff --git a/src/requirements.txt b/src/requirements.txt index 4ed100bf..8a69d860 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -2,7 +2,7 @@ aiodns aiohttp==3.3.0 asyncpg dateparser -https://github.com/Rapptz/discord.py/archive/860d6a9ace8248dfeec18b8b159e7b757d9f56bb.zip#egg=discord.py +https://github.com/Rapptz/discord.py/archive/aceec2009a7c819d2236884fa9ccc5ce58a92bea.zip#egg=discord.py humanize uvloop; sys.platform != 'win32' and sys.platform != 'cygwin' and sys.platform != 'cli' ciso8601