Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
TOKEN=
DEBUG= # 'true' or 'false', default 'false'

# 'true' or 'false', default 'false'
DEBUG=

DISCORD_UID_MAP="chair=1234,secretary=4567,user1=1234,user2=4567,user3=7890"

Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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 .
Expand Down
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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]
Expand All @@ -88,3 +89,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" }
2 changes: 1 addition & 1 deletion src/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
11 changes: 11 additions & 0 deletions src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,17 @@ 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,
}
Comment thread
a-Digi-a marked this conversation as resolved.

# TODO: query API/LDAP for these
ROLE_IDS: dict[str, int] = {
"all": 568762266992902179,
Expand Down
190 changes: 190 additions & 0 deletions src/extensions/ticket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import asyncio

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.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,
channel: arc.Option[
hikari.TextableChannel,
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!",
)
row = se.MessageActionRowBuilder()
row.add_interactive_button(
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)
elif event.interaction.custom_id == "honkhonk-close-ticket":
await close_ticket(event.interaction)
elif event.interaction.custom_id == "moomoo-close-ticket-confirm":
await delete_channel(event.interaction)

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",
component=se.TextInputBuilder(
custom_id="hophop-ticket-question",
placeholder="Ask your question here!",
style=hikari.TextInputStyle.PARAGRAPH,
required=True,
max_length=1000,
),
)

committee_select = se.TextSelectMenuBuilder(
custom_id="ruffruff-committee-select",
placeholder="Choose Committee Position to Contact",
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
Comment thread
novanai marked this conversation as resolved.
)

await interaction.create_modal_response(
title="Ticket",
custom_id="chirpchirp-ticket-channel",
components=[committee_label, text_label],
)


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 = interaction.components[1].component
assert isinstance(question_choice, TextInputInteractionComponent)
selected_question = question_choice.value

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
if hasattr(channel, "parent_id")
Comment thread
novanai marked this conversation as resolved.
and channel.parent_id == CATEGORY_IDS["technical"]
}
ticket_number = 1
channel_name = f"ticket-{interaction.user.username}"
while channel_name in existing_names:
channel_name = f"ticket-{interaction.user.username}-{ticket_number}"
ticket_number += 1

permission_role = ROLE_IDS[selected_committee]
created_channel = await plugin.client.rest.create_guild_text_channel(
interaction.guild_id,
channel_name,
category=CATEGORY_IDS["technical"],
permission_overwrites=[
hikari.PermissionOverwrite(
id=permission_role,
type=hikari.PermissionOverwriteType.ROLE,
allow=hikari.Permissions.VIEW_CHANNEL,
),
hikari.PermissionOverwrite(
id=interaction.user.id,
type=hikari.PermissionOverwriteType.MEMBER,
allow=hikari.Permissions.VIEW_CHANNEL,
),
hikari.PermissionOverwrite(
id=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,
"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 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(
title="Close Ticket?", description="Are you sure you want to close the ticket?"
)
row = se.MessageActionRowBuilder()
row.add_interactive_button(
hikari.ButtonStyle.PRIMARY,
"moomoo-close-ticket-confirm",
emoji="⛔",
label="Close Ticket",
)
await interaction.create_initial_response(
hikari.ResponseType.MESSAGE_CREATE,
embed=embed,
components=[row],
flags=hikari.MessageFlag.EPHEMERAL,
)


async def delete_channel(interaction: hikari.ComponentInteraction) -> None:
await interaction.create_initial_response(
hikari.ResponseType.MESSAGE_CREATE,
"Closing ticket...",
flags=hikari.MessageFlag.EPHEMERAL,
)
Comment thread
novanai marked this conversation as resolved.
await asyncio.sleep(3)
await plugin.client.rest.delete_channel(interaction.channel_id)


@arc.loader
def loader(client: Blockbot) -> None:
client.add_plugin(plugin)
10 changes: 3 additions & 7 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading