Skip to content

Commit 7380eaa

Browse files
committed
Multi machine join views
1 parent fb73bba commit 7380eaa

File tree

5 files changed

+133
-90
lines changed

5 files changed

+133
-90
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Remove machinedisplay table
2+
3+
Revision ID: 4656bfd26a60
4+
Revises: d00c347994e5
5+
Create Date: 2022-10-01 00:31:47.290937
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlalchemy_utils
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = '4656bfd26a60'
15+
down_revision = 'd00c347994e5'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.drop_table('machine_displays')
23+
# ### end Alembic commands ###
24+
25+
26+
def downgrade():
27+
# ### commands auto generated by Alembic - please adjust! ###
28+
op.create_table('machine_displays',
29+
sa.Column('discord_message_id', sa.BIGINT(), autoincrement=True, nullable=False),
30+
sa.Column('machine_hostname', sa.TEXT(), autoincrement=False, nullable=False),
31+
sa.PrimaryKeyConstraint('discord_message_id', name='machine_displays_pkey')
32+
)
33+
# ### end Alembic commands ###

luhack_bot/cogs/challenges.py

+2-33
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
import textwrap
33
from datetime import datetime
4-
from typing import Any, Callable, Coroutine, List, Literal, Optional, Tuple, TypeVar
4+
from typing import List, Literal, Optional, Tuple, TypeVar
55

66
import discord
77
import sqlalchemy as sa
@@ -14,6 +14,7 @@
1414
from luhack_bot.db.helpers import text_search
1515
from luhack_bot.db.models import Challenge, CompletedChallenge, User, db
1616
from luhack_bot.utils.checks import is_admin_int, is_authed, is_authed_int
17+
from luhack_bot.utils.list_sep_transform import ListSepTransformer, list_sep_choices
1718

1819
logger = logging.getLogger(__name__)
1920

