diff options
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | _pytest/test_linkifytext.py | 9 | ||||
-rw-r--r-- | wee_slack.py | 136 |
3 files changed, 112 insertions, 39 deletions
@@ -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 == '& < > \' "' 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("<", "<") - text = text.replace(">", ">") - text = text.replace("&", "&") + 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('&', '&') + .replace('<', '<') + .replace('>', '>') .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("<", "<") \ + .replace(">", ">") \ + .replace("&", "&") + + 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) |