From 1b02ac44847570610a8696323adeb1c3c36ae187 Mon Sep 17 00:00:00 2001 From: Trygve Aaberge Date: Mon, 19 Feb 2024 21:22:26 +0100 Subject: Add support for focus events (mouse/cursor mode) --- slack/commands.py | 79 ++++++++++++++++++++++++++++++++++++++++++++++++-- slack/shared.py | 8 +++++ slack/slack_buffer.py | 30 ++++++++++--------- slack/slack_message.py | 14 ++++++++- 4 files changed, 114 insertions(+), 17 deletions(-) diff --git a/slack/commands.py b/slack/commands.py index 7a336a4..76125a2 100644 --- a/slack/commands.py +++ b/slack/commands.py @@ -24,10 +24,10 @@ import weechat from slack.error import SlackError, SlackRtmError, UncaughtError from slack.log import open_debug_buffer, print_error from slack.python_compatibility import format_exception, removeprefix -from slack.shared import shared +from slack.shared import EMOJI_CHAR_OR_NAME_REGEX_STRING, shared from slack.slack_buffer import SlackBuffer from slack.slack_conversation import SlackConversation -from slack.slack_message import SlackTs +from slack.slack_message import SlackTs, ts_from_tag from slack.slack_thread import SlackThread from slack.slack_user import SlackUser from slack.slack_workspace import SlackWorkspace @@ -36,7 +36,7 @@ from slack.util import get_callback_name, get_resolved_futures, with_color from slack.weechat_config import WeeChatOption, WeeChatOptionTypes if TYPE_CHECKING: - from typing_extensions import Literal + from typing_extensions import Literal, assert_never Options = Dict[str, Union[str, Literal[True]]] WeechatCommandCallback = Callable[[str, str], None] @@ -46,6 +46,8 @@ if TYPE_CHECKING: T = TypeVar("T") +focus_events = ("auto", "message", "delete", "linkarchive", "reply", "thread") + def print_message_not_found_error(msg_id: str): if msg_id: @@ -641,6 +643,53 @@ def buffer_set_unread_cb(data: str, buffer: str, command: str) -> int: return weechat.WEECHAT_RC_OK +def focus_event_cb(data: str, signal: str, hashtable: Dict[str, str]) -> int: + tags = hashtable["_chat_line_tags"].split(",") + for tag in tags: + ts = ts_from_tag(tag) + if ts is not None: + break + else: + return weechat.WEECHAT_RC_OK + + buffer_pointer = hashtable["_buffer"] + slack_buffer = shared.buffers.get(buffer_pointer) + if slack_buffer is None: + return weechat.WEECHAT_RC_OK + + conversation = _get_conversation_from_buffer(slack_buffer) + if conversation is None: + return weechat.WEECHAT_RC_OK + + message_hash = f"${conversation.message_hashes[ts]}" + + if data not in focus_events: + print_error(f"Unknown focus event: {data}") + return weechat.WEECHAT_RC_OK + + if data == "auto": + emoji_match = re.match(EMOJI_CHAR_OR_NAME_REGEX_STRING, hashtable["_chat_eol"]) + if emoji_match is not None: + emoji = emoji_match.group("emoji_char") or emoji_match.group("emoji_name") + run_async(conversation.send_change_reaction(ts, emoji, "toggle")) + else: + weechat.command(buffer_pointer, f"/input insert {message_hash}") + elif data == "message": + weechat.command(buffer_pointer, f"/input insert {message_hash}") + elif data == "delete": + run_async(conversation.api.chat_delete_message(conversation, ts)) + elif data == "linkarchive": + url = _get_linkarchive_url(slack_buffer, ts) + weechat.command(buffer_pointer, f"/input insert {url}") + elif data == "reply": + weechat.command(buffer_pointer, f"/input insert /reply {message_hash}\\x20") + elif data == "thread": + run_async(conversation.open_thread(message_hash, switch=True)) + else: + assert_never(data) + return weechat.WEECHAT_RC_OK + + def register_commands(): weechat.hook_command_run( "/buffer set unread", get_callback_name(buffer_set_unread_cb), "" @@ -663,3 +712,27 @@ def register_commands(): get_callback_name(command_cb), cmd, ) + + for focus_event in focus_events: + weechat.hook_hsignal( + f"slack_focus_{focus_event}", + get_callback_name(focus_event_cb), + focus_event, + ) + + weechat.key_bind( + "mouse", + { + "@chat(python.*):button2": "hsignal:slack_focus_auto", + }, + ) + weechat.key_bind( + "cursor", + { + "@chat(python.*):D": "hsignal:slack_focus_delete", + "@chat(python.*):L": "hsignal:slack_focus_linkarchive; /cursor stop", + "@chat(python.*):M": "hsignal:slack_focus_message; /cursor stop", + "@chat(python.*):R": "hsignal:slack_focus_reply; /cursor stop", + "@chat(python.*):T": "hsignal:slack_focus_thread; /cursor stop", + }, + ) diff --git a/slack/shared.py b/slack/shared.py index 0aea81e..b4094cb 100644 --- a/slack/shared.py +++ b/slack/shared.py @@ -17,6 +17,14 @@ WeechatCallbackReturnType = Union[int, str, Dict[str, str], None] MESSAGE_ID_REGEX_STRING = r"(?P\d+|\$[0-9a-z]{3,})" REACTION_CHANGE_REGEX_STRING = r"(?P\+|-)" +EMOJI_CHAR_REGEX_STRING = "(?P[\U00000080-\U0010ffff]+)" +EMOJI_NAME_REGEX_STRING = ( + ":(?P[a-z0-9_+-]+(?:::skin-tone-[2-6](?:-[2-6])?)?):" +) +EMOJI_CHAR_OR_NAME_REGEX_STRING = ( + f"(?:{EMOJI_CHAR_REGEX_STRING}|{EMOJI_NAME_REGEX_STRING})" +) + class Shared: def __init__(self): diff --git a/slack/slack_buffer.py b/slack/slack_buffer.py index edb1815..64c2b83 100644 --- a/slack/slack_buffer.py +++ b/slack/slack_buffer.py @@ -19,8 +19,13 @@ from typing import ( import weechat from slack.log import print_error -from slack.shared import MESSAGE_ID_REGEX_STRING, REACTION_CHANGE_REGEX_STRING, shared -from slack.slack_message import SlackMessage, SlackTs +from slack.shared import ( + EMOJI_CHAR_OR_NAME_REGEX_STRING, + MESSAGE_ID_REGEX_STRING, + REACTION_CHANGE_REGEX_STRING, + shared, +) +from slack.slack_message import SlackMessage, SlackTs, ts_from_tag from slack.slack_user import Nick from slack.task import gather, run_async from slack.util import get_callback_name, htmlescape @@ -32,14 +37,6 @@ if TYPE_CHECKING: from slack.slack_conversation import SlackConversation from slack.slack_workspace import SlackWorkspace -EMOJI_CHAR_REGEX_STRING = "(?P[\U00000080-\U0010ffff]+)" -EMOJI_NAME_REGEX_STRING = ( - ":(?P[a-z0-9_+-]+(?:::skin-tone-[2-6](?:-[2-6])?)?):" -) -EMOJI_CHAR_OR_NAME_REGEX_STRING = ( - f"(?:{EMOJI_CHAR_REGEX_STRING}|{EMOJI_NAME_REGEX_STRING})" -) - def hdata_line_ts(line_pointer: str) -> Optional[SlackTs]: data = weechat.hdata_pointer(weechat.hdata_get("line"), line_pointer, "data") @@ -49,8 +46,9 @@ def hdata_line_ts(line_pointer: str) -> Optional[SlackTs]: tag = weechat.hdata_string( weechat.hdata_get("line_data"), data, f"{i}|tags_array" ) - if tag.startswith("slack_ts_"): - return SlackTs(tag[9:]) + ts = ts_from_tag(tag) + if ts is not None: + return ts return None @@ -449,10 +447,16 @@ class SlackBuffer(ABC): ) async def send_change_reaction( - self, ts: SlackTs, emoji_char: str, change_type: Literal["+", "-"] + self, ts: SlackTs, emoji_char: str, change_type: Literal["+", "-", "toggle"] ) -> None: emoji = shared.standard_emojis_inverse.get(emoji_char) emoji_name = emoji["name"] if emoji else emoji_char + + if change_type == "toggle": + message = self.messages[ts] + has_reacted = message.has_reacted(emoji_name) + change_type = "-" if has_reacted else "+" + await self.api.reactions_change(self.conversation, ts, emoji_name, change_type) async def edit_message(self, ts: SlackTs, old: str, new: str, flags: str): diff --git a/slack/slack_message.py b/slack/slack_message.py index 30f1ad4..5386f62 100644 --- a/slack/slack_message.py +++ b/slack/slack_message.py @@ -49,6 +49,8 @@ if TYPE_CHECKING: from slack.slack_thread import SlackThread from slack.slack_workspace import SlackWorkspace +ts_tag_prefix = "slack_ts_" + def format_date(timestamp: int, token_string: str, link: Optional[str] = None) -> str: ref_datetime = datetime.fromtimestamp(timestamp) @@ -126,6 +128,12 @@ def convert_int_to_roman(num: int) -> str: return roman_numeral +def ts_from_tag(tag: str) -> Optional[SlackTs]: + if tag.startswith(ts_tag_prefix): + return SlackTs(tag[len(ts_tag_prefix) :]) + return None + + class MessagePriority(Enum): NONE = "none" LOW = weechat.WEECHAT_HOTLIST_LOW @@ -556,6 +564,10 @@ class SlackMessage: reaction["count"] -= 1 self._rendered_message = None + def has_reacted(self, reaction_name: str) -> bool: + reaction = self._get_reaction(reaction_name) + return reaction is not None and self.workspace.my_user.id in reaction["users"] + def should_highlight(self, only_personal: bool) -> bool: # TODO: Highlight words from user preferences parsed_message = self.parse_message_text() @@ -570,7 +582,7 @@ class SlackMessage: async def tags(self, backlog: bool) -> str: nick = await self.nick() - tags = [f"slack_ts_{self.ts}", f"nick_{nick.raw_nick}"] + tags = [f"{ts_tag_prefix}{self.ts}", f"nick_{nick.raw_nick}"] if self.sender_user_id: tags.append(f"slack_user_id_{self.sender_user_id}") -- cgit