aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md6
-rw-r--r--_pytest/test_linkifytext.py9
-rw-r--r--wee_slack.py136
3 files changed, 112 insertions, 39 deletions
diff --git a/README.md b/README.md
index 4b670fa..0ab5ce5 100644
--- a/README.md
+++ b/README.md
@@ -119,7 +119,6 @@ Commands
Join a channel:
```
/join [channel]
-/slack join [channel]
```
Start a direct chat with someone:
@@ -156,6 +155,11 @@ Modify previous message:
s/old text/new text/
```
+Modify 3rd previous message:
+```
+3s/old text/new text/
+```
+
Replace all instances of text in previous message:
```
s/old text/new text/g
diff --git a/_pytest/test_linkifytext.py b/_pytest/test_linkifytext.py
index f9da3f9..010a48b 100644
--- a/_pytest/test_linkifytext.py
+++ b/_pytest/test_linkifytext.py
@@ -4,3 +4,12 @@ from wee_slack import linkify_text
# linkify_text('@ryan')
# assert False
+
+
+def test_linkifytext_does_partial_html_entity_encoding(mock_weechat, realish_eventrouter):
+ team = realish_eventrouter.teams.values()[0]
+ channel = team.channels.values()[0]
+
+ text = linkify_text('& < > \' "', team, channel)
+
+ assert text == '&amp; &lt; &gt; \' "'
diff --git a/wee_slack.py b/wee_slack.py
index 7b55d81..be0558e 100644
--- a/wee_slack.py
+++ b/wee_slack.py
@@ -610,15 +610,17 @@ def buffer_input_callback(signal, buffer_ptr, data):
eventrouter = eval(signal)
channel = eventrouter.weechat_controller.get_channel_from_buffer_ptr(buffer_ptr)
if not channel:
- return w.WEECHAT_RC_OK_EAT
+ return w.WEECHAT_RC_ERROR
reaction = re.match("^\s*(\d*)(\+|-):(.*):\s*$", data)
+ substitute = re.match("^(\d*)s/", data)
if reaction:
if reaction.group(2) == "+":
channel.send_add_reaction(int(reaction.group(1) or 1), reaction.group(3))
elif reaction.group(2) == "-":
channel.send_remove_reaction(int(reaction.group(1) or 1), reaction.group(3))
- elif data.startswith('s/'):
+ elif substitute:
+ msgno = int(substitute.group(1) or 1)
try:
old, new, flags = re.split(r'(?<!\\)/', data)[1:]
except ValueError:
@@ -628,11 +630,11 @@ def buffer_input_callback(signal, buffer_ptr, data):
# rid of escapes.
new = new.replace(r'\/', '/')
old = old.replace(r'\/', '/')
- channel.edit_previous_message(old, new, flags)
+ channel.edit_nth_previous_message(msgno, old, new, flags)
else:
channel.send_message(data)
# this is probably wrong channel.mark_read(update_remote=True, force=True)
- return w.WEECHAT_RC_ERROR
+ return w.WEECHAT_RC_OK
def buffer_switch_callback(signal, sig_type, data):
@@ -1082,6 +1084,14 @@ class SlackTeam(object):
dbg("Unexpected error: {}\nSent: {}".format(sys.exc_info()[0], data))
self.set_connected()
+ def update_member_presence(self, user, presence):
+ user.presence = presence
+
+ for c in self.channels:
+ c = self.channels[c]
+ if user.id in c.members:
+ c.update_nicklist(user.id)
+
class SlackChannel(object):
"""
@@ -1141,7 +1151,7 @@ class SlackChannel(object):
def set_unread_count_display(self, count):
self.unread_count_display = count
- if (self.unread_count_display > 0):
+ for c in range(self.unread_count_display):
if self.type == "im":
w.buffer_set(self.channel_buffer, "hotlist", "2")
else:
@@ -1337,8 +1347,8 @@ class SlackChannel(object):
modify_buffer_line(self.channel_buffer, text, ts.major, ts.minor)
return True
- def edit_previous_message(self, old, new, flags):
- message = self.my_last_message()
+ def edit_nth_previous_message(self, n, old, new, flags):
+ message = self.my_last_message(n)
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)
@@ -1351,11 +1361,13 @@ class SlackChannel(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):
+ def my_last_message(self, msgno):
for message in reversed(self.sorted_message_keys()):
m = self.messages[message]
if "user" in m.message_json and "text" in m.message_json and m.message_json["user"] == self.team.myidentifier:
- return m.message_json
+ msgno -= 1
+ if msgno == 0:
+ return m.message_json
def is_visible(self):
return w.buffer_get_integer(self.channel_buffer, "hidden") == 0
@@ -1461,13 +1473,12 @@ class SlackChannel(object):
w.buffer_set(self.channel_buffer, "nicklist", "1")
# create nicklists for the current channel if they don't exist
# if they do, use the existing pointer
- # TODO: put this back for mithrandir
- # here = w.nicklist_search_group(self.channel_buffer, '', NICK_GROUP_HERE)
- # if not here:
- # here = w.nicklist_add_group(self.channel_buffer, '', NICK_GROUP_HERE, "weechat.color.nicklist_group", 1)
- # afk = w.nicklist_search_group(self.channel_buffer, '', NICK_GROUP_AWAY)
- # if not afk:
- # afk = w.nicklist_add_group(self.channel_buffer, '', NICK_GROUP_AWAY, "weechat.color.nicklist_group", 1)
+ here = w.nicklist_search_group(self.channel_buffer, '', NICK_GROUP_HERE)
+ if not here:
+ here = w.nicklist_add_group(self.channel_buffer, '', NICK_GROUP_HERE, "weechat.color.nicklist_group", 1)
+ afk = w.nicklist_search_group(self.channel_buffer, '', NICK_GROUP_AWAY)
+ if not afk:
+ afk = w.nicklist_add_group(self.channel_buffer, '', NICK_GROUP_AWAY, "weechat.color.nicklist_group", 1)
if user and len(self.members) < 1000:
user = self.team.users[user]
@@ -1475,9 +1486,11 @@ class SlackChannel(object):
# since this is a change just remove it regardless of where it is
w.nicklist_remove_nick(self.channel_buffer, nick)
# now add it back in to whichever..
+ nick_group = afk
+ if self.team.is_user_present(user.identifier):
+ nick_group = here
if user.identifier in self.members:
- w.nicklist_add_nick(self.channel_buffer, "", user.name, user.color_name, "", "", 1)
- # w.nicklist_add_nick(self.channel_buffer, here, user.name, user.color_name, "", "", 1)
+ w.nicklist_add_nick(self.channel_buffer, nick_group, user.name, user.color_name, "", "", 1)
# if we didn't get a user, build a complete list. this is expensive.
else:
@@ -1487,11 +1500,14 @@ class SlackChannel(object):
user = self.team.users[user]
if user.deleted:
continue
- w.nicklist_add_nick(self.channel_buffer, "", user.name, user.color_name, "", "", 1)
- # w.nicklist_add_nick(self.channel_buffer, here, user.name, user.color_name, "", "", 1)
+ nick_group = afk
+ if self.team.is_user_present(user.identifier):
+ nick_group = here
+ w.nicklist_add_nick(self.channel_buffer, nick_group, user.name, user.color_name, "", "", 1)
except Exception as e:
dbg("DEBUG: {} {} {}".format(self.identifier, self.name, e))
else:
+ w.nicklist_remove_all(self.channel_buffer)
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)
@@ -1768,7 +1784,7 @@ class SlackThreadChannel(object):
self.channel_buffer = w.buffer_new(self.formatted_name(style="long_default"), "buffer_input_callback", "EVENTROUTER", "", "")
self.eventrouter.weechat_controller.register_buffer(self.channel_buffer, self)
w.buffer_set(self.channel_buffer, "localvar_set_type", 'channel')
- w.buffer_set(self.channel_buffer, "localvar_set_nick", self.team.nick)
+ w.buffer_set(self.channel_buffer, "localvar_set_nick", self.parent_message.team.nick)
w.buffer_set(self.channel_buffer, "localvar_set_channel", self.formatted_name())
w.buffer_set(self.channel_buffer, "short_name", self.formatted_name(style="sidebar", enable_color=True))
time_format = w.config_string(w.config_get("weechat.look.buffer_time_format"))
@@ -1895,7 +1911,7 @@ class SlackMessage(object):
def get_sender(self):
name = ""
name_plain = ""
- if 'bot_id' in self.message_json and self.message_json['bot_id'] is not None:
+ if self.message_json.get('bot_id') in self.team.bots:
name = "{} :]".format(self.team.bots[self.message_json["bot_id"]].formatted_name())
name_plain = "{}".format(self.team.bots[self.message_json["bot_id"]].formatted_name(enable_color=False))
elif 'user' in self.message_json:
@@ -2150,7 +2166,10 @@ def process_manual_presence_change(message_json, eventrouter, **kwargs):
def process_presence_change(message_json, eventrouter, **kwargs):
- kwargs["user"].presence = message_json["presence"]
+ if "user" in kwargs:
+ user = kwargs["user"]
+ team = kwargs["team"]
+ team.update_member_presence(user, message_json["presence"])
def process_pref_change(message_json, eventrouter, **kwargs):
@@ -2327,9 +2346,9 @@ def subprocess_message_deleted(message_json, eventrouter, channel, team):
def subprocess_channel_topic(message_json, eventrouter, channel, team):
- text = unfurl_refs(message_json["text"], ignore_alt_text=False)
+ text = unhtmlescape(unfurl_refs(message_json["text"], ignore_alt_text=False))
channel.buffer_prnt(w.prefix("network").rstrip(), text, message_json["ts"], tagset="muted")
- channel.render_topic(message_json["topic"])
+ channel.render_topic(unhtmlescape(message_json["topic"]))
def process_reply(message_json, eventrouter, **kwargs):
@@ -2498,10 +2517,7 @@ def render(message_json, team, channel, force=False):
text += unfurl_refs(unwrap_attachments(message_json, text_before), ignore_alt_text=config.unfurl_ignore_alt_text)
text = text.lstrip()
- text = text.replace("\t", " ")
- text = text.replace("&lt;", "<")
- text = text.replace("&gt;", ">")
- text = text.replace("&amp;", "&")
+ text = unhtmlescape(text.replace("\t", " "))
if message_json.get('mrkdwn', True):
text = render_formatting(text)
@@ -2520,9 +2536,16 @@ def linkify_text(message, team, channel):
usernames = team.get_username_map()
channels = team.get_channel_map()
message = (message
+ # Replace IRC formatting chars with Slack formatting chars.
.replace('\x02', '*')
.replace('\x1D', '_')
.replace('\x1F', config.map_underline_to)
+ # Escape chars that have special meaning to Slack. Note that we do not
+ # (and should not) perform full HTML entity-encoding here.
+ # See https://api.slack.com/docs/message-formatting for details.
+ .replace('&', '&amp;')
+ .replace('<', '&lt;')
+ .replace('>', '&gt;')
.split(' '))
for item in enumerate(message):
targets = re.match('^\s*([@#])([\w.-]+[\w. -])(\W*)', item[1])
@@ -2584,6 +2607,12 @@ def unfurl_ref(ref, ignore_alt_text=False):
return display_text
+def unhtmlescape(text):
+ return text.replace("&lt;", "<") \
+ .replace("&gt;", ">") \
+ .replace("&amp;", "&")
+
+
def unwrap_attachments(message_json, text_before):
attachment_text = ''
a = message_json.get("attachments", None)
@@ -2687,6 +2716,8 @@ def modify_buffer_line(buffer, new_line, timestamp, time_id):
# hold the structure of a line and of line data
struct_hdata_line = w.hdata_get('line')
struct_hdata_line_data = w.hdata_get('line_data')
+ # keep track of the number of lines with the matching time and id
+ number_of_matching_lines = 0
while line_pointer:
# get a pointer to the data in line_pointer via layout of struct_hdata_line
@@ -2697,13 +2728,32 @@ def modify_buffer_line(buffer, new_line, timestamp, time_id):
# prefix = w.hdata_string(struct_hdata_line_data, data, 'prefix')
if timestamp == int(line_timestamp) and int(time_id) == line_time_id:
- # w.prnt("", "found matching time date is {}, time is {} ".format(timestamp, line_timestamp))
- w.hdata_update(struct_hdata_line_data, data, {"message": new_line})
+ number_of_matching_lines += 1
+ elif number_of_matching_lines > 0:
+ # since number_of_matching_lines is non-zero, we have
+ # already reached the message and can stop traversing
break
- else:
- pass
+ else:
+ dbg(('Encountered line without any data while trying to modify '
+ 'line. This is not handled, so aborting modification.'))
+ return w.WEECHAT_RC_ERROR
# move backwards one line and try again - exit the while if you hit the end
line_pointer = w.hdata_move(struct_hdata_line, line_pointer, -1)
+
+ # split the message into at most the number of existing lines
+ lines = new_line.split('\n', number_of_matching_lines - 1)
+ # updating a line with a string containing newlines causes the lines to
+ # be broken when viewed in bare display mode
+ lines = [line.replace('\n', ' | ') for line in lines]
+ # pad the list with empty strings until the number of elements equals
+ # number_of_matching_lines
+ lines += [''] * (number_of_matching_lines - len(lines))
+
+ if line_pointer:
+ for line in lines:
+ line_pointer = w.hdata_move(struct_hdata_line, line_pointer, 1)
+ data = w.hdata_pointer(struct_hdata_line, line_pointer, 'data')
+ w.hdata_update(struct_hdata_line_data, data, {"message": line})
return w.WEECHAT_RC_OK
@@ -2722,10 +2772,20 @@ def modify_print_time(buffer, new_id, time):
struct_hdata_line = w.hdata_get('line')
struct_hdata_line_data = w.hdata_get('line_data')
- # get a pointer to the data in line_pointer via layout of struct_hdata_line
- data = w.hdata_pointer(struct_hdata_line, line_pointer, 'data')
- if data:
- w.hdata_update(struct_hdata_line_data, data, {"date_printed": new_id})
+ prefix = ''
+ while not prefix and line_pointer:
+ # get a pointer to the data in line_pointer via layout of struct_hdata_line
+ data = w.hdata_pointer(struct_hdata_line, line_pointer, 'data')
+ if data:
+ prefix = w.hdata_string(struct_hdata_line_data, data, 'prefix')
+ w.hdata_update(struct_hdata_line_data, data, {"date_printed": new_id})
+ else:
+ dbg('Encountered line without any data while setting message id.')
+ return w.WEECHAT_RC_ERROR
+ # move backwards one line and repeat, so all the lines of the message are set
+ # exit when you reach a prefix, which means you have reached the
+ # first line of the message, or if you hit the end
+ line_pointer = w.hdata_move(struct_hdata_line, line_pointer, -1)
return w.WEECHAT_RC_OK
@@ -3097,7 +3157,7 @@ def command_status(data, current_buffer, args):
profile = {"status_text":text,"status_emoji":emoji}
- s = SlackRequest(team.token, "users.profile.set", {"profile": profile}, team_hash=team.team_hash, channel_identifier=channel.identifier)
+ s = SlackRequest(team.token, "users.profile.set", {"profile": profile}, team_hash=team.team_hash)
EVENTROUTER.receive(s)