@@ -102,38 +103,6 @@ async def tag_autocomplete(
102103
return [app_commands.Choice(name=name, value=name) for name, in results]
103104

104105

105-
class ListSepTransformer(app_commands.Transformer):
106-
async def transform(
107-
self, interaction: discord.Interaction, value: str
108-
) -> list[str]:
109-
return [x.strip() for x in value.split(",")]
110-
111-
112-
def list_sep_choices(
113-
inner: Callable[
114-
[discord.Interaction, str], Coroutine[Any, Any, list[app_commands.Choice[str]]]
115-
]
116-
) -> Callable[
117-
[discord.Interaction, str], Coroutine[Any, Any, list[app_commands.Choice[str]]]
118-
]:
119-
async def impl(
120-
interaction: discord.Interaction, current: str
121-
) -> list[app_commands.Choice[str]]:
122-
*prev_completed, to_complete = [item.strip() for item in current.split(",")]
123-
124-
completed = await inner(interaction, to_complete)
125-
126-
return [
127-
app_commands.Choice(
128-
name=", ".join([*prev_completed, c.name]),
129-
value=", ".join([*prev_completed, c.value]),
130-
)
131-
for c in completed
132-
]
133-
134-
return impl
135-
136-
137106
CURRENT_SEASON = 3
138107

139108

luhack_bot/cogs/infra.py

+63-49
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22
import textwrap
3-
3+
import re
44
from typing import TYPE_CHECKING, Optional
55

66
import aioretry
@@ -10,17 +10,18 @@
1010
import cachetools
1111
import pygtrie
1212
import discord
13-
from discord import ui
13+
from discord import ButtonStyle, ComponentType, InteractionType, ui
1414
from discord.ext import commands
1515
from luhack_bot import secrets
1616
import sqlalchemy.dialects.postgresql as psa
1717

1818
from luhack_bot.cogs.challenges import logging
1919
from luhack_bot.cogs.verification import app_commands
2020
from luhack_bot.db.helpers import db
21-
from luhack_bot.db.models import Machine, MachineDisplay
21+
from luhack_bot.db.models import Machine
2222
from luhack_bot.utils.async_cache import async_cached
2323
from luhack_bot.utils.checks import is_admin_int, is_authed_int
24+
from luhack_bot.utils.list_sep_transform import ListSepTransformer, list_sep_choices
2425

2526

2627
logger = logging.getLogger(__name__)
@@ -170,36 +171,45 @@ async def generate_invite(node: str):
170171
return invite.json()["data"]["code"]
171172

172173

173-
class MachineInfoView(ui.View):
174+
@app_commands.guild_only()
175+
class Infra(commands.GroupCog, name="infra"):
174176
def __init__(self, bot: LUHackBot):
175177
self.bot = bot
176-
super().__init__(timeout=None)
178+
179+
super().__init__()
180+
181+
async def interaction_check(self, interaction: discord.Interaction):
182+
return await is_authed_int(interaction)
183+
184+
@commands.GroupCog.listener()
185+
async def on_interaction(self, interaction: discord.Interaction):
186+
if interaction.type != InteractionType.component:
187+
return
188+
assert interaction.data is not None
189+
if interaction.data.get("component_type") != ComponentType.button.value:
190+
return
191+
custom_id = interaction.data.get("custom_id")
192+
print(custom_id)
193+
assert isinstance(custom_id, str)
194+
if (m := re.fullmatch(r"machine_info_join:(\S+)", custom_id)) is not None:
195+
hostname = m.group(1)
196+
await self.send_join_info(interaction, hostname)
177197

178198
async def _borked(self, interaction: discord.Interaction):
179199
await interaction.followup.send(
180200
"This machine info card is borked, complain to ben", ephemeral=True
181201
)
182202

183-
@ui.button(label="Click here to join", custom_id="machine_info_join")
184-
async def join(self, interaction: discord.Interaction, button: ui.Button):
185-
assert interaction.message is not None
186-
203+
async def send_join_info(self, interaction: discord.Interaction, hostname: str):
187204
await interaction.response.defer(ephemeral=True)
188205

189-
display: Optional[MachineDisplay] = await MachineDisplay.get(
190-
interaction.message.id
191-
)
192-
if display is None:
193-
await self._borked(interaction)
194-
return
195-
196-
machines = await get_devices_with_hostname(display.machine_hostname)
206+
machines = await get_devices_with_hostname(hostname)
197207
if len(machines) == 0:
198208
await self._borked(interaction)
199209
return
200210

201211
if len(machines) > 1:
202-
logger.warn("Got more than one machine for %s", display.machine_hostname)
212+
logger.warn("Got more than one machine for %s", hostname)
203213

204214
machine = machines[0]
205215

@@ -218,20 +228,6 @@ async def join(self, interaction: discord.Interaction, button: ui.Button):
218228
msg, view=discord.ui.View().add_item(button), ephemeral=True
219229
)
220230

221-
222-
@app_commands.guild_only()
223-
class Infra(commands.GroupCog, name="infra"):
224-
def __init__(self, bot: LUHackBot):
225-
self.bot = bot
226-
227-
self._machine_info_view = MachineInfoView(bot)
228-
self.bot.add_view(self._machine_info_view)
229-
230-
super().__init__()
231-
232-
async def interaction_check(self, interaction: discord.Interaction):
233-
return await is_authed_int(interaction)
234-
235231
@app_commands.command(name="join")
236232
@app_commands.describe(name="The machine to join")
237233
@app_commands.autocomplete(name=machine_autocomplete)
@@ -271,35 +267,53 @@ async def join_server(self, interaction: discord.Interaction, *, name: str):
271267
await interaction.followup.send(msg, view=discord.ui.View().add_item(button))
272268

