From 49b1583ff3bf0a55402b2b17677550698a98079e Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 26 Jun 2023 19:16:25 +0200 Subject: [PATCH 1/6] Add command to trigger the transcription check ping manually --- bubbles/commands/check_ping.py | 11 +++++++++++ .../periodic/transcription_check_ping.py | 19 ++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 bubbles/commands/check_ping.py diff --git a/bubbles/commands/check_ping.py b/bubbles/commands/check_ping.py new file mode 100644 index 0000000..20f6ee3 --- /dev/null +++ b/bubbles/commands/check_ping.py @@ -0,0 +1,11 @@ +from utonium import Payload, Plugin + +from bubbles.commands.periodic.transcription_check_ping import transcription_check_ping + + +def check_ping(payload: Payload) -> None: + """!check_ping - Manually trigger a transcription check ping.""" + transcription_check_ping(payload.get_channel()) + + +PLUGIN = Plugin(func=check_ping, regex=r"^checkping") diff --git a/bubbles/commands/periodic/transcription_check_ping.py b/bubbles/commands/periodic/transcription_check_ping.py index 9df4755..78cd6c8 100644 --- a/bubbles/commands/periodic/transcription_check_ping.py +++ b/bubbles/commands/periodic/transcription_check_ping.py @@ -5,6 +5,8 @@ from enum import Enum from typing import Dict, List, Optional, Tuple, TypedDict +from slack_sdk.web import SlackResponse + from bubbles.commands.helper_functions_history.extract_author import extract_author from bubbles.commands.periodic import ( TRANSCRIPTION_CHECK_CHANNEL, @@ -295,7 +297,12 @@ def _get_check_reminder(aggregate: List) -> str: return reminder -def transcription_check_ping_callback() -> None: +def transcription_check_ping(channel: str) -> Optional[SlackResponse]: + """Send a reminder about open transcription checks. + + :param channel: The channel to send the reminder into. + :returns: The Slack response of sending the message, or None if something went wrong. + """ now = datetime.now(tz=timezone.utc) start_time = now - CHECK_SEARCH_START_DELTA @@ -309,7 +316,7 @@ def transcription_check_ping_callback() -> None: ) if not messages_response.get("ok"): logging.error(f"Failed to get check messages!\n{messages_response}") - return + return None # Get the reminder for the checks messages = messages_response["messages"] @@ -319,7 +326,7 @@ def transcription_check_ping_callback() -> None: # Post the reminder in Slack reminder_response = app.client.chat_postMessage( - channel=rooms_list[TRANSCRIPTION_CHECK_PING_CHANNEL], + channel=channel, link_names=1, text=reminder, unfurl_links=False, @@ -328,3 +335,9 @@ def transcription_check_ping_callback() -> None: ) if not reminder_response.get("ok"): logging.error(f"Failed to send reminder message!\n{reminder_response}") + + return reminder_response + + +def transcription_check_ping_callback() -> None: + transcription_check_ping(channel=rooms_list[TRANSCRIPTION_CHECK_CHANNEL]) From b460a7df11b83156f4b9623eef9896c7ac117384 Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 26 Jun 2023 19:28:03 +0200 Subject: [PATCH 2/6] Allow check ping to be filtered for only one user --- bubbles/commands/check_ping.py | 6 ++++-- .../commands/periodic/transcription_check_ping.py | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/bubbles/commands/check_ping.py b/bubbles/commands/check_ping.py index 20f6ee3..d7ee452 100644 --- a/bubbles/commands/check_ping.py +++ b/bubbles/commands/check_ping.py @@ -4,8 +4,10 @@ def check_ping(payload: Payload) -> None: - """!check_ping - Manually trigger a transcription check ping.""" - transcription_check_ping(payload.get_channel()) + """!check_ping [user] - Manually trigger a transcription check ping.""" + tokens = payload.cleaned_text.split() + + transcription_check_ping(payload.get_channel(), user_filter=tokens.get(1)) PLUGIN = Plugin(func=check_ping, regex=r"^checkping") diff --git a/bubbles/commands/periodic/transcription_check_ping.py b/bubbles/commands/periodic/transcription_check_ping.py index 78cd6c8..e3132a3 100644 --- a/bubbles/commands/periodic/transcription_check_ping.py +++ b/bubbles/commands/periodic/transcription_check_ping.py @@ -297,10 +297,13 @@ def _get_check_reminder(aggregate: List) -> str: return reminder -def transcription_check_ping(channel: str) -> Optional[SlackResponse]: +def transcription_check_ping( + channel: str, user_filter: Optional[str] = None +) -> Optional[SlackResponse]: """Send a reminder about open transcription checks. :param channel: The channel to send the reminder into. + :param user_filter: Only include checks of the given user (case-insensitive). :returns: The Slack response of sending the message, or None if something went wrong. """ now = datetime.now(tz=timezone.utc) @@ -321,6 +324,16 @@ def transcription_check_ping(channel: str) -> Optional[SlackResponse]: # Get the reminder for the checks messages = messages_response["messages"] checks = _extract_open_checks(messages) + + # Only consider checks for the given user, if specified + if user_filter is not None: + + def matches_filter(check: CheckData) -> bool: + check_user = check["user"] + return check_user and user_filter.lower() == check_user.lower() + + checks = [check for check in checks if matches_filter(check)] + aggregate = _aggregate_checks_by_time(checks) reminder = _get_check_reminder(aggregate) From 45b1216b611b1aa11163b3e6c3deae9820ea6edc Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 26 Jun 2023 19:31:17 +0200 Subject: [PATCH 3/6] Show checks from the start when using the check ping command --- bubbles/commands/check_ping.py | 2 +- bubbles/commands/periodic/transcription_check_ping.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bubbles/commands/check_ping.py b/bubbles/commands/check_ping.py index d7ee452..5e5dcdc 100644 --- a/bubbles/commands/check_ping.py +++ b/bubbles/commands/check_ping.py @@ -7,7 +7,7 @@ def check_ping(payload: Payload) -> None: """!check_ping [user] - Manually trigger a transcription check ping.""" tokens = payload.cleaned_text.split() - transcription_check_ping(payload.get_channel(), user_filter=tokens.get(1)) + transcription_check_ping(payload.get_channel(), user_filter=tokens.get(1), start_now=True) PLUGIN = Plugin(func=check_ping, regex=r"^checkping") diff --git a/bubbles/commands/periodic/transcription_check_ping.py b/bubbles/commands/periodic/transcription_check_ping.py index e3132a3..4e82b23 100644 --- a/bubbles/commands/periodic/transcription_check_ping.py +++ b/bubbles/commands/periodic/transcription_check_ping.py @@ -298,17 +298,21 @@ def _get_check_reminder(aggregate: List) -> str: def transcription_check_ping( - channel: str, user_filter: Optional[str] = None + channel: str, + user_filter: Optional[str] = None, + start_now: bool = False, ) -> Optional[SlackResponse]: """Send a reminder about open transcription checks. :param channel: The channel to send the reminder into. :param user_filter: Only include checks of the given user (case-insensitive). + :param start_now: If set to true, checks will be included in the reminders immediately. + Otherwise, they are only included after a delay. :returns: The Slack response of sending the message, or None if something went wrong. """ now = datetime.now(tz=timezone.utc) - start_time = now - CHECK_SEARCH_START_DELTA + start_time = now if start_now else now - CHECK_SEARCH_START_DELTA end_time = now - CHECK_SEARCH_END_DELTA messages_response = app.client.conversations_history( From 0c208556e97c2e60f1d8fb317810636c8a933ef4 Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 26 Jun 2023 19:34:38 +0200 Subject: [PATCH 4/6] Add username to message output if included in check ping command --- bubbles/commands/periodic/transcription_check_ping.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bubbles/commands/periodic/transcription_check_ping.py b/bubbles/commands/periodic/transcription_check_ping.py index 4e82b23..6f6e8d9 100644 --- a/bubbles/commands/periodic/transcription_check_ping.py +++ b/bubbles/commands/periodic/transcription_check_ping.py @@ -269,9 +269,12 @@ def _get_check_fragment(check: CheckData) -> str: return f"<{link}|u/{user}>" if link else f"{user} (LINK NOT FOUND)" -def _get_check_reminder(aggregate: List) -> str: +def _get_check_reminder(aggregate: List, user_filter: Optional[str] = None) -> str: """Get the reminder text for the checks.""" - reminder = "*Pending Transcription Checks:*\n\n" + if user_filter is not None: + reminder = f"*Pending Transcription Checks for u/{user_filter}:*\n\n" + else: + reminder = "*Pending Transcription Checks:*\n\n" for time_str, mod_aggregate in aggregate: reminder += f"*{time_str}*:\n" @@ -339,7 +342,7 @@ def matches_filter(check: CheckData) -> bool: checks = [check for check in checks if matches_filter(check)] aggregate = _aggregate_checks_by_time(checks) - reminder = _get_check_reminder(aggregate) + reminder = _get_check_reminder(aggregate, user_filter=user_filter) # Post the reminder in Slack reminder_response = app.client.chat_postMessage( From ca92f755d8d59b9a993daa19e75895c5a05f608f Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 26 Jun 2023 19:41:23 +0200 Subject: [PATCH 5/6] Handle username if prefix and formatting are included --- bubbles/commands/check_ping.py | 6 +++- bubbles/utils.py | 64 +++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/bubbles/commands/check_ping.py b/bubbles/commands/check_ping.py index 5e5dcdc..7701bf6 100644 --- a/bubbles/commands/check_ping.py +++ b/bubbles/commands/check_ping.py @@ -1,13 +1,17 @@ from utonium import Payload, Plugin from bubbles.commands.periodic.transcription_check_ping import transcription_check_ping +from bubbles.utils import parse_user def check_ping(payload: Payload) -> None: """!check_ping [user] - Manually trigger a transcription check ping.""" tokens = payload.cleaned_text.split() - transcription_check_ping(payload.get_channel(), user_filter=tokens.get(1), start_now=True) + user_raw = tokens.get(1) + user_filter = parse_user(user_raw) if user_raw is not None else None + + transcription_check_ping(payload.get_channel(), user_filter=user_filter, start_now=True) PLUGIN = Plugin(func=check_ping, regex=r"^checkping") diff --git a/bubbles/utils.py b/bubbles/utils.py index 8a8f3d4..eb1432c 100644 --- a/bubbles/utils.py +++ b/bubbles/utils.py @@ -1,7 +1,7 @@ import re import subprocess from datetime import datetime, timedelta -from typing import List, Optional +from typing import List, Optional, Match # First an amount and then a unit import pytz as pytz @@ -20,6 +20,18 @@ "years": re.compile(r"^y(?:ears?)?$"), } +# find a link in the slack format, then strip out the text at the end. +# they're formatted like this: +SLACK_TEXT_EXTRACTOR = re.compile( + # Allow long lines for the regex + # flake8: noqa: E501 + r"<(?P(?:https?://)?[\w-]+(?:\.[\w-]+)+\.?(?::\d+)?(?:/[^\s|]*)?)(?:\|(?P[^>]+))?>" +) + +BOLD_REGEX = re.compile(r"\*(?P[^*]+)\*") + +USERNAME_REGEX = re.compile(r"(?:/?u/)?(?P\S+)") + class TimeParseError(RuntimeError): """Exception raised when a time string is invalid.""" @@ -163,3 +175,53 @@ def parse_time_constraints( time_str = f"from {after_time_str} until {before_time_str}" return after_time, before_time, time_str + + +def extract_text_from_link(text: str) -> str: + """Strip link out of auto-generated slack fancy URLS and return the text only.""" + results = [_ for _ in re.finditer(SLACK_TEXT_EXTRACTOR, text)] + # we'll replace things going backwards so that we don't mess up indexing + results.reverse() + + def extract_text(mx: Match) -> str: + return mx["text"] or mx["url"] + + for match in results: + text = text[: match.start()] + extract_text(match) + text[match.end() :] + return text + + +def extract_url_from_link(text: str) -> str: + """Strip link out of auto-generated slack fancy URLS and return the link only.""" + results = [_ for _ in re.finditer(SLACK_TEXT_EXTRACTOR, text)] + # we'll replace things going backwards so that we don't mess up indexing + results.reverse() + + def extract_link(m: Match) -> str: + return m["url"] + + for match in results: + text = text[: match.start()] + extract_link(match) + text[match.end() :] + return text + + +def parse_user(text: str) -> Optional[str]: + """Parse a username argument of a Slack command to a user object. + + This takes care of link formatting, bold formatting and the u/ prefix. + Returns `None` if the user couldn't be found. + """ + # Remove link formatting + username = extract_text_from_link(text) + + # Remove bold formatting + bold_match = BOLD_REGEX.match(username) + if bold_match: + username = bold_match.group("content") + + # Remove u/ prefix + prefix_match = USERNAME_REGEX.match(username) + if prefix_match: + username = prefix_match.group("username") + + return username From 433eb8f4475a62adadf5553ce9e3435d8a9429e3 Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 26 Jun 2023 19:43:07 +0200 Subject: [PATCH 6/6] Add link to user if user is filtered in checkping command --- bubbles/commands/periodic/transcription_check_ping.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bubbles/commands/periodic/transcription_check_ping.py b/bubbles/commands/periodic/transcription_check_ping.py index 6f6e8d9..16b274a 100644 --- a/bubbles/commands/periodic/transcription_check_ping.py +++ b/bubbles/commands/periodic/transcription_check_ping.py @@ -272,7 +272,10 @@ def _get_check_fragment(check: CheckData) -> str: def _get_check_reminder(aggregate: List, user_filter: Optional[str] = None) -> str: """Get the reminder text for the checks.""" if user_filter is not None: - reminder = f"*Pending Transcription Checks for u/{user_filter}:*\n\n" + reminder = ( + f"*Pending Transcription Checks for " + f"*:*\n\n" + ) else: reminder = "*Pending Transcription Checks:*\n\n"