aboutsummaryrefslogtreecommitdiffstats
path: root/slack
diff options
context:
space:
mode:
authorTrygve Aaberge <trygveaa@gmail.com>2023-10-14 21:42:28 +0200
committerTrygve Aaberge <trygveaa@gmail.com>2024-02-18 11:32:54 +0100
commit2bf006184804104afe13bb11901c0d8674c3a1e2 (patch)
tree2c58b64361eb94567b7d9b4922bb9eb8647a3cbf /slack
parent300845598e26b0441afcd719a5b9e3af3e3aa45b (diff)
downloadwee-slack-2bf006184804104afe13bb11901c0d8674c3a1e2.tar.gz
Support adding and removing reactions
Diffstat (limited to 'slack')
-rw-r--r--slack/register.py3
-rw-r--r--slack/shared.py1
-rw-r--r--slack/slack_api.py25
-rw-r--r--slack/slack_buffer.py62
-rw-r--r--slack/slack_conversation.py2
5 files changed, 89 insertions, 4 deletions
diff --git a/slack/register.py b/slack/register.py
index 23a4d55..b907c49 100644
--- a/slack/register.py
+++ b/slack/register.py
@@ -119,6 +119,9 @@ def register():
shared.weechat_version = int(weechat.info_get("version_number", "") or 0)
shared.current_buffer_pointer = weechat.current_buffer()
shared.standard_emojis = load_standard_emojis()
+ shared.standard_emojis_inverse = {
+ value["unicode"]: value for value in shared.standard_emojis.values()
+ }
shared.workspaces = {}
shared.config = SlackConfig()
shared.config.config_read()
diff --git a/slack/shared.py b/slack/shared.py
index eeb6df5..32bad27 100644
--- a/slack/shared.py
+++ b/slack/shared.py
@@ -29,6 +29,7 @@ class Shared:
self.config: SlackConfig
self.uncaught_errors: List[UncaughtError] = []
self.standard_emojis: Dict[str, Emoji]
+ self.standard_emojis_inverse: Dict[str, Emoji]
self.highlight_tag = "highlight"
diff --git a/slack/slack_api.py b/slack/slack_api.py
index 7fff321..784bea6 100644
--- a/slack/slack_api.py
+++ b/slack/slack_api.py
@@ -26,6 +26,7 @@ if TYPE_CHECKING:
from slack_api.slack_users_prefs import SlackUsersPrefsGetResponse
from slack_edgeapi.slack_usergroups_info import SlackEdgeUsergroupsInfoResponse
from slack_edgeapi.slack_users_search import SlackUsersSearchResponse
+ from typing_extensions import Literal, assert_never
from slack.slack_conversation import SlackConversation
from slack.slack_workspace import SlackWorkspace
@@ -317,3 +318,27 @@ class SlackApi(SlackApiCommon):
if response["ok"] is False:
raise SlackApiError(self.workspace, method, response, params)
return response
+
+ async def reactions_change(
+ self,
+ conversation: SlackConversation,
+ ts: SlackTs,
+ name: str,
+ change_type: Literal["+", "-"],
+ ):
+ method = (
+ "reactions.add"
+ if change_type == "+"
+ else "reactions.remove"
+ if change_type == "-"
+ else assert_never(change_type)
+ )
+ params: Params = {
+ "channel": conversation.id,
+ "timestamp": ts,
+ "name": name,
+ }
+ response: SlackGenericResponse = await self._fetch(method, params)
+ if response["ok"] is False:
+ raise SlackApiError(self.workspace, method, response, params)
+ return response
diff --git a/slack/slack_buffer.py b/slack/slack_buffer.py
index 4fa5981..7a6faee 100644
--- a/slack/slack_buffer.py
+++ b/slack/slack_buffer.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import re
import time
from abc import ABC, abstractmethod
from contextlib import contextmanager
@@ -7,6 +8,7 @@ from typing import TYPE_CHECKING, Dict, List, Mapping, Optional, Set, Tuple
import weechat
+from slack.log import print_error
from slack.shared import shared
from slack.slack_message import SlackMessage, SlackTs
from slack.task import run_async
@@ -19,6 +21,17 @@ if TYPE_CHECKING:
from slack.slack_conversation import SlackConversation
from slack.slack_workspace import SlackWorkspace
+MESSAGE_ID_REGEX_STRING = r"(?P<msg_id>\d+|\$[0-9a-z]{3,})"
+REACTION_PREFIX_REGEX_STRING = rf"{MESSAGE_ID_REGEX_STRING}?(?P<reaction_change>\+|-)"
+
+EMOJI_CHAR_REGEX_STRING = "(?P<emoji_char>[\U00000080-\U0010ffff]+)"
+EMOJI_NAME_REGEX_STRING = (
+ ":(?P<emoji_name>[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")
@@ -311,14 +324,57 @@ class SlackBuffer(ABC):
weechat.buffer_set(self.buffer_pointer, "hotlist", "-1")
self.hotlist_tss.clear()
+ def ts_from_hash(self, ts_hash: str) -> Optional[SlackTs]:
+ return self.conversation.message_hashes.get_ts(ts_hash)
+
+ def ts_from_index(self, index: int) -> Optional[SlackTs]:
+ lines = weechat.hdata_pointer(
+ weechat.hdata_get("buffer"), self.buffer_pointer, "lines"
+ )
+ line = weechat.hdata_pointer(weechat.hdata_get("lines"), lines, "last_line")
+ move = -index + 1
+ if move:
+ line = weechat.hdata_move(weechat.hdata_get("line"), line, move)
+ return hdata_line_ts(line)
+
+ def ts_from_hash_or_index(self, hash_or_index: str) -> Optional[SlackTs]:
+ if not hash_or_index:
+ return self.ts_from_index(1)
+ elif hash_or_index.isdigit():
+ return self.ts_from_index(int(hash_or_index))
+ else:
+ return self.ts_from_hash(hash_or_index)
+
@abstractmethod
async def post_message(self, text: str) -> None:
raise NotImplementedError()
+ async def send_change_reaction(
+ self, ts: SlackTs, emoji_char: str, change_type: Literal["+", "-"]
+ ) -> None:
+ emoji = shared.standard_emojis_inverse.get(emoji_char)
+ emoji_name = emoji["name"] if emoji else emoji_char
+ await self._api.reactions_change(self.conversation, ts, emoji_name, change_type)
+
async def process_input(self, input_data: str):
- if input_data.startswith(("//", " ")):
- input_data = input_data[1:]
- await self.post_message(htmlescape(input_data))
+ reaction = re.match(
+ rf"{REACTION_PREFIX_REGEX_STRING}{EMOJI_CHAR_OR_NAME_REGEX_STRING}\s*$",
+ input_data,
+ )
+ if reaction:
+ msg_id = reaction.group("msg_id")
+ ts = self.ts_from_hash_or_index(msg_id)
+ if ts is None:
+ print_error(f"No slack message found for message id or index {msg_id}")
+ return
+ emoji = reaction.group("emoji_char") or reaction.group("emoji_name")
+ reaction_change_type = reaction.group("reaction_change")
+ if reaction_change_type == "+" or reaction_change_type == "-":
+ await self.send_change_reaction(ts, emoji, reaction_change_type)
+ else:
+ if input_data.startswith(("//", " ")):
+ input_data = input_data[1:]
+ await self.post_message(htmlescape(input_data))
def _buffer_input_cb(self, data: str, buffer: str, input_data: str) -> int:
run_async(self.process_input(input_data))
diff --git a/slack/slack_conversation.py b/slack/slack_conversation.py
index 652c8f2..2ac8f21 100644
--- a/slack/slack_conversation.py
+++ b/slack/slack_conversation.py
@@ -613,7 +613,7 @@ class SlackConversation(SlackBuffer):
)
async def open_thread(self, thread_hash: str, switch: bool = False):
- thread_ts = self.message_hashes.get_ts(thread_hash)
+ thread_ts = self.ts_from_hash(thread_hash)
if thread_ts:
thread_message = self.messages.get(thread_ts)
if thread_message is None: