aboutsummaryrefslogtreecommitdiffstats
path: root/wee_slack.py
diff options
context:
space:
mode:
Diffstat (limited to 'wee_slack.py')
-rw-r--r--wee_slack.py688
1 files changed, 421 insertions, 267 deletions
diff --git a/wee_slack.py b/wee_slack.py
index 94dce4c..16d5569 100644
--- a/wee_slack.py
+++ b/wee_slack.py
@@ -2,7 +2,9 @@
from __future__ import unicode_literals
+from collections import OrderedDict
from functools import wraps
+from itertools import islice
import time
import json
@@ -40,21 +42,28 @@ RECORD_DIR = "/tmp/weeslack-debug"
SLACK_API_TRANSLATOR = {
"channel": {
"history": "channels.history",
- "join": "channels.join",
- "leave": "channels.leave",
+ "join": "conversations.join",
+ "leave": "conversations.leave",
"mark": "channels.mark",
"info": "channels.info",
},
"im": {
"history": "im.history",
- "join": "im.open",
- "leave": "im.close",
+ "join": "conversations.open",
+ "leave": "conversations.close",
"mark": "im.mark",
},
+ "mpim": {
+ "history": "mpim.history",
+ "join": "mpim.open", # conversations.open lacks unread_count_display
+ "leave": "conversations.close",
+ "mark": "mpim.mark",
+ "info": "groups.info",
+ },
"group": {
"history": "groups.history",
- "join": "channels.join",
- "leave": "groups.leave",
+ "join": "conversations.join",
+ "leave": "conversations.leave",
"mark": "groups.mark",
"info": "groups.info"
},
@@ -95,6 +104,17 @@ def slack_buffer_required(f):
return wrapper
+def utf8_decode(f):
+ """
+ Decode all arguments from byte strings to unicode strings. Use this for
+ functions called from outside of this script, e.g. callbacks from weechat.
+ """
+ @wraps(f)
+ def wrapper(*args, **kwargs):
+ return f(*decode_from_utf8(args), **decode_from_utf8(kwargs))
+ return wrapper
+
+
NICK_GROUP_HERE = "0|Here"
NICK_GROUP_AWAY = "1|Away"
@@ -115,7 +135,7 @@ def encode_to_utf8(data):
if isinstance(data, bytes):
return data
elif isinstance(data, collections.Mapping):
- return dict(map(encode_to_utf8, data.iteritems()))
+ return type(data)(map(encode_to_utf8, data.iteritems()))
elif isinstance(data, collections.Iterable):
return type(data)(map(encode_to_utf8, data))
else:
@@ -128,7 +148,7 @@ def decode_from_utf8(data):
if isinstance(data, unicode):
return data
elif isinstance(data, collections.Mapping):
- return dict(map(decode_from_utf8, data.iteritems()))
+ return type(data)(map(decode_from_utf8, data.iteritems()))
elif isinstance(data, collections.Iterable):
return type(data)(map(decode_from_utf8, data))
else:
@@ -139,19 +159,31 @@ class WeechatWrapper(object):
def __init__(self, wrapped_class):
self.wrapped_class = wrapped_class
+ # Helper method used to encode/decode method calls.
+ def wrap_for_utf8(self, method):
+ def hooked(*args, **kwargs):
+ result = method(*encode_to_utf8(args), **encode_to_utf8(kwargs))
+ # Prevent wrapped_class from becoming unwrapped
+ if result == self.wrapped_class:
+ return self
+ return decode_from_utf8(result)
+ return hooked
+
+ # Encode and decode everything sent to/received from weechat. We use the
+ # unicode type internally in wee-slack, but has to send utf8 to weechat.
def __getattr__(self, attr):
orig_attr = self.wrapped_class.__getattribute__(attr)
if callable(orig_attr):
- def hooked(*args, **kwargs):
- result = orig_attr(*encode_to_utf8(args), **encode_to_utf8(kwargs))
- # Prevent wrapped_class from becoming unwrapped
- if result == self.wrapped_class:
- return self
- return decode_from_utf8(result)
- return hooked
+ return self.wrap_for_utf8(orig_attr)
else:
return decode_from_utf8(orig_attr)
+ # Ensure all lines sent to weechat specifies a prefix. For lines after the
+ # first, we want to disable the prefix, which is done by specifying a space.
+ def prnt_date_tags(self, buffer, date, tags, message):
+ message = message.replace("\n", "\n \t")
+ return self.wrap_for_utf8(self.wrapped_class.prnt_date_tags)(buffer, date, tags, message)
+
##### Helpers
@@ -561,17 +593,18 @@ def local_process_async_slack_api_request(request, event_router):
###### New Callbacks
+@utf8_decode
def receive_httprequest_callback(data, command, return_code, out, err):
"""
complete
This is a dirty hack. There must be a better way.
"""
# def url_processor_cb(data, command, return_code, out, err):
- data = decode_from_utf8(data)
EVENTROUTER.receive_httprequest_callback(data, command, return_code, out, err)
return w.WEECHAT_RC_OK
+@utf8_decode
def receive_ws_callback(*args):
"""
complete
@@ -583,11 +616,13 @@ def receive_ws_callback(*args):
return w.WEECHAT_RC_OK
+@utf8_decode
def reconnect_callback(*args):
EVENTROUTER.reconnect_if_disconnected()
return w.WEECHAT_RC_OK
+@utf8_decode
def buffer_closing_callback(signal, sig_type, data):
"""
complete
@@ -596,11 +631,11 @@ def buffer_closing_callback(signal, sig_type, data):
that is the only way we can do dependency injection via weechat
callback, hence the eval.
"""
- data = decode_from_utf8(data)
eval(signal).weechat_controller.unregister_buffer(data, True, False)
return w.WEECHAT_RC_OK
+@utf8_decode
def buffer_input_callback(signal, buffer_ptr, data):
"""
incomplete
@@ -608,13 +643,12 @@ def buffer_input_callback(signal, buffer_ptr, data):
this includes add/remove reactions, modifying messages, and
sending messages.
"""
- data = decode_from_utf8(data)
eventrouter = eval(signal)
channel = eventrouter.weechat_controller.get_channel_from_buffer_ptr(buffer_ptr)
if not channel:
return w.WEECHAT_RC_ERROR
- reaction = re.match("^\s*(\d*)(\+|-):(.*):\s*$", data)
+ reaction = re.match("^(\d*)(\+|-):(.*):\s*$", data)
substitute = re.match("^(\d*)s/", data)
if reaction:
if reaction.group(2) == "+":
@@ -634,11 +668,27 @@ def buffer_input_callback(signal, buffer_ptr, data):
old = old.replace(r'\/', '/')
channel.edit_nth_previous_message(msgno, old, new, flags)
else:
+ if data.startswith(('//', ' ')):
+ data = data[1:]
channel.send_message(data)
# this is probably wrong channel.mark_read(update_remote=True, force=True)
return w.WEECHAT_RC_OK
+# Workaround for supporting multiline messages. It intercepts before the input
+# callback is called, as this is called with the whole message, while it is
+# normally split on newline before being sent to buffer_input_callback
+def input_text_for_buffer_cb(data, modifier, current_buffer, string):
+ if current_buffer not in EVENTROUTER.weechat_controller.buffers:
+ return string
+ message = decode_from_utf8(string)
+ if not message.startswith("/") and "\n" in message:
+ buffer_input_callback("EVENTROUTER", current_buffer, message)
+ return ""
+ return string
+
+
+@utf8_decode
def buffer_switch_callback(signal, sig_type, data):
"""
incomplete
@@ -646,7 +696,6 @@ def buffer_switch_callback(signal, sig_type, data):
1) set read marker 2) determine if we have already populated
channel history data
"""
- data = decode_from_utf8(data)
eventrouter = eval(signal)
prev_buffer_ptr = eventrouter.weechat_controller.get_previous_buffer_ptr()
@@ -664,6 +713,7 @@ def buffer_switch_callback(signal, sig_type, data):
return w.WEECHAT_RC_OK
+@utf8_decode
def buffer_list_update_callback(data, somecount):
"""
incomplete
@@ -673,7 +723,6 @@ def buffer_list_update_callback(data, somecount):
to indicate typing via "#channel" <-> ">channel" and
user presence via " name" <-> "+name".
"""
- data = decode_from_utf8(data)
eventrouter = eval(data)
# global buffer_list_update
@@ -692,8 +741,8 @@ def quit_notification_callback(signal, sig_type, data):
stop_talking_to_slack()
+@utf8_decode
def typing_notification_cb(signal, sig_type, data):
- data = decode_from_utf8(data)
msg = w.buffer_get_string(data, "input")
if len(msg) > 8 and msg[:1] != "/":
global typing_timer
@@ -709,14 +758,14 @@ def typing_notification_cb(signal, sig_type, data):
return w.WEECHAT_RC_OK
+@utf8_decode
def typing_update_cb(data, remaining_calls):
- data = decode_from_utf8(data)
w.bar_item_update("slack_typing_notice")
return w.WEECHAT_RC_OK
+@utf8_decode
def slack_never_away_cb(data, remaining_calls):
- data = decode_from_utf8(data)
if config.never_away:
for t in EVENTROUTER.teams.values():
slackbot = t.get_channel_map()['slackbot']
@@ -726,6 +775,7 @@ def slack_never_away_cb(data, remaining_calls):
return w.WEECHAT_RC_OK
+@utf8_decode
def typing_bar_item_cb(data, current_buffer, args):
"""
Privides a bar item indicating who is typing in the current channel AND
@@ -760,13 +810,12 @@ def typing_bar_item_cb(data, current_buffer, args):
return typing
+@utf8_decode
def nick_completion_cb(data, completion_item, current_buffer, completion):
"""
Adds all @-prefixed nicks to completion list
"""
- data = decode_from_utf8(data)
- completion = decode_from_utf8(completion)
current_buffer = w.current_buffer()
current_channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer, None)
@@ -779,13 +828,12 @@ def nick_completion_cb(data, completion_item, current_buffer, completion):
return w.WEECHAT_RC_OK
+@utf8_decode
def emoji_completion_cb(data, completion_item, current_buffer, completion):
"""
Adds all :-prefixed emoji to completion list
"""
- data = decode_from_utf8(data)
- completion = decode_from_utf8(completion)
current_buffer = w.current_buffer()
current_channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer, None)
@@ -796,6 +844,7 @@ def emoji_completion_cb(data, completion_item, current_buffer, completion):
return w.WEECHAT_RC_OK
+@utf8_decode
def complete_next_cb(data, current_buffer, command):
"""Extract current word, if it is equal to a nick, prefix it with @ and
rely on nick_completion_cb adding the @-prefixed versions to the
@@ -804,8 +853,6 @@ def complete_next_cb(data, current_buffer, command):
"""
- data = decode_from_utf8(data)
- command = decode_from_utf8(data)
current_buffer = w.current_buffer()
current_channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer, None)
@@ -987,9 +1034,9 @@ class SlackTeam(object):
self.eventrouter.weechat_controller.register_buffer(self.channel_buffer, self)
w.buffer_set(self.channel_buffer, "localvar_set_type", 'server')
w.buffer_set(self.channel_buffer, "localvar_set_nick", self.nick)
+ w.buffer_set(self.channel_buffer, "localvar_set_server", self.preferred_name)
if w.config_string(w.config_get('irc.look.server_buffer')) == 'merge_with_core':
w.buffer_merge(self.channel_buffer, w.buffer_search_main())
- w.buffer_set(self.channel_buffer, "nicklist", "1")
def set_muted_channels(self, muted_str):
self.muted_channels = {x for x in muted_str.split(',')}
@@ -1004,7 +1051,7 @@ class SlackTeam(object):
return self.domain
def buffer_prnt(self, data):
- w.prnt_date_tags(self.channel_buffer, SlackTS().major, tag("backlog"), data)
+ w.prnt_date_tags(self.channel_buffer, SlackTS().major, tag("team"), data)
def get_channel_map(self):
return {v.slack_name: k for k, v in self.channels.iteritems()}
@@ -1035,7 +1082,7 @@ class SlackTeam(object):
else:
return False
- def mark_read(self):
+ def mark_read(self, ts=None, update_remote=True, force=False):
pass
def connect(self):
@@ -1051,7 +1098,7 @@ class SlackTeam(object):
self.set_connected()
self.connecting = False
except Exception as e:
- dbg("websocket connection error: {}".format(e))
+ dbg("websocket connection error: {}".format(decode_from_utf8(e)))
self.connecting = False
return False
else:
@@ -1116,14 +1163,14 @@ class SlackChannel(object):
self.members = set(kwargs.get('members', set()))
self.eventrouter = eventrouter
self.slack_name = kwargs["name"]
- self.slack_topic = kwargs.get("topic", {"value": ""})
self.slack_purpose = kwargs.get("purpose", {"value": ""})
+ self.topic = kwargs.get("topic", {}).get("value", "")
self.identifier = kwargs["id"]
self.last_read = SlackTS(kwargs.get("last_read", SlackTS()))
self.channel_buffer = None
self.team = kwargs.get('team', None)
self.got_history = False
- self.messages = {}
+ self.messages = OrderedDict()
self.hashed_messages = {}
self.new_messages = False
self.typing = {}
@@ -1158,8 +1205,12 @@ class SlackChannel(object):
return True
return False
+ def get_members(self):
+ return self.members
+
def set_unread_count_display(self, count):
self.unread_count_display = count
+ self.new_messages = bool(self.unread_count_display)
for c in range(self.unread_count_display):
if self.type == "im":
w.buffer_set(self.channel_buffer, "hotlist", "2")
@@ -1167,11 +1218,10 @@ class SlackChannel(object):
w.buffer_set(self.channel_buffer, "hotlist", "1")
def formatted_name(self, style="default", typing=False, **kwargs):
- if config.channel_name_typing_indicator:
- if not typing:
- prepend = "#"
- else:
- prepend = ">"
+ if typing and config.channel_name_typing_indicator:
+ prepend = ">"
+ elif self.type == "group":
+ prepend = config.group_name_prefix
else:
prepend = "#"
select = {
@@ -1183,15 +1233,18 @@ class SlackChannel(object):
}
return select[style]
- def render_topic(self, topic=None):
+ def render_topic(self):
if self.channel_buffer:
- if not topic:
- if self.slack_topic['value'] != "":
- topic = self.slack_topic['value']
- else:
- topic = self.slack_purpose['value']
+ if self.topic != "":
+ topic = self.topic
+ else:
+ topic = self.slack_purpose['value']
w.buffer_set(self.channel_buffer, "title", topic)
+ def set_topic(self, value):
+ self.topic = value
+ self.render_topic()
+
def update_from_message_json(self, message_json):
for key, value in message_json.items():
setattr(self, key, value)
@@ -1199,7 +1252,7 @@ class SlackChannel(object):
def open(self, update_remote=True):
if update_remote:
if "join" in SLACK_API_TRANSLATOR[self.type]:
- s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["join"], {"name": self.name}, team_hash=self.team.team_hash, channel_identifier=self.identifier)
+ s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["join"], {"channel": self.identifier}, team_hash=self.team.team_hash, channel_identifier=self.identifier)
self.eventrouter.receive(s)
self.create_buffer()
self.active = True
@@ -1207,22 +1260,20 @@ class SlackChannel(object):
# self.create_buffer()
def check_should_open(self, force=False):
- try:
- if self.is_archived:
- return
- except:
- pass
+ if hasattr(self, "is_archived") and self.is_archived:
+ return
+
if force:
self.create_buffer()
- else:
- for reason in ["is_member", "is_open", "unread_count_display"]:
- try:
- if eval("self." + reason):
- self.create_buffer()
- if config.background_load_all_history:
- self.get_history(slow_queue=True)
- except:
- pass
+ return
+
+ # Only check is_member if is_open is not set, because in some cases
+ # (e.g. group DMs), is_member should be ignored in favor of is_open.
+ is_open = self.is_open if hasattr(self, "is_open") else self.is_member
+ if is_open or self.unread_count_display:
+ self.create_buffer()
+ if config.background_load_all_history:
+ self.get_history(slow_queue=True)
def set_related_server(self, team):
self.team = team
@@ -1230,7 +1281,7 @@ class SlackChannel(object):
def set_highlights(self):
# highlight my own name and any set highlights
if self.channel_buffer:
- highlights = self.team.highlight_words.union({'@' + self.team.nick, "!here", "!channel", "!everyone"})
+ highlights = self.team.highlight_words.union({'@' + self.team.nick, self.team.myidentifier, "!here", "!channel", "!everyone"})
h_str = ",".join(highlights)
w.buffer_set(self.channel_buffer, "highlight_words", h_str)
@@ -1267,13 +1318,13 @@ class SlackChannel(object):
if self.type == "im":
if "join" in SLACK_API_TRANSLATOR[self.type]:
- s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["join"], {"user": self.user, "return_im": "true"}, team_hash=self.team.team_hash, channel_identifier=self.identifier)
+ s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["join"], {"users": self.user, "return_im": True}, team_hash=self.team.team_hash, channel_identifier=self.identifier)
self.eventrouter.receive(s)
def destroy_buffer(self, update_remote):
if self.channel_buffer is not None:
self.channel_buffer = None
- self.messages = {}
+ self.messages = OrderedDict()
self.hashed_messages = {}
self.got_history = False
# if update_remote and not eventrouter.shutting_down:
@@ -1335,14 +1386,14 @@ class SlackChannel(object):
if from_me:
message.message_json["user"] = team.myidentifier
self.messages[SlackTS(message.ts)] = message
- if len(self.messages.keys()) > SCROLLBACK_SIZE:
- mk = self.messages.keys()
- mk.sort()
- for k in mk[:SCROLLBACK_SIZE]:
- msg_to_delete = self.messages[k]
- if msg_to_delete.hash:
- del self.hashed_messages[msg_to_delete.hash]
- del self.messages[k]
+
+ sorted_messages = sorted(self.messages.items())
+ messages_to_delete = sorted_messages[:-SCROLLBACK_SIZE]
+ messages_to_keep = sorted_messages[-SCROLLBACK_SIZE:]
+ for message_hash in [m[1].hash for m in messages_to_delete]:
+ if message_hash in self.hashed_messages:
+ del self.hashed_messages[message_hash]
+ self.messages = OrderedDict(messages_to_keep)
def change_message(self, ts, text=None, suffix=None):
ts = SlackTS(ts)
@@ -1371,8 +1422,8 @@ class SlackChannel(object):
self.eventrouter.receive(s)
def my_last_message(self, msgno):
- for message in reversed(self.sorted_message_keys()):
- m = self.messages[message]
+ 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:
@@ -1402,17 +1453,15 @@ class SlackChannel(object):
def send_change_reaction(self, method, msg_number, reaction):
if 0 < msg_number < len(self.messages):
- timestamp = self.sorted_message_keys()[-msg_number]
+ 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)
- def sorted_message_keys(self):
- keys = []
- for k in self.messages:
- if type(self.messages[k]) == SlackMessage:
- keys.append(k)
- return sorted(keys)
+ def main_message_keys_reversed(self):
+ return (key for key in reversed(self.messages)
+ if type(self.messages[key]) == SlackMessage)
# Typing related
def set_typing(self, user):
@@ -1454,7 +1503,7 @@ class SlackChannel(object):
def mark_read(self, ts=None, update_remote=True, force=False):
if not ts:
- ts = SlackTS()
+ ts = next(self.main_message_keys_reversed(), SlackTS())
if self.new_messages or force:
if self.channel_buffer:
w.buffer_set(self.channel_buffer, "unread", "")
@@ -1477,7 +1526,7 @@ class SlackChannel(object):
def update_nicklist(self, user=None):
if not self.channel_buffer:
return
- if self.type not in ["channel", "group"]:
+ if self.type not in ["channel", "group", "mpim"]:
return
w.buffer_set(self.channel_buffer, "nicklist", "1")
# create nicklists for the current channel if they don't exist
@@ -1514,7 +1563,7 @@ class SlackChannel(object):
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))
+ dbg("DEBUG: {} {} {}".format(self.identifier, self.name, decode_from_utf8(e)))
else:
w.nicklist_remove_all(self.channel_buffer)
for fn in ["1| too", "2| many", "3| users", "4| to", "5| show"]:
@@ -1565,6 +1614,9 @@ class SlackDMChannel(SlackChannel):
def set_name(self, slack_name):
self.name = slack_name
+ def get_members(self):
+ return {self.user}
+
def create_buffer(self):
if not self.channel_buffer:
super(SlackDMChannel, self).create_buffer()
@@ -1605,7 +1657,7 @@ class SlackDMChannel(SlackChannel):
self.eventrouter.receive(s)
if update_remote:
if "join" in SLACK_API_TRANSLATOR[self.type]:
- s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["join"], {"user": self.user}, team_hash=self.team.team_hash, channel_identifier=self.identifier)
+ s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["join"], {"users": self.user, "return_im": True}, team_hash=self.team.team_hash, channel_identifier=self.identifier)
self.eventrouter.receive(s)
self.create_buffer()
@@ -1629,12 +1681,11 @@ class SlackGroupChannel(SlackChannel):
def __init__(self, eventrouter, **kwargs):
super(SlackGroupChannel, self).__init__(eventrouter, **kwargs)
- self.name = "#" + kwargs['name']
self.type = "group"
self.set_name(self.slack_name)
def set_name(self, slack_name):
- self.name = "#" + slack_name
+ self.name = config.group_name_prefix + slack_name
# def formatted_name(self, prepend="#", enable_color=True, basic=False):
# return prepend + self.slack_name
@@ -1650,29 +1701,33 @@ class SlackMPDMChannel(SlackChannel):
super(SlackMPDMChannel, self).__init__(eventrouter, **kwargs)
n = kwargs.get('name')
self.set_name(n)
- self.type = "group"
+ self.type = "mpim"
- def open(self, update_remote=False):
+ def open(self, update_remote=True):
self.create_buffer()
self.active = True
self.get_history()
if "info" in SLACK_API_TRANSLATOR[self.type]:
- s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["info"], {"name": self.identifier}, team_hash=self.team.team_hash, channel_identifier=self.identifier)
+ s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["info"], {"channel": self.identifier}, team_hash=self.team.team_hash, channel_identifier=self.identifier)
+ self.eventrouter.receive(s)
+ if update_remote and 'join' in SLACK_API_TRANSLATOR[self.type]:
+ s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]['join'], {'users': ','.join(self.members)}, team_hash=self.team.team_hash, channel_identifier=self.identifier)
self.eventrouter.receive(s)
# self.create_buffer()
+ @staticmethod
+ def adjust_name(n):
+ return "|".join("-".join(n.split("-")[1:-1]).split("--"))
+
def set_name(self, n):
- self.name = "|".join("-".join(n.split("-")[1:-1]).split("--"))
+ self.name = self.adjust_name(n)
def formatted_name(self, style="default", typing=False, **kwargs):
- adjusted_name = "|".join("-".join(self.slack_name.split("-")[1:-1]).split("--"))
- if config.channel_name_typing_indicator:
- if not typing:
- prepend = "#"
- else:
- prepend = ">"
+ adjusted_name = self.adjust_name(self.slack_name)
+ if typing and config.channel_name_typing_indicator:
+ prepend = ">"
else:
- prepend = "#"
+ prepend = "@"
select = {
"default": adjusted_name,
"sidebar": prepend + adjusted_name,
@@ -2040,79 +2095,67 @@ def handle_rtmstart(login_data, eventrouter):
"""
This handles the main entry call to slack, rtm.start
"""
- if login_data["ok"]:
-
- metadata = pickle.loads(login_data["wee_slack_request_metadata"])
-
- # Let's reuse a team if we have it already.
- th = SlackTeam.generate_team_hash(login_data['self']['name'], login_data['team']['domain'])
- if not eventrouter.teams.get(th):
+ metadata = pickle.loads(login_data["wee_slack_request_metadata"])
- users = {}
- for item in login_data["users"]:
- users[item["id"]] = SlackUser(**item)
- # users.append(SlackUser(**item))
+ if not login_data["ok"]:
+ w.prnt("", "ERROR: Failed connecting to Slack with token {}: {}"
+ .format(metadata.token, login_data["error"]))
+ return
- bots = {}
- for item in login_data["bots"]:
- bots[item["id"]] = SlackBot(**item)
+ # Let's reuse a team if we have it already.
+ th = SlackTeam.generate_team_hash(login_data['self']['name'], login_data['team']['domain'])
+ if not eventrouter.teams.get(th):
- channels = {}
- for item in login_data["channels"]:
- channels[item["id"]] = SlackChannel(eventrouter, **item)
+ users = {}
+ for item in login_data["users"]:
+ users[item["id"]] = SlackUser(**item)
- for item in login_data["ims"]:
- channels[item["id"]] = SlackDMChannel(eventrouter, users, **item)
+ bots = {}
+ for item in login_data["bots"]:
+ bots[item["id"]] = SlackBot(**item)
- for item in login_data["groups"]:
- if item["name"].startswith('mpdm-'):
- channels[item["id"]] = SlackMPDMChannel(eventrouter, **item)
- else:
- channels[item["id"]] = SlackGroupChannel(eventrouter, **item)
-
- t = SlackTeam(
- eventrouter,
- metadata.token,
- login_data['url'],
- login_data["team"]["domain"],
- login_data["self"]["name"],
- login_data["self"]["id"],
- users,
- bots,
- channels,
- muted_channels=login_data["self"]["prefs"]["muted_channels"],
- highlight_words=login_data["self"]["prefs"]["highlight_words"],
- )
- eventrouter.register_team(t)
+ channels = {}
+ for item in login_data["channels"]:
+ channels[item["id"]] = SlackChannel(eventrouter, **item)
- else:
- t = eventrouter.teams.get(th)
- t.set_reconnect_url(login_data['url'])
- t.connect()
+ for item in login_data["ims"]:
+ channels[item["id"]] = SlackDMChannel(eventrouter, users, **item)
- # web_socket_url = login_data['url']
- # try:
- # ws = create_connection(web_socket_url, sslopt=sslopt_ca_certs)
- # w.hook_fd(ws.sock._sock.fileno(), 1, 0, 0, "receive_ws_callback", t.get_team_hash())
- # #ws_hook = w.hook_fd(ws.sock._sock.fileno(), 1, 0, 0, "receive_ws_callback", pickle.dumps(t))
- # ws.sock.setblocking(0)
- # t.attach_websocket(ws)
- # t.set_connected()
- # except Exception as e:
- # dbg("websocket connection error: {}".format(e))
- # return False
+ for item in login_data["groups"]:
+ if item["name"].startswith('mpdm-'):
+ channels[item["id"]] = SlackMPDMChannel(eventrouter, **item)
+ else:
+ channels[item["id"]] = SlackGroupChannel(eventrouter, **item)
+
+ t = SlackTeam(
+ eventrouter,
+ metadata.token,
+ login_data['url'],
+ login_data["team"]["domain"],
+ login_data["self"]["name"],
+ login_data["self"]["id"],
+ users,
+ bots,
+ channels,
+ muted_channels=login_data["self"]["prefs"]["muted_channels"],
+ highlight_words=login_data["self"]["prefs"]["highlight_words"],
+ )
+ eventrouter.register_team(t)
- t.buffer_prnt('Connected to Slack')
- t.buffer_prnt('{:<20} {}'.format("Websocket URL", login_data["url"]))
- t.buffer_prnt('{:<20} {}'.format("User name", login_data["self"]["name"]))
- t.buffer_prnt('{:<20} {}'.format("User ID", login_data["self"]["id"]))
- t.buffer_prnt('{:<20} {}'.format("Team name", login_data["team"]["name"]))
- t.buffer_prnt('{:<20} {}'.format("Team domain", login_data["team"]["domain"]))
- t.buffer_prnt('{:<20} {}'.format("Team id", login_data["team"]["id"]))
+ else:
+ t = eventrouter.teams.get(th)
+ t.set_reconnect_url(login_data['url'])
+ t.connect()
- dbg("connected to {}".format(t.domain))
+ t.buffer_prnt('Connected to Slack')
+ t.buffer_prnt('{:<20} {}'.format("Websocket URL", login_data["url"]))
+ t.buffer_prnt('{:<20} {}'.format("User name", login_data["self"]["name"]))
+ t.buffer_prnt('{:<20} {}'.format("User ID", login_data["self"]["id"]))
+ t.buffer_prnt('{:<20} {}'.format("Team name", login_data["team"]["name"]))
+ t.buffer_prnt('{:<20} {}'.format("Team domain", login_data["team"]["domain"]))
+ t.buffer_prnt('{:<20} {}'.format("Team id", login_data["team"]["id"]))
- # self.identifier = self.domain
+ dbg("connected to {}".format(t.domain))
def handle_emojilist(emoji_json, eventrouter, **kwargs):
@@ -2137,13 +2180,21 @@ def handle_groupsinfo(group_json, eventrouter, **kwargs):
group_id = group_json['group']['id']
group.set_unread_count_display(unread_count_display)
-def handle_imopen(im_json, eventrouter, **kwargs):
- request_metadata = pickle.loads(im_json["wee_slack_request_metadata"])
- team = eventrouter.teams[request_metadata.team_hash]
- im = team.channels[request_metadata.channel_identifier]
- unread_count_display = im_json['channel']['unread_count_display']
- im.set_unread_count_display(unread_count_display)
-
+def handle_conversationsopen(conversation_json, eventrouter, object_name='channel', **kwargs):
+ request_metadata = pickle.loads(conversation_json["wee_slack_request_metadata"])
+ # Set unread count if the channel isn't new (channel_identifier exists)
+ if hasattr(request_metadata, 'channel_identifier'):
+ channel_id = request_metadata.channel_identifier
+ team = eventrouter.teams[request_metadata.team_hash]
+ conversation = team.channels[channel_id]
+ unread_count_display = conversation_json[object_name]['unread_count_display']
+ conversation.set_unread_count_display(unread_count_display)
+
+
+def handle_mpimopen(mpim_json, eventrouter, object_name='group', **kwargs):
+ handle_conversationsopen(mpim_json, eventrouter, object_name, **kwargs)
+
+
def handle_groupshistory(message_json, eventrouter, **kwargs):
handle_history(message_json, eventrouter, **kwargs)
@@ -2156,6 +2207,10 @@ def handle_imhistory(message_json, eventrouter, **kwargs):
handle_history(message_json, eventrouter, **kwargs)
+def handle_mpimhistory(message_json, eventrouter, **kwargs):
+ handle_history(message_json, eventrouter, **kwargs)
+
+
def handle_history(message_json, eventrouter, **kwargs):
request_metadata = pickle.loads(message_json["wee_slack_request_metadata"])
kwargs['team'] = eventrouter.teams[request_metadata.team_hash]
@@ -2350,8 +2405,7 @@ def subprocess_message_changed(message_json, eventrouter, channel, team):
else:
message_json["fallback"] = m["fallback"]
- text_before = (len(new_message['text']) > 0)
- new_message["text"] += unwrap_attachments(message_json, text_before)
+ new_message["text"] += unwrap_attachments(message_json, new_message["text"])
if "edited" in new_message:
channel.change_message(new_message["ts"], new_message["text"], ' (edited)')
else:
@@ -2365,7 +2419,7 @@ def subprocess_message_deleted(message_json, eventrouter, channel, team):
def subprocess_channel_topic(message_json, eventrouter, channel, team):
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(unhtmlescape(message_json["topic"]))
+ channel.set_topic(unhtmlescape(message_json["topic"]))
def process_reply(message_json, eventrouter, **kwargs):
@@ -2533,10 +2587,9 @@ def render(message_json, team, channel, force=False):
else:
text = ""
- text = unfurl_refs(text, ignore_alt_text=config.unfurl_ignore_alt_text)
+ text = unfurl_refs(text)
- text_before = (len(text) > 0)
- text += unfurl_refs(unwrap_attachments(message_json, text_before), ignore_alt_text=config.unfurl_ignore_alt_text)
+ text += unfurl_refs(unwrap_attachments(message_json, text))
text = text.lstrip()
text = unhtmlescape(text.replace("\t", " "))
@@ -2593,7 +2646,7 @@ def linkify_text(message, team, channel):
return " ".join(message)
-def unfurl_refs(text, ignore_alt_text=False):
+def unfurl_refs(text, ignore_alt_text=None, auto_link_display=None):
"""
input : <@U096Q7CQM|someuser> has joined the channel
ouput : someuser has joined the channel
@@ -2603,14 +2656,21 @@ def unfurl_refs(text, ignore_alt_text=False):
# - <#C2147483705|#otherchannel>
# - <@U2147483697|@othernick>
# Test patterns lives in ./_pytest/test_unfurl.py
- matches = re.findall(r"(<[@#]?(?:[^<]*)>)", text)
+
+ if ignore_alt_text is None:
+ ignore_alt_text = config.unfurl_ignore_alt_text
+ if auto_link_display is None:
+ auto_link_display = config.unfurl_auto_link_display
+
+ matches = re.findall(r"(<[@#]?(?:[^>]*)>)", text)
for m in matches:
# Replace them with human readable strings
- text = text.replace(m, unfurl_ref(m[1:-1], ignore_alt_text))
+ text = text.replace(
+ m, unfurl_ref(m[1:-1], ignore_alt_text, auto_link_display))
return text
-def unfurl_ref(ref, ignore_alt_text=False):
+def unfurl_ref(ref, ignore_alt_text, auto_link_display):
id = ref.split('|')[0]
display_text = ref
if ref.find('|') > -1:
@@ -2623,7 +2683,14 @@ def unfurl_ref(ref, ignore_alt_text=False):
display_text = ref.split('|')[1]
else:
url, desc = ref.split('|', 1)
- display_text = "{} ({})".format(url, desc)
+ match_url = r"^\w+:(//)?{}$".format(re.escape(desc))
+ url_matches_desc = re.match(match_url, url)
+ if url_matches_desc and auto_link_display == "text":
+ display_text = desc
+ elif url_matches_desc and auto_link_display == "url":
+ display_text = url
+ else:
+ display_text = "{} ({})".format(url, desc)
else:
display_text = resolve_ref(ref)
return display_text
@@ -2636,11 +2703,12 @@ def unhtmlescape(text):
def unwrap_attachments(message_json, text_before):
- attachment_text = ''
+ text_before_unescaped = unhtmlescape(text_before)
+ attachment_texts = []
a = message_json.get("attachments", None)
if a:
if text_before:
- attachment_text = '\n'
+ attachment_texts.append('')
for attachment in a:
# Attachments should be rendered roughly like:
#
@@ -2655,14 +2723,18 @@ def unwrap_attachments(message_json, text_before):
if 'pretext' in attachment:
t.append(attachment['pretext'])
title = attachment.get('title', None)
- title_link = attachment.get('title_link', None)
+ title_link = attachment.get('title_link', '')
+ if title_link in text_before_unescaped:
+ title_link = ''
if title and title_link:
t.append('%s%s (%s)' % (prepend_title_text, title, title_link,))
prepend_title_text = ''
elif title and not title_link:
- t.append(prepend_title_text + title)
+ t.append('%s%s' % (prepend_title_text, title,))
prepend_title_text = ''
- t.append(attachment.get("from_url", ""))
+ from_url = attachment.get('from_url', '')
+ if from_url not in text_before_unescaped and from_url != title_link:
+ t.append(from_url)
atext = attachment.get("text", None)
if atext:
@@ -2679,8 +2751,8 @@ def unwrap_attachments(message_json, text_before):
fallback = attachment.get("fallback", None)
if t == [] and fallback:
t.append(fallback)
- attachment_text += "\n".join([x.strip() for x in t if x])
- return attachment_text
+ attachment_texts.append("\n".join([x.strip() for x in t if x]))
+ return "\n".join(attachment_texts)
def resolve_ref(ref):
@@ -2819,20 +2891,20 @@ def tag(tagset, user=None):
else:
default_tag = 'nick_unknown'
tagsets = {
+ # messages in the team/server buffer, e.g. "new channel created"
+ "team": "irc_notice,notify_private,log3",
# when replaying something old
- "backlog": "no_highlight,notify_none,logger_backlog_end",
+ "backlog": "irc_privmsg,no_highlight,notify_none,logger_backlog",
# when posting messages to a muted channel
- "muted": "no_highlight,notify_none,logger_backlog_end",
- # when my nick is in the message
- "highlightme": "notify_highlight,log1",
+ "muted": "irc_privmsg,no_highlight,notify_none,log1",
# when receiving a direct message
- "dm": "notify_private,notify_message,log1,irc_privmsg",
- "dmfromme": "notify_none,log1,irc_privmsg",
+ "dm": "irc_privmsg,notify_private,log1",
+ "dmfromme": "irc_privmsg,no_highlight,notify_none,log1",
# when this is a join/leave, attach for smart filter ala:
# if user in [x.strip() for x in w.prefix("join"), w.prefix("quit")]
- "joinleave": "irc_smart_filter,no_highlight",
+ "joinleave": "irc_smart_filter,no_highlight,log4",
# catchall ?
- "default": "notify_message,log1",
+ "default": "irc_privmsg,notify_message,log1",
}
return default_tag + "," + tagsets[tagset]
@@ -2840,9 +2912,8 @@ def tag(tagset, user=None):
@slack_buffer_or_ignore
+@utf8_decode
def part_command_cb(data, current_buffer, args):
- data = decode_from_utf8(data)
- args = decode_from_utf8(args)
e = EVENTROUTER
args = args.split()
if len(args) > 1:
@@ -2857,64 +2928,67 @@ def part_command_cb(data, current_buffer, args):
return w.WEECHAT_RC_OK_EAT
-@slack_buffer_or_ignore
-def topic_command_cb(data, current_buffer, args):
- n = len(args.split())
- if n < 2:
- channel = channels.find(current_buffer)
- if channel:
- w.prnt(current_buffer, 'Topic for {} is "{}"'.format(channel.name, channel.topic))
- return w.WEECHAT_RC_OK_EAT
- elif command_topic(data, current_buffer, args.split(None, 1)[1]):
- return w.WEECHAT_RC_OK_EAT
- else:
- return w.WEECHAT_RC_ERROR
+def parse_topic_command(command):
+ args = command.split()[1:]
+ channel_name = None
+ topic = None
+
+ if args:
+ if args[0].startswith('#'):
+ channel_name = args[0][1:]
+ topic = args[1:]
+ else:
+ topic = args
+ if topic == []:
+ topic = None
+ if topic:
+ topic = ' '.join(topic)
+ if topic == '-delete':
+ topic = ''
-@slack_buffer_required
-def command_topic(data, current_buffer, args):
+ return channel_name, topic
+
+
+@slack_buffer_or_ignore
+@utf8_decode
+def topic_command_cb(data, current_buffer, command):
"""
Change the topic of a channel
- /slack topic [<channel>] [<topic>|-delete]
+ /topic [<channel>] [<topic>|-delete]
"""
- data = decode_from_utf8(data)
- args = decode_from_utf8(args)
- e = EVENTROUTER
- team = e.weechat_controller.buffers[current_buffer].team
- # server = servers.find(current_domain_name())
- args = args.split(' ')
- if len(args) > 2 and args[1].startswith('#'):
- cmap = team.get_channel_map()
- channel_name = args[1][1:]
- channel = team.channels[cmap[channel_name]]
- topic = " ".join(args[2:])
+
+ channel_name, topic = parse_topic_command(command)
+
+ team = EVENTROUTER.weechat_controller.buffers[current_buffer].team
+ if channel_name:
+ channel = team.channels.get(team.get_channel_map().get(channel_name))
else:
- channel = e.weechat_controller.buffers[current_buffer]
- topic = " ".join(args[1:])
+ channel = EVENTROUTER.weechat_controller.buffers[current_buffer]
- if channel:
- if topic == "-delete":
- topic = ''
- s = SlackRequest(team.token, "channels.setTopic", {"channel": channel.identifier, "topic": topic}, team_hash=team.team_hash)
- EVENTROUTER.receive(s)
+ if not channel:
+ w.prnt(team.channel_buffer, "#{}: No such channel".format(channel_name))
return w.WEECHAT_RC_OK_EAT
+
+ if topic is None:
+ w.prnt(channel.channel_buffer, 'Topic for {} is "{}"'.format(channel.name, channel.topic))
else:
- return w.WEECHAT_RC_ERROR_EAT
+ s = SlackRequest(team.token, "channels.setTopic", {"channel": channel.identifier, "topic": topic}, team_hash=team.team_hash)
+ EVENTROUTER.receive(s)
+ return w.WEECHAT_RC_OK_EAT
@slack_buffer_or_ignore
+@utf8_decode
def me_command_cb(data, current_buffer, args):
- data = decode_from_utf8(data)
- args = decode_from_utf8(args)
message = "_{}_".format(args.split(' ', 1)[1])
buffer_input_callback("EVENTROUTER", current_buffer, message)
return w.WEECHAT_RC_OK_EAT
@slack_buffer_or_ignore
+@utf8_decode
def msg_command_cb(data, current_buffer, args):
- data = decode_from_utf8(data)
- args = decode_from_utf8(args)
dbg("msg_command_cb")
aargs = args.split(None, 2)
who = aargs[1]
@@ -2933,32 +3007,80 @@ def msg_command_cb(data, current_buffer, args):
return w.WEECHAT_RC_OK_EAT
+@slack_buffer_required
+@utf8_decode
+def command_channels(data, current_buffer, args):
+ e = EVENTROUTER
+ team = e.weechat_controller.buffers[current_buffer].team
+
+ team.buffer_prnt("Channels:")
+ for channel in team.get_channel_map():
+ team.buffer_prnt(" {}".format(channel))
+ return w.WEECHAT_RC_OK_EAT
+
+
+@slack_buffer_required
+@utf8_decode
+def command_users(data, current_buffer, args):
+ e = EVENTROUTER
+ team = e.weechat_controller.buffers[current_buffer].team
+
+ team.buffer_prnt("Users:")
+ for user in team.users.values():
+ team.buffer_prnt(" {:<25}({})".format(user.name, user.presence))
+ return w.WEECHAT_RC_OK_EAT
+
+
@slack_buffer_or_ignore
+@utf8_decode
def command_talk(data, current_buffer, args):
"""
- Open a chat with the specified user
- /slack talk [user]
+ Open a chat with the specified user(s)
+ /slack talk <user>[,<user2>[,<user3>...]]
"""
- data = decode_from_utf8(data)
- args = decode_from_utf8(args)
e = EVENTROUTER
team = e.weechat_controller.buffers[current_buffer].team
channel_name = args.split(' ')[1]
- c = team.get_channel_map()
- if channel_name not in c:
- u = team.get_username_map()
- if channel_name in u:
- s = SlackRequest(team.token, "im.open", {"user": u[channel_name]}, team_hash=team.team_hash)
- EVENTROUTER.receive(s)
- dbg("found user")
- # refresh channel map here
- c = team.get_channel_map()
if channel_name.startswith('#'):
channel_name = channel_name[1:]
- if channel_name in c:
- chan = team.channels[c[channel_name]]
+
+ # Try finding the channel by name
+ chan = team.channels.get(team.get_channel_map().get(channel_name))
+
+ # If the channel doesn't exist, try finding a DM or MPDM instead
+ if not chan:
+ # Get the IDs of the users
+ u = team.get_username_map()
+ users = set()
+ for user in channel_name.split(','):
+ if user.startswith('@'):
+ user = user[1:]
+ if user in u:
+ users.add(u[user])
+
+ if users:
+ if len(users) > 1:
+ channel_type = 'mpim'
+ # Add the current user since MPDMs include them as a member
+ users.add(team.myidentifier)
+ else:
+ channel_type = 'im'
+
+ # Try finding the channel by type and members
+ for channel in team.channels.itervalues():
+ if (channel.type == channel_type and
+ channel.get_members() == users):
+ chan = channel
+ break
+
+ # If the DM or MPDM doesn't exist, create it
+ if not chan:
+ s = SlackRequest(team.token, SLACK_API_TRANSLATOR[channel_type]['join'], {'users': ','.join(users)}, team_hash=team.team_hash)
+ EVENTROUTER.receive(s)
+
+ if chan:
chan.open()
if config.switch_buffer_on_join:
w.buffer_set(chan.channel_buffer, "display", "1")
@@ -2971,9 +3093,8 @@ def command_showmuted(data, current_buffer, args):
w.prnt(EVENTROUTER.weechat_controller.buffers[current].team.channel_buffer, str(EVENTROUTER.weechat_controller.buffers[current].team.muted_channels))
+@utf8_decode
def thread_command_callback(data, current_buffer, args):
- data = decode_from_utf8(data)
- args = decode_from_utf8(args)
current = w.current_buffer()
channel = EVENTROUTER.weechat_controller.buffers.get(current)
if channel:
@@ -2988,22 +3109,22 @@ def thread_command_callback(data, current_buffer, args):
pm.thread_channel = tc
tc.open()
# tc.create_buffer()
+ if config.switch_buffer_on_join:
+ w.buffer_set(tc.channel_buffer, "display", "1")
return w.WEECHAT_RC_OK_EAT
elif args[0] == '/reply':
count = int(args[1])
msg = " ".join(args[2:])
- mkeys = channel.sorted_message_keys()
- mkeys.reverse()
- parent_id = str(mkeys[count - 1])
+ 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.")
return w.WEECHAT_RC_OK_EAT
+@utf8_decode
def rehistory_command_callback(data, current_buffer, args):
- data = decode_from_utf8(data)
- args = decode_from_utf8(args)
current = w.current_buffer()
channel = EVENTROUTER.weechat_controller.buffers.get(current)
channel.got_history = False
@@ -3013,9 +3134,8 @@ def rehistory_command_callback(data, current_buffer, args):
@slack_buffer_required
+@utf8_decode
def hide_command_callback(data, current_buffer, args):
- data = decode_from_utf8(data)
- args = decode_from_utf8(args)
c = EVENTROUTER.weechat_controller.buffers.get(current_buffer, None)
if c:
name = c.formatted_name(style='long_default')
@@ -3024,9 +3144,8 @@ def hide_command_callback(data, current_buffer, args):
return w.WEECHAT_RC_OK_EAT
+@utf8_decode
def slack_command_cb(data, current_buffer, args):
- data = decode_from_utf8(data)
- args = decode_from_utf8(args)
a = args.split(' ', 1)
if len(a) > 1:
function_name, args = a[0], args
@@ -3135,9 +3254,8 @@ def command_upload(data, current_buffer, args):
w.hook_process(command, config.slack_timeout, '', '')
+@utf8_decode
def away_command_cb(data, current_buffer, args):
- data = decode_from_utf8(data)
- args = decode_from_utf8(args)
# TODO: reimplement all.. maybe
(all, message) = re.match("^/away(?:\s+(-all))?(?:\s+(.+))?", args).groups()
if message is None:
@@ -3195,9 +3313,8 @@ def command_back(data, current_buffer, args):
@slack_buffer_required
+@utf8_decode
def label_command_cb(data, current_buffer, args):
- data = decode_from_utf8(data)
- args = decode_from_utf8(args)
channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer)
if channel and channel.type == 'thread':
aargs = args.split(None, 2)
@@ -3206,6 +3323,21 @@ def label_command_cb(data, current_buffer, args):
w.buffer_set(channel.channel_buffer, "short_name", new_name)
+@utf8_decode
+def set_unread_cb(data, current_buffer, command):
+ for channel in EVENTROUTER.weechat_controller.buffers.values():
+ channel.mark_read()
+ return w.WEECHAT_RC_OK
+
+
+@slack_buffer_or_ignore
+@utf8_decode
+def set_unread_current_buffer_cb(data, current_buffer, command):
+ channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer)
+ channel.mark_read()
+ return w.WEECHAT_RC_OK
+
+
def command_p(data, current_buffer, args):
args = args.split(' ', 1)[1]
w.prnt("", "{}".format(eval(args)))
@@ -3273,7 +3405,8 @@ def setup_hooks():
w.hook_signal('buffer_switch', "buffer_switch_callback", "EVENTROUTER")
w.hook_signal('window_switch', "buffer_switch_callback", "EVENTROUTER")
w.hook_signal('quit', "quit_notification_cb", "")
- w.hook_signal('input_text_changed', "typing_notification_cb", "")
+ if config.send_typing_notice:
+ w.hook_signal('input_text_changed', "typing_notification_cb", "")
w.hook_command(
# Command name and description
@@ -3295,7 +3428,7 @@ def setup_hooks():
w.hook_command_run('/join', 'command_talk', '')
w.hook_command_run('/part', 'part_command_cb', '')
w.hook_command_run('/leave', 'part_command_cb', '')
- w.hook_command_run('/topic', 'command_topic', '')
+ w.hook_command_run('/topic', 'topic_command_cb', '')
w.hook_command_run('/thread', 'thread_command_callback', '')
w.hook_command_run('/reply', 'thread_command_callback', '')
w.hook_command_run('/rehistory', 'rehistory_command_callback', '')
@@ -3303,6 +3436,8 @@ def setup_hooks():
w.hook_command_run('/msg', 'msg_command_cb', '')
w.hook_command_run('/label', 'label_command_cb', '')
w.hook_command_run("/input complete_next", "complete_next_cb", "")
+ w.hook_command_run("/input set_unread", "set_unread_cb", "")
+ w.hook_command_run("/input set_unread_current_buffer", "set_unread_current_buffer_cb", "")
w.hook_command_run('/away', 'away_command_cb', '')
w.hook_completion("nicks", "complete @-nicks for slack", "nick_completion_cb", "")
@@ -3373,6 +3508,9 @@ class PluginConfig(object):
'distracting_channels': Setting(
default='',
desc='List of channels to hide.'),
+ 'group_name_prefix': Setting(
+ default='&',
+ desc='The prefix of buffer names for groups (private channels).'),
'map_underline_to': Setting(
default='_',
desc='When sending underlined text to slack, use this formatting'
@@ -3391,6 +3529,10 @@ class PluginConfig(object):
default='italic',
desc='When receiving bold text from Slack, render it as this in weechat.'
' If your terminal lacks italic support, consider using "underline" instead.'),
+ 'send_typing_notice': Setting(
+ default='true',
+ desc='Alert Slack users when you are typing a message in the input bar '
+ '(Requires reload)'),
'server_aliases': Setting(
default='',
desc='A comma separated list of `subdomain:alias` pairs. The alias'
@@ -3423,6 +3565,15 @@ class PluginConfig(object):
desc='When displaying ("unfurling") links to channels/users/etc,'
' ignore the "alt text" present in the message and instead use the'
' canonical name of the thing being linked to.'),
+ 'unfurl_auto_link_display': Setting(
+ default='both',
+ desc='When displaying ("unfurling") links to channels/users/etc,'
+ ' determine what is displayed when the text matches the url'
+ ' without the protocol. This happens when Slack automatically'
+ ' creates links, e.g. from words separated by dots or email'
+ ' addresses. Set it to "text" to only display the text written by'
+ ' the user, "url" to only display the url or "both" (the default)'
+ ' to display both.'),
'unhide_buffers_with_activity': Setting(
default='false',
desc='When activity occurs on a buffer, unhide it even if it was'
@@ -3483,11 +3634,13 @@ class PluginConfig(object):
return int(w.config_get_plugin(key))
get_debug_level = get_int
+ get_group_name_prefix = get_string
get_map_underline_to = get_string
get_render_bold_as = get_string
get_render_italic_as = get_string
get_slack_timeout = get_int
get_thread_suffix_color = get_string
+ get_unfurl_auto_link_display = get_string
def get_distracting_channels(self, key):
return [x.strip() for x in w.config_get_plugin(key).split(',')]
@@ -3583,6 +3736,7 @@ if __name__ == "__main__":
# main_weechat_buffer = w.info_get("irc_buffer", "{}.{}".format(domain, "DOESNOTEXIST!@#$"))
w.hook_config("plugins.var.python." + SCRIPT_NAME + ".*", "config_changed_cb", "")
+ w.hook_modifier("input_text_for_buffer", "input_text_for_buffer_cb", "")
EMOJI.extend(load_emoji())
setup_hooks()