From 64444c34cc87f6dbb094b0dee7fd20a7567f76ab Mon Sep 17 00:00:00 2001 From: a-Digi-a Date: Sun, 14 Jun 2026 19:48:57 +0100 Subject: [PATCH 01/11] initial commit --- .env.sample | 31 ---------- pyproject.toml | 5 +- src/config.py | 12 ++++ src/extensions/ticket.py | 126 +++++++++++++++++++++++++++++++++++++++ uv.lock | 10 +--- 5 files changed, 145 insertions(+), 39 deletions(-) delete mode 100644 .env.sample create mode 100644 src/extensions/ticket.py diff --git a/.env.sample b/.env.sample deleted file mode 100644 index 3c28718..0000000 --- a/.env.sample +++ /dev/null @@ -1,31 +0,0 @@ -TOKEN= -DEBUG= # 'true' or 'false', default 'false' - -DISCORD_UID_MAP="chair=1234,secretary=4567,user1=1234,user2=4567,user3=7890" - -LDAP_USERNAME= -LDAP_PASSWORD= - -AGENDA_TEMPLATE_URL="https://md.redbrick.dcu.ie/foo" - -DB_HOST=postgres:5432 -DB_NAME=blockbot -DB_USER=blockbot -DB_PASSWORD= - -RCON_HOST= -RCON_PORT= -RCON_PASSWORD= - -MINIO_ENDPOINT=minio:9000 -MINIO_SECURE=false -MINIO_REGION=us-east-1 -MINIO_ACCESS_KEY= -MINIO_SECRET_KEY= - -# all of these can be set to 'false' and default to 'true' -DB_ENABLED= -LDAP_ENABLED= -PERMS_ENABLED= -RCON_ENABLED= -MINIO_ENABLED= diff --git a/pyproject.toml b/pyproject.toml index 0c28a2e..6f72ff1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "asyncpg>=0.31.0", "dnspython>=2.8.0", "fortune-python>=1.1.2", - "hikari>=2.5.0", + "hikari", "hikari-arc>=2.2.0", "hikari-miru>=4.2.0", "pyfiglet>=1.0.4", @@ -88,3 +88,6 @@ required-version = "~=0.11" [tool.pyright] pythonVersion = "3.14" typeCheckingMode = "strict" + +[tool.uv.sources] +hikari = { git = "https://github.com/mplatypus/hikari.git", branch = "feature/modals-v2" } diff --git a/src/config.py b/src/config.py index efb6c10..9944149 100644 --- a/src/config.py +++ b/src/config.py @@ -134,6 +134,18 @@ def set_env_var( "waiting-room": 627548568613552138, } +CATEGORY_IDS: dict[str, int] = { + "official": 568810044309766151, + "administration": 568810044309766151, + "technical": 1202325296133701723, + "general": 627868752310042627, + "general-bots": 1202319516407697438, + "webgroup": 1202321207811395624, + "voice-channels": 568403964098248723, + "archive": 958448000496173056, + "test-category": 1515768461043961967, +} + # TODO: query API/LDAP for these ROLE_IDS: dict[str, int] = { "all": 568762266992902179, diff --git a/src/extensions/ticket.py b/src/extensions/ticket.py new file mode 100644 index 0000000..565fc6c --- /dev/null +++ b/src/extensions/ticket.py @@ -0,0 +1,126 @@ +import arc +import hikari +from hikari.impl import special_endpoints as se +from hikari.interactions.interaction_components import ( + TextInputInteractionComponent, + TextSelectMenuInteractionComponent, +) + +from src.config import CATEGORY_IDS, ROLE_IDS +from src.models import Blockbot, BlockbotContext, BlockbotPlugin + +plugin = BlockbotPlugin("Ticketing System") + + +@plugin.include +@arc.slash_command("create_ticket_message", "Create Ticket Message") +async def options( + ctx: BlockbotContext, + channel_option: arc.Option[ + hikari.TextableChannel, + arc.ChannelParams("A textable channel option.", name="channel"), + ], +) -> None: + embed = hikari.Embed(title="Tickets", description="Create a ticket here!") + row = se.MessageActionRowBuilder() + row.add_interactive_button( + hikari.ButtonStyle.PRIMARY, "meowmeow1", emoji="✉️", label="Create Ticket" + ) + await ctx.client.rest.create_message(channel_option, embed=embed, components=[row]) + await ctx.respond("Sent Message") + + +@plugin.listen() +async def button_click(event: hikari.InteractionCreateEvent) -> None: + if not isinstance(event.interaction, hikari.ComponentInteraction): + return + if event.interaction.custom_id != "meowmeow1": + return + + text_label = se.LabelComponentBuilder( + label="Question", + component=se.TextInputBuilder( + custom_id="hophop1", + style=hikari.TextInputStyle.PARAGRAPH, + required=True, + max_length=1000, + ), + ) + + committee_select = se.TextSelectMenuBuilder( + custom_id="ruffruff1", + placeholder="Choose Committee Position", + min_values=1, + max_values=1, + ) + for value in ("admins", "helpdesk", "committee"): + committee_select.add_option(value.title(), value) + committee_label = se.LabelComponentBuilder( + label="Committee", component=committee_select + ) + + await event.interaction.create_modal_response( + title="Ticket", + custom_id="chirpchirp1", + components=[committee_label, text_label], + ) + + +@plugin.listen() +async def modal_submit(event: hikari.InteractionCreateEvent) -> None: + if not isinstance(event.interaction, hikari.ModalInteraction): + return + if event.interaction.custom_id != "chirpchirp1": + return + + committee_choice = event.interaction.components[0].component + assert isinstance(committee_choice, TextSelectMenuInteractionComponent) + selected_committee = committee_choice.values[0] + + question_choice = event.interaction.components[1].component + assert isinstance(question_choice, TextInputInteractionComponent) + selected_question = question_choice.value + + # TODO: Make channel name work with multiple tickets per user + channel_name = f"ticket-{event.interaction.user.username}" + assert event.interaction.guild_id is not None + permission_role = ROLE_IDS[selected_committee] + created_channel = await event.app.rest.create_guild_text_channel( + event.interaction.guild_id, + channel_name, + category=CATEGORY_IDS["test-category"], + permission_overwrites=[ + hikari.PermissionOverwrite( + id=permission_role, + type=hikari.PermissionOverwriteType.ROLE, + allow=hikari.Permissions.VIEW_CHANNEL, + ), + hikari.PermissionOverwrite( + id=event.interaction.user.id, + type=hikari.PermissionOverwriteType.MEMBER, + allow=hikari.Permissions.VIEW_CHANNEL, + ), + hikari.PermissionOverwrite( + id=event.interaction.guild_id, + type=hikari.PermissionOverwriteType.ROLE, + deny=hikari.Permissions.VIEW_CHANNEL, + ), + ], + ) + + embed = hikari.Embed(title="Ticket", description=selected_question) + row = se.MessageActionRowBuilder() + row.add_interactive_button( + hikari.ButtonStyle.PRIMARY, "honkhonk1", emoji="⛔", label="Close Ticket" + ) + await event.app.rest.create_message(created_channel, embed=embed, components=[row]) + await event.interaction.create_initial_response( + hikari.ResponseType.MESSAGE_CREATE, + f"Ticket Created here: <#{created_channel.id}>!", + flags=hikari.MessageFlag.EPHEMERAL, + ) + + +@arc.loader +def loader(client: Blockbot) -> None: + client.add_plugin(plugin) diff --git a/uv.lock b/uv.lock index d9e862c..3ae20f2 100644 --- a/uv.lock +++ b/uv.lock @@ -230,7 +230,7 @@ requires-dist = [ { name = "asyncpg", specifier = ">=0.31.0" }, { name = "dnspython", specifier = ">=2.8.0" }, { name = "fortune-python", specifier = ">=1.1.2" }, - { name = "hikari", specifier = ">=2.5.0" }, + { name = "hikari", git = "https://github.com/mplatypus/hikari.git?branch=feature%2Fmodals-v2" }, { name = "hikari-arc", specifier = ">=2.2.0" }, { name = "hikari-miru", specifier = ">=4.2.0" }, { name = "miniopy-async", specifier = ">=1.23.5" }, @@ -435,18 +435,14 @@ wheels = [ [[package]] name = "hikari" -version = "2.5.0" -source = { registry = "https://pypi.org/simple" } +version = "2.5.1.dev0" +source = { git = "https://github.com/mplatypus/hikari.git?branch=feature%2Fmodals-v2#c73864e36c0b3c1cc604cf587c0a351421a08ea0" } dependencies = [ { name = "aiohttp" }, { name = "attrs" }, { name = "colorlog" }, { name = "multidict" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/bd/e4bd16c662fe7614bb6f38aba40a095aef998af0602674fe24740d3814cd/hikari-2.5.0.tar.gz", hash = "sha256:b8fe7e5cd5adfedcdcc488a86781bc946002fb79d984a473736624dd8201993b", size = 1924829, upload-time = "2025-10-31T12:34:27.838Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/66/c5/256c7d498cbe8e67edfd9bb2971740eadb662effcf9b9c1aff8e6d21e90d/hikari-2.5.0-py3-none-any.whl", hash = "sha256:fd108b51c73f46b562ac9e7cd48d4f74655224083ae37a319a417c30418a40ec", size = 580206, upload-time = "2025-10-31T12:34:25.633Z" }, -] [[package]] name = "hikari-arc" From 3e644c00ad95e2a5ebbeff260580c3bb1c94cbb3 Mon Sep 17 00:00:00 2001 From: a-Digi-a Date: Mon, 15 Jun 2026 16:49:03 +0100 Subject: [PATCH 02/11] complete ticketing system --- src/config.py | 1 - src/extensions/ticket.py | 47 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/config.py b/src/config.py index 9944149..e1415ff 100644 --- a/src/config.py +++ b/src/config.py @@ -143,7 +143,6 @@ def set_env_var( "webgroup": 1202321207811395624, "voice-channels": 568403964098248723, "archive": 958448000496173056, - "test-category": 1515768461043961967, } # TODO: query API/LDAP for these diff --git a/src/extensions/ticket.py b/src/extensions/ticket.py index 565fc6c..327ff14 100644 --- a/src/extensions/ticket.py +++ b/src/extensions/ticket.py @@ -81,14 +81,25 @@ async def modal_submit(event: hikari.InteractionCreateEvent) -> None: assert isinstance(question_choice, TextInputInteractionComponent) selected_question = question_choice.value - # TODO: Make channel name work with multiple tickets per user - channel_name = f"ticket-{event.interaction.user.username}" assert event.interaction.guild_id is not None + channels = await event.app.rest.fetch_guild_channels(event.interaction.guild_id) + existing_names = { + channel.name + for channel in channels + if hasattr(channel, "parent_id") + and channel.parent_id == CATEGORY_IDS["technical"] + } + ticket_number = 1 + channel_name = f"ticket-{event.interaction.user.username}" + while channel_name in existing_names: + channel_name = f"ticket-{event.interaction.user.username}-{ticket_number}" + ticket_number += 1 + permission_role = ROLE_IDS[selected_committee] created_channel = await event.app.rest.create_guild_text_channel( event.interaction.guild_id, channel_name, - category=CATEGORY_IDS["test-category"], + category=CATEGORY_IDS["technical"], permission_overwrites=[ hikari.PermissionOverwrite( id=permission_role, @@ -119,6 +130,36 @@ async def modal_submit(event: hikari.InteractionCreateEvent) -> None: f"Ticket Created here: <#{created_channel.id}>!", flags=hikari.MessageFlag.EPHEMERAL, ) + await event.app.rest.create_message(created_channel.id, f"<@&{permission_role}> <@{event.interaction.user.id}>") + + +@plugin.listen() +async def close_ticket(event: hikari.InteractionCreateEvent) -> None: + if not isinstance(event.interaction, hikari.ComponentInteraction): + return + if event.interaction.custom_id != "honkhonk1": + return + + embed = hikari.Embed(title="Close Ticket?", description="Are you sure you want to close the ticket?") + row = se.MessageActionRowBuilder() + row.add_interactive_button( + hikari.ButtonStyle.PRIMARY, "moomoo1", emoji="⛔", label="Close Ticket" + ) + await event.interaction.create_initial_response(hikari.ResponseType.MESSAGE_CREATE, embed=embed, components=[row], flags=hikari.MessageFlag.EPHEMERAL) + +@plugin.listen() +async def delete_channel(event: hikari.InteractionCreateEvent) -> None: + if not isinstance(event.interaction, hikari.ComponentInteraction): + return + if event.interaction.custom_id != "moomoo1": + return + + await event.interaction.create_initial_response( + hikari.ResponseType.MESSAGE_CREATE, + "Closing ticket...", + flags=hikari.MessageFlag.EPHEMERAL, + ) + await event.app.rest.delete_channel(event.interaction.channel_id) @arc.loader From 636ab730afb93d29e7f7a2c986f833bc17393149 Mon Sep 17 00:00:00 2001 From: a-Digi-a Date: Mon, 15 Jun 2026 16:51:46 +0100 Subject: [PATCH 03/11] fix .env.sample --- .env.sample | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .env.sample diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..1ff6cf7 --- /dev/null +++ b/.env.sample @@ -0,0 +1,33 @@ +TOKEN= + +# 'true' or 'false', default 'false' +DEBUG= + +DISCORD_UID_MAP="chair=1234,secretary=4567,user1=1234,user2=4567,user3=7890" + +LDAP_USERNAME= +LDAP_PASSWORD= + +AGENDA_TEMPLATE_URL="https://md.redbrick.dcu.ie/foo" + +DB_HOST=postgres:5432 +DB_NAME=blockbot +DB_USER=blockbot +DB_PASSWORD= + +RCON_HOST= +RCON_PORT= +RCON_PASSWORD= + +MINIO_ENDPOINT=minio:9000 +MINIO_SECURE=false +MINIO_REGION=us-east-1 +MINIO_ACCESS_KEY= +MINIO_SECRET_KEY= + +# all of these can be set to 'false' and default to 'true' +DB_ENABLED= +LDAP_ENABLED= +PERMS_ENABLED= +RCON_ENABLED= +MINIO_ENABLED= From 9f17a6fa73b56b7dae8245eaee7ee4a675c006f9 Mon Sep 17 00:00:00 2001 From: a-Digi-a Date: Mon, 15 Jun 2026 16:53:21 +0100 Subject: [PATCH 04/11] fix formatting --- src/extensions/ticket.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/extensions/ticket.py b/src/extensions/ticket.py index 327ff14..d824524 100644 --- a/src/extensions/ticket.py +++ b/src/extensions/ticket.py @@ -130,7 +130,9 @@ async def modal_submit(event: hikari.InteractionCreateEvent) -> None: f"Ticket Created here: <#{created_channel.id}>!", flags=hikari.MessageFlag.EPHEMERAL, ) - await event.app.rest.create_message(created_channel.id, f"<@&{permission_role}> <@{event.interaction.user.id}>") + await event.app.rest.create_message( + created_channel.id, f"<@&{permission_role}> <@{event.interaction.user.id}>" + ) @plugin.listen() @@ -140,12 +142,20 @@ async def close_ticket(event: hikari.InteractionCreateEvent) -> None: if event.interaction.custom_id != "honkhonk1": return - embed = hikari.Embed(title="Close Ticket?", description="Are you sure you want to close the ticket?") + embed = hikari.Embed( + title="Close Ticket?", description="Are you sure you want to close the ticket?" + ) row = se.MessageActionRowBuilder() row.add_interactive_button( hikari.ButtonStyle.PRIMARY, "moomoo1", emoji="⛔", label="Close Ticket" ) - await event.interaction.create_initial_response(hikari.ResponseType.MESSAGE_CREATE, embed=embed, components=[row], flags=hikari.MessageFlag.EPHEMERAL) + await event.interaction.create_initial_response( + hikari.ResponseType.MESSAGE_CREATE, + embed=embed, + components=[row], + flags=hikari.MessageFlag.EPHEMERAL, + ) + @plugin.listen() async def delete_channel(event: hikari.InteractionCreateEvent) -> None: @@ -155,10 +165,10 @@ async def delete_channel(event: hikari.InteractionCreateEvent) -> None: return await event.interaction.create_initial_response( - hikari.ResponseType.MESSAGE_CREATE, - "Closing ticket...", - flags=hikari.MessageFlag.EPHEMERAL, - ) + hikari.ResponseType.MESSAGE_CREATE, + "Closing ticket...", + flags=hikari.MessageFlag.EPHEMERAL, + ) await event.app.rest.delete_channel(event.interaction.channel_id) From 0ff7ed82225d1a0570e04919fcd452e588466168 Mon Sep 17 00:00:00 2001 From: a-Digi-a Date: Mon, 15 Jun 2026 17:07:47 +0100 Subject: [PATCH 05/11] add git to dockerfile --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 349276d..7b62bed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,8 @@ FROM python:3.14.3-alpine3.23 COPY --from=ghcr.io/astral-sh/uv:0.11 /uv /uvx /bin/ +RUN apk add --no-cache git + WORKDIR /app COPY pyproject.toml . From 6fc5f33b5b719f07334e5d4ed6119c30f228a454 Mon Sep 17 00:00:00 2001 From: a-Digi-a Date: Mon, 15 Jun 2026 17:22:36 +0100 Subject: [PATCH 06/11] make ticketing command admin-only --- src/extensions/ticket.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/extensions/ticket.py b/src/extensions/ticket.py index d824524..08f0b96 100644 --- a/src/extensions/ticket.py +++ b/src/extensions/ticket.py @@ -11,7 +11,6 @@ plugin = BlockbotPlugin("Ticketing System") - @plugin.include @arc.slash_command("create_ticket_message", "Create Ticket Message") async def options( @@ -21,6 +20,10 @@ async def options( arc.ChannelParams("A textable channel option.", name="channel"), ], ) -> None: + assert ctx.member is not None + if ROLE_IDS["admins"] not in ctx.member.role_ids: + await ctx.respond("You don't have permission to use this command!", flags=hikari.MessageFlag.EPHEMERAL) + return embed = hikari.Embed(title="Tickets", description="Create a ticket here!") row = se.MessageActionRowBuilder() row.add_interactive_button( From bff242448efa5d5959363f83e3b238714dae1116 Mon Sep 17 00:00:00 2001 From: a-Digi-a Date: Mon, 15 Jun 2026 17:24:28 +0100 Subject: [PATCH 07/11] fix formatting (again) --- src/extensions/ticket.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/extensions/ticket.py b/src/extensions/ticket.py index 08f0b96..a4832a3 100644 --- a/src/extensions/ticket.py +++ b/src/extensions/ticket.py @@ -11,6 +11,7 @@ plugin = BlockbotPlugin("Ticketing System") + @plugin.include @arc.slash_command("create_ticket_message", "Create Ticket Message") async def options( @@ -22,7 +23,10 @@ async def options( ) -> None: assert ctx.member is not None if ROLE_IDS["admins"] not in ctx.member.role_ids: - await ctx.respond("You don't have permission to use this command!", flags=hikari.MessageFlag.EPHEMERAL) + await ctx.respond( + "You don't have permission to use this command!", + flags=hikari.MessageFlag.EPHEMERAL, + ) return embed = hikari.Embed(title="Tickets", description="Create a ticket here!") row = se.MessageActionRowBuilder() From ef82f5c79e3552cb61f5d2c7919e314982b641ba Mon Sep 17 00:00:00 2001 From: a-Digi-a Date: Mon, 15 Jun 2026 17:33:46 +0100 Subject: [PATCH 08/11] use hook for command permission --- src/extensions/ticket.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/extensions/ticket.py b/src/extensions/ticket.py index a4832a3..dd59335 100644 --- a/src/extensions/ticket.py +++ b/src/extensions/ticket.py @@ -7,12 +7,14 @@ ) from src.config import CATEGORY_IDS, ROLE_IDS +from src.hooks import restrict_to_roles from src.models import Blockbot, BlockbotContext, BlockbotPlugin plugin = BlockbotPlugin("Ticketing System") @plugin.include +@arc.with_hook(restrict_to_roles(role_ids=[ROLE_IDS["committee"]])) @arc.slash_command("create_ticket_message", "Create Ticket Message") async def options( ctx: BlockbotContext, @@ -21,13 +23,6 @@ async def options( arc.ChannelParams("A textable channel option.", name="channel"), ], ) -> None: - assert ctx.member is not None - if ROLE_IDS["admins"] not in ctx.member.role_ids: - await ctx.respond( - "You don't have permission to use this command!", - flags=hikari.MessageFlag.EPHEMERAL, - ) - return embed = hikari.Embed(title="Tickets", description="Create a ticket here!") row = se.MessageActionRowBuilder() row.add_interactive_button( From e3fba9639d283095c76d5fd5bc97f2c3e7bf5f45 Mon Sep 17 00:00:00 2001 From: a-Digi-a Date: Thu, 18 Jun 2026 21:17:51 +0100 Subject: [PATCH 09/11] fix issues from code review --- src/bot.py | 2 +- src/extensions/ticket.py | 107 ++++++++++++++++++--------------------- 2 files changed, 51 insertions(+), 58 deletions(-) diff --git a/src/bot.py b/src/bot.py index e140709..fa41eba 100644 --- a/src/bot.py +++ b/src/bot.py @@ -22,7 +22,7 @@ ) client = Blockbot(bot, invocation_contexts=[hikari.ApplicationContextType.GUILD]) -miru_client = miru.Client.from_arc(client) +miru_client = miru.Client.from_arc(client, ignore_unknown_interactions=True) client.set_type_dependency(miru.Client, miru_client) diff --git a/src/extensions/ticket.py b/src/extensions/ticket.py index dd59335..337a4c0 100644 --- a/src/extensions/ticket.py +++ b/src/extensions/ticket.py @@ -1,3 +1,5 @@ +import asyncio + import arc import hikari from hikari.impl import special_endpoints as se @@ -18,31 +20,42 @@ @arc.slash_command("create_ticket_message", "Create Ticket Message") async def options( ctx: BlockbotContext, - channel_option: arc.Option[ + channel: arc.Option[ hikari.TextableChannel, - arc.ChannelParams("A textable channel option.", name="channel"), + arc.ChannelParams("Ticket message channel name"), ], ) -> None: - embed = hikari.Embed(title="Tickets", description="Create a ticket here!") + embed = hikari.Embed(title="Tickets", description="If you need to contact the admins, helpdesk or committee, create a ticket here!") row = se.MessageActionRowBuilder() row.add_interactive_button( - hikari.ButtonStyle.PRIMARY, "meowmeow1", emoji="✉️", label="Create Ticket" + hikari.ButtonStyle.PRIMARY, "meowmeow-create-ticket", emoji="✉️", label="Create Ticket" ) - await ctx.client.rest.create_message(channel_option, embed=embed, components=[row]) + await ctx.client.rest.create_message(channel, embed=embed, components=[row]) await ctx.respond("Sent Message") - @plugin.listen() -async def button_click(event: hikari.InteractionCreateEvent) -> None: - if not isinstance(event.interaction, hikari.ComponentInteraction): - return - if event.interaction.custom_id != "meowmeow1": - return +async def component_interaction(event: hikari.InteractionCreateEvent) -> None: + + if isinstance(event.interaction, hikari.ComponentInteraction): + if event.interaction.custom_id == "meowmeow-create-ticket": + await button_click(event.interaction) + if event.interaction.custom_id == "honkhonk-close-ticket": + await close_ticket(event.interaction) + if event.interaction.custom_id == "moomoo-close-ticket-confirm": + await delete_channel(event.interaction) + + if isinstance(event.interaction, hikari.ModalInteraction): + if event.interaction.custom_id == "chirpchirp-ticket-channel": + await modal_submit(event.interaction) + + +async def button_click(interaction: hikari.ComponentInteraction) -> None: text_label = se.LabelComponentBuilder( label="Question", component=se.TextInputBuilder( - custom_id="hophop1", + custom_id="hophop-ticket-question", + placeholder="Ask your question here!", style=hikari.TextInputStyle.PARAGRAPH, required=True, max_length=1000, @@ -50,8 +63,8 @@ async def button_click(event: hikari.InteractionCreateEvent) -> None: ) committee_select = se.TextSelectMenuBuilder( - custom_id="ruffruff1", - placeholder="Choose Committee Position", + custom_id="ruffruff-committee-select", + placeholder="Choose Committee Position to Contact", min_values=1, max_values=1, ) @@ -61,30 +74,24 @@ async def button_click(event: hikari.InteractionCreateEvent) -> None: label="Committee", component=committee_select ) - await event.interaction.create_modal_response( + await interaction.create_modal_response( title="Ticket", - custom_id="chirpchirp1", + custom_id="chirpchirp-ticket-channel", components=[committee_label, text_label], ) -@plugin.listen() -async def modal_submit(event: hikari.InteractionCreateEvent) -> None: - if not isinstance(event.interaction, hikari.ModalInteraction): - return - if event.interaction.custom_id != "chirpchirp1": - return - - committee_choice = event.interaction.components[0].component +async def modal_submit(interaction: hikari.ModalInteraction) -> None: + committee_choice = interaction.components[0].component assert isinstance(committee_choice, TextSelectMenuInteractionComponent) selected_committee = committee_choice.values[0] - question_choice = event.interaction.components[1].component + question_choice = interaction.components[1].component assert isinstance(question_choice, TextInputInteractionComponent) selected_question = question_choice.value - assert event.interaction.guild_id is not None - channels = await event.app.rest.fetch_guild_channels(event.interaction.guild_id) + assert interaction.guild_id is not None + channels = await plugin.client.rest.fetch_guild_channels(interaction.guild_id) existing_names = { channel.name for channel in channels @@ -92,14 +99,14 @@ async def modal_submit(event: hikari.InteractionCreateEvent) -> None: and channel.parent_id == CATEGORY_IDS["technical"] } ticket_number = 1 - channel_name = f"ticket-{event.interaction.user.username}" + channel_name = f"ticket-{interaction.user.username}" while channel_name in existing_names: - channel_name = f"ticket-{event.interaction.user.username}-{ticket_number}" + channel_name = f"ticket-{interaction.user.username}-{ticket_number}" ticket_number += 1 permission_role = ROLE_IDS[selected_committee] - created_channel = await event.app.rest.create_guild_text_channel( - event.interaction.guild_id, + created_channel = await plugin.client.rest.create_guild_text_channel( + interaction.guild_id, channel_name, category=CATEGORY_IDS["technical"], permission_overwrites=[ @@ -109,12 +116,12 @@ async def modal_submit(event: hikari.InteractionCreateEvent) -> None: allow=hikari.Permissions.VIEW_CHANNEL, ), hikari.PermissionOverwrite( - id=event.interaction.user.id, + id=interaction.user.id, type=hikari.PermissionOverwriteType.MEMBER, allow=hikari.Permissions.VIEW_CHANNEL, ), hikari.PermissionOverwrite( - id=event.interaction.guild_id, + id=interaction.guild_id, type=hikari.PermissionOverwriteType.ROLE, deny=hikari.Permissions.VIEW_CHANNEL, ), @@ -124,34 +131,25 @@ async def modal_submit(event: hikari.InteractionCreateEvent) -> None: embed = hikari.Embed(title="Ticket", description=selected_question) row = se.MessageActionRowBuilder() row.add_interactive_button( - hikari.ButtonStyle.PRIMARY, "honkhonk1", emoji="⛔", label="Close Ticket" + hikari.ButtonStyle.PRIMARY, "honkhonk-close-ticket", emoji="⛔", label="Close Ticket" ) - await event.app.rest.create_message(created_channel, embed=embed, components=[row]) - await event.interaction.create_initial_response( + await plugin.client.rest.create_message(created_channel, f"<@&{permission_role}> <@{interaction.user.id}>", embed=embed, components=[row], user_mentions=True, role_mentions=True) + await interaction.create_initial_response( hikari.ResponseType.MESSAGE_CREATE, f"Ticket Created here: <#{created_channel.id}>!", flags=hikari.MessageFlag.EPHEMERAL, ) - await event.app.rest.create_message( - created_channel.id, f"<@&{permission_role}> <@{event.interaction.user.id}>" - ) - -@plugin.listen() -async def close_ticket(event: hikari.InteractionCreateEvent) -> None: - if not isinstance(event.interaction, hikari.ComponentInteraction): - return - if event.interaction.custom_id != "honkhonk1": - return +async def close_ticket(interaction: hikari.ComponentInteraction) -> None: embed = hikari.Embed( title="Close Ticket?", description="Are you sure you want to close the ticket?" ) row = se.MessageActionRowBuilder() row.add_interactive_button( - hikari.ButtonStyle.PRIMARY, "moomoo1", emoji="⛔", label="Close Ticket" + hikari.ButtonStyle.PRIMARY, "moomoo-close-ticket-confirm", emoji="⛔", label="Close Ticket" ) - await event.interaction.create_initial_response( + await interaction.create_initial_response( hikari.ResponseType.MESSAGE_CREATE, embed=embed, components=[row], @@ -159,19 +157,14 @@ async def close_ticket(event: hikari.InteractionCreateEvent) -> None: ) -@plugin.listen() -async def delete_channel(event: hikari.InteractionCreateEvent) -> None: - if not isinstance(event.interaction, hikari.ComponentInteraction): - return - if event.interaction.custom_id != "moomoo1": - return - - await event.interaction.create_initial_response( +async def delete_channel(interaction: hikari.ComponentInteraction) -> None: + await interaction.create_initial_response( hikari.ResponseType.MESSAGE_CREATE, "Closing ticket...", flags=hikari.MessageFlag.EPHEMERAL, ) - await event.app.rest.delete_channel(event.interaction.channel_id) + await asyncio.sleep(3) + await plugin.client.rest.delete_channel(interaction.channel_id) @arc.loader From 7a1bdbc9d73c5c4da71fc6e85013faef3c44e0fe Mon Sep 17 00:00:00 2001 From: a-Digi-a Date: Thu, 18 Jun 2026 21:23:04 +0100 Subject: [PATCH 10/11] fix formatting (again) (again) --- pyproject.toml | 1 + src/extensions/ticket.py | 38 +++++++++++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6f72ff1..59cd811 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,7 @@ ignore = [ "PLR0913", # too-many-arguments "COM812", # missing-trailing-comma "TC002", # typing-only-third-party-import + "SIM102", # collapsible-if ] [tool.ruff.lint.pydocstyle] diff --git a/src/extensions/ticket.py b/src/extensions/ticket.py index 337a4c0..0368ea3 100644 --- a/src/extensions/ticket.py +++ b/src/extensions/ticket.py @@ -25,31 +25,37 @@ async def options( arc.ChannelParams("Ticket message channel name"), ], ) -> None: - embed = hikari.Embed(title="Tickets", description="If you need to contact the admins, helpdesk or committee, create a ticket here!") + embed = hikari.Embed( + title="Tickets", + description="If you need to contact the admins, helpdesk or committee, create a ticket here!", + ) row = se.MessageActionRowBuilder() row.add_interactive_button( - hikari.ButtonStyle.PRIMARY, "meowmeow-create-ticket", emoji="✉️", label="Create Ticket" + hikari.ButtonStyle.PRIMARY, + "meowmeow-create-ticket", + emoji="✉️", + label="Create Ticket", ) await ctx.client.rest.create_message(channel, embed=embed, components=[row]) await ctx.respond("Sent Message") + @plugin.listen() async def component_interaction(event: hikari.InteractionCreateEvent) -> None: if isinstance(event.interaction, hikari.ComponentInteraction): if event.interaction.custom_id == "meowmeow-create-ticket": await button_click(event.interaction) - if event.interaction.custom_id == "honkhonk-close-ticket": + elif event.interaction.custom_id == "honkhonk-close-ticket": await close_ticket(event.interaction) - if event.interaction.custom_id == "moomoo-close-ticket-confirm": + elif event.interaction.custom_id == "moomoo-close-ticket-confirm": await delete_channel(event.interaction) - if isinstance(event.interaction, hikari.ModalInteraction): + elif isinstance(event.interaction, hikari.ModalInteraction): if event.interaction.custom_id == "chirpchirp-ticket-channel": await modal_submit(event.interaction) - async def button_click(interaction: hikari.ComponentInteraction) -> None: text_label = se.LabelComponentBuilder( label="Question", @@ -131,15 +137,26 @@ async def modal_submit(interaction: hikari.ModalInteraction) -> None: embed = hikari.Embed(title="Ticket", description=selected_question) row = se.MessageActionRowBuilder() row.add_interactive_button( - hikari.ButtonStyle.PRIMARY, "honkhonk-close-ticket", emoji="⛔", label="Close Ticket" + hikari.ButtonStyle.PRIMARY, + "honkhonk-close-ticket", + emoji="⛔", + label="Close Ticket", + ) + await plugin.client.rest.create_message( + created_channel, + f"<@&{permission_role}> <@{interaction.user.id}>", + embed=embed, + components=[row], + user_mentions=True, + role_mentions=True, ) - await plugin.client.rest.create_message(created_channel, f"<@&{permission_role}> <@{interaction.user.id}>", embed=embed, components=[row], user_mentions=True, role_mentions=True) await interaction.create_initial_response( hikari.ResponseType.MESSAGE_CREATE, f"Ticket Created here: <#{created_channel.id}>!", flags=hikari.MessageFlag.EPHEMERAL, ) + async def close_ticket(interaction: hikari.ComponentInteraction) -> None: embed = hikari.Embed( @@ -147,7 +164,10 @@ async def close_ticket(interaction: hikari.ComponentInteraction) -> None: ) row = se.MessageActionRowBuilder() row.add_interactive_button( - hikari.ButtonStyle.PRIMARY, "moomoo-close-ticket-confirm", emoji="⛔", label="Close Ticket" + hikari.ButtonStyle.PRIMARY, + "moomoo-close-ticket-confirm", + emoji="⛔", + label="Close Ticket", ) await interaction.create_initial_response( hikari.ResponseType.MESSAGE_CREATE, From eedc8b51cced0fe722cf47331407aa7b3db797ee Mon Sep 17 00:00:00 2001 From: Digi <86414214+a-Digi-a@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:33:51 +0100 Subject: [PATCH 11/11] Apply suggestions from code review Co-authored-by: nova <110734810+novanai@users.noreply.github.com> --- src/extensions/ticket.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/extensions/ticket.py b/src/extensions/ticket.py index 0368ea3..07995a8 100644 --- a/src/extensions/ticket.py +++ b/src/extensions/ticket.py @@ -42,7 +42,6 @@ async def options( @plugin.listen() async def component_interaction(event: hikari.InteractionCreateEvent) -> None: - if isinstance(event.interaction, hikari.ComponentInteraction): if event.interaction.custom_id == "meowmeow-create-ticket": await button_click(event.interaction) @@ -158,7 +157,6 @@ async def modal_submit(interaction: hikari.ModalInteraction) -> None: async def close_ticket(interaction: hikari.ComponentInteraction) -> None: - embed = hikari.Embed( title="Close Ticket?", description="Are you sure you want to close the ticket?" )