aboutsummaryrefslogtreecommitdiffstats
path: root/slack
diff options
context:
space:
mode:
authorTrygve Aaberge <trygveaa@gmail.com>2023-08-20 21:09:10 +0200
committerTrygve Aaberge <trygveaa@gmail.com>2024-02-18 11:32:53 +0100
commitf3f97e5cbb97ed7a04bc3e40a110805ff0ae53c8 (patch)
treed6abf2212800d82e3e845a4ec5f70dd031fa5f94 /slack
parent9a92344a77731f443d19dfdb24a6a47a1b7dc9ce (diff)
downloadwee-slack-f3f97e5cbb97ed7a04bc3e40a110805ff0ae53c8.tar.gz
Render reactions after messages
Diffstat (limited to 'slack')
-rw-r--r--slack/config.py17
-rw-r--r--slack/slack_conversation.py21
-rw-r--r--slack/slack_message.py89
-rw-r--r--slack/slack_user.py6
-rw-r--r--slack/slack_workspace.py36
-rw-r--r--slack/util.py4
6 files changed, 151 insertions, 22 deletions
diff --git a/slack/config.py b/slack/config.py
index 0b717fa..a3b01ce 100644
--- a/slack/config.py
+++ b/slack/config.py
@@ -76,11 +76,17 @@ class SlackConfigSectionColor:
self.reaction_suffix = WeeChatOption(
self._section,
"reaction_suffix",
- "text color for the [:wave:(@user)] suffix on messages that have"
- " reactions attached to them.",
+ "color for the reactions after a message",
WeeChatColor("darkgray"),
)
+ self.reaction_self_suffix = WeeChatOption(
+ self._section,
+ "reaction_self_suffix",
+ "color for the reactions after a message, for reactions you have added",
+ WeeChatColor("blue"),
+ )
+
self.user_mention_color = WeeChatOption(
self._section,
"user_mention_color",
@@ -116,6 +122,13 @@ class SlackConfigSectionLook:
callback_change=self.config_change_color_nicks_in_nicklist_cb,
)
+ self.display_reaction_nicks = WeeChatOption(
+ self._section,
+ "display_reaction_nicks",
+ "display the name of the reacting user(s) after each reaction",
+ False,
+ )
+
self.external_user_suffix = WeeChatOption(
self._section,
"external_user_suffix",
diff --git a/slack/slack_conversation.py b/slack/slack_conversation.py
index ddc2054..9b49466 100644
--- a/slack/slack_conversation.py
+++ b/slack/slack_conversation.py
@@ -375,19 +375,36 @@ class SlackConversation:
f"{self.buffer_pointer};off;{user.nick()}",
)
+ async def rerender_message(self, message: SlackMessage):
+ modify_buffer_line(
+ self.buffer_pointer, message.ts, await message.render_message(rerender=True)
+ )
+
async def change_message(self, data: SlackMessageChanged):
ts = SlackTs(data["ts"])
message = self._messages.get(ts)
if message:
message.update_message_json(data["message"])
- modify_buffer_line(self.buffer_pointer, ts, await message.render_message())
+ await self.rerender_message(message)
async def delete_message(self, data: SlackMessageDeleted):
ts = SlackTs(data["deleted_ts"])
message = self._messages.get(ts)
if message:
message.deleted = True
- modify_buffer_line(self.buffer_pointer, ts, await message.render_message())
+ await self.rerender_message(message)
+
+ async def reaction_add(self, message_ts: SlackTs, reaction: str, user_id: str):
+ message = self._messages.get(message_ts)
+ if message:
+ message.reaction_add(reaction, user_id)
+ await self.rerender_message(message)
+
+ async def reaction_remove(self, message_ts: SlackTs, reaction: str, user_id: str):
+ message = self._messages.get(message_ts)
+ if message:
+ message.reaction_remove(reaction, user_id)
+ await self.rerender_message(message)
async def typing_add_user(self, user_id: str, thread_ts: Optional[str]):
if not shared.config.look.typing_status_nicks.value:
diff --git a/slack/slack_message.py b/slack/slack_message.py
index 792b064..64214c5 100644
--- a/slack/slack_message.py
+++ b/slack/slack_message.py
@@ -15,6 +15,7 @@ from slack.util import with_color
if TYPE_CHECKING:
from slack_api.slack_conversations_history import SlackMessage as SlackMessageDict
+ from slack_api.slack_conversations_history import SlackMessageReaction
from slack.slack_conversation import SlackConversation
from slack.slack_workspace import SlackWorkspace
@@ -91,6 +92,32 @@ class SlackMessage:
self._rendered_prefix = None
self._rendered_message = None
+ def _get_reaction(self, reaction_name: str):
+ for reaction in self._message_json.get("reactions", []):
+ if reaction["name"] == reaction_name:
+ return reaction
+
+ def reaction_add(self, reaction_name: str, user_id: str):
+ reaction = self._get_reaction(reaction_name)
+ if reaction:
+ if user_id not in reaction["users"]:
+ reaction["users"].append(user_id)
+ reaction["count"] += 1
+ else:
+ if "reactions" not in self._message_json:
+ self._message_json["reactions"] = []
+ self._message_json["reactions"].append(
+ {"name": reaction_name, "users": [user_id], "count": 1}
+ )
+ self._rendered_message = None
+
+ def reaction_remove(self, reaction_name: str, user_id: str):
+ reaction = self._get_reaction(reaction_name)
+ if reaction and user_id in reaction["users"]:
+ reaction["users"].remove(user_id)
+ reaction["count"] -= 1
+ self._rendered_message = None
+
async def tags(self, backlog: bool = False) -> str:
nick = await self._nick(colorize=False, only_nick=True)
tags = [f"slack_ts_{self.ts}", f"nick_{nick}"]
@@ -173,10 +200,8 @@ class SlackMessage:
self._rendered_prefix = await self._render_prefix()
return self._rendered_prefix
- async def _render_message(self) -> str:
- if self._deleted:
- return with_color(shared.config.color.deleted_message.value, "(deleted)")
- elif self._message_json.get("subtype") in [
+ async def _render_message_text(self) -> str:
+ if self._message_json.get("subtype") in [
"channel_join",
"group_join",
"channel_leave",
@@ -205,16 +230,23 @@ class SlackMessage:
return f"{await self._nick()} {text_action} {text_conversation_name}{inviter_text}"
else:
- text = await self._unfurl_refs(self._message_json["text"])
+ return await self._unfurl_refs(self._message_json["text"])
+
+ async def _render_message(self) -> str:
+ if self._deleted:
+ return with_color(shared.config.color.deleted_message.value, "(deleted)")
+ else:
+ text = await self._render_message_text()
text_edited = (
f" {with_color(shared.config.color.edited_message_suffix.value, '(edited)')}"
if self._message_json.get("edited")
else ""
)
- return text + text_edited
+ reactions = await self._create_reactions_string()
+ return text + text_edited + reactions
- async def render_message(self) -> str:
- if self._rendered_message is not None:
+ async def render_message(self, rerender: bool = False) -> str:
+ if self._rendered_message is not None and not rerender:
return self._rendered_message
self._rendered_message = await self._render_message()
return self._rendered_message
@@ -278,3 +310,44 @@ class SlackMessage:
return match[0]
return re_mention.sub(unfurl_ref, message)
+
+ async def _create_reaction_string(self, reaction: SlackMessageReaction) -> str:
+ if shared.config.look.display_reaction_nicks.value:
+ # TODO: initialize_items?
+ users = await gather(
+ *(self.workspace.users[user_id] for user_id in reaction["users"])
+ )
+ nicks = ",".join(user.nick() for user in users)
+ users_str = f"({nicks})"
+ else:
+ users_str = len(reaction["users"])
+
+ reaction_string = f":{reaction['name']}:{users_str}"
+
+ if self.workspace.my_user.id in reaction["users"]:
+ return with_color(
+ shared.config.color.reaction_self_suffix.value,
+ reaction_string,
+ reset_color=shared.config.color.reaction_suffix.value,
+ )
+ else:
+ return reaction_string
+
+ async def _create_reactions_string(self) -> str:
+ reactions = self._message_json.get("reactions", [])
+ reactions_with_users = [
+ reaction for reaction in reactions if len(reaction["users"]) > 0
+ ]
+ reaction_strings = await gather(
+ *(
+ self._create_reaction_string(reaction)
+ for reaction in reactions_with_users
+ )
+ )
+ reactions_string = " ".join(reaction_strings)
+ if reactions_string:
+ return " " + with_color(
+ shared.config.color.reaction_suffix.value, f"[{reactions_string}]"
+ )
+ else:
+ return ""
diff --git a/slack/slack_user.py b/slack/slack_user.py
index aa84089..8e5bea5 100644
--- a/slack/slack_user.py
+++ b/slack/slack_user.py
@@ -58,8 +58,12 @@ class SlackUser:
return cls(workspace, info_response["user"])
@property
+ def id(self) -> str:
+ return self._info["id"]
+
+ @property
def is_self(self) -> bool:
- return self._info["id"] == self.workspace.my_user._info["id"]
+ return self.id == self.workspace.my_user.id
def nick(self, colorize: bool = False, only_nick: bool = False) -> str:
nick = self._name_without_spaces()
diff --git a/slack/slack_workspace.py b/slack/slack_workspace.py
index 28b9b4b..f721bda 100644
--- a/slack/slack_workspace.py
+++ b/slack/slack_workspace.py
@@ -26,7 +26,7 @@ from slack.proxy import Proxy
from slack.shared import shared
from slack.slack_api import SlackApi
from slack.slack_conversation import SlackConversation
-from slack.slack_message import SlackMessage
+from slack.slack_message import SlackMessage, SlackTs
from slack.slack_user import SlackBot, SlackUser, SlackUsergroup
from slack.task import Future, Task, create_task, gather, run_async
from slack.util import get_callback_name
@@ -273,13 +273,25 @@ class SlackWorkspace:
async def _ws_recv(self, data: SlackRtmMessage):
try:
- channel_id = "channel" in data and data["channel"]
- if channel_id in self.open_conversations:
- channel = self.open_conversations[channel_id]
+ if data["type"] == "hello":
+ return
+ elif data["type"] == "reaction_added" or data["type"] == "reaction_removed":
+ channel_id = data["item"]["channel"]
+ elif "channel" in data and type(data["channel"]) == str:
+ channel_id = data["channel"]
else:
- channel = None
+ weechat.prnt("", f"\t{self.name} received: {json.dumps(data)}")
+ return
+
+ channel = self.open_conversations.get(channel_id)
+ if channel is None:
+ weechat.prnt(
+ "",
+ f"\t{self.name} received for not open conversation, discarding: {json.dumps(data)}",
+ )
+ return
- if data["type"] == "message" and channel is not None:
+ if data["type"] == "message":
if "subtype" in data and data["subtype"] == "message_changed":
await channel.change_message(data)
elif "subtype" in data and data["subtype"] == "message_deleted":
@@ -289,7 +301,17 @@ class SlackWorkspace:
else:
message = SlackMessage(channel, data)
await channel.add_message(message)
- elif data["type"] == "user_typing" and channel is not None:
+ elif data["type"] == "reaction_added" and data["item"]["type"] == "message":
+ await channel.reaction_add(
+ SlackTs(data["item"]["ts"]), data["reaction"], data["user"]
+ )
+ elif (
+ data["type"] == "reaction_removed" and data["item"]["type"] == "message"
+ ):
+ await channel.reaction_remove(
+ SlackTs(data["item"]["ts"]), data["reaction"], data["user"]
+ )
+ elif data["type"] == "user_typing":
await channel.typing_add_user(data["user"], data.get("thread_ts"))
else:
weechat.prnt("", f"\t{self.name} received: {json.dumps(data)}")
diff --git a/slack/util.py b/slack/util.py
index 8dfc967..e216576 100644
--- a/slack/util.py
+++ b/slack/util.py
@@ -13,5 +13,5 @@ def get_callback_name(callback: Callable[..., WeechatCallbackReturnType]) -> str
return callback_id
-def with_color(color: str, string: str):
- return f"{weechat.color(color)}{string}{weechat.color('reset')}"
+def with_color(color: str, string: str, reset_color: str = "reset"):
+ return f"{weechat.color(color)}{string}{weechat.color(reset_color)}"