From 660c4dc9fe4d3a06840bd2825dee2da2cd107007 Mon Sep 17 00:00:00 2001 From: wizzdom Date: Sat, 23 Nov 2024 02:09:54 +0000 Subject: [PATCH 01/10] add /agenda command --- .env.sample | 3 + src/config.py | 4 ++ src/extensions/agenda.py | 152 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 src/extensions/agenda.py diff --git a/.env.sample b/.env.sample index efb7da1..f3de66f 100644 --- a/.env.sample +++ b/.env.sample @@ -5,3 +5,6 @@ DISCORD_UID_MAP="user1=1234,user2=4567,user3=7890" LDAP_USERNAME= LDAP_PASSWORD= + + +AGENDA_TEMPLATE_URL="https://md.redbrick.dcu.ie/foo" diff --git a/src/config.py b/src/config.py index e651c60..9e7d37a 100644 --- a/src/config.py +++ b/src/config.py @@ -13,6 +13,8 @@ "bot-private": 853071983452225536, "bots-cmt": 1162038557922312312, "action-items": 1029132014210793513, + "cowboys-and-cowgirls-committee": 578712722330353684, + "committee-announcements": 763113612340363304, } # TODO: query API/LDAP for these @@ -30,3 +32,5 @@ LDAP_USERNAME = os.environ.get("LDAP_USERNAME") LDAP_PASSWORD = os.environ.get("LDAP_PASSWORD") + +AGENDA_TEMPLATE_URL = os.environ.get("AGENDA_TEMPLATE_URL") diff --git a/src/extensions/agenda.py b/src/extensions/agenda.py new file mode 100644 index 0000000..7cfa4f3 --- /dev/null +++ b/src/extensions/agenda.py @@ -0,0 +1,152 @@ +import arc +import hikari +import aiohttp +from urllib.parse import urlparse +import datetime + +from src.utils import role_mention, hedgedoc_login +from src.hooks import restrict_to_channels, restrict_to_roles +from src.config import CHANNEL_IDS, ROLE_IDS, UID_MAPS, AGENDA_TEMPLATE_URL + + +plugin = arc.GatewayPlugin(name="Agenda") + + +def generate_date_choices(): + """Generate date options for the next 7 days.""" + today = datetime.date.today() + return [(today + datetime.timedelta(days=i)).strftime("%Y-%m-%d") for i in range(7)] + + +def generate_time_choices(): + """Generate time options for every hour""" + base_time = datetime.time(0, 0) + times = [] + for hour in range(24): + current_time = ( + datetime.datetime.combine(datetime.date.today(), base_time) + + datetime.timedelta(hours=hour) + ).time() + times.append(current_time.strftime("%H:%M")) + return times + + +@plugin.include +@arc.with_hook(restrict_to_channels(channel_ids=[CHANNEL_IDS["bots-cmt"]])) +@arc.with_hook(restrict_to_roles(role_ids=[ROLE_IDS["committee"]])) +@arc.slash_command( + "agenda", + "Generate a new agenda for committee meetings", + is_dm_enabled=False, + autodefer=arc.AutodeferMode.EPHEMERAL, +) +async def gen_agenda( + ctx: arc.GatewayContext, + date: arc.Option[ + str, + arc.StrParams("Select a date", choices=generate_date_choices()), + ], + time: arc.Option[ + str, + arc.StrParams( + "Enter the time in HH:MM format", choices=generate_time_choices() + ), + ], + room: arc.Option[ + str, + arc.StrParams("Select a Room"), + ], + url: arc.Option[ + str, arc.StrParams("URL of the agenda template from the MD") + ] = AGENDA_TEMPLATE_URL, + aiohttp_client: aiohttp.ClientSession = arc.inject(), +) -> None: + """Create a new agenda for committee meetings""" + + parsed_date = datetime.datetime.strptime(date, "%Y-%m-%d").date() + parsed_time = datetime.datetime.strptime(time, "%H:%M").time() + + parsed_datetime = datetime.datetime.combine(parsed_date, parsed_time) + + DATE = parsed_datetime.strftime("%Y-%m-%d") + TIME = parsed_datetime.strftime("%H:%M") + full_datetime = parsed_datetime.strftime("%Y-%m-%d %A %H:%M") + + if "https://md.redbrick.dcu.ie" not in url: + await ctx.respond( + f"❌ `{url}` is not a valid MD URL. Please provide a valid URL.", + flags=hikari.MessageFlag.EPHEMERAL, + ) + return + + await hedgedoc_login(aiohttp_client) + + parsed_url = urlparse(url) + request_url = ( + f"{parsed_url.scheme}://{parsed_url.hostname}{parsed_url.path}/download" + ) + + async with aiohttp_client.get(request_url) as response: + if response.status != 200: + await ctx.respond( + f"❌ Failed to fetch the minutes. Status code: `{response.status}`", + flags=hikari.MessageFlag.EPHEMERAL, + ) + return + + content = await response.text() + + modified_content = content.format(DATE=DATE, TIME=TIME, ROOM=room) + + post_url = f"{parsed_url.scheme}://{parsed_url.hostname}/new" + post_headers = {"Content-Type": "text/markdown"} + + async with aiohttp_client.post( + url=post_url, + headers=post_headers, + data=modified_content, + ) as response: + if response.status != 200: + await ctx.respond( + f"❌ Failed to generate the agenda. Status code: `{response.status}`", + flags=hikari.MessageFlag.EPHEMERAL, + ) + return + + new_agenda_url = response.url + announce_text = f""" +## 📣 Agenda for this week's meeting | {full_datetime} | {room} <:bigRed:634311607039819776> + + +[{DATE} Agenda](<{new_agenda_url}>) + +- Please fill in your sections with anything you would like to discuss. +- Put your Redbrick `username` beside any agenda items you add. +- If you can't attend the meeting, please DM <@{UID_MAPS["kronos"]}> with your reason. +- React with <:bigRed:634311607039819776> if you can make it. + +||{role_mention(ROLE_IDS["committee"])}|| + """ + + announce = await plugin.client.rest.create_message( + CHANNEL_IDS["bots-cmt"], + content=announce_text, + ) + + await plugin.client.rest.add_reaction( + channel=announce.channel_id, + message=announce.id, + emoji="<:bigRed:634311607039819776>", + ) + + # respond with success if it executes successfully + await ctx.respond( + "✅ Agenda generated. Announcement sent successfully!", + flags=hikari.MessageFlag.EPHEMERAL, + ) + return + + +@arc.loader +def loader(client: arc.GatewayClient) -> None: + client.add_plugin(plugin) From 096bc23aa9f9e2caa2f213308bf557dcc345bec9 Mon Sep 17 00:00:00 2001 From: wizzdom Date: Sat, 23 Nov 2024 02:21:02 +0000 Subject: [PATCH 02/10] fix emoji reaction --- src/extensions/agenda.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions/agenda.py b/src/extensions/agenda.py index 7cfa4f3..813a5e3 100644 --- a/src/extensions/agenda.py +++ b/src/extensions/agenda.py @@ -136,7 +136,7 @@ async def gen_agenda( await plugin.client.rest.add_reaction( channel=announce.channel_id, message=announce.id, - emoji="<:bigRed:634311607039819776>", + emoji=hikari.CustomEmoji(id=634311607039819776, name="bigRed"), ) # respond with success if it executes successfully From 2b7c283cea1a5f902e005d46f9352ca51c3cd35d Mon Sep 17 00:00:00 2001 From: wizzdom Date: Sat, 23 Nov 2024 02:24:59 +0000 Subject: [PATCH 03/10] really fix emoji reaction --- src/extensions/agenda.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/extensions/agenda.py b/src/extensions/agenda.py index 813a5e3..1b1655b 100644 --- a/src/extensions/agenda.py +++ b/src/extensions/agenda.py @@ -136,7 +136,9 @@ async def gen_agenda( await plugin.client.rest.add_reaction( channel=announce.channel_id, message=announce.id, - emoji=hikari.CustomEmoji(id=634311607039819776, name="bigRed"), + emoji=hikari.CustomEmoji( + id=634311607039819776, name="bigRed", is_animated=False + ), ) # respond with success if it executes successfully From 3cebc2e69210e4cde4441b22aed2d1f3855b8c31 Mon Sep 17 00:00:00 2001 From: wizzdom Date: Sat, 23 Nov 2024 02:29:36 +0000 Subject: [PATCH 04/10] update envvars for nomad job --- .github/deploy/production.hcl | 1 + .github/deploy/review.hcl | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/deploy/production.hcl b/.github/deploy/production.hcl index 7a313d5..1f25176 100644 --- a/.github/deploy/production.hcl +++ b/.github/deploy/production.hcl @@ -27,6 +27,7 @@ TOKEN={{ key "blockbot/discord/token" }} LDAP_USERNAME={{ key "blockbot/ldap/username" }} LDAP_PASSWORD={{ key "blockbot/ldap/password" }} DISCORD_UID_MAP={{ key "blockbot/discord/uid_map" }} +AGENDA_TEMPLATE_URL={{ key "blockbot/agenda/template_url" }} EOF destination = "local/.env" env = true diff --git a/.github/deploy/review.hcl b/.github/deploy/review.hcl index 3197e6a..7c4daf5 100644 --- a/.github/deploy/review.hcl +++ b/.github/deploy/review.hcl @@ -28,6 +28,7 @@ DEBUG=true LDAP_USERNAME={{ key "blockbot-dev/ldap/username" }} LDAP_PASSWORD={{ key "blockbot-dev/ldap/password" }} DISCORD_UID_MAP={{ key "blockbot-dev/discord/uid_map" }} +AGENDA_TEMPLATE_URL={{ key "blockbot-dev/agenda/template_url" }} EOF destination = "local/.env" env = true From 42d7975fed4b4cbb299335edbc8ddc668999b649 Mon Sep 17 00:00:00 2001 From: wizzdom Date: Sat, 23 Nov 2024 02:30:48 +0000 Subject: [PATCH 05/10] make mentions actually mention --- src/extensions/agenda.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/extensions/agenda.py b/src/extensions/agenda.py index 1b1655b..1420b11 100644 --- a/src/extensions/agenda.py +++ b/src/extensions/agenda.py @@ -130,6 +130,9 @@ async def gen_agenda( announce = await plugin.client.rest.create_message( CHANNEL_IDS["bots-cmt"], + mentions_everyone=False, + user_mentions=True, + role_mentions=True, content=announce_text, ) From 35d0e651eae7e4113ff32716709c4bc8e5578d87 Mon Sep 17 00:00:00 2001 From: wizzdom Date: Sat, 23 Nov 2024 03:39:07 +0000 Subject: [PATCH 06/10] make command group, add template subcommand --- src/extensions/agenda.py | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/extensions/agenda.py b/src/extensions/agenda.py index 1420b11..8e295b5 100644 --- a/src/extensions/agenda.py +++ b/src/extensions/agenda.py @@ -11,6 +11,8 @@ plugin = arc.GatewayPlugin(name="Agenda") +agenda = plugin.include_slash_group("agenda", "Interact with the agenda.") + def generate_date_choices(): """Generate date options for the next 7 days.""" @@ -31,13 +33,12 @@ def generate_time_choices(): return times -@plugin.include +@agenda.include @arc.with_hook(restrict_to_channels(channel_ids=[CHANNEL_IDS["bots-cmt"]])) @arc.with_hook(restrict_to_roles(role_ids=[ROLE_IDS["committee"]])) -@arc.slash_command( - "agenda", +@arc.slash_subcommand( + "generate", "Generate a new agenda for committee meetings", - is_dm_enabled=False, autodefer=arc.AutodeferMode.EPHEMERAL, ) async def gen_agenda( @@ -61,7 +62,7 @@ async def gen_agenda( ] = AGENDA_TEMPLATE_URL, aiohttp_client: aiohttp.ClientSession = arc.inject(), ) -> None: - """Create a new agenda for committee meetings""" + """Generate a new agenda for committee meetings""" parsed_date = datetime.datetime.strptime(date, "%Y-%m-%d").date() parsed_time = datetime.datetime.strptime(time, "%H:%M").time() @@ -70,7 +71,7 @@ async def gen_agenda( DATE = parsed_datetime.strftime("%Y-%m-%d") TIME = parsed_datetime.strftime("%H:%M") - full_datetime = parsed_datetime.strftime("%Y-%m-%d %A %H:%M") + full_datetime = parsed_datetime.strftime("%A, %Y-%m-%d %H:%M") if "https://md.redbrick.dcu.ie" not in url: await ctx.respond( @@ -152,6 +153,33 @@ async def gen_agenda( return +@agenda.include +@arc.with_hook(restrict_to_roles(role_ids=[ROLE_IDS["committee"]])) +@arc.slash_subcommand( + "template", + "View the agenda template", +) +async def view_template( + ctx: arc.GatewayContext, +) -> None: + """View the agenda template""" + url = AGENDA_TEMPLATE_URL + image = "https://cdn.redbrick.dcu.ie/hedgedoc-uploads/sonic-the-hedgedoc.png" + + embed = hikari.Embed( + title="Agenda Template", + url=url, + description="Click the link above to view the agenda template.\n\n **NOTE:** Any edits made to this template will affect the generated agenda.", + colour=0x5865F2, + ) + embed = embed.set_image(image) + + await ctx.respond( + embed, + flags=hikari.MessageFlag.EPHEMERAL, + ) + + @arc.loader def loader(client: arc.GatewayClient) -> None: client.add_plugin(plugin) From e5d273e2fa26d87be8aa327d757d3dd8ca9efa73 Mon Sep 17 00:00:00 2001 From: wizzdom Date: Sat, 23 Nov 2024 03:50:28 +0000 Subject: [PATCH 07/10] cleanup, restrict to proper channels --- src/extensions/agenda.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/extensions/agenda.py b/src/extensions/agenda.py index 8e295b5..1c03c51 100644 --- a/src/extensions/agenda.py +++ b/src/extensions/agenda.py @@ -34,7 +34,15 @@ def generate_time_choices(): @agenda.include -@arc.with_hook(restrict_to_channels(channel_ids=[CHANNEL_IDS["bots-cmt"]])) +@arc.with_hook( + restrict_to_channels( + channel_ids=[ + CHANNEL_IDS[ + "bots-cmt", "committee-announcements", "cowboys-and-cowgirls-committee" + ] + ] + ) +) @arc.with_hook(restrict_to_roles(role_ids=[ROLE_IDS["committee"]])) @arc.slash_subcommand( "generate", @@ -73,6 +81,8 @@ async def gen_agenda( TIME = parsed_datetime.strftime("%H:%M") full_datetime = parsed_datetime.strftime("%A, %Y-%m-%d %H:%M") + ROOM = room + if "https://md.redbrick.dcu.ie" not in url: await ctx.respond( f"❌ `{url}` is not a valid MD URL. Please provide a valid URL.", @@ -97,7 +107,7 @@ async def gen_agenda( content = await response.text() - modified_content = content.format(DATE=DATE, TIME=TIME, ROOM=room) + modified_content = content.format(DATE=DATE, TIME=TIME, ROOM=ROOM) post_url = f"{parsed_url.scheme}://{parsed_url.hostname}/new" post_headers = {"Content-Type": "text/markdown"} @@ -116,7 +126,7 @@ async def gen_agenda( new_agenda_url = response.url announce_text = f""" -## 📣 Agenda for this week's meeting | {full_datetime} | {room} <:bigRed:634311607039819776> +## 📣 Agenda for this week's meeting | {full_datetime} | {ROOM} <:bigRed:634311607039819776> [{DATE} Agenda](<{new_agenda_url}>) @@ -130,7 +140,7 @@ async def gen_agenda( """ announce = await plugin.client.rest.create_message( - CHANNEL_IDS["bots-cmt"], + CHANNEL_IDS["committee-announcements"], mentions_everyone=False, user_mentions=True, role_mentions=True, From 6e9319ca6ca1347bf0566908b5a472ac3192eb29 Mon Sep 17 00:00:00 2001 From: wizzdom Date: Sun, 24 Nov 2024 10:06:54 +0000 Subject: [PATCH 08/10] hooks: fix channel_ids list --- src/extensions/agenda.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/extensions/agenda.py b/src/extensions/agenda.py index 1c03c51..33cc837 100644 --- a/src/extensions/agenda.py +++ b/src/extensions/agenda.py @@ -37,9 +37,9 @@ def generate_time_choices(): @arc.with_hook( restrict_to_channels( channel_ids=[ - CHANNEL_IDS[ - "bots-cmt", "committee-announcements", "cowboys-and-cowgirls-committee" - ] + CHANNEL_IDS["bots-cmt"], + CHANNEL_IDS["committee-announcements"], + CHANNEL_IDS["cowboys-and-cowgirls-committee"], ] ) ) From a6f382ad155260e4c253ca303315cad491944cdf Mon Sep 17 00:00:00 2001 From: wizzdom Date: Tue, 26 Nov 2024 23:56:53 +0000 Subject: [PATCH 09/10] fix error message, remove incorrect use of constants --- src/extensions/agenda.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/extensions/agenda.py b/src/extensions/agenda.py index 33cc837..1159e8f 100644 --- a/src/extensions/agenda.py +++ b/src/extensions/agenda.py @@ -77,11 +77,9 @@ async def gen_agenda( parsed_datetime = datetime.datetime.combine(parsed_date, parsed_time) - DATE = parsed_datetime.strftime("%Y-%m-%d") - TIME = parsed_datetime.strftime("%H:%M") - full_datetime = parsed_datetime.strftime("%A, %Y-%m-%d %H:%M") - - ROOM = room + formatted_date = parsed_datetime.strftime("%Y-%m-%d") + formatted_time = parsed_datetime.strftime("%H:%M") + formatted_datetime = parsed_datetime.strftime("%A, %Y-%m-%d %H:%M") if "https://md.redbrick.dcu.ie" not in url: await ctx.respond( @@ -100,14 +98,17 @@ async def gen_agenda( async with aiohttp_client.get(request_url) as response: if response.status != 200: await ctx.respond( - f"❌ Failed to fetch the minutes. Status code: `{response.status}`", + f"❌ Failed to fetch the agenda template. Status code: `{ + response.status}`", flags=hikari.MessageFlag.EPHEMERAL, ) return content = await response.text() - modified_content = content.format(DATE=DATE, TIME=TIME, ROOM=ROOM) + modified_content = content.format( + DATE=formatted_date, TIME=formatted_time, ROOM=room + ) post_url = f"{parsed_url.scheme}://{parsed_url.hostname}/new" post_headers = {"Content-Type": "text/markdown"} @@ -119,17 +120,18 @@ async def gen_agenda( ) as response: if response.status != 200: await ctx.respond( - f"❌ Failed to generate the agenda. Status code: `{response.status}`", + f"❌ Failed to generate the agenda. Status code: `{ + response.status}`", flags=hikari.MessageFlag.EPHEMERAL, ) return new_agenda_url = response.url announce_text = f""" -## 📣 Agenda for this week's meeting | {full_datetime} | {ROOM} <:bigRed:634311607039819776> +## 📣 Agenda for this week's meeting | {formatted_datetime} | {room} <:bigRed:634311607039819776> -[{DATE} Agenda](<{new_agenda_url}>) +[{formatted_date} Agenda](<{new_agenda_url}>) - Please fill in your sections with anything you would like to discuss. - Put your Redbrick `username` beside any agenda items you add. From ac0be4c71a7c1d001818618aeee8918c2b7778ab Mon Sep 17 00:00:00 2001 From: nova Date: Tue, 3 Dec 2024 21:55:32 +0000 Subject: [PATCH 10/10] typing & formatting improvements --- src/extensions/agenda.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/extensions/agenda.py b/src/extensions/agenda.py index 1159e8f..31e279d 100644 --- a/src/extensions/agenda.py +++ b/src/extensions/agenda.py @@ -14,16 +14,16 @@ agenda = plugin.include_slash_group("agenda", "Interact with the agenda.") -def generate_date_choices(): +def generate_date_choices() -> list[str]: """Generate date options for the next 7 days.""" today = datetime.date.today() return [(today + datetime.timedelta(days=i)).strftime("%Y-%m-%d") for i in range(7)] -def generate_time_choices(): +def generate_time_choices() -> list[str]: """Generate time options for every hour""" base_time = datetime.time(0, 0) - times = [] + times: list[str] = [] for hour in range(24): current_time = ( datetime.datetime.combine(datetime.date.today(), base_time) @@ -98,8 +98,7 @@ async def gen_agenda( async with aiohttp_client.get(request_url) as response: if response.status != 200: await ctx.respond( - f"❌ Failed to fetch the agenda template. Status code: `{ - response.status}`", + f"❌ Failed to fetch the agenda template. Status code: `{response.status}`", flags=hikari.MessageFlag.EPHEMERAL, ) return @@ -120,8 +119,7 @@ async def gen_agenda( ) as response: if response.status != 200: await ctx.respond( - f"❌ Failed to generate the agenda. Status code: `{ - response.status}`", + f"❌ Failed to generate the agenda. Status code: `{response.status}`", flags=hikari.MessageFlag.EPHEMERAL, ) return @@ -135,7 +133,7 @@ async def gen_agenda( - Please fill in your sections with anything you would like to discuss. - Put your Redbrick `username` beside any agenda items you add. -- If you can't attend the meeting, please DM <@{UID_MAPS["kronos"]}> with your reason. +- If you can't attend the meeting, please DM <@{UID_MAPS["kronos"]}> with your reason. - React with <:bigRed:634311607039819776> if you can make it. ||{role_mention(ROLE_IDS["committee"])}|| @@ -152,9 +150,7 @@ async def gen_agenda( await plugin.client.rest.add_reaction( channel=announce.channel_id, message=announce.id, - emoji=hikari.CustomEmoji( - id=634311607039819776, name="bigRed", is_animated=False - ), + emoji=hikari.CustomEmoji.parse("<:bigRed:634311607039819776>"), ) # respond with success if it executes successfully