aboutsummaryrefslogtreecommitdiffstats
path: root/slack
diff options
context:
space:
mode:
Diffstat (limited to 'slack')
-rw-r--r--slack/slack_conversation.py74
-rw-r--r--slack/slack_message.py19
-rw-r--r--slack/slack_workspace.py2
3 files changed, 90 insertions, 5 deletions
diff --git a/slack/slack_conversation.py b/slack/slack_conversation.py
index 01a1fc8..ecf6d9b 100644
--- a/slack/slack_conversation.py
+++ b/slack/slack_conversation.py
@@ -1,15 +1,16 @@
from __future__ import annotations
+import hashlib
import time
from collections import OrderedDict
from contextlib import contextmanager
-from typing import TYPE_CHECKING, List, Optional, Union
+from typing import TYPE_CHECKING, Dict, List, NoReturn, Optional, Union
import weechat
from slack.shared import shared
from slack.slack_message import SlackMessage, SlackTs
-from slack.task import gather
+from slack.task import gather, run_async
from slack.util import get_callback_name
if TYPE_CHECKING:
@@ -17,6 +18,7 @@ if TYPE_CHECKING:
from slack_rtm.slack_rtm_message import (
SlackMessageChanged,
SlackMessageDeleted,
+ SlackMessageReplied,
SlackShRoomJoin,
SlackShRoomUpdate,
)
@@ -145,6 +147,64 @@ def modify_buffer_line(buffer_pointer: str, ts: SlackTs, new_text: str):
weechat.hdata_update(weechat.hdata_get("line_data"), data, {"message": line})
+def sha1_hex(string: str) -> str:
+ return str(hashlib.sha1(string.encode()).hexdigest())
+
+
+def hash_from_ts(ts: SlackTs) -> str:
+ return sha1_hex(str(ts))
+
+
+class SlackConversationMessageHashes(Dict[SlackTs, str]):
+ def __init__(self, conversation: SlackConversation):
+ self._conversation = conversation
+ self._inverse_map: Dict[str, SlackTs] = {}
+
+ def __setitem__(self, key: SlackTs, value: str) -> NoReturn:
+ raise RuntimeError("Set from outside isn't allowed")
+
+ def __delitem__(self, key: SlackTs) -> None:
+ if key in self:
+ hash_key = self[key]
+ del self._inverse_map[hash_key]
+ super().__delitem__(key)
+
+ def _setitem(self, key: SlackTs, value: str) -> None:
+ super().__setitem__(key, value)
+
+ def __missing__(self, key: SlackTs) -> str:
+ hash_len = 3
+ full_hash = hash_from_ts(key)
+ short_hash = full_hash[:hash_len]
+
+ while any(
+ existing_hash.startswith(short_hash) for existing_hash in self._inverse_map
+ ):
+ hash_len += 1
+ short_hash = full_hash[:hash_len]
+
+ if short_hash[:-1] in self._inverse_map:
+ ts_with_same_hash = self._inverse_map.pop(short_hash[:-1])
+ other_full_hash = hash_from_ts(ts_with_same_hash)
+ other_short_hash = other_full_hash[:hash_len]
+
+ while short_hash == other_short_hash:
+ hash_len += 1
+ short_hash = full_hash[:hash_len]
+ other_short_hash = other_full_hash[:hash_len]
+
+ self._setitem(ts_with_same_hash, other_short_hash)
+ self._inverse_map[other_short_hash] = ts_with_same_hash
+
+ other_message = self._conversation.get_message(ts_with_same_hash)
+ if other_message:
+ run_async(self._conversation.rerender_message(other_message))
+
+ self._setitem(key, short_hash)
+ self._inverse_map[short_hash] = key
+ return self[key]
+
+
class SlackConversation:
def __init__(
self,
@@ -162,6 +222,7 @@ class SlackConversation:
self.history_filled = False
self.history_pending = False
self.nicklist_needs_refresh = True
+ self.message_hashes = SlackConversationMessageHashes(self)
self.completion_context: Literal[
"NO_COMPLETION",
@@ -250,6 +311,9 @@ class SlackConversation:
finally:
self.completion_context = "ACTIVE_COMPLETION"
+ def get_message(self, ts: SlackTs) -> Optional[SlackMessage]:
+ return self._messages.get(ts)
+
async def open_if_open(self):
if "is_open" in self._info:
if self._info["is_open"]:
@@ -390,7 +454,9 @@ class SlackConversation:
for message in self._messages.values():
await self.rerender_message(message)
- async def change_message(self, data: SlackMessageChanged):
+ async def change_message(
+ self, data: Union[SlackMessageChanged, SlackMessageReplied]
+ ):
ts = SlackTs(data["ts"])
message = self._messages.get(ts)
if message:
@@ -399,6 +465,8 @@ class SlackConversation:
async def delete_message(self, data: SlackMessageDeleted):
ts = SlackTs(data["deleted_ts"])
+ if ts in self.message_hashes:
+ del self.message_hashes[ts]
message = self._messages.get(ts)
if message:
message.deleted = True
diff --git a/slack/slack_message.py b/slack/slack_message.py
index dc98875..d919cde 100644
--- a/slack/slack_message.py
+++ b/slack/slack_message.py
@@ -114,6 +114,10 @@ class SlackMessage:
return self.conversation.workspace
@property
+ def hash(self) -> str:
+ return self.conversation.message_hashes[self.ts]
+
+ @property
def is_bot_message(self) -> bool:
return (
"subtype" in self._message_json
@@ -344,7 +348,8 @@ class SlackMessage:
else ""
)
reactions = await self._create_reactions_string()
- return text + text_edited + reactions
+ thread = self._create_thread_string()
+ return text + text_edited + reactions + thread
async def render_message(self, rerender: bool = False) -> str:
if self._rendered_message is not None and not rerender:
@@ -512,6 +517,18 @@ class SlackMessage:
else:
return ""
+ def _create_thread_string(self) -> str:
+ if "reply_count" not in self._message_json:
+ return ""
+
+ reply_count = self._message_json["reply_count"]
+ if not reply_count:
+ return ""
+
+ subscribed_text = " Subscribed" if self._message_json.get("subscribed") else ""
+ text = f"[ Thread: {self.hash} Replies: {reply_count}{subscribed_text} ]"
+ return " " + with_color(nick_color(str(self.hash)), text)
+
async def _render_blocks(self, blocks: List[SlackMessageBlock]) -> List[str]:
block_texts: List[str] = []
for block in blocks:
diff --git a/slack/slack_workspace.py b/slack/slack_workspace.py
index ad36c3d..675809d 100644
--- a/slack/slack_workspace.py
+++ b/slack/slack_workspace.py
@@ -299,7 +299,7 @@ class SlackWorkspace:
elif "subtype" in data and data["subtype"] == "message_deleted":
await channel.delete_message(data)
elif "subtype" in data and data["subtype"] == "message_replied":
- pass
+ await channel.change_message(data)
else:
message = SlackMessage(channel, data)
await channel.add_new_message(message)