From 2db250cee0002a53c0d8dc1be0132c4c980280da Mon Sep 17 00:00:00 2001 From: transcental Date: Sun, 29 Jun 2025 02:53:00 +0100 Subject: [PATCH] multi transcripts (#17) --- .env.sample | 10 +- nephthys/actions/resolve.py | 3 +- nephthys/commands/dm_magic_link.py | 17 ++- nephthys/data/transcript.py | 37 ----- nephthys/events/channel_join.py | 10 +- nephthys/events/channel_left.py | 12 -- nephthys/events/message.py | 22 ++- nephthys/macros/faq.py | 3 +- nephthys/macros/identity.py | 3 +- nephthys/transcripts/__init__.py | 9 ++ nephthys/transcripts/transcript.py | 131 ++++++++++++++++++ nephthys/transcripts/transcripts/identity.py | 30 ++++ .../transcripts/summer_of_making.py | 30 ++++ nephthys/utils/env.py | 21 +++ nephthys/views/home/unknown_user.py | 9 +- 15 files changed, 266 insertions(+), 81 deletions(-) delete mode 100644 nephthys/data/transcript.py create mode 100644 nephthys/transcripts/__init__.py create mode 100644 nephthys/transcripts/transcript.py create mode 100644 nephthys/transcripts/transcripts/identity.py create mode 100644 nephthys/transcripts/transcripts/summer_of_making.py diff --git a/.env.sample b/.env.sample index 513a1fe..f22e4cf 100644 --- a/.env.sample +++ b/.env.sample @@ -1,5 +1,13 @@ SLACK_BOT_TOKEN="xoxb-..." -SLACK_SIGNING_SECRET="" +SLACK_USER_TOKEN="xoxp-..." +SLACK_SIGNING_SECRET="..." ENVIRONMENT="development" PORT=3000 SLACK_HEARTBEAT_CHANNEL="C..." +SLACK_TICKET_CHANNEL="C..." +SLACK_BTS_CHANNEL="C..." +SLACK_USER_GROUP="S..." +SLACK_MAINTAINER_ID="U..." +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/nephthys" +SITE_URL="https://summer.hackclub.com" +SITE_API_KEY="..." diff --git a/nephthys/actions/resolve.py b/nephthys/actions/resolve.py index 36fcbe0..3c441a8 100644 --- a/nephthys/actions/resolve.py +++ b/nephthys/actions/resolve.py @@ -2,7 +2,6 @@ from datetime import datetime from slack_sdk.web.async_client import AsyncWebClient -from nephthys.data.transcript import Transcript from nephthys.utils.delete_thread import add_thread_to_delete_queue from nephthys.utils.env import env from nephthys.utils.logging import send_heartbeat @@ -51,7 +50,7 @@ async def resolve(ts: str, resolver: str, client: AsyncWebClient): await client.chat_postMessage( channel=env.slack_help_channel, - text=Transcript.ticket_resolve.format(user_id=resolver), + text=env.transcript.ticket_resolve.format(user_id=resolver), thread_ts=ts, ) diff --git a/nephthys/commands/dm_magic_link.py b/nephthys/commands/dm_magic_link.py index c9cc32e..1085422 100644 --- a/nephthys/commands/dm_magic_link.py +++ b/nephthys/commands/dm_magic_link.py @@ -5,7 +5,6 @@ from urllib.parse import quote from slack_bolt.async_app import AsyncAck from slack_sdk.web.async_client import AsyncWebClient -from nephthys.data.transcript import Transcript from nephthys.utils.env import env from nephthys.utils.logging import send_heartbeat @@ -21,7 +20,7 @@ async def dm_magic_link_cmd_callback( await client.chat_postEphemeral( channel=body["channel_id"], user=user_id, - text=Transcript.dm_magic_link_no_permission, + text=env.transcript.dm_magic_link_no_permission, ) return @@ -30,7 +29,7 @@ async def dm_magic_link_cmd_callback( await client.chat_postEphemeral( channel=body["channel_id"], user=body["user_id"], - text=Transcript.dm_magic_link_no_user, + text=env.transcript.dm_magic_link_no_user, ) parsed = re.search(r"<@([UW][A-Z0-9]+)(?:\|[^>]+)?>", cmd_text) slack_id = parsed.group(1) if parsed else None @@ -38,7 +37,7 @@ async def dm_magic_link_cmd_callback( await client.chat_postEphemeral( channel=body["channel_id"], user=body["user_id"], - text=Transcript.dm_magic_link_no_user, + text=env.transcript.dm_magic_link_no_user, ) return @@ -47,7 +46,7 @@ async def dm_magic_link_cmd_callback( await client.chat_postEphemeral( channel=body["channel_id"], user=body["user_id"], - text=Transcript.dm_magic_link_no_user, + text=env.transcript.dm_magic_link_no_user, ) return @@ -64,7 +63,7 @@ async def dm_magic_link_cmd_callback( await client.chat_postEphemeral( channel=body["channel_id"], user=body["user_id"], - text=Transcript.dm_magic_link_error.format(status=res.status), + text=env.transcript.dm_magic_link_error.format(status=res.status), ) return @@ -78,18 +77,18 @@ async def dm_magic_link_cmd_callback( await client.chat_postEphemeral( channel=body["channel_id"], user=body["user_id"], - text=Transcript.dm_magic_link_error.format(status="No link returned"), + text=env.transcript.dm_magic_link_error.format(status="No link returned"), ) return await client.chat_postEphemeral( channel=body["channel_id"], user=body["user_id"], - text=Transcript.dm_magic_link_success, + text=env.transcript.dm_magic_link_success, ) await client.chat_postMessage( channel=slack_id, - text=Transcript.dm_magic_link_message.format(magic_link=magic_link), + text=env.transcript.dm_magic_link_message.format(magic_link=magic_link), ) logging.info(f"Sent magic link to {slack_id}") diff --git a/nephthys/data/transcript.py b/nephthys/data/transcript.py deleted file mode 100644 index c0e190f..0000000 --- a/nephthys/data/transcript.py +++ /dev/null @@ -1,37 +0,0 @@ -from nephthys.utils.env import env - - -class Transcript: - FAQ_LINK = "https://hackclub.slack.com/docs/T0266FRGM/F090MQF0H2Q" - EXPLORPHEUS_PFP = "https://hc-cdn.hel1.your-objectstorage.com/s/v3/d6d828d6ba656d09a62add59dc07e2974bfdb38f_image.png" - IDENTITY_HELP_CHANNEL = "C092833JXKK" - - first_ticket_create = f""" -oh, hey (user) it looks like this is your first time here, welcome! someone should be along to help you soon but in the mean time i suggest you read the faq <{FAQ_LINK}|here>, it answers a lot of common questions. -if your question has been answered, please hit the button below to mark it as resolved - """ - - ticket_create = f""" -someone should be along to help you soon but in the mean time i suggest you read the faq <{FAQ_LINK}|here> to make sure your question hasn't already been answered. if it has been, please hit the button below to mark it as resolved :D - """ - - ticket_resolve = f""" -oh, oh! it looks like this post has been marked as resolved by <@{{user_id}}>! if you have any more questions, please make a new post in <#{env.slack_help_channel}> and someone'll be happy to help you out! not me though, i'm just a silly racoon ^-^ - """ - - home_unknown_user_title = ":upside-down_orpheus: woah, stop right there {name}!" - home_unknown_user_text = f"heyyyy, heidi here! it looks like i'm not allowed to show ya this. sorry! if you think this is a mistake, please reach out to <@{env.slack_maintainer_id}> and she'll lmk what to do!" - - not_allowed_channel = f"heya, it looks like you're not supposed to be in that channel, pls talk to <@{env.slack_maintainer_id}> if that's wrong" - - dm_magic_link_no_user = ":rac_cute: heya, please provide the user you want me to dm" - - dm_magic_link_error = f":rac_nooo: something went wrong while generating the magic link, please bug <@{env.slack_maintainer_id}> (status: {{status}})" - - dm_magic_link_success = ( - ":rac_cute: magic link sent! tell em to check their dms with me :D" - ) - - dm_magic_link_message = ":rac_cute: hey there! i got told that you got a bit stuck so here's a magic link for ya :D\n{magic_link}" - - dm_magic_link_no_permission = f":rac_nooo: you don't have permission to use this command, please bug <@{env.slack_maintainer_id}> if you think this is a mistake" diff --git a/nephthys/events/channel_join.py b/nephthys/events/channel_join.py index 36a708e..f6c12f2 100644 --- a/nephthys/events/channel_join.py +++ b/nephthys/events/channel_join.py @@ -1,23 +1,15 @@ from slack_bolt.context.ack.async_ack import AsyncAck from slack_sdk.web.async_client import AsyncWebClient -from nephthys.data.transcript import Transcript from nephthys.tasks.update_helpers import update_helpers from nephthys.utils.env import env async def channel_join(ack: AsyncAck, event: dict, client: AsyncWebClient): await ack() - user_id = event["user"] channel_id = event["channel"] if channel_id in [env.slack_bts_channel, env.slack_ticket_channel]: - users = await client.usergroups_users_list(usergroup=env.slack_user_group) - if user_id not in users.get("users", []): - await client.conversations_kick(channel=channel_id, user=user_id) - await client.chat_postMessage( - channel=user_id, text=Transcript.not_allowed_channel - ) - await update_helpers() + await update_helpers() else: return diff --git a/nephthys/events/channel_left.py b/nephthys/events/channel_left.py index 6045562..90a179a 100644 --- a/nephthys/events/channel_left.py +++ b/nephthys/events/channel_left.py @@ -13,18 +13,6 @@ async def channel_left(ack: AsyncAck, event: dict, client: AsyncWebClient): if channel_id == env.slack_help_channel: return - users = await client.usergroups_users_list(usergroup=env.slack_user_group) - new_users = users.get("users", []) - - try: - new_users.remove(user_id) - except ValueError: - return - - await client.usergroups_users_update( - usergroup=env.slack_user_group, users=new_users - ) - await env.db.user.update(where={"slackId": user_id}, data={"helper": False}) try: diff --git a/nephthys/events/message.py b/nephthys/events/message.py index f3b4447..090aa5b 100644 --- a/nephthys/events/message.py +++ b/nephthys/events/message.py @@ -3,12 +3,11 @@ from typing import Dict from slack_sdk.web.async_client import AsyncWebClient -from nephthys.data.transcript import Transcript from nephthys.macros import run_macro from nephthys.utils.env import env from nephthys.utils.logging import send_heartbeat -ALLOWED_SUBTYPES = ["file_share", "me_message"] +ALLOWED_SUBTYPES = ["file_share", "me_message", "thread_broadcast"] async def on_message(event: Dict[str, Any], client: AsyncWebClient): @@ -18,6 +17,21 @@ async def on_message(event: Dict[str, Any], client: AsyncWebClient): if "subtype" in event and event["subtype"] not in ALLOWED_SUBTYPES: return + if event.get("subtype") == "thread_broadcast": + await client.chat_delete( + channel=event["channel"], + ts=event["ts"], + as_user=True, + token=env.slack_user_token, + broadcast_delete=True, + ) + await client.chat_postEphemeral( + channel=event["channel"], + user=event["user"], + text=env.transcript.thread_broadcast_delete, + thread_ts=event["thread_ts"] if "thread_ts" in event else event["ts"], + ) + user = event.get("user", "unknown") text = event.get("text", "") @@ -118,9 +132,9 @@ async def on_message(event: Dict[str, Any], client: AsyncWebClient): ) text = ( - Transcript.first_ticket_create.replace("(user)", display_name) + env.transcript.first_ticket_create.replace("(user)", display_name) if past_tickets == 0 - else Transcript.ticket_create.replace("(user)", display_name) + else env.transcript.ticket_create.replace("(user)", display_name) ) ticket_url = f"https://hackclub.slack.com/archives/{env.slack_ticket_channel}/p{ticket['ts'].replace('.', '')}" diff --git a/nephthys/macros/faq.py b/nephthys/macros/faq.py index 9d1d594..7821672 100644 --- a/nephthys/macros/faq.py +++ b/nephthys/macros/faq.py @@ -1,5 +1,4 @@ from nephthys.actions.resolve import resolve -from nephthys.data.transcript import Transcript from nephthys.macros.types import Macro from nephthys.utils.env import env @@ -18,7 +17,7 @@ class FAQ(Macro): or user_info["user"]["name"] ) await env.slack_client.chat_postMessage( - text=f"hey, {name}! this question is answered in the faq i sent earlier, please make sure to check it out! :rac_cute:\n\n", + text=f"hey, {name}! this question is answered in the faq i sent earlier, please make sure to check it out! :rac_cute:\n\n", channel=env.slack_help_channel, thread_ts=ticket.msgTs, ) diff --git a/nephthys/macros/identity.py b/nephthys/macros/identity.py index fc684ed..03e6e30 100644 --- a/nephthys/macros/identity.py +++ b/nephthys/macros/identity.py @@ -1,5 +1,4 @@ from nephthys.actions.resolve import resolve -from nephthys.data.transcript import Transcript from nephthys.macros.types import Macro from nephthys.utils.env import env @@ -18,7 +17,7 @@ class Identity(Macro): or user_info["user"]["name"] ) await env.slack_client.chat_postMessage( - text=f"hey, {name}! please could you ask questions about identity verification in <#{Transcript.IDENTITY_HELP_CHANNEL}>? :rac_cute:\n\nit helps the verification team keep track of questions easier!", + text=f"hey, {name}! please could you ask questions about identity verification in <#{env.transcript.identity_help_channel}>? :rac_cute:\n\nit helps the verification team keep track of questions easier!", channel=env.slack_help_channel, thread_ts=ticket.msgTs, ) diff --git a/nephthys/transcripts/__init__.py b/nephthys/transcripts/__init__.py new file mode 100644 index 0000000..3405adb --- /dev/null +++ b/nephthys/transcripts/__init__.py @@ -0,0 +1,9 @@ +from typing import List +from typing import Type + +from nephthys.transcripts.transcript import Transcript +from nephthys.transcripts.transcripts.identity import Identity +from nephthys.transcripts.transcripts.summer_of_making import SummerOfMaking + + +transcripts: List[Type[Transcript]] = [Identity, SummerOfMaking] diff --git a/nephthys/transcripts/transcript.py b/nephthys/transcripts/transcript.py new file mode 100644 index 0000000..cfb39d0 --- /dev/null +++ b/nephthys/transcripts/transcript.py @@ -0,0 +1,131 @@ +from pydantic import BaseModel +from pydantic import Field +from pydantic import model_validator + + +class Transcript(BaseModel): + """Class to hold all the transcript messages and links used in the bot.""" + + class Config: + """Configuration for the Pydantic model.""" + + extra = "forbid" + + program_name: str = Field( + default="Summer of Making", description="Name of the program" + ) + program_owner: str = Field( + default="U054VC2KM9P", + description="Slack ID of the support manager", + ) + help_channel: str = Field( + default="", + description="Slack channel ID for help requests", + ) + ticket_channel: str = Field( + default="", + description="Slack channel ID for ticket creation", + ) + team_channel: str = Field( + default="", + description="Slack channel ID for team discussions and stats", + ) + + @property + def program_snake_case(self) -> str: + """Snake case version of the program name.""" + return self.program_name.lower().replace(" ", "_") + + faq_link: str = Field( + default="https://hackclub.slack.com/docs/T0266FRGM/F093F8D7EE9", + description="FAQ link URL", + ) + + summer_help_channel: str = Field( + default="C091D312J85", description="Summer help channel ID" + ) + + identity_help_channel: str = Field( + default="C092833JXKK", description="Identity help channel ID" + ) + + first_ticket_create: str = Field( + default="", description="Message for first-time ticket creators" + ) + + ticket_create: str = Field(default="", description="Message for ticket creation") + + ticket_resolve: str = Field( + default="", description="Message when ticket is resolved" + ) + + thread_broadcast_delete: str = Field( + default="hey! please keep your messages *all in one thread* to make it easier to read! i've gone ahead and removed that message from the channel for ya :D", + ) + + home_unknown_user_title: str = Field( + default=":upside-down_orpheus: woah, stop right there {name}!", + description="Title for unknown user on home page", + ) + + home_unknown_user_text: str = Field( + default="", description="Text for unknown user on home page" + ) + + not_allowed_channel: str = Field( + default="", description="Message for unauthorized channel access" + ) + + # this stuff is only required for summer of making, but it's easier to keep it here :p + dm_magic_link_no_user: str = Field( + default=":rac_cute: heya, please provide the user you want me to dm", + description="Message when no user provided for magic link DM", + ) + + dm_magic_link_error: str = Field( + default="", description="Error message for magic link generation" + ) + + dm_magic_link_success: str = Field( + default=":rac_cute: magic link sent! tell em to check their dms with me :D", + description="Success message for magic link DM", + ) + + dm_magic_link_message: str = Field( + default=":rac_cute: hey there! i got told that you got a bit stuck so here's a magic link for ya :D\n{magic_link}", + description="Magic link DM message", + ) + + dm_magic_link_no_permission: str = Field( + default="", description="No permission message for magic link command" + ) + + @model_validator(mode="after") + def set_default_messages(self): + """Set default values for messages that reference other fields""" + if not self.first_ticket_create: + self.first_ticket_create = f"""oh, hey (user) it looks like this is your first time here, welcome! someone should be along to help you soon but in the mean time i suggest you read the faq <{self.faq_link}|here>, it answers a lot of common questions. +if your question has been answered, please hit the button below to mark it as resolved + """ + + if not self.ticket_create: + self.ticket_create = f"""someone should be along to help you soon but in the mean time i suggest you read the faq <{self.faq_link}|here> to make sure your question hasn't already been answered. if it has been, please hit the button below to mark it as resolved :D + """ + + if not self.ticket_resolve: + self.ticket_resolve = f"""oh, oh! it looks like this post has been marked as resolved by <@{{user_id}}>! if you have any more questions, please make a new post in <#{self.help_channel}> and someone'll be happy to help you out! not me though, i'm just a silly racoon ^-^ + """ + + if not self.home_unknown_user_text: + self.home_unknown_user_text = f"heyyyy, heidi here! it looks like i'm not allowed to show ya this. sorry! if you think this is a mistake, please reach out to <@{self.program_owner}> and she'll lmk what to do!" + + if not self.not_allowed_channel: + self.not_allowed_channel = f"heya, it looks like you're not supposed to be in that channel, pls talk to <@{self.program_owner}> if that's wrong" + + if not self.dm_magic_link_error: + self.dm_magic_link_error = f":rac_nooo: something went wrong while generating the magic link, please bug <@{self.program_owner}> (status: {{status}})" + + if not self.dm_magic_link_no_permission: + self.dm_magic_link_no_permission = f":rac_nooo: you don't have permission to use this command, please bug <@{self.program_owner}> if you think this is a mistake" + + return self diff --git a/nephthys/transcripts/transcripts/identity.py b/nephthys/transcripts/transcripts/identity.py new file mode 100644 index 0000000..404fba9 --- /dev/null +++ b/nephthys/transcripts/transcripts/identity.py @@ -0,0 +1,30 @@ +from nephthys.transcripts.transcript import Transcript + + +class Identity(Transcript): + """Transcript for identity-help""" + + program_name: str = "Identity" + program_owner: str = "U054VC2KM9P" + + help_channel: str = "C092833JXKK" + ticket_channel: str = "C093FV95GGJ" + team_channel: str = "C091URN0G9G" + + faq_link: str = "https://hackclub.slack.com/docs/T0266FRGM/F090MQF0H2Q" + summer_help_channel: str = "C091D312J85" + identity_help_channel: str = "C092833JXKK" + + first_ticket_create: str = f""" +hi (user)! it looks like this is your first time here, welcome! someone should be along to help you soon but please read the <{faq_link}|faq>, it answers a lot of common questions. +if your question has been answered, please hit the button below to mark it as resolved +""" + ticket_create: str = f"someone should be along to help you soon but in the mean time i suggest you read the faq <{faq_link}|here> to make sure your question hasn't already been answered. if it has been, please hit the button below to mark it as resolved :D" + ticket_resolve: str = f"oh, oh! it looks like this post has been marked as resolved by <@{{user_id}}>! if you have any more questions, please make a new post in <#{help_channel}> and someone'll be happy to help you out! not me though, i'm just a silly racoon ^-^" + + home_unknown_user_title: str = ( + ":upside-down_orpheus: woah, stop right there {name}!" + ) + home_unknown_user_text: str = f"oh, oh! it looks like this post has been marked as resolved by <@{{user_id}}>! if you have any more questions, please make a new post in <#{help_channel}> and someone'll be happy to help you out! not me though, i'm just a silly racoon ^-^" + + not_allowed_channel: str = f"heya, it looks like you're not supposed to be in that channel, pls talk to <@{program_owner}> if that's wrong" diff --git a/nephthys/transcripts/transcripts/summer_of_making.py b/nephthys/transcripts/transcripts/summer_of_making.py new file mode 100644 index 0000000..ff45b96 --- /dev/null +++ b/nephthys/transcripts/transcripts/summer_of_making.py @@ -0,0 +1,30 @@ +from nephthys.transcripts.transcript import Transcript + + +class SummerOfMaking(Transcript): + """Transcript for Summer of Making program.""" + + program_name: str = "Summer of Making" + program_owner: str = "U054VC2KM9P" + + help_channel: str = "C091D312J85" + ticket_channel: str = "C091D31UC4D" + team_channel: str = "C0917TW3Z2P" + + faq_link: str = "https://hackclub.slack.com/docs/T0266FRGM/F090MQF0H2Q" + summer_help_channel: str = "C091D312J85" + identity_help_channel: str = "C092833JXKK" + + first_ticket_create: str = f""" +oh, hey (user) it looks like this is your first time here, welcome! someone should be along to help you soon but in the mean time i suggest you read the faq <{faq_link}|here>, it answers a lot of common questions. +if your question has been answered, please hit the button below to mark it as resolved +""" + ticket_create: str = f"someone should be along to help you soon but in the mean time i suggest you read the faq <{faq_link}|here> to make sure your question hasn't already been answered. if it has been, please hit the button below to mark it as resolved :D" + ticket_resolve: str = f"oh, oh! it looks like this post has been marked as resolved by <@{{user_id}}>! if you have any more questions, please make a new post in <#{help_channel}> and someone'll be happy to help you out! not me though, i'm just a silly racoon ^-^" + + home_unknown_user_title: str = ( + ":upside-down_orpheus: woah, stop right there {name}!" + ) + home_unknown_user_text: str = f"oh, oh! it looks like this post has been marked as resolved by <@{{user_id}}>! if you have any more questions, please make a new post in <#{help_channel}> and someone'll be happy to help you out! not me though, i'm just a silly racoon ^-^" + + not_allowed_channel: str = f"heya, it looks like you're not supposed to be in that channel, pls talk to <@{program_owner}> if that's wrong" diff --git a/nephthys/utils/env.py b/nephthys/utils/env.py index cd26b66..6e8d4f1 100644 --- a/nephthys/utils/env.py +++ b/nephthys/utils/env.py @@ -4,6 +4,8 @@ from aiohttp import ClientSession from dotenv import load_dotenv from slack_sdk.web.async_client import AsyncWebClient +from nephthys.transcripts import transcripts +from nephthys.transcripts.transcript import Transcript from prisma import Prisma load_dotenv(override=True) @@ -26,6 +28,7 @@ class Environment: self.slack_bts_channel = os.environ.get("SLACK_BTS_CHANNEL", "unset") self.slack_user_group = os.environ.get("SLACK_USER_GROUP", "unset") self.slack_maintainer_id = os.environ.get("SLACK_MAINTAINER_ID", "unset") + self.program = os.environ.get("PROGRAM", "summer_of_making") self.port = int(os.environ.get("PORT", 3000)) @@ -36,8 +39,26 @@ class Environment: if unset: raise ValueError(f"Missing environment variables: {', '.join(unset)}") + transcript_instances = [program() for program in transcripts] + valid_programs = [ + program.program_snake_case for program in transcript_instances + ] + if self.program not in valid_programs: + raise ValueError( + f"Invalid PROGRAM environment variable: {self.program}. " + f"Must be one of {valid_programs}" + ) + self.session: ClientSession self.db = Prisma() + self.transcript = next( + ( + program + for program in transcript_instances + if program.program_snake_case == self.program + ), + Transcript(), + ) self.slack_client = AsyncWebClient(token=self.slack_bot_token) diff --git a/nephthys/views/home/unknown_user.py b/nephthys/views/home/unknown_user.py index c16dd5d..44155df 100644 --- a/nephthys/views/home/unknown_user.py +++ b/nephthys/views/home/unknown_user.py @@ -1,4 +1,4 @@ -from nephthys.data.transcript import Transcript +from nephthys.utils.env import env def get_unknown_user_view(name: str): @@ -9,13 +9,16 @@ def get_unknown_user_view(name: str): "type": "header", "text": { "type": "plain_text", - "text": Transcript.home_unknown_user_title.format(name=name), + "text": env.transcript.home_unknown_user_title.format(name=name), "emoji": True, }, }, { "type": "section", - "text": {"type": "mrkdwn", "text": Transcript.home_unknown_user_text}, + "text": { + "type": "mrkdwn", + "text": env.transcript.home_unknown_user_text, + }, }, ], }