diff options
Diffstat (limited to 'wee_slack.py')
-rw-r--r-- | wee_slack.py | 734 |
1 files changed, 507 insertions, 227 deletions
diff --git a/wee_slack.py b/wee_slack.py index ef41c6f..a3d43ce 100644 --- a/wee_slack.py +++ b/wee_slack.py @@ -1,5 +1,6 @@ -#-*- coding: utf-8 -*- -# +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals from functools import wraps @@ -12,7 +13,7 @@ import re import urllib import sys import traceback -#import collections +import collections import ssl import random import string @@ -21,7 +22,7 @@ from websocket import create_connection, WebSocketConnectionClosedException # hack to make tests possible.. better way? try: - import weechat as w + import weechat except: pass @@ -70,6 +71,7 @@ SLACK_API_TRANSLATOR = { ###### Decorators have to be up here + def slack_buffer_or_ignore(f): """ Only run this function if we're in a slack buffer, else ignore @@ -81,6 +83,7 @@ def slack_buffer_or_ignore(f): return f(data, current_buffer, *args, **kwargs) return wrapper + def slack_buffer_required(f): """ Only run this function if we're in a slack buffer, else print error @@ -102,16 +105,64 @@ if hasattr(ssl, "get_default_verify_paths") and callable(ssl.get_default_verify_ if ssl_defaults.cafile is not None: sslopt_ca_certs = {'ca_certs': ssl_defaults.cafile} +###### Unicode handling + + +def encode_to_utf8(data): + if isinstance(data, unicode): + return data.encode('utf-8') + if isinstance(data, bytes): + return data + elif isinstance(data, collections.Mapping): + return dict(map(encode_to_utf8, data.iteritems())) + elif isinstance(data, collections.Iterable): + return type(data)(map(encode_to_utf8, data)) + else: + return data + + +def decode_from_utf8(data): + if isinstance(data, bytes): + return data.decode('utf-8') + if isinstance(data, unicode): + return data + elif isinstance(data, collections.Mapping): + return dict(map(decode_from_utf8, data.iteritems())) + elif isinstance(data, collections.Iterable): + return type(data)(map(decode_from_utf8, data)) + else: + return data + + +class WeechatWrapper(object): + def __init__(self, wrapped_class): + self.wrapped_class = wrapped_class + + 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 + else: + return decode_from_utf8(orig_attr) + + ##### BEGIN NEW IGNORED_EVENTS = [ "hello", - #"pref_change", - #"reconnect_url", + # "pref_change", + # "reconnect_url", ] ###### New central Event router + class EventRouter(object): def __init__(self): @@ -188,7 +239,7 @@ class EventRouter(object): """ data = self.context.get(identifier, None) if data: - #dbg("retrieved eontext {} ".format(identifier)) + # dbg("retrieved context {} ".format(identifier)) return data def delete_context(self, identifier): @@ -196,7 +247,7 @@ class EventRouter(object): Requests can span multiple requests, so we may need to delete this as a last step """ if identifier in self.context: - #dbg("deleted eontext {} ".format(identifier)) + # dbg("deleted eontext {} ".format(identifier)) del self.context[identifier] def shutdown(self): @@ -236,7 +287,7 @@ class EventRouter(object): """ try: # Read the data from the websocket associated with this team. - data = self.teams[team_hash].ws.recv() + data = decode_from_utf8(self.teams[team_hash].ws.recv()) message_json = json.loads(data) metadata = WeeSlackMetadata({ "team": team_hash, @@ -246,7 +297,7 @@ class EventRouter(object): self.record_event(message_json, 'type', 'websocket') self.receive_json(json.dumps(message_json)) except WebSocketConnectionClosedException: - #TODO: handle reconnect here + # TODO: handle reconnect here self.teams[team_hash].set_disconnected() return w.WEECHAT_RC_OK except Exception: @@ -271,17 +322,17 @@ class EventRouter(object): if return_code == 0: if len(out) > 0: if request_metadata.response_id in self.reply_buffer: - #dbg("found response id in reply_buffer", True) + # dbg("found response id in reply_buffer", True) self.reply_buffer[request_metadata.response_id] += out else: - #dbg("didn't find response id in reply_buffer", True) + # dbg("didn't find response id in reply_buffer", True) self.reply_buffer[request_metadata.response_id] = "" self.reply_buffer[request_metadata.response_id] += out try: j = json.loads(self.reply_buffer[request_metadata.response_id]) except: pass - #dbg("Incomplete json, awaiting more", True) + # dbg("Incomplete json, awaiting more", True) try: j["wee_slack_process_method"] = request_metadata.request_normalized j["wee_slack_request_metadata"] = pickle.dumps(request_metadata) @@ -315,6 +366,7 @@ class EventRouter(object): dbg("RECEIVED JSON of len {}".format(len(data))) message_json = json.loads(data) self.queue.append(message_json) + def receive(self, dataobj): """ complete @@ -324,6 +376,7 @@ class EventRouter(object): """ dbg("RECEIVED FROM QUEUE") self.queue.append(dataobj) + def receive_slow(self, dataobj): """ complete @@ -333,6 +386,7 @@ class EventRouter(object): """ dbg("RECEIVED FROM QUEUE") self.slow_queue.append(dataobj) + def handle_next(self): """ complete @@ -341,10 +395,10 @@ class EventRouter(object): useful metadata and context to events as they are processed. """ if len(self.slow_queue) > 0 and ((self.slow_queue_timer + 1) < time.time()): - #for q in self.slow_queue[0]: + # for q in self.slow_queue[0]: dbg("from slow queue", 0) self.queue.append(self.slow_queue.pop()) - #self.slow_queue = [] + # self.slow_queue = [] self.slow_queue_timer = time.time() if len(self.queue) > 0: j = self.queue.pop(0) @@ -378,7 +432,7 @@ class EventRouter(object): meta = j.get("wee_slack_metadata", None) if meta: try: - if isinstance(meta, str): + if isinstance(meta, basestring): dbg("string of metadata") team = meta.get("team", None) if team: @@ -401,6 +455,7 @@ class EventRouter(object): else: raise ProcessNotImplemented(function_name) + def handle_next(*args): """ complete @@ -416,33 +471,38 @@ def handle_next(*args): pass return w.WEECHAT_RC_OK + class WeechatController(object): """ Encapsulates our interaction with weechat """ + def __init__(self, eventrouter): self.eventrouter = eventrouter self.buffers = {} self.previous_buffer = None self.buffer_list_stale = False + def iter_buffers(self): for b in self.buffers: yield (b, self.buffers[b]) + def register_buffer(self, buffer_ptr, channel): """ complete Adds a weechat buffer to the list of handled buffers for this EventRouter """ - if isinstance(buffer_ptr, str): + if isinstance(buffer_ptr, basestring): self.buffers[buffer_ptr] = channel else: raise InvalidType(type(buffer_ptr)) + def unregister_buffer(self, buffer_ptr, update_remote=False, close_buffer=False): """ complete Adds a weechat buffer to the list of handled buffers for this EventRouter """ - if isinstance(buffer_ptr, str): + if isinstance(buffer_ptr, basestring): try: self.buffers[buffer_ptr].destroy_buffer(update_remote) if close_buffer: @@ -452,22 +512,28 @@ class WeechatController(object): dbg("Tried to close unknown buffer") else: raise InvalidType(type(buffer_ptr)) + def get_channel_from_buffer_ptr(self, buffer_ptr): return self.buffers.get(buffer_ptr, None) + def get_all(self, buffer_ptr): return self.buffers + def get_previous_buffer_ptr(self): return self.previous_buffer + def set_previous_buffer(self, data): self.previous_buffer = data + def check_refresh_buffer_list(self): return self.buffer_list_stale and self.last_buffer_list_update + 1 < time.time() + def set_refresh_buffer_list(self, setting): self.buffer_list_stale = setting - ###### New Local Processors + def local_process_async_slack_api_request(request, event_router): """ complete @@ -480,21 +546,24 @@ def local_process_async_slack_api_request(request, event_router): params = {'useragent': 'wee_slack {}'.format(SCRIPT_VERSION)} request.tried() context = event_router.store_context(request) - #TODO: let flashcode know about this bug - i have to 'clear' the hashtable or retry requests fail + # TODO: let flashcode know about this bug - i have to 'clear' the hashtable or retry requests fail w.hook_process_hashtable('url:', params, config.slack_timeout, "", context) w.hook_process_hashtable(weechat_request, params, config.slack_timeout, "receive_httprequest_callback", context) ###### New Callbacks + 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): + # 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 + def receive_ws_callback(*args): """ complete @@ -505,10 +574,12 @@ def receive_ws_callback(*args): EVENTROUTER.receive_ws_callback(args[0]) return w.WEECHAT_RC_OK + def reconnect_callback(*args): EVENTROUTER.reconnect_if_disconnected() return w.WEECHAT_RC_OK + def buffer_closing_callback(signal, sig_type, data): """ complete @@ -517,9 +588,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 + def buffer_input_callback(signal, buffer_ptr, data): """ incomplete @@ -527,6 +600,7 @@ 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: @@ -548,12 +622,13 @@ def buffer_input_callback(signal, buffer_ptr, data): # rid of escapes. new = new.replace(r'\/', '/') old = old.replace(r'\/', '/') - channel.edit_previous_message(old.decode("utf-8"), new.decode("utf-8"), flags) + channel.edit_previous_message(old, new, flags) else: channel.send_message(data) - #this is probably wrong channel.mark_read(update_remote=True, force=True) + # this is probably wrong channel.mark_read(update_remote=True, force=True) return w.WEECHAT_RC_ERROR + def buffer_switch_callback(signal, sig_type, data): """ incomplete @@ -561,6 +636,7 @@ 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() @@ -577,6 +653,7 @@ def buffer_switch_callback(signal, sig_type, data): eventrouter.weechat_controller.set_previous_buffer(data) return w.WEECHAT_RC_OK + def buffer_list_update_callback(data, somecount): """ incomplete @@ -586,8 +663,9 @@ 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 + # global buffer_list_update for b in eventrouter.weechat_controller.iter_buffers(): b[1].refresh() @@ -599,10 +677,13 @@ def buffer_list_update_callback(data, somecount): # eventrouter.weechat_controller.set_refresh_buffer_list(False) return w.WEECHAT_RC_OK + def quit_notification_callback(signal, sig_type, data): stop_talking_to_slack() + 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 @@ -617,11 +698,15 @@ def typing_notification_cb(signal, sig_type, data): typing_timer = now return w.WEECHAT_RC_OK + def typing_update_cb(data, remaining_calls): + data = decode_from_utf8(data) w.bar_item_update("slack_typing_notice") return w.WEECHAT_RC_OK + 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'] @@ -630,6 +715,7 @@ def slack_never_away_cb(data, remaining_calls): channel.team.send_to_websocket(request, expect_reply=False) return w.WEECHAT_RC_OK + def typing_bar_item_cb(data, current_buffer, args): """ Privides a bar item indicating who is typing in the current channel AND @@ -663,11 +749,14 @@ def typing_bar_item_cb(data, current_buffer, args): return typing + 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) @@ -679,11 +768,14 @@ def nick_completion_cb(data, completion_item, current_buffer, completion): w.hook_completion_list_add(completion, "@" + u.slack_name, 1, w.WEECHAT_LIST_POS_SORT) return w.WEECHAT_RC_OK + 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) @@ -693,6 +785,7 @@ def emoji_completion_cb(data, completion_item, current_buffer, completion): w.hook_completion_list_add(completion, ":" + e + ":", 0, w.WEECHAT_LIST_POS_SORT) return w.WEECHAT_RC_OK + 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 @@ -701,10 +794,12 @@ 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) - #channel = channels.find(current_buffer) + # channel = channels.find(current_buffer) if not hasattr(current_channel, 'members') or current_channel is None or current_channel.members is None: return w.WEECHAT_RC_OK @@ -739,10 +834,12 @@ def complete_next_cb(data, current_buffer, command): return w.WEECHAT_RC_OK_EAT return w.WEECHAT_RC_OK + def script_unloaded(): stop_talking_to_slack() return w.WEECHAT_RC_OK + def stop_talking_to_slack(): """ complete @@ -753,15 +850,16 @@ def stop_talking_to_slack(): EVENTROUTER.shutdown() return w.WEECHAT_RC_OK - ##### New Classes + class SlackRequest(object): """ complete Encapsulates a Slack api request. Valuable as an object that we can add to the queue and/or retry. makes a SHA of the requst url and current time so we can re-tag this on the way back through. """ + def __init__(self, token, request, post_data={}, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) @@ -774,31 +872,37 @@ class SlackRequest(object): post_data["token"] = token self.post_data = post_data self.params = {'useragent': 'wee_slack {}'.format(SCRIPT_VERSION)} - self.url = 'https://{}/api/{}?{}'.format(self.domain, request, urllib.urlencode(post_data)) + self.url = 'https://{}/api/{}?{}'.format(self.domain, request, urllib.urlencode(encode_to_utf8(post_data))) self.response_id = sha.sha("{}{}".format(self.url, self.start_time)).hexdigest() self.retries = kwargs.get('retries', 3) # def __repr__(self): # return "URL: {} Tries: {} ID: {}".format(self.url, self.tries, self.response_id) + def request_string(self): return "{}".format(self.url) + def tried(self): self.tries += 1 self.response_id = sha.sha("{}{}".format(self.url, time.time())).hexdigest() + def should_try(self): return self.tries < self.retries + def retry_ready(self): return (self.start_time + (self.tries**2)) < time.time() + class SlackTeam(object): """ incomplete Team object under which users and channels live.. Does lots. """ + def __init__(self, eventrouter, token, websocket_url, subdomain, nick, myidentifier, users, bots, channels, **kwargs): self.ws_url = websocket_url self.connected = False self.connecting = False - #self.ws = None + # self.ws = None self.ws_counter = 0 self.ws_replies = {} self.eventrouter = eventrouter @@ -819,7 +923,6 @@ class SlackTeam(object): self.users = users self.bots = bots self.team_hash = SlackTeam.generate_team_hash(self.nick, self.subdomain) - #self.team_hash = str(sha.sha("{}{}".format(self.nick, self.subdomain)).hexdigest()) self.name = self.domain self.channel_buffer = None self.got_history = True @@ -833,21 +936,26 @@ class SlackTeam(object): self.users[self.myidentifier].force_color(w.config_string(w.config_get('weechat.color.chat_nick_self'))) # This highlight step must happen after we have set related server self.set_highlight_words(kwargs.get('highlight_words', "")) + def __eq__(self, compare_str): if compare_str == self.token or compare_str == self.domain or compare_str == self.subdomain: return True else: return False + def add_channel(self, channel): self.channels[channel["id"]] = channel channel.set_related_server(self) -# def connect_request_generate(self): -# return SlackRequest(self.token, 'rtm.start', {}) - #def close_all_buffers(self): + + # def connect_request_generate(self): + # return SlackRequest(self.token, 'rtm.start', {}) + + # def close_all_buffers(self): # for channel in self.channels: # self.eventrouter.weechat_controller.unregister_buffer(channel.channel_buffer, update_remote=False, close_buffer=True) # #also close this server buffer # self.eventrouter.weechat_controller.unregister_buffer(self.channel_buffer, update_remote=False, close_buffer=True) + def create_buffer(self): if not self.channel_buffer: if config.short_buffer_names: @@ -864,40 +972,54 @@ class SlackTeam(object): 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(',')} + def set_highlight_words(self, highlight_str): self.highlight_words = {x for x in highlight_str.split(',')} if len(self.highlight_words) > 0: for v in self.channels.itervalues(): v.set_highlights() + def formatted_name(self, **kwargs): return self.domain + def buffer_prnt(self, data): w.prnt_date_tags(self.channel_buffer, SlackTS().major, tag("backlog"), data) + def get_channel_map(self): return {v.slack_name: k for k, v in self.channels.iteritems()} + def get_username_map(self): return {v.slack_name: k for k, v in self.users.iteritems()} + def get_team_hash(self): return self.team_hash + @staticmethod def generate_team_hash(nick, subdomain): return str(sha.sha("{}{}".format(nick, subdomain)).hexdigest()) + def refresh(self): self.rename() + def rename(self): pass - #def attach_websocket(self, ws): + + # def attach_websocket(self, ws): # self.ws = ws + def is_user_present(self, user_id): user = self.users.get(user_id) if user.presence == 'active': return True else: return False + def mark_read(self): pass + def connect(self): if not self.connected and not self.connecting: self.connecting = True @@ -907,7 +1029,7 @@ class SlackTeam(object): w.hook_fd(ws.sock._sock.fileno(), 1, 0, 0, "receive_ws_callback", self.get_team_hash()) ws.sock.setblocking(0) self.ws = ws - #self.attach_websocket(ws) + # self.attach_websocket(ws) self.set_connected() self.connecting = False except Exception as e: @@ -915,42 +1037,49 @@ class SlackTeam(object): self.connecting = False return False else: - #The fast reconnect failed, so start over-ish + # The fast reconnect failed, so start over-ish for chan in self.channels: self.channels[chan].got_history = False s = SlackRequest(self.token, 'rtm.start', {}, retries=999) self.eventrouter.receive(s) self.connecting = False - #del self.eventrouter.teams[self.get_team_hash()] + # del self.eventrouter.teams[self.get_team_hash()] self.set_reconnect_url(None) + def set_connected(self): self.connected = True + def set_disconnected(self): self.connected = False + def set_reconnect_url(self, url): self.ws_url = url + def next_ws_transaction_id(self): if self.ws_counter > 999: self.ws_counter = 0 self.ws_counter += 1 return self.ws_counter + def send_to_websocket(self, data, expect_reply=True): data["id"] = self.next_ws_transaction_id() message = json.dumps(data) try: if expect_reply: self.ws_replies[data["id"]] = data - self.ws.send(message) + self.ws.send(encode_to_utf8(message)) dbg("Sent {}...".format(message[:100])) except: print "WS ERROR" dbg("Unexpected error: {}\nSent: {}".format(sys.exc_info()[0], data)) self.set_connected() + class SlackChannel(object): """ Represents an individual slack channel. """ + def __init__(self, eventrouter, **kwargs): # We require these two things for a vaid object, # the rest we can just learn from slack @@ -964,7 +1093,6 @@ class SlackChannel(object): self.slack_purpose = kwargs.get("purpose", {"value": ""}) self.identifier = kwargs["id"] self.last_read = SlackTS(kwargs.get("last_read", SlackTS())) - #print self.last_read self.channel_buffer = None self.team = kwargs.get('team', None) self.got_history = False @@ -974,20 +1102,25 @@ class SlackChannel(object): self.typing = {} self.type = 'channel' self.set_name(self.slack_name) - #short name relates to the localvar we change for typing indication + # short name relates to the localvar we change for typing indication self.current_short_name = self.name self.update_nicklist() + def __eq__(self, compare_str): if compare_str == self.slack_name or compare_str == self.formatted_name() or compare_str == self.formatted_name(style="long_default"): return True else: return False + def __repr__(self): return "Name:{} Identifier:{}".format(self.name, self.identifier) + def set_name(self, slack_name): self.name = "#" + slack_name + def refresh(self): return self.rename() + def rename(self): if self.channel_buffer: new_name = self.formatted_name(typing=self.is_someone_typing(), style="sidebar") @@ -996,6 +1129,7 @@ class SlackChannel(object): w.buffer_set(self.channel_buffer, "short_name", new_name) return True return False + def formatted_name(self, style="default", typing=False, **kwargs): if config.channel_name_typing_indicator: if not typing: @@ -1012,20 +1146,20 @@ class SlackChannel(object): "long_base": "{}.{}".format(self.team.preferred_name, self.slack_name), } return select[style] + def render_topic(self, topic=None): if self.channel_buffer: if not topic: if self.slack_topic['value'] != "": - encoded_topic = self.slack_topic['value'].encode('utf-8') + topic = self.slack_topic['value'] else: - encoded_topic = self.slack_purpose['value'].encode('utf-8') - else: - encoded_topic = topic.encode('utf-8') - self.encoded_topic = topic.encode('utf-8') - w.buffer_set(self.channel_buffer, "title", encoded_topic) + topic = self.slack_purpose['value'] + w.buffer_set(self.channel_buffer, "title", topic) + def update_from_message_json(self, message_json): for key, value in message_json.items(): setattr(self, key, value) + def open(self, update_remote=True): if update_remote: if "join" in SLACK_API_TRANSLATOR[self.type]: @@ -1037,7 +1171,8 @@ class SlackChannel(object): 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) self.eventrouter.receive(s) - #self.create_buffer() + # self.create_buffer() + def check_should_open(self, force=False): try: if self.is_archived: @@ -1055,14 +1190,17 @@ class SlackChannel(object): self.get_history(slow_queue=True) except: pass + def set_related_server(self, team): self.team = team + def set_highlights(self): - #highlight my own name and any set highlights + # highlight my own name and any set highlights if self.channel_buffer: highlights = self.team.highlight_words.union({'@' + self.team.nick, "!here", "!channel", "!everyone"}) h_str = ",".join(highlights) w.buffer_set(self.channel_buffer, "highlight_words", h_str) + def create_buffer(self): """ incomplete (muted doesn't work) @@ -1081,11 +1219,11 @@ class SlackChannel(object): self.render_topic() self.eventrouter.weechat_controller.set_refresh_buffer_list(True) if self.channel_buffer: - #if self.team.server_alias: - #w.buffer_set(self.channel_buffer, "localvar_set_server", self.team.server_alias) - #else: + # if self.team.server_alias: + # w.buffer_set(self.channel_buffer, "localvar_set_server", self.team.server_alias) + # else: w.buffer_set(self.channel_buffer, "localvar_set_server", self.team.preferred_name) - #else: + # else: # self.eventrouter.weechat_controller.register_buffer(self.channel_buffer, self) try: for c in range(self.unread_count_display): @@ -1095,40 +1233,42 @@ class SlackChannel(object): w.buffer_set(self.channel_buffer, "hotlist", "1") else: pass - #dbg("no unread in {}".format(self.name)) + # dbg("no unread in {}".format(self.name)) except: pass self.update_nicklist() - #dbg("exception no unread count") - #if self.unread_count != 0 and not self.muted: + # dbg("exception no unread count") + # if self.unread_count != 0 and not self.muted: # w.buffer_set(self.channel_buffer, "hotlist", "1") + def destroy_buffer(self, update_remote): if self.channel_buffer is not None: self.channel_buffer = None self.messages = {} self.hashed_messages = {} self.got_history = False - #if update_remote and not eventrouter.shutting_down: + # if update_remote and not eventrouter.shutting_down: self.active = False if update_remote and not self.eventrouter.shutting_down: s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["leave"], {"channel": self.identifier}, team_hash=self.team.team_hash, channel_identifier=self.identifier) self.eventrouter.receive(s) + def buffer_prnt(self, nick, text, timestamp=str(time.time()), tagset=None, tag_nick=None, **kwargs): data = "{}\t{}".format(nick, text) ts = SlackTS(timestamp) last_read = SlackTS(self.last_read) - #without this, DMs won't open automatically + # without this, DMs won't open automatically if not self.channel_buffer and ts > last_read: self.open(update_remote=False) if self.channel_buffer: - #backlog messages - we will update the read marker as we print these + # backlog messages - we will update the read marker as we print these backlog = True if ts <= last_read else False if tagset: tags = tag(tagset, user=tag_nick) self.new_messages = True - #we have to infer the tagset because we weren't told + # we have to infer the tagset because we weren't told elif ts <= last_read: tags = tag("backlog", user=tag_nick) elif self.type in ["im", "mpdm"]: @@ -1151,14 +1291,16 @@ class SlackChannel(object): self.mark_read(ts, update_remote=False, force=True) except: dbg("Problem processing buffer_prnt") + def send_message(self, message, request_dict_ext={}): - #team = self.eventrouter.teams[self.team] + # team = self.eventrouter.teams[self.team] message = linkify_text(message, self.team, self) dbg(message) request = {"type": "message", "channel": self.identifier, "text": message, "_team": self.team.team_hash, "user": self.team.myidentifier} request.update(request_dict_ext) self.team.send_to_websocket(request) self.mark_read(update_remote=False, force=True) + def store_message(self, message, team, from_me=False): if not self.active: return @@ -1173,6 +1315,7 @@ class SlackChannel(object): if msg_to_delete.hash: del self.hashed_messages[msg_to_delete.hash] del self.messages[k] + def change_message(self, ts, text=None, suffix=None): ts = SlackTS(ts) if ts in self.messages: @@ -1184,6 +1327,7 @@ class SlackChannel(object): text = m.render(force=True) 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() if new == "" and old == "": @@ -1195,18 +1339,21 @@ class SlackChannel(object): num_replace = 0 new_message = re.sub(old, new, message["text"], num_replace) if new_message != message["text"]: - s = SlackRequest(self.team.token, "chat.update", {"channel": self.identifier, "ts": message['ts'], "text": new_message.encode("utf-8")}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + 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): 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 + def is_visible(self): return w.buffer_get_integer(self.channel_buffer, "hidden") == 0 + def get_history(self, slow_queue=False): if not self.got_history: - #we have probably reconnected. flush the buffer + # we have probably reconnected. flush the buffer if self.team.connected: w.buffer_clear(self.channel_buffer) self.buffer_prnt('', 'getting channel history...', tagset='backlog') @@ -1216,32 +1363,39 @@ class SlackChannel(object): else: self.eventrouter.receive_slow(s) self.got_history = True + def send_add_reaction(self, msg_number, reaction): self.send_change_reaction("reactions.add", msg_number, reaction) + def send_remove_reaction(self, msg_number, reaction): self.send_change_reaction("reactions.remove", msg_number, reaction) + def send_change_reaction(self, method, msg_number, reaction): if 0 < msg_number < len(self.messages): timestamp = self.sorted_message_keys()[-msg_number] 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) + # Typing related def set_typing(self, user): if self.channel_buffer and self.is_visible(): self.typing[user] = time.time() self.eventrouter.weechat_controller.set_refresh_buffer_list(True) + def unset_typing(self, user): if self.channel_buffer and self.is_visible(): u = self.typing.get(user, None) if u: self.eventrouter.weechat_controller.set_refresh_buffer_list(True) + def is_someone_typing(self): """ Walks through dict of typing folks in a channel and fast @@ -1255,6 +1409,7 @@ class SlackChannel(object): self.typing = {} self.eventrouter.weechat_controller.set_refresh_buffer_list(True) return False + def get_typing_list(self): """ Returns the names of everyone in the channel who is currently typing. @@ -1266,6 +1421,7 @@ class SlackChannel(object): else: del self.typing[user] return typing + def mark_read(self, ts=None, update_remote=True, force=False): if not ts: ts = SlackTS() @@ -1277,16 +1433,17 @@ class SlackChannel(object): s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["mark"], {"channel": self.identifier, "ts": ts}, team_hash=self.team.team_hash, channel_identifier=self.identifier) self.eventrouter.receive(s) self.new_messages = False + def user_joined(self, user_id): - #ugly hack - for some reason this gets turned into a list + # ugly hack - for some reason this gets turned into a list self.members = set(self.members) self.members.add(user_id) self.update_nicklist(user_id) + def user_left(self, user_id): - #pass - #filter(lambda u: u != user_id, self.members) self.members.discard(user_id) self.update_nicklist(user_id) + def update_nicklist(self, user=None): if not self.channel_buffer: return @@ -1295,12 +1452,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: + # 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_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: @@ -1311,7 +1468,7 @@ class SlackChannel(object): # now add it back in to whichever.. 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, here, user.name, user.color_name, "", "", 1) # if we didn't get a user, build a complete list. this is expensive. else: @@ -1322,12 +1479,13 @@ class SlackChannel(object): 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) + # w.nicklist_add_nick(self.channel_buffer, here, user.name, user.color_name, "", "", 1) except Exception as e: dbg("DEBUG: {} {} {}".format(self.identifier, self.name, e)) else: 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) @@ -1337,15 +1495,15 @@ class SlackChannel(object): if ts in self.messages and not self.messages[ts].hash: message = self.messages[ts] tshash = calc_hash(message) - l = 3 - shorthash = tshash[:l] + hl = 3 + shorthash = tshash[:hl] while any(x.startswith(shorthash) for x in self.hashed_messages): - l += 1 - shorthash = tshash[:l] + 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)[:l] + 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)) @@ -1361,6 +1519,7 @@ class SlackDMChannel(SlackChannel): Subclass of a normal channel for person-to-person communication, which has some important differences. """ + def __init__(self, eventrouter, users, **kwargs): dmuser = kwargs["user"] kwargs["name"] = users[dmuser].name @@ -1368,19 +1527,23 @@ class SlackDMChannel(SlackChannel): self.type = 'im' self.update_color() self.set_name(self.slack_name) + def set_name(self, slack_name): self.name = slack_name + def create_buffer(self): if not self.channel_buffer: super(SlackDMChannel, self).create_buffer() w.buffer_set(self.channel_buffer, "localvar_set_type", 'private') + def update_color(self): if config.colorize_private_chats: - self.color_name = w.info_get('irc_nick_color_name', self.name.encode('utf-8')) + self.color_name = w.info_get('irc_nick_color_name', self.name) self.color = w.color(self.color_name) else: self.color = "" self.color_name = "" + def formatted_name(self, style="default", typing=False, present=True, enable_color=False, **kwargs): if config.colorize_private_chats and enable_color: print_color = self.color @@ -1398,9 +1561,10 @@ class SlackDMChannel(SlackChannel): "long_base": "{}.{}".format(self.team.preferred_name, self.slack_name), } return print_color + select[style] + def open(self, update_remote=True): self.create_buffer() - #self.active = True + # 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) @@ -1410,6 +1574,7 @@ class SlackDMChannel(SlackChannel): s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["join"], {"user": self.user}, team_hash=self.team.team_hash, channel_identifier=self.identifier) self.eventrouter.receive(s) self.create_buffer() + def rename(self): if self.channel_buffer: new_name = self.formatted_name(style="sidebar", present=self.team.is_user_present(self.user), enable_color=config.colorize_private_chats) @@ -1418,6 +1583,7 @@ class SlackDMChannel(SlackChannel): w.buffer_set(self.channel_buffer, "short_name", new_name) return True return False + def refresh(self): return self.rename() @@ -1426,26 +1592,32 @@ class SlackGroupChannel(SlackChannel): """ A group channel is a private discussion group. """ + 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 - #def formatted_name(self, prepend="#", enable_color=True, basic=False): + + # def formatted_name(self, prepend="#", enable_color=True, basic=False): # return prepend + self.slack_name + class SlackMPDMChannel(SlackChannel): """ An MPDM channel is a special instance of a 'group' channel. We change the name to look less terrible in weechat. """ + def __init__(self, eventrouter, **kwargs): super(SlackMPDMChannel, self).__init__(eventrouter, **kwargs) n = kwargs.get('name') self.set_name(n) self.type = "group" + def open(self, update_remote=False): self.create_buffer() self.active = True @@ -1453,9 +1625,11 @@ class SlackMPDMChannel(SlackChannel): 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) self.eventrouter.receive(s) - #self.create_buffer() + # self.create_buffer() + def set_name(self, n): self.name = "|".join("-".join(n.split("-")[1:-1]).split("--")) + 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: @@ -1474,28 +1648,29 @@ class SlackMPDMChannel(SlackChannel): } return select[style] -# def formatted_name(self, **kwargs): -# return self.name def rename(self): pass + class SlackThreadChannel(object): """ A thread channel is a virtual channel. We don't inherit from SlackChannel, because most of how it operates will be different. """ + def __init__(self, eventrouter, parent_message): self.eventrouter = eventrouter self.parent_message = parent_message self.channel_buffer = None - #self.identifier = "" - #self.name = "#" + kwargs['name'] + # self.identifier = "" + # self.name = "#" + kwargs['name'] self.type = "thread" self.got_history = False self.label = None - #self.set_name(self.slack_name) - #def set_name(self, slack_name): + # self.set_name(self.slack_name) + # def set_name(self, slack_name): # self.name = "#" + slack_name + def formatted_name(self, style="default", **kwargs): hash_or_ts = self.parent_message.hash or self.parent_message.ts styles = { @@ -1504,8 +1679,10 @@ class SlackThreadChannel(object): "sidebar": " +{}".format(hash_or_ts), } return styles[style] + def refresh(self): self.rename() + def mark_read(self, ts=None, update_remote=True, force=False): if self.channel_buffer: w.buffer_set(self.channel_buffer, "unread", "") @@ -1515,40 +1692,41 @@ class SlackThreadChannel(object): data = "{}\t{}".format(nick, text) ts = SlackTS(timestamp) if self.channel_buffer: - #backlog messages - we will update the read marker as we print these - #backlog = False - #if ts <= SlackTS(self.last_read): + # backlog messages - we will update the read marker as we print these + # backlog = False + # if ts <= SlackTS(self.last_read): # tags = tag("backlog") # backlog = True - #elif self.type in ["im", "mpdm"]: + # elif self.type in ["im", "mpdm"]: # tags = tag("dm") # self.new_messages = True - #else: + # else: tags = tag("default") - #self.new_messages = True + # self.new_messages = True w.prnt_date_tags(self.channel_buffer, ts.major, tags, data) modify_print_time(self.channel_buffer, ts.minorstr(), ts.major) - #if backlog: + # if backlog: # self.mark_read(ts, update_remote=False, force=True) + def get_history(self): self.got_history = True for message in self.parent_message.submessages: - #message = SlackMessage(message_json, team, channel) + # message = SlackMessage(message_json, team, channel) text = message.render() - #print text + # print text suffix = '' if 'edited' in message.message_json: suffix = ' (edited)' - #try: + # try: # channel.unread_count += 1 - #except: + # except: # channel.unread_count = 1 self.buffer_prnt(message.sender, text + suffix, message.ts) def send_message(self, message): - #team = self.eventrouter.teams[self.team] + # team = self.eventrouter.teams[self.team] message = linkify_text(message, self.parent_message.team, self) dbg(message) request = {"type": "message", "channel": self.parent_message.channel.identifier, "text": message, "_team": self.parent_message.team.team_hash, "user": self.parent_message.team.myidentifier, "thread_ts": str(self.parent_message.ts)} @@ -1559,10 +1737,10 @@ class SlackThreadChannel(object): self.create_buffer() self.active = True self.get_history() - #if "info" in SLACK_API_TRANSLATOR[self.type]: + # 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) # self.eventrouter.receive(s) - #if update_remote: + # 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) # self.eventrouter.receive(s) @@ -1586,11 +1764,11 @@ class SlackThreadChannel(object): time_format = w.config_string(w.config_get("weechat.look.buffer_time_format")) parent_time = time.localtime(SlackTS(self.parent_message.ts).major) topic = '{} {} | {}'.format(time.strftime(time_format, parent_time), self.parent_message.sender, self.parent_message.render() ) - w.buffer_set(self.channel_buffer, "title", topic.encode('utf-8')) + w.buffer_set(self.channel_buffer, "title", topic) - #self.eventrouter.weechat_controller.set_refresh_buffer_list(True) + # self.eventrouter.weechat_controller.set_refresh_buffer_list(True) - #try: + # try: # if self.unread_count != 0: # for c in range(1, self.unread_count): # if self.type == "im": @@ -1600,22 +1778,25 @@ class SlackThreadChannel(object): # else: # pass # #dbg("no unread in {}".format(self.name)) - #except: + # except: # pass - #dbg("exception no unread count") - #if self.unread_count != 0 and not self.muted: + # dbg("exception no unread count") + # if self.unread_count != 0 and not self.muted: # w.buffer_set(self.channel_buffer, "hotlist", "1") + def destroy_buffer(self, update_remote): if self.channel_buffer is not None: self.channel_buffer = None self.got_history = False - #if update_remote and not eventrouter.shutting_down: + # if update_remote and not eventrouter.shutting_down: self.active = False + class SlackUser(object): """ Represends an individual slack user. Also where you set their name formatting. """ + def __init__(self, **kwargs): # We require these two things for a vaid object, # the rest we can just learn from slack @@ -1625,22 +1806,27 @@ class SlackUser(object): for key, value in kwargs.items(): setattr(self, key, value) self.update_color() + def __repr__(self): return "Name:{} Identifier:{}".format(self.name, self.identifier) + def force_color(self, color_name): self.color_name = color_name self.color = w.color(self.color_name) + def update_color(self): # This will automatically be none/"" if the user has disabled nick # colourization. - self.color_name = w.info_get('nick_color_name', self.name.encode('utf-8')) + self.color_name = w.info_get('nick_color_name', self.name) self.color = w.color(self.color_name) + def formatted_name(self, prepend="", enable_color=True): if enable_color: return self.color + prepend + self.name else: return prepend + self.name + class SlackBot(SlackUser): """ Basically the same as a user, but split out to identify and for future @@ -1649,6 +1835,7 @@ class SlackBot(SlackUser): def __init__(self, **kwargs): super(SlackBot, self).__init__(**kwargs) + class SlackMessage(object): """ Represents a single slack message and associated context/metadata. @@ -1673,7 +1860,7 @@ class SlackMessage(object): self.suffix = '' self.ts = SlackTS(message_json['ts']) text = self.message_json.get('text', '') - if text.startswith('_') and text.endswith('_') and not 'subtype' in message_json: + if text.startswith('_') and text.endswith('_') and 'subtype' not in message_json: message_json['text'] = text[1:-1] message_json['subtype'] = 'me_message' if message_json.get('subtype') == 'me_message' and not message_json['text'].startswith(self.sender): @@ -1681,22 +1868,26 @@ class SlackMessage(object): def __hash__(self): return hash(self.ts) + def render(self, force=False): if len(self.submessages) > 0: return "{} {} {}".format(render(self.message_json, self.team, self.channel, force), self.suffix, "{}[ Thread: {} Replies: {} ]".format(w.color(config.thread_suffix_color), self.hash or self.ts, len(self.submessages))) return "{} {}".format(render(self.message_json, self.team, self.channel, force), self.suffix) + def change_text(self, new_text): self.message_json["text"] = new_text dbg(self.message_json) + def change_suffix(self, new_suffix): self.suffix = new_suffix dbg(self.message_json) - def get_sender(self, utf8=True): - name = u"" - name_plain = u"" + + def get_sender(self): + name = "" + name_plain = "" if 'bot_id' in self.message_json and self.message_json['bot_id'] is not None: - name = u"{} :]".format(self.team.bots[self.message_json["bot_id"]].formatted_name()) - name_plain = u"{}".format(self.team.bots[self.message_json["bot_id"]].formatted_name(enable_color=False)) + 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: if self.message_json['user'] == self.team.myidentifier: name = self.team.users[self.team.myidentifier].name @@ -1704,23 +1895,21 @@ class SlackMessage(object): elif self.message_json['user'] in self.team.users: u = self.team.users[self.message_json['user']] if u.is_bot: - name = u"{} :]".format(u.formatted_name()) + name = "{} :]".format(u.formatted_name()) else: - name = u"{}".format(u.formatted_name()) - name_plain = u"{}".format(u.formatted_name(enable_color=False)) + name = "{}".format(u.formatted_name()) + name_plain = "{}".format(u.formatted_name(enable_color=False)) elif 'username' in self.message_json: - name = u"-{}-".format(self.message_json["username"]) - name_plain = u"{}".format(self.message_json["username"]) + name = "-{}-".format(self.message_json["username"]) + name_plain = "{}".format(self.message_json["username"]) elif 'service_name' in self.message_json: - name = u"-{}-".format(self.message_json["service_name"]) - name_plain = u"{}".format(self.message_json["service_name"]) - else: - name = u"" - name_plain = u"" - if utf8: - return (name.encode('utf-8'), name_plain.encode('utf-8')) + name = "-{}-".format(self.message_json["service_name"]) + name_plain = "{}".format(self.message_json["service_name"]) else: - return (name, name_plain) + name = "" + name_plain = "" + return (name, name_plain) + def add_reaction(self, reaction, user): m = self.message_json.get('reactions', None) if m: @@ -1730,9 +1919,10 @@ class SlackMessage(object): r["users"].append(user) found = True if not found: - self.message_json["reactions"].append({u"name": reaction, u"users": [user]}) + self.message_json["reactions"].append({"name": reaction, "users": [user]}) else: - self.message_json["reactions"] = [{u"name": reaction, u"users": [user]}] + self.message_json["reactions"] = [{"name": reaction, "users": [user]}] + def remove_reaction(self, reaction, user): m = self.message_json.get('reactions', None) if m: @@ -1742,10 +1932,11 @@ class SlackMessage(object): else: pass + class SlackThreadMessage(SlackMessage): + def __init__(self, parent_id, *args): super(SlackThreadMessage, self).__init__(*args) - #super(SlackBot, self).__init__(**kwargs) self.parent_id = parent_id @@ -1753,18 +1944,23 @@ class WeeSlackMetadata(object): """ A simple container that we pickle/unpickle to hold data. """ + def __init__(self, meta): self.meta = meta + def jsonify(self): return self.meta + class SlackTS(object): + def __init__(self, ts=None): if ts: self.major, self.minor = [int(x) for x in ts.split('.', 1)] else: self.major = int(time.time()) self.minor = 0 + def __cmp__(self, other): if isinstance(other, SlackTS): if self.major < other.major: @@ -1786,19 +1982,25 @@ class SlackTS(object): return 1 elif s == other: return 0 + def __hash__(self): return hash("{}.{}".format(self.major, self.minor)) + def __repr__(self): return str("{0}.{1:06d}".format(self.major, self.minor)) + def split(self, *args, **kwargs): return [self.major, self.minor] + def majorstr(self): return str(self.major) + def minorstr(self): return str(self.minor) ###### New handlers + def handle_rtmstart(login_data, eventrouter): """ This handles the main entry call to slack, rtm.start @@ -1807,14 +2009,14 @@ def handle_rtmstart(login_data, eventrouter): metadata = pickle.loads(login_data["wee_slack_request_metadata"]) - #Let's reuse a team if we have it already. + # 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): users = {} for item in login_data["users"]: users[item["id"]] = SlackUser(**item) - #users.append(SlackUser(**item)) + # users.append(SlackUser(**item)) bots = {} for item in login_data["bots"]: @@ -1853,39 +2055,43 @@ def handle_rtmstart(login_data, eventrouter): t.set_reconnect_url(login_data['url']) t.connect() - #web_socket_url = login_data['url'] - #try: + # 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: + # except Exception as e: # dbg("websocket connection error: {}".format(e)) # return False t.buffer_prnt('Connected to Slack') - t.buffer_prnt('{:<20} {}'.format(u"Websocket URL", login_data["url"])) - t.buffer_prnt('{:<20} {}'.format(u"User name", login_data["self"]["name"])) - t.buffer_prnt('{:<20} {}'.format(u"User ID", login_data["self"]["id"])) - t.buffer_prnt('{:<20} {}'.format(u"Team name", login_data["team"]["name"])) - t.buffer_prnt('{:<20} {}'.format(u"Team domain", login_data["team"]["domain"])) - t.buffer_prnt('{:<20} {}'.format(u"Team id", login_data["team"]["id"])) + 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"])) dbg("connected to {}".format(t.domain)) - #self.identifier = self.domain + # self.identifier = self.domain + def handle_groupshistory(message_json, eventrouter, **kwargs): handle_history(message_json, eventrouter, **kwargs) + def handle_channelshistory(message_json, eventrouter, **kwargs): handle_history(message_json, eventrouter, **kwargs) + def handle_imhistory(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] @@ -1903,9 +2109,11 @@ def handle_history(message_json, eventrouter, **kwargs): ###### New/converted process_ and subprocess_ methods + def process_reconnect_url(message_json, eventrouter, **kwargs): kwargs['team'].set_reconnect_url(message_json['url']) + def process_manual_presence_change(message_json, eventrouter, **kwargs): process_presence_change(message_json, eventrouter, **kwargs) @@ -1913,15 +2121,17 @@ def process_manual_presence_change(message_json, eventrouter, **kwargs): def process_presence_change(message_json, eventrouter, **kwargs): kwargs["user"].presence = message_json["presence"] + def process_pref_change(message_json, eventrouter, **kwargs): team = kwargs["team"] - if message_json['name'] == u'muted_channels': + if message_json['name'] == 'muted_channels': team.set_muted_channels(message_json['value']) - elif message_json['name'] == u'highlight_words': + elif message_json['name'] == 'highlight_words': team.set_highlight_words(message_json['value']) else: dbg("Preference change not implemented: {}\n".format(message_json['name'])) + def process_user_typing(message_json, eventrouter, **kwargs): channel = kwargs["channel"] team = kwargs["team"] @@ -1929,19 +2139,22 @@ def process_user_typing(message_json, eventrouter, **kwargs): channel.set_typing(team.users.get(message_json["user"]).name) w.bar_item_update("slack_typing_notice") + def process_team_join(message_json, eventrouter, **kwargs): user = message_json['user'] team = kwargs["team"] team.users[user["id"]] = SlackUser(**user) + def process_pong(message_json, eventrouter, **kwargs): pass + def process_message(message_json, eventrouter, store=True, **kwargs): channel = kwargs["channel"] team = kwargs["team"] - #try: - # send these subtype messages elsewhere + # try: + # send these subtype messages elsewhere known_subtypes = [ 'thread_message', 'message_replied', @@ -1950,8 +2163,8 @@ def process_message(message_json, eventrouter, store=True, **kwargs): 'channel_join', 'channel_leave', 'channel_topic', - #'group_join', - #'group_leave', + # 'group_join', + # 'group_leave', ] if "thread_ts" in message_json and "reply_count" not in message_json: message_json["subtype"] = "thread_message" @@ -1990,16 +2203,15 @@ def process_message(message_json, eventrouter, store=True, **kwargs): if store: channel.store_message(message, team) dbg("NORMAL REPLY {}".format(message_json)) - #except: - # channel.buffer_prnt("WEE-SLACK-ERROR", json.dumps(message_json).encode('utf-8'), message_json["ts"], **kwargs) + # except: + # channel.buffer_prnt("WEE-SLACK-ERROR", json.dumps(message_json), message_json["ts"], **kwargs) # traceback.print_exc() + def subprocess_thread_message(message_json, eventrouter, channel, team): - #print ("THREADED: " + str(message_json)) + # print ("THREADED: " + str(message_json)) parent_ts = message_json.get('thread_ts', None) if parent_ts: - #parent_ts = SlackTS(parent_ts) - parent_ts = parent_ts parent_message = channel.messages.get(SlackTS(parent_ts), None) if parent_message: message = SlackThreadMessage(parent_ts, message_json, team, channel) @@ -2009,7 +2221,7 @@ def subprocess_thread_message(message_json, eventrouter, channel, team): channel.change_message(parent_ts) text = message.render() - #channel.buffer_prnt(message.sender, text, message.ts, **kwargs) + # channel.buffer_prnt(message.sender, text, message.ts, **kwargs) if parent_message.thread_channel: parent_message.thread_channel.buffer_prnt(message.sender, text, message.ts) @@ -2025,11 +2237,12 @@ def subprocess_thread_message(message_json, eventrouter, channel, team): # else: # dbg("COULDN'T find orig message {}".format(message_json['thread_ts']), main_buffer=True) - #if threadinfo[0]: + # if threadinfo[0]: # channel.messages[threadinfo[1]].become_thread() # message_json["item"]["ts"], message_json) - #channel.change_message(message_json["thread_ts"], None, message_json["text"]) - #channel.become_thread(message_json["item"]["ts"], message_json) + # channel.change_message(message_json["thread_ts"], None, message_json["text"]) + # channel.become_thread(message_json["item"]["ts"], message_json) + def subprocess_channel_join(message_json, eventrouter, channel, team): joinprefix = w.prefix("join") @@ -2037,23 +2250,25 @@ def subprocess_channel_join(message_json, eventrouter, channel, team): channel.buffer_prnt(joinprefix, message.render(), message_json["ts"], tagset='joinleave') channel.user_joined(message_json['user']) + def subprocess_channel_leave(message_json, eventrouter, channel, team): leaveprefix = w.prefix("quit") message = SlackMessage(message_json, team, channel, override_sender=leaveprefix) channel.buffer_prnt(leaveprefix, message.render(), message_json["ts"], tagset='joinleave') channel.user_left(message_json['user']) - #channel.update_nicklist(message_json['user']) - #channel.update_nicklist() + # channel.update_nicklist(message_json['user']) + # channel.update_nicklist() + def subprocess_message_replied(message_json, eventrouter, channel, team): pass - #print ("REPLIED: " + str(message_json)) + def subprocess_message_changed(message_json, eventrouter, channel, team): m = message_json.get("message", None) if m: new_message = m - #message = SlackMessage(new_message, team, channel) + # message = SlackMessage(new_message, team, channel) if "attachments" in m: message_json["attachments"] = m["attachments"] if "text" in m: @@ -2075,17 +2290,17 @@ def subprocess_message_changed(message_json, eventrouter, channel, team): else: channel.change_message(new_message["ts"], new_message["text"]) + def subprocess_message_deleted(message_json, eventrouter, channel, team): channel.change_message(message_json["deleted_ts"], "(deleted)", '') + def subprocess_channel_topic(message_json, eventrouter, channel, team): text = unfurl_refs(message_json["text"], ignore_alt_text=False) - if type(text) != unicode: - text = text.decode('utf-8', 'ignore') - text = text.encode('utf-8') channel.buffer_prnt(w.prefix("network").rstrip(), text, message_json["ts"], tagset="muted") channel.render_topic(message_json["topic"]) + def process_reply(message_json, eventrouter, **kwargs): dbg('processing reply') team = kwargs["team"] @@ -2101,10 +2316,8 @@ def process_reply(message_json, eventrouter, **kwargs): c = original_message_json.get('channel', None) channel = team.channels[c] m = SlackMessage(original_message_json, team, channel) - # m = Message(message_json, server=server) - #dbg(m, True) - #if "type" in message_json: + # if "type" in message_json: # if message_json["type"] == "message" and "channel" in message_json.keys(): # message_json["ts"] = message_json["ts"] # channels.find(message_json["channel"]).store_message(m, from_me=True) @@ -2117,6 +2330,7 @@ def process_reply(message_json, eventrouter, **kwargs): except KeyError: dbg("Unexpected reply {}".format(message_json)) + def process_channel_marked(message_json, eventrouter, **kwargs): """ complete @@ -2127,29 +2341,39 @@ def process_channel_marked(message_json, eventrouter, **kwargs): channel.mark_read(ts=ts, force=True, update_remote=False) else: dbg("tried to mark something weird {}".format(message_json)) + + def process_group_marked(message_json, eventrouter, **kwargs): process_channel_marked(message_json, eventrouter, **kwargs) + + def process_im_marked(message_json, eventrouter, **kwargs): process_channel_marked(message_json, eventrouter, **kwargs) + + def process_mpim_marked(message_json, eventrouter, **kwargs): process_channel_marked(message_json, eventrouter, **kwargs) + def process_channel_joined(message_json, eventrouter, **kwargs): item = message_json["channel"] kwargs['team'].channels[item["id"]].update_from_message_json(item) kwargs['team'].channels[item["id"]].open() + def process_channel_created(message_json, eventrouter, **kwargs): item = message_json["channel"] c = SlackChannel(eventrouter, team=kwargs["team"], **item) kwargs['team'].channels[item["id"]] = c kwargs['team'].buffer_prnt('Channel created: {}'.format(c.slack_name)) + def process_channel_rename(message_json, eventrouter, **kwargs): item = message_json["channel"] channel = kwargs['team'].channels[item["id"]] channel.slack_name = message_json['channel']['name'] + def process_im_created(message_json, eventrouter, **kwargs): team = kwargs['team'] item = message_json["channel"] @@ -2157,17 +2381,20 @@ def process_im_created(message_json, eventrouter, **kwargs): team.channels[item["id"]] = c kwargs['team'].buffer_prnt('IM channel created: {}'.format(c.name)) + def process_im_open(message_json, eventrouter, **kwargs): channel = kwargs['channel'] item = message_json kwargs['team'].channels[item["channel"]].check_should_open(True) w.buffer_set(channel.channel_buffer, "hotlist", "2") + def process_im_close(message_json, eventrouter, **kwargs): item = message_json cbuf = kwargs['team'].channels[item["channel"]].channel_buffer eventrouter.weechat_controller.unregister_buffer(cbuf, False, True) + def process_group_joined(message_json, eventrouter, **kwargs): item = message_json["channel"] if item["name"].startswith("mpdm-"): @@ -2177,6 +2404,7 @@ def process_group_joined(message_json, eventrouter, **kwargs): kwargs['team'].channels[item["id"]] = c kwargs['team'].channels[item["id"]].open() + def process_reaction_added(message_json, eventrouter, **kwargs): channel = kwargs['team'].channels[message_json["item"]["channel"]] if message_json["item"].get("type") == "message": @@ -2189,6 +2417,7 @@ def process_reaction_added(message_json, eventrouter, **kwargs): else: dbg("reaction to item type not supported: " + str(message_json)) + def process_reaction_removed(message_json, eventrouter, **kwargs): channel = kwargs['team'].channels[message_json["item"]["channel"]] if message_json["item"].get("type") == "message": @@ -2203,6 +2432,7 @@ def process_reaction_removed(message_json, eventrouter, **kwargs): ###### New module/global methods + def render(message_json, team, channel, force=False): # If we already have a rendered version in the object, just return that. if not force and message_json.get("_rendered_text", ""): @@ -2216,9 +2446,9 @@ def render(message_json, team, channel, force=False): if message_json['text'] is not None: text = message_json["text"] else: - text = u"" + text = "" else: - text = u"" + text = "" text = unfurl_refs(text, ignore_alt_text=config.unfurl_ignore_alt_text) @@ -2235,20 +2465,15 @@ def render(message_json, team, channel, force=False): text = re.sub(r'(^| )_([^_]+)_([^a-zA-Z0-9_]|$)', r'\1{}\2{}\3'.format(w.color('underline'), w.color('-underline')), text) - if type(text) is not unicode: - text = text.decode('UTF-8', 'replace') - text = text.encode('utf-8') - # if self.threads: # text += " [Replies: {} Thread ID: {} ] ".format(len(self.threads), self.thread_id) # #for thread in self.threads: text += create_reaction_string(message_json.get("reactions", "")) - message_json["_rendered_text"] = text - return text + def linkify_text(message, team, channel): # The get_username_map function is a bit heavy, but this whole # function is only called on message send.. @@ -2257,9 +2482,7 @@ def linkify_text(message, team, channel): message = message.replace('\x02', '*').replace('\x1F', '_').split(' ') for item in enumerate(message): targets = re.match('^\s*([@#])([\w.-]+[\w. -])(\W*)', item[1]) - #print targets if targets and targets.groups()[0] == '@': - #print targets.groups() named = targets.groups() if named[1] in ["group", "channel", "here"]: message[item[0]] = "<!{}>".format(named[1]) @@ -2277,9 +2500,10 @@ def linkify_text(message, team, channel): except: message[item[0]] = "#{}{}".format(named[1], named[2]) - #dbg(message) + # dbg(message) return " ".join(message) + def unfurl_refs(text, ignore_alt_text=False): """ input : <@U096Q7CQM|someuser> has joined the channel @@ -2296,6 +2520,7 @@ def unfurl_refs(text, ignore_alt_text=False): text = text.replace(m, unfurl_ref(m[1:-1], ignore_alt_text)) return text + def unfurl_ref(ref, ignore_alt_text=False): id = ref.split('|')[0] display_text = ref @@ -2309,17 +2534,18 @@ def unfurl_ref(ref, ignore_alt_text=False): display_text = ref.split('|')[1] else: url, desc = ref.split('|', 1) - display_text = u"{} ({})".format(url, desc) + display_text = "{} ({})".format(url, desc) else: display_text = resolve_ref(ref) return display_text + def unwrap_attachments(message_json, text_before): attachment_text = '' a = message_json.get("attachments", None) if a: if text_before: - attachment_text = u'\n' + attachment_text = '\n' for attachment in a: # Attachments should be rendered roughly like: # @@ -2363,29 +2589,30 @@ def unwrap_attachments(message_json, text_before): def resolve_ref(ref): - #TODO: This hack to use eventrouter needs to go - #this resolver should probably move to the slackteam or eventrouter itself - #global EVENTROUTER + # TODO: This hack to use eventrouter needs to go + # this resolver should probably move to the slackteam or eventrouter itself + # global EVENTROUTER if 'EVENTROUTER' in globals(): e = EVENTROUTER if ref.startswith('@U') or ref.startswith('@W'): for t in e.teams.keys(): if ref[1:] in e.teams[t].users: - #try: + # try: return "@{}".format(e.teams[t].users[ref[1:]].name) - #except: + # except: # dbg("NAME: {}".format(ref)) elif ref.startswith('#C'): for t in e.teams.keys(): if ref[1:] in e.teams[t].channels: - #try: + # try: return "{}".format(e.teams[t].channels[ref[1:]].name) - #except: + # except: # dbg("CHANNEL: {}".format(ref)) # Something else, just return as-is return ref + def create_reaction_string(reactions): count = 0 if not isinstance(reactions, list): @@ -2406,6 +2633,7 @@ def create_reaction_string(reactions): reaction_string = '' return reaction_string + def modify_buffer_line(buffer, new_line, timestamp, time_id): # get a pointer to this buffer's lines own_lines = w.hdata_pointer(w.hdata_get('buffer'), buffer, 'own_lines') @@ -2440,6 +2668,7 @@ def modify_print_time(buffer, new_id, time): This overloads the time printed field to let us store the slack per message unique id that comes after the "." in a slack ts """ + # get a pointer to this buffer's lines own_lines = w.hdata_pointer(w.hdata_get('buffer'), buffer, 'own_lines') if own_lines: @@ -2456,6 +2685,7 @@ def modify_print_time(buffer, new_id, time): return w.WEECHAT_RC_OK + def tag(tagset, user=None): if user: user.replace(" ", "_") @@ -2463,28 +2693,30 @@ def tag(tagset, user=None): else: default_tag = 'nick_unknown' tagsets = { - #when replaying something old + # when replaying something old "backlog": "no_highlight,notify_none,logger_backlog_end", - #when posting messages to a muted channel + # when posting messages to a muted channel "muted": "no_highlight,notify_none,logger_backlog_end", - #when my nick is in the message + # when my nick is in the message "highlightme": "notify_highlight,log1", - #when receiving a direct message + # when receiving a direct message "dm": "notify_private,notify_message,log1,irc_privmsg", "dmfromme": "notify_none,log1,irc_privmsg", - #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")] + # 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", - #catchall ? + # catchall ? "default": "notify_message,log1", } return default_tag + "," + tagsets[tagset] - ###### New/converted command_ commands + @slack_buffer_or_ignore 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: @@ -2498,6 +2730,7 @@ def part_command_cb(data, current_buffer, args): e.weechat_controller.unregister_buffer(current_buffer, update_remote=True, close_buffer=True) return w.WEECHAT_RC_OK_EAT + @slack_buffer_or_ignore def topic_command_cb(data, current_buffer, args): n = len(args.split()) @@ -2511,15 +2744,18 @@ def topic_command_cb(data, current_buffer, args): else: return w.WEECHAT_RC_ERROR + @slack_buffer_required def command_topic(data, current_buffer, args): """ Change the topic of a channel /slack 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()) + # server = servers.find(current_domain_name()) args = args.split(' ') if len(args) > 2 and args[1].startswith('#'): cmap = team.get_channel_map() @@ -2539,14 +2775,20 @@ def command_topic(data, current_buffer, args): else: return w.WEECHAT_RC_ERROR_EAT + @slack_buffer_or_ignore 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 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] @@ -2561,12 +2803,16 @@ def msg_command_cb(data, current_buffer, args): channel.send_message(message) return w.WEECHAT_RC_OK_EAT + @slack_buffer_or_ignore def command_talk(data, current_buffer, args): """ Open a chat with the specified user /slack talk [user] """ + + 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] @@ -2577,7 +2823,7 @@ def command_talk(data, current_buffer, args): 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 + # refresh channel map here c = team.get_channel_map() if channel_name.startswith('#'): @@ -2590,11 +2836,15 @@ def command_talk(data, current_buffer, args): return w.WEECHAT_RC_OK_EAT return w.WEECHAT_RC_OK_EAT + def command_showmuted(data, current_buffer, args): current = w.current_buffer() w.prnt(EVENTROUTER.weechat_controller.buffers[current].team.channel_buffer, str(EVENTROUTER.weechat_controller.buffers[current].team.muted_channels)) + 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: @@ -2608,7 +2858,7 @@ def thread_command_callback(data, current_buffer, args): tc = SlackThreadChannel(EVENTROUTER, pm) pm.thread_channel = tc tc.open() - #tc.create_buffer() + # tc.create_buffer() return w.WEECHAT_RC_OK_EAT elif args[0] == '/reply': count = int(args[1]) @@ -2621,7 +2871,10 @@ def thread_command_callback(data, current_buffer, args): w.prnt(current, "Invalid thread command.") return w.WEECHAT_RC_OK_EAT + 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 @@ -2629,8 +2882,11 @@ def rehistory_command_callback(data, current_buffer, args): channel.get_history() return w.WEECHAT_RC_OK_EAT + @slack_buffer_required 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') @@ -2638,7 +2894,10 @@ def hide_command_callback(data, current_buffer, args): w.buffer_set(c.channel_buffer, "hidden", "1") return w.WEECHAT_RC_OK_EAT + 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 @@ -2651,6 +2910,7 @@ def slack_command_cb(data, current_buffer, args): w.prnt("", "Command not found: " + function_name) return w.WEECHAT_RC_OK + @slack_buffer_required def command_distracting(data, current_buffer, args): channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer, None) @@ -2662,9 +2922,11 @@ def command_distracting(data, current_buffer, args): config.distracting_channels.pop(config.distracting_channels.index(fullname)) save_distracting_channels() + def save_distracting_channels(): w.config_set_plugin('distracting_channels', ','.join(config.distracting_channels)) + @slack_buffer_required def command_slash(data, current_buffer, args): """ @@ -2687,6 +2949,7 @@ def command_slash(data, current_buffer, args): s = SlackRequest(team.token, "chat.command", {"command": command, "text": text, 'channel': channel.identifier}, team_hash=team.team_hash, channel_identifier=channel.identifier) EVENTROUTER.receive(s) + @slack_buffer_required def command_mute(data, current_buffer, args): current = w.current_buffer() @@ -2699,9 +2962,10 @@ def command_mute(data, current_buffer, args): s = SlackRequest(team.token, "users.prefs.set", {"name": "muted_channels", "value": ",".join(team.muted_channels)}, team_hash=team.team_hash, channel_identifier=channel_id) EVENTROUTER.receive(s) + @slack_buffer_required def command_openweb(data, current_buffer, args): - #if done from server buffer, open slack for reals + # if done from server buffer, open slack for reals channel = EVENTROUTER.weechat_controller.buffers[current_buffer] if isinstance(channel, SlackTeam): url = "https://{}".format(channel.team.domain) @@ -2710,22 +2974,24 @@ def command_openweb(data, current_buffer, args): url = "https://{}/archives/{}/p{}000000".format(channel.team.domain, channel.slack_name, now.majorstr()) w.prnt_date_tags(channel.team.channel_buffer, SlackTS().major, "openweb,logger_backlog_end,notify_none", url) + def command_nodistractions(data, current_buffer, args): global hide_distractions hide_distractions = not hide_distractions if config.distracting_channels != ['']: for channel in config.distracting_channels: dbg('hiding channel {}'.format(channel)) - #try: + # try: for c in EVENTROUTER.weechat_controller.buffers.itervalues(): if c == channel: dbg('found channel {} to hide'.format(channel)) w.buffer_set(c.channel_buffer, "hidden", str(int(hide_distractions))) - #except: + # except: # dbg("Can't hide channel {} .. removing..".format(channel), main_buffer=True) # config.distracting_channels.pop(config.distracting_channels.index(channel)) # save_distracting_channels() + @slack_buffer_required def command_upload(data, current_buffer, args): channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer) @@ -2739,8 +3005,11 @@ def command_upload(data, current_buffer, args): command = 'curl -F file=@{} -F channels={} -F token={} {}'.format(file_path, channel.identifier, team.token, url) w.hook_process(command, config.slack_timeout, '', '') + def away_command_cb(data, current_buffer, args): - #TODO: reimplement all.. maybe + 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: command_back(data, current_buffer, args) @@ -2748,6 +3017,7 @@ def away_command_cb(data, current_buffer, args): command_away(data, current_buffer, args) return w.WEECHAT_RC_OK + @slack_buffer_required def command_away(data, current_buffer, args): """ @@ -2794,8 +3064,11 @@ def command_back(data, current_buffer, args): s = SlackRequest(team.token, "presence.set", {"presence": "active"}, team_hash=team.team_hash) EVENTROUTER.receive(s) + @slack_buffer_required 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) @@ -2803,12 +3076,14 @@ def label_command_cb(data, current_buffer, args): channel.label = new_name w.buffer_set(channel.channel_buffer, "short_name", new_name) + def command_p(data, current_buffer, args): args = args.split(' ', 1)[1] w.prnt("", "{}".format(eval(args))) ###### NEW EXCEPTIONS + class ProcessNotImplemented(Exception): """ Raised when we try to call process_(something), but @@ -2817,6 +3092,7 @@ class ProcessNotImplemented(Exception): def __init__(self, function_name): super(ProcessNotImplemented, self).__init__(function_name) + class InvalidType(Exception): """ Raised when we do type checking to ensure objects of the wrong @@ -2827,6 +3103,7 @@ class InvalidType(Exception): ###### New but probably old and need to migrate + def closed_slack_debug_buffer_cb(data, buffer): global slack_debug slack_debug = None @@ -2842,11 +3119,12 @@ def create_slack_debug_buffer(): slack_debug = w.buffer_new("slack-debug", "", "", "closed_slack_debug_buffer_cb", "") w.buffer_set(slack_debug, "notify", "0") + def load_emoji(): try: global EMOJI DIR = w.info_get("weechat_dir", "") - #no idea why this does't work w/o checking the type?! + # no idea why this does't work w/o checking the type?! dbg(type(DIR), 0) ef = open('{}/weemoji.json'.format(DIR), 'r') EMOJI = json.loads(ef.read()) @@ -2855,6 +3133,7 @@ def load_emoji(): dbg("Unexpected error: {}".format(sys.exc_info()), 5) return w.WEECHAT_RC_OK + def setup_hooks(): cmds = {k[8:]: v for k, v in globals().items() if k.startswith("command_")} @@ -2884,7 +3163,7 @@ def setup_hooks(): '|'.join(cmds.keys()), # Function name 'slack_command_cb', '') - #w.hook_command('me', '', 'stuff', 'stuff2', '', 'me_command_cb', '') + # w.hook_command('me', '', 'stuff', 'stuff2', '', 'me_command_cb', '') w.hook_command_run('/me', 'me_command_cb', '') w.hook_command_run('/query', 'command_talk', '') @@ -2905,11 +3184,10 @@ def setup_hooks(): w.hook_completion("emoji", "complete :emoji: for slack", "emoji_completion_cb", "") # Hooks to fix/implement - #w.hook_timer(1000 * 60 * 5, 0, 0, "cache_write_cb", "") - #w.hook_signal('buffer_opened', "buffer_opened_cb", "") - #w.hook_signal('window_scrolled', "scrolled_cb", "") - #w.hook_timer(3000, 0, 0, "slack_connection_persistence_cb", "") - + # w.hook_timer(1000 * 60 * 5, 0, 0, "cache_write_cb", "") + # w.hook_signal('buffer_opened', "buffer_opened_cb", "") + # w.hook_signal('window_scrolled', "scrolled_cb", "") + # w.hook_timer(3000, 0, 0, "slack_connection_persistence_cb", "") ##### END NEW @@ -2918,25 +3196,24 @@ def dbg(message, level=0, main_buffer=False, fout=False): """ send debug output to the slack-debug buffer and optionally write to a file. """ - #TODO: do this smarter - #return + # TODO: do this smarter + # return if level >= config.debug_level: global debug_string message = "DEBUG: {}".format(message) - # message = message.encode('utf-8', 'replace') if fout: file('/tmp/debug.log', 'a+').writelines(message + '\n') if main_buffer: - #w.prnt("", "---------") + # w.prnt("", "---------") w.prnt("", "slack: " + message) else: if slack_debug and (not debug_string or debug_string in message): - #w.prnt(slack_debug, "---------") + # w.prnt(slack_debug, "---------") w.prnt(slack_debug, message) - ###### Config code + class PluginConfig(object): # Default settings. # These are in the (string) format that weechat expects; at __init__ time @@ -3047,6 +3324,7 @@ def setup_trace(): now = time.time() f = open('{}/{}-trace.json'.format(RECORD_DIR, now), 'w') + def trace_calls(frame, event, arg): global f if event != 'call': @@ -3071,6 +3349,8 @@ def trace_calls(frame, event, arg): # Main if __name__ == "__main__": + w = WeechatWrapper(weechat) + if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "script_unloaded", ""): @@ -3081,11 +3361,11 @@ if __name__ == "__main__": global EVENTROUTER EVENTROUTER = EventRouter() - #setup_trace() + # setup_trace() - #WEECHAT_HOME = w.info_get("weechat_dir", "") - #CACHE_NAME = "slack.cache" - #STOP_TALKING_TO_SLACK = False + # WEECHAT_HOME = w.info_get("weechat_dir", "") + # CACHE_NAME = "slack.cache" + # STOP_TALKING_TO_SLACK = False # Global var section slack_debug = None @@ -3093,17 +3373,17 @@ if __name__ == "__main__": config_changed_cb = config.config_changed typing_timer = time.time() - #domain = None - #previous_buffer = None - #slack_buffer = None + # domain = None + # previous_buffer = None + # slack_buffer = None - #never_away = False + # never_away = False hide_distractions = False - #hotlist = w.infolist_get("hotlist", "", "") - #main_weechat_buffer = w.info_get("irc_buffer", "{}.{}".format(domain, "DOESNOTEXIST!@#$")) + # hotlist = w.infolist_get("hotlist", "", "") + # main_weechat_buffer = w.info_get("irc_buffer", "{}.{}".format(domain, "DOESNOTEXIST!@#$")) - #message_cache = collections.defaultdict(list) - #if config.cache_messages: + # message_cache = collections.defaultdict(list) + # if config.cache_messages: # cache_load() w.hook_config("plugins.var.python." + SCRIPT_NAME + ".*", "config_changed_cb", "") |