aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-08-31 09:22:00 +0200
committerTrygve Aaberge <trygveaa@gmail.com>2018-10-29 17:33:56 +0100
commit00cf876691ef40b0975abd5b94675aaf1a59fd68 (patch)
tree6c43d4663e71f8fc96a3a632b4227543bc7a0ff5
parentbfd5fbb9595d63510614adf897c33c945d0c0fe3 (diff)
downloadwee-slack-00cf876691ef40b0975abd5b94675aaf1a59fd68.tar.gz
Allow selection of a message using mouse or cursor in /reply / reaction / edition
-rw-r--r--README.md18
-rw-r--r--wee_slack.py182
2 files changed, 145 insertions, 55 deletions
diff --git a/README.md b/README.md
index 4701c69..a87ee95 100644
--- a/README.md
+++ b/README.md
@@ -267,6 +267,24 @@ To enable tab completion of emojis, copy or symlink the `weemoji.json` file to y
/set weechat.completion.default_template "%(nicks)|%(irc_channels)|%(emoji)"
```
+#### Cursor and mouse mode
+
+The cursor mode and mouse mode can be used to interact with older messages, for editing, deleting, reacting and replying to a message. The default behavior when right-clicking on a message is to paste its id in the input. It can be used in `/reply`, `s/` substitution/deletion and in `+:emoji:` commands instead of a message number.
+
+In cursor mode, the `M` key achieves the same result (memo: the default for weechat is to paste the message with `m`, `M` simply copies the id).
+In addition, `R` will prepare a `/reply id` and `D` will delete the message (provided it’s yours).
+
+Please see weechat’s documentation about [how to use the cursor mode](https://weechat.org/files/doc/stable/weechat_user.en.html#key_bindings_cursor_context) or [adapt the bindings](https://weechat.org/files/doc/stable/weechat_user.en.html#command_weechat_key) to your preference.
+
+Default:
+```
+/key bindctxt mouse @chat(python.*.slack.com.*):button2 hsignal:slack_mouse
+/key bindctxt cursor @chat(python.*.slack.com.*):M hsignal:slack_cursor_message
+/key bindctxt cursor @chat(python.*.slack.com.*):D hsignal:slack_cursor_delete
+/key bindctxt cursor @chat(python.*.slack.com.*):R hsignal:slack_cursor_reply
+```
+hsignals `slack_mouse` and `slack_cursor_message` currently have the same meaning but may be subject to evolutions.
+
Removing a team
---------------
diff --git a/wee_slack.py b/wee_slack.py
index 4857cfa..d1a526b 100644
--- a/wee_slack.py
+++ b/wee_slack.py
@@ -716,15 +716,24 @@ def buffer_input_callback(signal, buffer_ptr, data):
if not channel:
return w.WEECHAT_RC_ERROR
- reaction = re.match("^(\d*)(\+|-):(.*):\s*$", data)
- substitute = re.match("^(\d*)s/", data)
+ def get_id(message_id):
+ if not message_id:
+ return 1
+ elif message_id[0] == "$":
+ return message_id[1:]
+ else:
+ return int(message_id)
+
+ message_id_regex = "(\d*|\$[0-9a-fA-F]{3,})"
+ reaction = re.match("^{}(\+|-):(.*):\s*$".format(message_id_regex), data)
+ substitute = re.match("^{}s/".format(message_id_regex), data)
if reaction:
if reaction.group(2) == "+":
- channel.send_add_reaction(int(reaction.group(1) or 1), reaction.group(3))
+ channel.send_add_reaction(get_id(reaction.group(1)), reaction.group(3))
elif reaction.group(2) == "-":
- channel.send_remove_reaction(int(reaction.group(1) or 1), reaction.group(3))
+ channel.send_remove_reaction(get_id(reaction.group(1)), reaction.group(3))
elif substitute:
- msgno = int(substitute.group(1) or 1)
+ msg_id = get_id(substitute.group(1))
try:
old, new, flags = re.split(r'(?<!\\)/', data)[1:]
except ValueError:
@@ -734,7 +743,7 @@ def buffer_input_callback(signal, buffer_ptr, data):
# rid of escapes.
new = new.replace(r'\/', '/')
old = old.replace(r'\/', '/')
- channel.edit_nth_previous_message(msgno, old, new, flags)
+ channel.edit_nth_previous_message(msg_id, old, new, flags)
else:
if data.startswith(('//', ' ')):
data = data[1:]
@@ -1248,22 +1257,31 @@ class SlackTeam(object):
class SlackChannelCommon(object):
- def send_add_reaction(self, msg_number, reaction):
- self.send_change_reaction("reactions.add", msg_number, reaction)
+ def send_add_reaction(self, msg_id, reaction):
+ self.send_change_reaction("reactions.add", msg_id, reaction)
- def send_remove_reaction(self, msg_number, reaction):
- self.send_change_reaction("reactions.remove", msg_number, reaction)
+ def send_remove_reaction(self, msg_id, reaction):
+ self.send_change_reaction("reactions.remove", msg_id, reaction)
- def send_change_reaction(self, method, msg_number, reaction):
- if 0 < msg_number < len(self.messages):
+ def send_change_reaction(self, method, msg_id, reaction):
+ if type(msg_id) is not int:
+ if msg_id in self.hashed_messages:
+ timestamp = str(self.hashed_messages[msg_id].ts)
+ else:
+ return
+ elif 0 < msg_id <= len(self.messages):
keys = self.main_message_keys_reversed()
- timestamp = next(islice(keys, msg_number - 1, None))
- data = {"channel": self.identifier, "timestamp": timestamp, "name": reaction}
- s = SlackRequest(self.team.token, method, data)
- self.eventrouter.receive(s)
+ timestamp = next(islice(keys, msg_id - 1, None))
+ else:
+ return
+ data = {"channel": self.identifier, "timestamp": timestamp, "name": reaction}
+ s = SlackRequest(self.team.token, method, data)
+ self.eventrouter.receive(s)
- def edit_nth_previous_message(self, n, old, new, flags):
- message = self.my_last_message(n)
+ def edit_nth_previous_message(self, msg_id, old, new, flags):
+ message = self.my_last_message(msg_id)
+ if message is None:
+ return
if new == "" and old == "":
s = SlackRequest(self.team.token, "chat.delete", {"channel": self.identifier, "ts": message['ts']}, team_hash=self.team.team_hash, channel_identifier=self.identifier)
self.eventrouter.receive(s)
@@ -1276,13 +1294,18 @@ class SlackChannelCommon(object):
s = SlackRequest(self.team.token, "chat.update", {"channel": self.identifier, "ts": message['ts'], "text": new_message}, team_hash=self.team.team_hash, channel_identifier=self.identifier)
self.eventrouter.receive(s)
- def my_last_message(self, msgno):
- for key in self.main_message_keys_reversed():
- m = self.messages[key]
- if "user" in m.message_json and "text" in m.message_json and m.message_json["user"] == self.team.myidentifier:
- msgno -= 1
- if msgno == 0:
- return m.message_json
+ def my_last_message(self, msg_id):
+ if type(msg_id) is not int:
+ m = self.hashed_messages.get(msg_id)
+ if m is not None and m.message_json.get("user") == self.team.myidentifier:
+ return m.message_json
+ else:
+ for key in self.main_message_keys_reversed():
+ m = self.messages[key]
+ if m.message_json.get("user") == self.team.myidentifier:
+ msg_id -= 1
+ if msg_id == 0:
+ return m.message_json
def change_message(self, ts, message_json=None, text=None):
ts = SlackTS(ts)
@@ -1303,6 +1326,37 @@ class SlackChannelCommon(object):
new_text = thread_channel.render(m, force=True)
modify_buffer_line(thread_channel.channel_buffer, new_text, ts.major, ts.minor)
+ def hash_message(self, ts):
+ ts = SlackTS(ts)
+
+ def calc_hash(msg):
+ return sha.sha(str(msg.ts)).hexdigest()
+
+ if ts in self.messages and not self.messages[ts].hash:
+ message = self.messages[ts]
+ tshash = calc_hash(message)
+ hl = 3
+ shorthash = tshash[:hl]
+ while any(x.startswith(shorthash) for x in self.hashed_messages):
+ hl += 1
+ shorthash = tshash[:hl]
+
+ if shorthash[:-1] in self.hashed_messages:
+ col_msg = self.hashed_messages.pop(shorthash[:-1])
+ col_new_hash = calc_hash(col_msg)[:hl]
+ col_msg.hash = col_new_hash
+ self.hashed_messages[col_new_hash] = col_msg
+ self.change_message(str(col_msg.ts))
+ if col_msg.thread_channel:
+ col_msg.thread_channel.rename()
+
+ self.hashed_messages[shorthash] = message
+ message.hash = shorthash
+ return shorthash
+ elif ts in self.messages:
+ return self.messages[ts].hash
+
+
class SlackChannel(SlackChannelCommon):
"""
@@ -1725,33 +1779,6 @@ class SlackChannel(SlackChannelCommon):
for fn in ["1| too", "2| many", "3| users", "4| to", "5| show"]:
w.nicklist_add_group(self.channel_buffer, '', fn, w.color('white'), 1)
- def hash_message(self, ts):
- ts = SlackTS(ts)
-
- def calc_hash(msg):
- return sha.sha(str(msg.ts)).hexdigest()
-
- if ts in self.messages and not self.messages[ts].hash:
- message = self.messages[ts]
- tshash = calc_hash(message)
- hl = 3
- shorthash = tshash[:hl]
- while any(x.startswith(shorthash) for x in self.hashed_messages):
- hl += 1
- shorthash = tshash[:hl]
-
- if shorthash[:-1] in self.hashed_messages:
- col_msg = self.hashed_messages.pop(shorthash[:-1])
- col_new_hash = calc_hash(col_msg)[:hl]
- col_msg.hash = col_new_hash
- self.hashed_messages[col_new_hash] = col_msg
- self.change_message(str(col_msg.ts))
- if col_msg.thread_channel:
- col_msg.thread_channel.rename()
-
- self.hashed_messages[shorthash] = message
- message.hash = shorthash
-
def render(self, message, force=False):
text = message.render(force)
if isinstance(message, SlackThreadMessage):
@@ -1948,6 +1975,7 @@ class SlackThreadChannel(SlackChannelCommon):
def __init__(self, eventrouter, parent_message):
self.eventrouter = eventrouter
self.parent_message = parent_message
+ self.hashed_messages = {}
self.channel_buffer = None
# self.identifier = ""
# self.name = "#" + kwargs['name']
@@ -3508,10 +3536,16 @@ def thread_command_callback(data, current_buffer, args):
pm.open_thread(switch=config.switch_buffer_on_join)
return w.WEECHAT_RC_OK_EAT
elif args[0] == '/reply':
- count = int(args[1])
+ if args[1][0] == "$":
+ if args[1][1:] in channel.hashed_messages:
+ parent_id = str(channel.hashed_messages[args[1][1:]].ts)
+ else:
+ return w.WEECHAT_RC_OK_EAT
+ else:
+ count = int(args[1])
+ mkeys = channel.main_message_keys_reversed()
+ parent_id = str(next(islice(mkeys, count - 1, None)))
msg = " ".join(args[2:])
- mkeys = channel.main_message_keys_reversed()
- parent_id = str(next(islice(mkeys, count - 1, None)))
channel.send_message(msg, request_dict_ext={"thread_ts": parent_id})
return w.WEECHAT_RC_OK_EAT
w.prnt(current, "Invalid thread command.")
@@ -3693,6 +3727,30 @@ def command_status(data, current_buffer, args):
s = SlackRequest(team.token, "users.profile.set", {"profile": profile}, team_hash=team.team_hash)
EVENTROUTER.receive(s)
+@utf8_decode
+def line_event_cb(data, signal, hashtable):
+ buffer_pointer = hashtable["_buffer"]
+ line_timestamp = hashtable["_chat_line_date"]
+ line_time_id = hashtable["_chat_line_date_printed"]
+ channel = EVENTROUTER.weechat_controller.buffers.get(buffer_pointer)
+
+ if line_timestamp and line_time_id and isinstance(channel, SlackChannelCommon):
+ ts = SlackTS("{}.{}".format(line_timestamp, line_time_id))
+
+ message_hash = channel.hash_message(ts)
+ if message_hash is None:
+ return w.WEECHAT_RC_OK
+ message_hash = "$" + message_hash
+
+ if data == "message":
+ w.command(buffer_pointer, "/cursor stop")
+ w.command(buffer_pointer, "/input insert {}".format(message_hash))
+ elif data == "delete":
+ w.command(buffer_pointer, "/input send {}s///".format(message_hash))
+ elif data == "reply":
+ w.command(buffer_pointer, "/cursor stop")
+ w.command(buffer_pointer, "/input insert /reply {}\\x20".format(message_hash))
+ return w.WEECHAT_RC_OK
@slack_buffer_required
def command_back(data, current_buffer, args):
@@ -3828,6 +3886,20 @@ def setup_hooks():
w.hook_completion("nicks", "complete @-nicks for slack", "nick_completion_cb", "")
w.hook_completion("emoji", "complete :emoji: for slack", "emoji_completion_cb", "")
+ w.key_bind("mouse", {
+ "@chat(python.*.slack.com.*):button2": "hsignal:slack_mouse",
+ })
+ w.key_bind("cursor", {
+ "@chat(python.*.slack.com.*):M": "hsignal:slack_cursor_message",
+ "@chat(python.*.slack.com.*):D": "hsignal:slack_cursor_delete",
+ "@chat(python.*.slack.com.*):R": "hsignal:slack_cursor_reply",
+ })
+
+ w.hook_hsignal("slack_mouse", "line_event_cb", "message")
+ w.hook_hsignal("slack_cursor_delete", "line_event_cb", "delete")
+ w.hook_hsignal("slack_cursor_reply", "line_event_cb", "reply")
+ w.hook_hsignal("slack_cursor_message", "line_event_cb", "message")
+
# Hooks to fix/implement
# w.hook_signal('buffer_opened', "buffer_opened_cb", "")
# w.hook_signal('window_scrolled', "scrolled_cb", "")