From 0c5e91456b76ddef4d130daae9a5f3a32e857d46 Mon Sep 17 00:00:00 2001 From: Artur Czepiel Date: Sun, 9 Mar 2025 15:01:53 +0100 Subject: [PATCH 1/3] scheduled messages v1 --- deploy/playbooks/01_setup.yml | 12 ++++++ deploy/playbooks/03_app.yml | 11 +++++ intbot/core/bot/scheduled_messages.py | 40 +++++++++++++++++++ .../commands/send_scheduled_message.py | 28 +++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 intbot/core/bot/scheduled_messages.py create mode 100644 intbot/core/management/commands/send_scheduled_message.py diff --git a/deploy/playbooks/01_setup.yml b/deploy/playbooks/01_setup.yml index 4768bf3..7865900 100644 --- a/deploy/playbooks/01_setup.yml +++ b/deploy/playbooks/01_setup.yml @@ -92,4 +92,16 @@ - name: Display the public key debug: msg: "For private repositories, make sure to put this key as deploy key on github: {{ deploy_key.content | b64decode }}" + + - name: Install cron + apt: + name: cron + state: present + update_cache: yes + + - name: Enable cron service + service: + name: cron + enabled: yes + state: started diff --git a/deploy/playbooks/03_app.yml b/deploy/playbooks/03_app.yml index 2a90d57..7ab77bb 100644 --- a/deploy/playbooks/03_app.yml +++ b/deploy/playbooks/03_app.yml @@ -45,5 +45,16 @@ - name: Migrate on prod shell: "make prod/migrate" + - name: Configure scheduled message for standup + become: yes + cron: + name: "Monday standup message" + user: "{{ app_user }}" + minute: "0" + hour: "11" + weekday: "1" + job: "cd /home/{{ app_user }} && docker compose exec -T app make in-container/manage ARG='send_scheduled_message --type=standup'" + state: present + - name: Restart everything and finish shell: "docker compose up -d" diff --git a/intbot/core/bot/scheduled_messages.py b/intbot/core/bot/scheduled_messages.py new file mode 100644 index 0000000..fcf9d37 --- /dev/null +++ b/intbot/core/bot/scheduled_messages.py @@ -0,0 +1,40 @@ +""" +Factory functions for scheduled messages +""" + +from typing import Dict, Callable + +from django.utils import timezone + +from core.models import DiscordMessage +from core.bot.channel_router import Channels + + +def standup_message_factory() -> DiscordMessage: + """Factory for weekly standup message.""" + today = timezone.now() + week_number = today.isocalendar()[1] + + content = ( + f"## Monday Standup - Week {week_number}\n\n" + f"Good morning team! Please share:\n\n" + f"1. What you accomplished last week\n" + f"2. What you're planning for this week\n" + f"3. Any blockers or assistance needed" + ) + + # Using the test channel for now - replace with appropriate channel later + channel = Channels.test_channel + + return DiscordMessage( + channel_id=channel.channel_id, + channel_name=channel.channel_name, + content=content, + sent_at=None + ) + + +# Registry of message factories +MESSAGE_FACTORIES: Dict[str, Callable[[], DiscordMessage]] = { + "standup": standup_message_factory, +} \ No newline at end of file diff --git a/intbot/core/management/commands/send_scheduled_message.py b/intbot/core/management/commands/send_scheduled_message.py new file mode 100644 index 0000000..dc39fa0 --- /dev/null +++ b/intbot/core/management/commands/send_scheduled_message.py @@ -0,0 +1,28 @@ +from django.core.management.base import BaseCommand + +from core.bot.scheduled_messages import MESSAGE_FACTORIES + + +class Command(BaseCommand): + help = "Sends a scheduled message to Discord" + + def add_arguments(self, parser): + parser.add_argument( + "--type", + required=True, + choices=MESSAGE_FACTORIES.keys(), + help="Message type to send" + ) + + def handle(self, *args, **options): + message_type = options["type"] + + factory = MESSAGE_FACTORIES[message_type] + message = factory() + message.save() + + self.stdout.write( + self.style.SUCCESS( + f"Scheduled '{message_type}' message for channel {message.channel_name}" + ) + ) \ No newline at end of file From 314c1512032a1cae54f612c8a021ddbddc280d55 Mon Sep 17 00:00:00 2001 From: Artur Czepiel Date: Mon, 12 May 2025 09:25:53 +0200 Subject: [PATCH 2/3] improved cron messages --- deploy/playbooks/03_app.yml | 11 --------- deploy/playbooks/04_cron.yml | 8 +++++++ deploy/templates/app/Makefile.app.j2 | 5 +++- intbot/core/bot/channel_router.py | 6 +++++ intbot/core/bot/config.py | 10 ++++++++ intbot/core/bot/scheduled_messages.py | 20 +++++++--------- .../commands/send_scheduled_message.py | 23 +++++++++---------- intbot/intbot/settings.py | 6 +++++ 8 files changed, 53 insertions(+), 36 deletions(-) create mode 100644 intbot/core/bot/config.py diff --git a/deploy/playbooks/03_app.yml b/deploy/playbooks/03_app.yml index 7ab77bb..2a90d57 100644 --- a/deploy/playbooks/03_app.yml +++ b/deploy/playbooks/03_app.yml @@ -45,16 +45,5 @@ - name: Migrate on prod shell: "make prod/migrate" - - name: Configure scheduled message for standup - become: yes - cron: - name: "Monday standup message" - user: "{{ app_user }}" - minute: "0" - hour: "11" - weekday: "1" - job: "cd /home/{{ app_user }} && docker compose exec -T app make in-container/manage ARG='send_scheduled_message --type=standup'" - state: present - - name: Restart everything and finish shell: "docker compose up -d" diff --git a/deploy/playbooks/04_cron.yml b/deploy/playbooks/04_cron.yml index 2b9f8fc..e9b2cd6 100644 --- a/deploy/playbooks/04_cron.yml +++ b/deploy/playbooks/04_cron.yml @@ -15,3 +15,11 @@ minute: "5" hour: "6" job: "make prod/cron/pretix" + + - name: "Schedule standup message on Monday morning" + ansible.builtin.cron: + name: "Send a standup message" + minute: "5" + hour: "9" + weekday: "1" + job: "make prod/cron/standup" diff --git a/deploy/templates/app/Makefile.app.j2 b/deploy/templates/app/Makefile.app.j2 index 5a21e97..9a016ad 100644 --- a/deploy/templates/app/Makefile.app.j2 +++ b/deploy/templates/app/Makefile.app.j2 @@ -7,7 +7,7 @@ prod/migrate: $(MAKE_APP) in-container/migrate prod/shell: - $(MAKE_APP) in-container/shell + $(MAKE_APP) in-container/manage ARG="shell_plus" prod/db_shell: $(MAKE_APP) in-container/db_shell @@ -22,5 +22,8 @@ prod/cron/pretalx: prod/cron/pretix: $(MAKE_APP) in-container/manage ARG="download_pretix_data --event=ep2025" +prod/cron/standup: + $(MAKE_APP) in-container/manage ARG="send_scheduled_message --template=standup" + logs: docker compose logs -f diff --git a/intbot/core/bot/channel_router.py b/intbot/core/bot/channel_router.py index be00b10..3209259 100644 --- a/intbot/core/bot/channel_router.py +++ b/intbot/core/bot/channel_router.py @@ -77,6 +77,12 @@ class Channels: channel_name=settings.DISCORD_GRANTS_CHANNEL_NAME, ) + # scheduled messages + standup_channel = DiscordChannel( + channel_id=settings.DISCORD_STANDUP_CHANNEL_ID, + channel_name=settings.DISCORD_STANDUP_CHANNEL_NAME, + ) + def discord_channel_router(wh: Webhook) -> DiscordChannel: if wh.source == "github": diff --git a/intbot/core/bot/config.py b/intbot/core/bot/config.py new file mode 100644 index 0000000..af1e4af --- /dev/null +++ b/intbot/core/bot/config.py @@ -0,0 +1,10 @@ +""" +Configuration for all things discord related +""" +from django.conf import settings + +class Roles: + # We keep this statically defined, because we want to use it in templates + # for scheduled messages, and we want to make the scheduling available + # withotu access to the discord server. + board_member_role_id = settings.DISCORD_BOARD_MEMBER_ROLE_ID diff --git a/intbot/core/bot/scheduled_messages.py b/intbot/core/bot/scheduled_messages.py index fcf9d37..a810c2d 100644 --- a/intbot/core/bot/scheduled_messages.py +++ b/intbot/core/bot/scheduled_messages.py @@ -4,27 +4,23 @@ from typing import Dict, Callable -from django.utils import timezone - from core.models import DiscordMessage from core.bot.channel_router import Channels +from core.bot.config import Roles def standup_message_factory() -> DiscordMessage: """Factory for weekly standup message.""" - today = timezone.now() - week_number = today.isocalendar()[1] - content = ( - f"## Monday Standup - Week {week_number}\n\n" - f"Good morning team! Please share:\n\n" - f"1. What you accomplished last week\n" - f"2. What you're planning for this week\n" - f"3. Any blockers or assistance needed" + f"## Happy Monday <@&{Roles.board_member_role_id}>!\n\n" + f"Let's keep everyone in the loop :)\n\n" + f"(1) What you worked on last week\n" + f"(2) What are you planning to work on this week\n" + f"(3) Are there any blockers or where could you use some help?" ) # Using the test channel for now - replace with appropriate channel later - channel = Channels.test_channel + channel = Channels.standup_channel return DiscordMessage( channel_id=channel.channel_id, @@ -37,4 +33,4 @@ def standup_message_factory() -> DiscordMessage: # Registry of message factories MESSAGE_FACTORIES: Dict[str, Callable[[], DiscordMessage]] = { "standup": standup_message_factory, -} \ No newline at end of file +} diff --git a/intbot/core/management/commands/send_scheduled_message.py b/intbot/core/management/commands/send_scheduled_message.py index dc39fa0..46ba41a 100644 --- a/intbot/core/management/commands/send_scheduled_message.py +++ b/intbot/core/management/commands/send_scheduled_message.py @@ -1,28 +1,27 @@ -from django.core.management.base import BaseCommand - from core.bot.scheduled_messages import MESSAGE_FACTORIES +from django.core.management.base import BaseCommand class Command(BaseCommand): help = "Sends a scheduled message to Discord" - + def add_arguments(self, parser): parser.add_argument( - "--type", + "--template", required=True, choices=MESSAGE_FACTORIES.keys(), - help="Message type to send" + help="Message template to send", ) - + def handle(self, *args, **options): - message_type = options["type"] - - factory = MESSAGE_FACTORIES[message_type] + message_template = options["template"] + + factory = MESSAGE_FACTORIES[message_template] message = factory() message.save() - + self.stdout.write( self.style.SUCCESS( - f"Scheduled '{message_type}' message for channel {message.channel_name}" + f"Scheduled '{message_template}' message for channel {message.channel_name}" ) - ) \ No newline at end of file + ) diff --git a/intbot/intbot/settings.py b/intbot/intbot/settings.py index 80b5fe6..1550ea3 100644 --- a/intbot/intbot/settings.py +++ b/intbot/intbot/settings.py @@ -178,6 +178,12 @@ def get(name) -> str: DISCORD_GRANTS_CHANNEL_ID = get("DISCORD_GRANTS_CHANNEL_ID") DISCORD_GRANTS_CHANNEL_NAME = get("DISCORD_GRANTS_CHANNEL_NAME") +DISCORD_STANDUP_CHANNEL_ID = get("DISCORD_STANDUP_CHANNEL_ID") +DISCORD_STANDUP_CHANNEL_NAME = get("DISCORD_STANDUP_CHANNEL_NAME") + +# Discord Roles +DISCORD_BOARD_MEMBER_ROLE_ID = get("DISCORD_BOARD_MEMBER_ROLE_ID") + # Github GITHUB_API_TOKEN = get("GITHUB_API_TOKEN") GITHUB_WEBHOOK_SECRET_TOKEN = get("GITHUB_WEBHOOK_SECRET_TOKEN") From 44f3be782c44a4167d7cb8dd8c2052293338c597 Mon Sep 17 00:00:00 2001 From: Artur Czepiel Date: Mon, 12 May 2025 09:28:24 +0200 Subject: [PATCH 3/3] remove cron installation --- deploy/playbooks/01_setup.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/deploy/playbooks/01_setup.yml b/deploy/playbooks/01_setup.yml index 7865900..0e2be55 100644 --- a/deploy/playbooks/01_setup.yml +++ b/deploy/playbooks/01_setup.yml @@ -92,16 +92,3 @@ - name: Display the public key debug: msg: "For private repositories, make sure to put this key as deploy key on github: {{ deploy_key.content | b64decode }}" - - - name: Install cron - apt: - name: cron - state: present - update_cache: yes - - - name: Enable cron service - service: - name: cron - enabled: yes - state: started -