273269
@app_commands.command(name="display")
274-
@app_commands.describe(name="The machine to generate a display for")
275-
@app_commands.autocomplete(name=machine_autocomplete)
270+
@app_commands.describe(hostnames="The machine to generate a display for")
271+
@app_commands.autocomplete(hostnames=list_sep_choices(hostname_autocomplete))
276272
@app_commands.default_permissions(manage_channels=True)
277273
@app_commands.check(is_admin_int)
278-
async def display_server(self, interaction: discord.Interaction, *, name: str):
279-
"""Generate a message with info about a machine."""
274+
async def display_server(
275+
self,
276+
interaction: discord.Interaction,
277+
*,
278+
hostnames: app_commands.Transform[list[str], ListSepTransformer],
279+
):
280+
"""Generate a message with info about some machines."""
280281

281-
if (device := await get_device(name)) is None:
282-
await interaction.response.send_message(
283-
"I don't know that device", ephemeral=True
284-
)
282+
await interaction.response.defer()
283+
284+
machines: list[Device] = []
285+
for hostname in hostnames:
286+
machines.extend(await get_devices_with_hostname(hostname))
287+
288+
if not machines:
289+
await interaction.followup.send("No servers lol")
285290
return
286291

287-
ip = device.addresses[0]
292+
names = ", ".join(f"`{machine.name}`" for machine in machines)
293+
ips = ", ".join(f"`{machine.addresses[0]}`" for machine in machines)
294+
s = "s" if len(machines) > 1 else ""
288295

289296
msg = textwrap.dedent(
290297
f"""
291-
**Machine:** `{device.name}`
292-
**IP:** `{ip}`
298+
**Machine{s}:** {names}
299+
**IP{s}:** {ips}
300+
Click one of the buttons to join
293301
"""
294302
)
295303

296-
await interaction.response.send_message(
304+
view = ui.View(timeout=None)
305+
for machine in machines:
306+
view.add_item(
307+
ui.Button(
308+
style=discord.ButtonStyle.grey,
309+
label=f"Join {machine.name}",
310+
custom_id=f"machine_info_join:{machine.hostname}",
311+
)
312+
)
313+
314+
await interaction.followup.send(
297315
msg,
298-
view=self._machine_info_view,
299-
)
300-
message = await interaction.original_response()
301-
await MachineDisplay.create(
302-
discord_message_id=message.id, machine_hostname=device.hostname
316+
view=view,
303317
)
304318

305319
@app_commands.command(name="describe")

luhack_bot/db/models.py

-8
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,3 @@ class Machine(db.Model):
160160

161161
hostname = db.Column(db.Text(), nullable=False, unique=True)
162162
description = db.Column(db.Text(), nullable=False)
163-
164-
class MachineDisplay(db.Model):
165-
"""Machine display messages"""
166-
167-
__tablename__ = "machine_displays"
168-
169-
discord_message_id = db.Column(db.BigInteger(), primary_key=True)
170-
machine_hostname = db.Column(db.Text(), nullable=False)
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from discord import app_commands
2+
import discord
3+
from typing import Callable, Coroutine, Any
4+
5+
6+
class ListSepTransformer(app_commands.Transformer):
7+
async def transform(
8+
self, interaction: discord.Interaction, value: str
9+
) -> list[str]:
10+
return [x.strip() for x in value.split(",")]
11+
12+
13+
def list_sep_choices(
14+
inner: Callable[
15+
[discord.Interaction, str], Coroutine[Any, Any, list[app_commands.Choice[str]]]
16+
]
17+
) -> Callable[
18+
[discord.Interaction, str], Coroutine[Any, Any, list[app_commands.Choice[str]]]
19+
]:
20+
async def impl(
21+
interaction: discord.Interaction, current: str
22+
) -> list[app_commands.Choice[str]]:
23+
*prev_completed, to_complete = [item.strip() for item in current.split(",")]
24+
25+
completed = await inner(interaction, to_complete)
26+
27+
return [
28+
app_commands.Choice(
29+
name=", ".join([*prev_completed, c.name]),
30+
value=", ".join([*prev_completed, c.value]),
31+
)
32+
for c in completed
33+
]
34+
35+
return impl

0 commit comments

Comments
 (0)