aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--slack/slack_api.py31
-rw-r--r--slack/slack_buffer.py104
-rw-r--r--slack/slack_message.py4
3 files changed, 119 insertions, 20 deletions
diff --git a/slack/slack_api.py b/slack/slack_api.py
index 784bea6..2264e36 100644
--- a/slack/slack_api.py
+++ b/slack/slack_api.py
@@ -319,6 +319,37 @@ class SlackApi(SlackApiCommon):
raise SlackApiError(self.workspace, method, response, params)
return response
+ async def chat_update_message(
+ self,
+ conversation: SlackConversation,
+ ts: SlackTs,
+ text: str,
+ ):
+ method = "chat.update"
+ params: Params = {
+ "channel": conversation.id,
+ "ts": ts,
+ "text": text,
+ "as_user": True,
+ "link_names": True,
+ }
+ response: SlackGenericResponse = await self._fetch(method, params)
+ if response["ok"] is False:
+ raise SlackApiError(self.workspace, method, response, params)
+ return response
+
+ async def chat_delete_message(self, conversation: SlackConversation, ts: SlackTs):
+ method = "chat.delete"
+ params: Params = {
+ "channel": conversation.id,
+ "ts": ts,
+ "as_user": True,
+ }
+ response: SlackGenericResponse = await self._fetch(method, params)
+ if response["ok"] is False:
+ raise SlackApiError(self.workspace, method, response, params)
+ return response
+
async def reactions_change(
self,
conversation: SlackConversation,
diff --git a/slack/slack_buffer.py b/slack/slack_buffer.py
index 7a6faee..46147cc 100644
--- a/slack/slack_buffer.py
+++ b/slack/slack_buffer.py
@@ -15,21 +15,21 @@ from slack.task import run_async
from slack.util import get_callback_name, htmlescape
if TYPE_CHECKING:
- from typing_extensions import Literal
+ from typing_extensions import Literal, assert_never
from slack.slack_api import SlackApi
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>\+|-)"
+REACTION_CHANGE_REGEX_STRING = r"(?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})"
+ f"(?:{EMOJI_CHAR_REGEX_STRING}|{EMOJI_NAME_REGEX_STRING})"
)
@@ -327,21 +327,49 @@ class SlackBuffer(ABC):
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]:
+ def ts_from_index(
+ self, index: int, message_filter: Optional[Literal["sender_self"]] = None
+ ) -> Optional[SlackTs]:
+ if index < 0:
+ return
+
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]:
+ line = weechat.hdata_pointer(weechat.hdata_get("lines"), lines, "last_line")
+ while line and index:
+ if not message_filter:
+ index -= 1
+ elif message_filter == "sender_self":
+ ts = hdata_line_ts(line)
+ if ts is not None:
+ message = self.messages[ts]
+ if (
+ message.sender_user_id == self.workspace.my_user.id
+ and message.subtype in [None, "me_message", "thread_broadcast"]
+ ):
+ index -= 1
+ else:
+ assert_never(message_filter)
+
+ if index == 0:
+ break
+
+ line = weechat.hdata_move(weechat.hdata_get("line"), line, -1)
+
+ if line:
+ return hdata_line_ts(line)
+
+ def ts_from_hash_or_index(
+ self,
+ hash_or_index: str,
+ message_filter: Optional[Literal["sender_self"]] = None,
+ ) -> Optional[SlackTs]:
if not hash_or_index:
- return self.ts_from_index(1)
+ return self.ts_from_index(1, message_filter)
elif hash_or_index.isdigit():
- return self.ts_from_index(int(hash_or_index))
+ return self.ts_from_index(int(hash_or_index), message_filter)
else:
return self.ts_from_hash(hash_or_index)
@@ -356,21 +384,57 @@ class SlackBuffer(ABC):
emoji_name = emoji["name"] if emoji else emoji_char
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):
+ message = self.messages[ts]
+
+ if new == "" and old == "":
+ await self._api.chat_delete_message(self.conversation, message.ts)
+ else:
+ num_replace = 0 if "g" in flags else 1
+ f = re.UNICODE
+ f |= re.IGNORECASE if "i" in flags else 0
+ f |= re.MULTILINE if "m" in flags else 0
+ f |= re.DOTALL if "s" in flags else 0
+ old_message_text = message.text
+ new_message_text = re.sub(old, new, old_message_text, num_replace, f)
+ if new_message_text != old_message_text:
+ await self._api.chat_update_message(
+ self.conversation, message.ts, new_message_text
+ )
+ else:
+ print_error("The regex didn't match any part of the message")
+
async def process_input(self, input_data: str):
- reaction = re.match(
- rf"{REACTION_PREFIX_REGEX_STRING}{EMOJI_CHAR_OR_NAME_REGEX_STRING}\s*$",
+ special = re.match(
+ rf"{MESSAGE_ID_REGEX_STRING}?(?:{REACTION_CHANGE_REGEX_STRING}{EMOJI_CHAR_OR_NAME_REGEX_STRING}\s*|s/)",
input_data,
)
- if reaction:
- msg_id = reaction.group("msg_id")
- ts = self.ts_from_hash_or_index(msg_id)
+ if special:
+ msg_id = special.group("msg_id")
+ emoji = special.group("emoji_char") or special.group("emoji_name")
+ reaction_change_type = special.group("reaction_change")
+
+ message_filter = "sender_self" if not emoji else None
+ ts = self.ts_from_hash_or_index(msg_id, message_filter)
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 == "-":
+
+ if emoji and (reaction_change_type == "+" or reaction_change_type == "-"):
await self.send_change_reaction(ts, emoji, reaction_change_type)
+ else:
+ try:
+ old, new, flags = re.split(r"(?<!\\)/", input_data)[1:]
+ except ValueError:
+ print_error(
+ "Incomplete regex for changing a message, "
+ "it should be in the form s/old text/new text/"
+ )
+ else:
+ # Replacement string in re.sub() is a string, not a regex, so get rid of escapes
+ new = new.replace(r"\/", "/")
+ old = old.replace(r"\/", "/")
+ await self.edit_message(ts, old, new, flags)
else:
if input_data.startswith(("//", " ")):
input_data = input_data[1:]
diff --git a/slack/slack_message.py b/slack/slack_message.py
index c986edd..2da4042 100644
--- a/slack/slack_message.py
+++ b/slack/slack_message.py
@@ -387,6 +387,10 @@ class SlackMessage:
assert_never(priority)
@property
+ def text(self) -> str:
+ return self._message_json["text"]
+
+ @property
def deleted(self) -> bool:
return self._deleted or self._message_json.get("subtype") == "tombstone"