diff --git a/nephthys/events/message.py b/nephthys/events/message.py index e668561..d4c3c8c 100644 --- a/nephthys/events/message.py +++ b/nephthys/events/message.py @@ -11,6 +11,7 @@ from nephthys.macros import run_macro from nephthys.utils.env import env from nephthys.utils.logging import send_heartbeat from nephthys.utils.performance import perf_timer +from nephthys.utils.slack_user import get_user_profile from nephthys.utils.ticket_methods import delete_and_clean_up_ticket from prisma.enums import TicketStatus from prisma.models import User @@ -136,42 +137,25 @@ async def handle_new_question( client (AsyncWebClient): Slack API client. db_user (User | None): The database user object, or None if user doesn't exist yet. """ + author_id = event.get("user", "unknown") + text = event.get("text", "") async with perf_timer("Slack user info fetch"): - user = event.get("user", "unknown") - text = event.get("text", "") - user_info_response = await client.users_info(user=user) or {} - - user_info = user_info_response.get("user") - if user_info: - profile_pic: str | None = user_info["profile"].get("image_512", "") - display_name: str = ( - user_info["profile"]["display_name"] or user_info["real_name"] - ) - else: - profile_pic = None - display_name = "Explorer" + author = await get_user_profile(author_id) if db_user: async with perf_timer("Getting ticket count from DB"): past_tickets = await env.db.ticket.count(where={"openedById": db_user.id}) else: past_tickets = 0 - username = (user_info or {}).get( - "name" - ) # this should never actually be empty but if it is, that is a major issue - + username = author.display_name() async with perf_timer("Creating user in DB"): - if not username: - await send_heartbeat( - f"SOMETHING HAS GONE TERRIBLY WRONG <@{user}> has no username found - <@{env.slack_maintainer_id}>" - ) db_user = await env.db.user.upsert( where={ - "slackId": user, + "slackId": author_id, }, data={ - "create": {"slackId": user, "username": username}, - "update": {"slackId": user, "username": username}, + "create": {"slackId": author_id, "username": username}, + "update": {"slackId": author_id, "username": username}, }, ) @@ -180,8 +164,8 @@ async def handle_new_question( event, client, past_tickets=past_tickets, - display_name=display_name, - profile_pic=profile_pic, + display_name=author.display_name(), + profile_pic=author.profile_pic_512x() or "", ) ticket_message_ts = ticket_message["ts"] @@ -190,9 +174,9 @@ async def handle_new_question( return user_facing_message_text = ( - env.transcript.first_ticket_create.replace("(user)", display_name) + env.transcript.first_ticket_create.replace("(user)", author.display_name()) if past_tickets == 0 - else env.transcript.ticket_create.replace("(user)", display_name) + else env.transcript.ticket_create.replace("(user)", author.display_name()) ) ticket_url = f"https://hackclub.slack.com/archives/{env.slack_ticket_channel}/p{ticket_message_ts.replace('.', '')}" diff --git a/nephthys/macros/faq.py b/nephthys/macros/faq.py index 835cb1c..91b91f7 100644 --- a/nephthys/macros/faq.py +++ b/nephthys/macros/faq.py @@ -1,6 +1,7 @@ from nephthys.actions.resolve import resolve from nephthys.macros.types import Macro from nephthys.utils.env import env +from nephthys.utils.slack_user import get_user_profile from nephthys.utils.ticket_methods import reply_to_ticket @@ -14,14 +15,9 @@ class FAQ(Macro): sender = await env.db.user.find_first(where={"id": ticket.openedById}) if not sender: return - user_info = await env.slack_client.users_info(user=sender.slackId) - name = ( - user_info["user"]["profile"].get("display_name") - or user_info["user"]["profile"].get("real_name") - or user_info["user"]["name"] - ) + user = await get_user_profile(sender.slackId) await reply_to_ticket( - 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<{env.transcript.faq_link}|here it is again>", + text=env.transcript.faq_macro.replace("(user)", user.display_name()), ticket=ticket, client=env.slack_client, ) diff --git a/nephthys/macros/fraud.py b/nephthys/macros/fraud.py index 17c109c..6a0e2dc 100644 --- a/nephthys/macros/fraud.py +++ b/nephthys/macros/fraud.py @@ -1,6 +1,7 @@ from nephthys.actions.resolve import resolve from nephthys.macros.types import Macro from nephthys.utils.env import env +from nephthys.utils.slack_user import get_user_profile from nephthys.utils.ticket_methods import reply_to_ticket @@ -9,19 +10,14 @@ class Fraud(Macro): async def run(self, ticket, helper, **kwargs): """ - A simple macro telling people to use Fraudpheus + A simple macro telling people to DM the Fraud Squad """ sender = await env.db.user.find_first(where={"id": ticket.openedById}) if not sender: return - user_info = await env.slack_client.users_info(user=sender.slackId) - name = ( - user_info["user"]["profile"].get("display_name") - or user_info["user"]["profile"].get("real_name") - or user_info["user"]["name"] - ) + user = await get_user_profile(sender.slackId) await reply_to_ticket( - text=f"Hiya {name}! Would you mind directing any fraud related queries to <@U091HC53CE8>? :rac_cute:\n\nIt'll keep your case confidential and make it easier for the fraud team to keep track of!", + text=env.transcript.fraud_macro.replace("(user)", user.display_name()), ticket=ticket, client=env.slack_client, ) diff --git a/nephthys/macros/identity.py b/nephthys/macros/identity.py index 150746c..7f2955a 100644 --- a/nephthys/macros/identity.py +++ b/nephthys/macros/identity.py @@ -1,6 +1,7 @@ from nephthys.actions.resolve import resolve from nephthys.macros.types import Macro from nephthys.utils.env import env +from nephthys.utils.slack_user import get_user_profile from nephthys.utils.ticket_methods import reply_to_ticket @@ -14,14 +15,9 @@ class Identity(Macro): sender = await env.db.user.find_first(where={"id": ticket.openedById}) if not sender: return - user_info = await env.slack_client.users_info(user=sender.slackId) - name = ( - user_info["user"]["profile"].get("display_name") - or user_info["user"]["profile"].get("real_name") - or user_info["user"]["name"] - ) + user = await get_user_profile(sender.slackId) await reply_to_ticket( - 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!", + text=env.transcript.identity_macro.replace("(user)", user.display_name()), ticket=ticket, client=env.slack_client, ) diff --git a/nephthys/macros/reopen.py b/nephthys/macros/reopen.py index 407a81c..ce28299 100644 --- a/nephthys/macros/reopen.py +++ b/nephthys/macros/reopen.py @@ -1,6 +1,9 @@ +import logging + from nephthys.macros.types import Macro from nephthys.utils.env import env from nephthys.utils.logging import send_heartbeat +from nephthys.utils.slack_user import get_user_profile from nephthys.utils.ticket_methods import reply_to_ticket from prisma.enums import TicketStatus @@ -31,20 +34,18 @@ class Reopen(Macro): client=env.slack_client, ) - user_info = await env.slack_client.users_info(user=ticket.openedBy.slackId) - name = ( - user_info["user"]["profile"]["display_name"] - or user_info["user"]["real_name"] - or "Explorer" - ) - profile_pic = user_info["user"]["profile"].get("image_512", "") + if not ticket.openedBy: + await send_heartbeat( + f"Attempted to reopen ticket (TS {ticket.msgTs}) but ticket author has not been recorded" + ) + return + author_id = ticket.openedBy.slackId + author = await get_user_profile(author_id) thread_url = f"https://hackclub.slack.com/archives/{env.slack_help_channel}/p{ticket.msgTs.replace('.', '')}" - use_impersonation = await env.workspace_admin_available() - backend_message = await env.slack_client.chat_postMessage( channel=env.slack_ticket_channel, - text=f"Reopened ticket from <@{ticket.openedBy.slackId}>: {ticket.description}", + text=f"Reopened ticket from <@{author_id}>: {ticket.description}", blocks=[ { "type": "input", @@ -65,18 +66,21 @@ class Reopen(Macro): "elements": [ { "type": "mrkdwn", - "text": f"Reopened by <@{helper.slackId}>. Originally submitted by <@{ticket.openedBy.slackId}>. <{thread_url}|View thread>.", + "text": f"Reopened by <@{helper.slackId}>. Originally submitted by <@{author_id}>. <{thread_url}|View thread>.", } ], }, ], - username=name if use_impersonation else None, - icon_url=profile_pic if use_impersonation else None, + username=author.display_name(), + icon_url=author.profile_pic_512x() or "", unfurl_links=True, unfurl_media=True, ) new_ticket_ts = backend_message["ts"] + if not new_ticket_ts: + logging.error(f"Invalid Slack message creation response: {backend_message}") + raise ValueError("Invalid Slack message creation response: no ts") await env.db.ticket.update( where={"id": ticket.id}, data={"ticketTs": new_ticket_ts}, diff --git a/nephthys/macros/shipcertqueue.py b/nephthys/macros/shipcertqueue.py index 00d4627..8c75017 100644 --- a/nephthys/macros/shipcertqueue.py +++ b/nephthys/macros/shipcertqueue.py @@ -1,6 +1,7 @@ from nephthys.actions.resolve import resolve from nephthys.macros.types import Macro from nephthys.utils.env import env +from nephthys.utils.slack_user import get_user_profile from nephthys.utils.ticket_methods import reply_to_ticket @@ -14,14 +15,9 @@ class ShipCertQueue(Macro): sender = await env.db.user.find_first(where={"id": ticket.openedById}) if not sender: return - user_info = await env.slack_client.users_info(user=sender.slackId) - name = ( - user_info["user"]["profile"].get("display_name") - or user_info["user"]["profile"].get("real_name") - or user_info["user"]["name"] - ) + user = await get_user_profile(sender.slackId) await reply_to_ticket( - text=f"Hi {name}! Unfortunately, there is a backlog of projects awaiting ship certification; please be patient. \n\n *pssst... voting more will move your project further towards the front of the queue.*", + text=f"Hi {user.display_name()}! Unfortunately, there is a backlog of projects awaiting ship certification; please be patient. \n\n *pssst... voting more will move your project further towards the front of the queue.*", ticket=ticket, client=env.slack_client, ) diff --git a/nephthys/transcripts/transcript.py b/nephthys/transcripts/transcript.py index 912a1e0..8e26acb 100644 --- a/nephthys/transcripts/transcript.py +++ b/nephthys/transcripts/transcript.py @@ -77,6 +77,19 @@ class Transcript(BaseModel): 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", ) + faq_macro: str = Field( + default="", description="Message to be sent when the FAQ macro is used" + ) + + fraud_macro: str = Field( + default="Hiya (user)! Would you mind directing any fraud related queries to <@U091HC53CE8>? :rac_cute:\n\nIt'll keep your case confidential and make it easier for the fraud team to keep track of!", + description="Message to be sent when the fraud macro is used", + ) + + identity_macro: str = Field( + default="", description="Message to be sent when the identity macro is used" + ) + home_unknown_user_title: str = Field( default=":upside-down_orpheus: woah, stop right there {name}!", description="Title for unknown user on home page", @@ -134,6 +147,12 @@ if your question has been answered, please hit the button below to mark it as re self.ticket_resolve_stale = f""":rac_nooo: it looks like this post is a bit old! if you still need help, please make a new post in <#{self.help_channel}> and someone'll be happy to help you out! ^~^ """ + if not self.faq_macro: + self.faq_macro = f"hey, (user)! this question is answered in the faq i sent earlier, please make sure to check it out! :rac_cute:\n\n<{self.faq_link}|here it is again>" + + if not self.identity_macro: + self.identity_macro = f"hey, (user)! please could you ask questions about identity verification in <#{self.identity_help_channel}>? :rac_cute:\n\nit helps the verification team keep track of questions easier!" + 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!" diff --git a/nephthys/utils/slack_user.py b/nephthys/utils/slack_user.py new file mode 100644 index 0000000..8e48a90 --- /dev/null +++ b/nephthys/utils/slack_user.py @@ -0,0 +1,36 @@ +import logging + +from slack_sdk.web.async_slack_response import AsyncSlackResponse + +from nephthys.utils.env import env + + +class UserProfileWrapper: + def __init__(self, users_info_response: AsyncSlackResponse): + user_data = users_info_response.get("user") + if not user_data: + raise ValueError(f"Slack user not found: {users_info_response}") + self.raw_data = user_data + + def display_name(self) -> str: + display_name = ( + self.raw_data["profile"].get("display_name") + or self.raw_data["profile"].get("real_name") + or self.raw_data["name"] + ) + # This should never actually be empty but if it is, that is a major issue + if not display_name: + logging.error( + f"SOMETHING HAS GONE TERRIBLY WRONG - user has no username: {self.raw_data}" + ) + return "" # idk + return display_name + + def profile_pic_512x(self) -> str | None: + return self.raw_data["profile"].get("image_512") + + +async def get_user_profile(slack_id: str) -> UserProfileWrapper: + """Retrieve the user's display name from Slack given their Slack ID.""" + response = await env.slack_client.users_info(user=slack_id) + return UserProfileWrapper(response)