diff options
author | Trygve Aaberge <trygveaa@gmail.com> | 2021-03-20 13:41:02 +0100 |
---|---|---|
committer | Trygve Aaberge <trygveaa@gmail.com> | 2021-03-20 13:42:40 +0100 |
commit | 373baece5094b5bedf10e08ea95d09be3619fd23 (patch) | |
tree | de7b6c84c3512c7a08aecf05c351936389bcdebf /wee_slack.py | |
parent | bf9ef6f4bcd580a9b4ba858537d0a99cae7bb87e (diff) | |
download | wee-slack-373baece5094b5bedf10e08ea95d09be3619fd23.tar.gz |
Format all python files with black
Diffstat (limited to 'wee_slack.py')
-rw-r--r-- | wee_slack.py | 3016 |
1 files changed, 2006 insertions, 1010 deletions
diff --git a/wee_slack.py b/wee_slack.py index 7ae1c5b..c9d0c60 100644 --- a/wee_slack.py +++ b/wee_slack.py @@ -35,7 +35,7 @@ sys.modules["numpy"] = None from websocket import ABNF, create_connection, WebSocketConnectionClosedException try: - basestring # Python 2 + basestring # Python 2 unicode str = unicode except NameError: # Python 3 @@ -45,6 +45,7 @@ try: from collections.abc import Mapping, Reversible, KeysView, ItemsView, ValuesView except: from collections import Mapping, KeysView, ItemsView, ValuesView + Reversible = object try: @@ -100,7 +101,7 @@ SLACK_API_TRANSLATOR = { "join": "conversations.join", "leave": "conversations.leave", "mark": "conversations.mark", - "info": "conversations.info" + "info": "conversations.info", }, "private": { "history": "conversations.history", @@ -121,9 +122,7 @@ SLACK_API_TRANSLATOR = { "join": None, "leave": None, "mark": "subscriptions.thread.mark", - } - - + }, } CONFIG_PREFIX = "plugins.var.python." + SCRIPT_NAME @@ -135,11 +134,13 @@ def slack_buffer_or_ignore(f): """ Only run this function if we're in a slack buffer, else ignore """ + @wraps(f) def wrapper(data, current_buffer, *args, **kwargs): if current_buffer not in EVENTROUTER.weechat_controller.buffers: return w.WEECHAT_RC_OK return f(data, current_buffer, *args, **kwargs) + return wrapper @@ -147,13 +148,20 @@ def slack_buffer_required(f): """ Only run this function if we're in a slack buffer, else print error """ + @wraps(f) def wrapper(data, current_buffer, *args, **kwargs): if current_buffer not in EVENTROUTER.weechat_controller.buffers: - command_name = f.__name__.replace('command_', '', 1) - w.prnt('', 'slack: command "{}" must be executed on slack buffer'.format(command_name)) + command_name = f.__name__.replace("command_", "", 1) + w.prnt( + "", + 'slack: command "{}" must be executed on slack buffer'.format( + command_name + ), + ) return w.WEECHAT_RC_ERROR return f(data, current_buffer, *args, **kwargs) + return wrapper @@ -162,9 +170,11 @@ 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 @@ -176,7 +186,7 @@ sslopt_ca_certs = {} if hasattr(ssl, "get_default_verify_paths") and callable(ssl.get_default_verify_paths): ssl_defaults = ssl.get_default_verify_paths() if ssl_defaults.cafile is not None: - sslopt_ca_certs = {'ca_certs': ssl_defaults.cafile} + sslopt_ca_certs = {"ca_certs": ssl_defaults.cafile} EMOJI = {} EMOJI_WITH_SKIN_TONES_REVERSE = {} @@ -188,7 +198,7 @@ def encode_to_utf8(data): if sys.version_info.major > 2: return data elif isinstance(data, unicode): - return data.encode('utf-8') + return data.encode("utf-8") if isinstance(data, bytes): return data elif isinstance(data, collections.Mapping): @@ -203,7 +213,7 @@ def decode_from_utf8(data): if sys.version_info.major > 2: return data elif isinstance(data, bytes): - return data.decode('utf-8') + return data.decode("utf-8") if isinstance(data, unicode): return data elif isinstance(data, collections.Mapping): @@ -226,6 +236,7 @@ class WeechatWrapper(object): 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 @@ -241,12 +252,14 @@ class WeechatWrapper(object): # 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) + return self.wrap_for_utf8(self.wrapped_class.prnt_date_tags)( + buffer, date, tags, message + ) class ProxyWrapper(object): def __init__(self): - self.proxy_name = w.config_string(w.config_get('weechat.network.proxy_curl')) + self.proxy_name = w.config_string(w.config_get("weechat.network.proxy_curl")) self.proxy_string = "" self.proxy_type = "" self.proxy_address = "" @@ -257,15 +270,30 @@ class ProxyWrapper(object): if self.proxy_name: self.proxy_string = "weechat.proxy.{}".format(self.proxy_name) - self.proxy_type = w.config_string(w.config_get("{}.type".format(self.proxy_string))) + self.proxy_type = w.config_string( + w.config_get("{}.type".format(self.proxy_string)) + ) if self.proxy_type == "http": - self.proxy_address = w.config_string(w.config_get("{}.address".format(self.proxy_string))) - self.proxy_port = w.config_integer(w.config_get("{}.port".format(self.proxy_string))) - self.proxy_user = w.config_string(w.config_get("{}.username".format(self.proxy_string))) - self.proxy_password = w.config_string(w.config_get("{}.password".format(self.proxy_string))) + self.proxy_address = w.config_string( + w.config_get("{}.address".format(self.proxy_string)) + ) + self.proxy_port = w.config_integer( + w.config_get("{}.port".format(self.proxy_string)) + ) + self.proxy_user = w.config_string( + w.config_get("{}.username".format(self.proxy_string)) + ) + self.proxy_password = w.config_string( + w.config_get("{}.password".format(self.proxy_string)) + ) self.has_proxy = True else: - w.prnt("", "\nWarning: weechat.network.proxy_curl is set to {} type (name : {}, conf string : {}). Only HTTP proxy is supported.\n\n".format(self.proxy_type, self.proxy_name, self.proxy_string)) + w.prnt( + "", + "\nWarning: weechat.network.proxy_curl is set to {} type (name : {}, conf string : {}). Only HTTP proxy is supported.\n\n".format( + self.proxy_type, self.proxy_name, self.proxy_string + ), + ) def curl(self): if not self.has_proxy: @@ -315,28 +343,30 @@ class ValuesViewReversible(ValuesView, Reversible): ##### Helpers -def colorize_string(color, string, reset_color='reset'): +def colorize_string(color, string, reset_color="reset"): if color: return w.color(color) + string + w.color(reset_color) else: return string -def print_error(message, buffer='', warning=False): - prefix = 'Warning' if warning else 'Error' - w.prnt(buffer, '{}{}: {}'.format(w.prefix('error'), prefix, message)) +def print_error(message, buffer="", warning=False): + prefix = "Warning" if warning else "Error" + w.prnt(buffer, "{}{}: {}".format(w.prefix("error"), prefix, message)) def print_message_not_found_error(msg_id): if msg_id: - print_error("Invalid id given, must be an existing id or a number greater " + - "than 0 and less than the number of messages in the channel") + print_error( + "Invalid id given, must be an existing id or a number greater " + + "than 0 and less than the number of messages in the channel" + ) else: print_error("No messages found in channel") def token_for_print(token): - return '{}...{}'.format(token[:15], token[-10:]) + return "{}...{}".format(token[:15], token[-10:]) def format_exc_tb(): @@ -345,7 +375,7 @@ def format_exc_tb(): def format_exc_only(): etype, value, _ = sys.exc_info() - return ''.join(decode_from_utf8(traceback.format_exception_only(etype, value))) + return "".join(decode_from_utf8(traceback.format_exception_only(etype, value))) def get_localvar_type(slack_type): @@ -361,40 +391,54 @@ def get_nick_color(nick): def get_thread_color(thread_id): - if config.color_thread_suffix == 'multiple': + if config.color_thread_suffix == "multiple": return get_nick_color(thread_id) else: return config.color_thread_suffix def sha1_hex(s): - return str(hashlib.sha1(s.encode('utf-8')).hexdigest()) + return str(hashlib.sha1(s.encode("utf-8")).hexdigest()) def get_functions_with_prefix(prefix): - return {name[len(prefix):]: ref for name, ref in globals().items() - if name.startswith(prefix)} + return { + name[len(prefix) :]: ref + for name, ref in globals().items() + if name.startswith(prefix) + } def handle_socket_error(exception, team, caller_name): - if not (isinstance(exception, WebSocketConnectionClosedException) or - exception.errno in (errno.EPIPE, errno.ECONNRESET, errno.ETIMEDOUT)): + if not ( + isinstance(exception, WebSocketConnectionClosedException) + or exception.errno in (errno.EPIPE, errno.ECONNRESET, errno.ETIMEDOUT) + ): raise - w.prnt(team.channel_buffer, - 'Lost connection to slack team {} (on {}), reconnecting.'.format( - team.domain, caller_name)) - dbg('Socket failed on {} with exception:\n{}'.format( - caller_name, format_exc_tb()), level=5) + w.prnt( + team.channel_buffer, + "Lost connection to slack team {} (on {}), reconnecting.".format( + team.domain, caller_name + ), + ) + dbg( + "Socket failed on {} with exception:\n{}".format(caller_name, format_exc_tb()), + level=5, + ) team.set_disconnected() -MESSAGE_ID_REGEX_STRING = r'(?P<msg_id>\d+|\$[0-9a-fA-F]{3,})' -REACTION_PREFIX_REGEX_STRING = r'{}?(?P<reaction_change>\+|-)'.format(MESSAGE_ID_REGEX_STRING) +MESSAGE_ID_REGEX_STRING = r"(?P<msg_id>\d+|\$[0-9a-fA-F]{3,})" +REACTION_PREFIX_REGEX_STRING = r"{}?(?P<reaction_change>\+|-)".format( + MESSAGE_ID_REGEX_STRING +) -EMOJI_CHAR_REGEX_STRING = '(?P<emoji_char>[\U00000080-\U0010ffff]+)' -EMOJI_NAME_REGEX_STRING = ':(?P<emoji_name>[a-z0-9_+-]+):' -EMOJI_CHAR_OR_NAME_REGEX_STRING = '({}|{})'.format(EMOJI_CHAR_REGEX_STRING, EMOJI_NAME_REGEX_STRING) +EMOJI_CHAR_REGEX_STRING = "(?P<emoji_char>[\U00000080-\U0010ffff]+)" +EMOJI_NAME_REGEX_STRING = ":(?P<emoji_name>[a-z0-9_+-]+):" +EMOJI_CHAR_OR_NAME_REGEX_STRING = "({}|{})".format( + EMOJI_CHAR_REGEX_STRING, EMOJI_NAME_REGEX_STRING +) EMOJI_NAME_REGEX = re.compile(EMOJI_NAME_REGEX_STRING) EMOJI_CHAR_OR_NAME_REGEX = re.compile(EMOJI_CHAR_OR_NAME_REGEX_STRING) @@ -404,12 +448,12 @@ def regex_match_to_emoji(match, include_name=False): full_match = match.group() char = EMOJI.get(emoji, full_match) if include_name and char != full_match: - return '{} ({})'.format(char, full_match) + return "{} ({})".format(char, full_match) return char def replace_string_with_emoji(text): - if config.render_emoji_as_string == 'both': + if config.render_emoji_as_string == "both": return EMOJI_NAME_REGEX.sub( partial(regex_match_to_emoji, include_name=True), text, @@ -430,8 +474,8 @@ def replace_emoji_with_string(text): ###### New central Event router -class EventRouter(object): +class EventRouter(object): def __init__(self): """ complete @@ -483,19 +527,19 @@ class EventRouter(object): if team: team_subdomain = team.subdomain else: - team_json = message_json.get('team') + team_json = message_json.get("team") if team_json: - team_subdomain = team_json.get('domain') + team_subdomain = team_json.get("domain") else: - team_subdomain = 'unknown_team' + team_subdomain = "unknown_team" directory = "{}/{}".format(RECORD_DIR, team_subdomain) if subdir: directory = "{}/{}".format(directory, subdir) if not os.path.exists(directory): os.makedirs(directory) - mtype = message_json.get(file_name_field, 'unknown') - f = open('{}/{}-{}.json'.format(directory, now, mtype), 'w') + mtype = message_json.get(file_name_field, "unknown") + f = open("{}/{}-{}.json".format(directory, now, mtype), "w") f.write("{}".format(json.dumps(message_json))) f.close() @@ -505,7 +549,9 @@ class EventRouter(object): weechat's "callback_data" has a limited size and weechat will crash if you exceed this size. """ - identifier = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(40)) + identifier = "".join( + random.choice(string.ascii_uppercase + string.digits) for _ in range(40) + ) self.context[identifier] = data dbg("stored context {} {} ".format(identifier, data.url)) return identifier @@ -549,10 +595,17 @@ class EventRouter(object): for team in self.teams.values(): time_since_last_ping = time.time() - team.last_ping_time time_since_last_pong = time.time() - team.last_pong_time - if team.connected and time_since_last_ping < 5 and time_since_last_pong > 30: - w.prnt(team.channel_buffer, - 'Lost connection to slack team {} (no pong), reconnecting.'.format( - team.domain)) + if ( + team.connected + and time_since_last_ping < 5 + and time_since_last_pong > 30 + ): + w.prnt( + team.channel_buffer, + "Lost connection to slack team {} (no pong), reconnecting.".format( + team.domain + ), + ) team.set_disconnected() if not team.connected: team.connect(reconnect=True) @@ -576,7 +629,7 @@ class EventRouter(object): # No more data to read at this time. return w.WEECHAT_RC_OK except (WebSocketConnectionClosedException, socket.error) as e: - handle_socket_error(e, team, 'receive') + handle_socket_error(e, team, "receive") return w.WEECHAT_RC_OK if opcode == ABNF.OPCODE_PONG: @@ -585,9 +638,9 @@ class EventRouter(object): elif opcode != ABNF.OPCODE_TEXT: return w.WEECHAT_RC_OK - message_json = json.loads(data.decode('utf-8')) + message_json = json.loads(data.decode("utf-8")) if self.recording: - self.record_event(message_json, team, 'type', 'websocket') + self.record_event(message_json, team, "type", "websocket") message_json["wee_slack_metadata_team"] = team self.receive(message_json) return w.WEECHAT_RC_OK @@ -603,21 +656,32 @@ class EventRouter(object): where the request originated and route properly. """ request_metadata = self.retrieve_context(data) - dbg("RECEIVED CALLBACK with request of {} id of {} and code {} of length {}".format(request_metadata.request, request_metadata.response_id, return_code, len(out))) + dbg( + "RECEIVED CALLBACK with request of {} id of {} and code {} of length {}".format( + request_metadata.request, + request_metadata.response_id, + return_code, + len(out), + ) + ) if return_code == 0: if len(out) > 0: if request_metadata.response_id not in self.reply_buffer: self.reply_buffer[request_metadata.response_id] = StringIO() self.reply_buffer[request_metadata.response_id].write(out) try: - j = json.loads(self.reply_buffer[request_metadata.response_id].getvalue()) + j = json.loads( + self.reply_buffer[request_metadata.response_id].getvalue() + ) except: pass # dbg("Incomplete json, awaiting more", True) try: j["wee_slack_process_method"] = request_metadata.request_normalized if self.recording: - self.record_event(j, request_metadata.team, 'wee_slack_process_method', 'http') + self.record_event( + j, request_metadata.team, "wee_slack_process_method", "http" + ) j["wee_slack_request_metadata"] = request_metadata self.reply_buffer.pop(request_metadata.response_id) self.receive(j) @@ -637,14 +701,30 @@ class EventRouter(object): else: self.reply_buffer.pop(request_metadata.response_id, None) self.delete_context(data) - if request_metadata.request.startswith('rtm.'): - retry_text = ('retrying' if request_metadata.should_try() else - 'will not retry after too many failed attempts') - w.prnt('', ('Failed connecting to slack team with token {}, {}. ' + - 'If this persists, try increasing slack_timeout. Error (code {}): {}') - .format(token_for_print(request_metadata.token), retry_text, return_code, err)) - dbg('rtm.start failed with return_code {}. stack:\n{}' - .format(return_code, ''.join(traceback.format_stack())), level=5) + if request_metadata.request.startswith("rtm."): + retry_text = ( + "retrying" + if request_metadata.should_try() + else "will not retry after too many failed attempts" + ) + w.prnt( + "", + ( + "Failed connecting to slack team with token {}, {}. " + + "If this persists, try increasing slack_timeout. Error (code {}): {}" + ).format( + token_for_print(request_metadata.token), + retry_text, + return_code, + err, + ), + ) + dbg( + "rtm.start failed with return_code {}. stack:\n{}".format( + return_code, "".join(traceback.format_stack()) + ), + level=5, + ) self.receive(request_metadata) return w.WEECHAT_RC_OK @@ -670,13 +750,17 @@ class EventRouter(object): wanted_interval = 100 if len(self.slow_queue) > 0 or len(self.queue) > 0: wanted_interval = 10 - if self.handle_next_hook is None or wanted_interval != self.handle_next_hook_interval: + if ( + self.handle_next_hook is None + or wanted_interval != self.handle_next_hook_interval + ): if self.handle_next_hook: w.unhook(self.handle_next_hook) - self.handle_next_hook = w.hook_timer(wanted_interval, 0, 0, "handle_next", "") + self.handle_next_hook = w.hook_timer( + wanted_interval, 0, 0, "handle_next", "" + ) self.handle_next_hook_interval = wanted_interval - if len(self.slow_queue) > 0 and ((self.slow_queue_timer + 1) < time.time()): dbg("from slow queue", 0) self.queue.append(self.slow_queue.pop()) @@ -721,14 +805,23 @@ class EventRouter(object): if team: if "channel" in j: - channel_id = j["channel"]["id"] if type(j["channel"]) == dict else j["channel"] + channel_id = ( + j["channel"]["id"] + if type(j["channel"]) == dict + else j["channel"] + ) channel = team.channels.get(channel_id, channel) if "user" in j: - user_id = j["user"]["id"] if type(j["user"]) == dict else j["user"] - metadata['user'] = team.users.get(user_id) + user_id = ( + j["user"]["id"] if type(j["user"]) == dict else j["user"] + ) + metadata["user"] = team.users.get(user_id) dbg("running {}".format(function_name)) - if function_name.startswith("local_") and function_name in self.local_proc: + if ( + function_name.startswith("local_") + and function_name in self.local_proc + ): self.local_proc[function_name](j, self, team, channel, metadata) elif function_name in self.proc: self.proc[function_name](j, self, team, channel, metadata) @@ -797,6 +890,7 @@ class WeechatController(object): def set_previous_buffer(self, data): self.previous_buffer = data + ###### New Local Processors @@ -807,14 +901,25 @@ def local_process_async_slack_api_request(request, event_router): DEBUGGING!!! The context here cannot be very large. Weechat will crash. """ if not event_router.shutting_down: - weechat_request = 'url:{}'.format(request.request_string()) - weechat_request += '&nonce={}'.format(''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(4))) - params = {'useragent': 'wee_slack {}'.format(SCRIPT_VERSION)} + weechat_request = "url:{}".format(request.request_string()) + weechat_request += "&nonce={}".format( + "".join( + random.choice(string.ascii_uppercase + string.digits) for _ in range(4) + ) + ) + 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 - w.hook_process_hashtable('url:', params, config.slack_timeout, "", context) - w.hook_process_hashtable(weechat_request, params, config.slack_timeout, "receive_httprequest_callback", context) + 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 @@ -827,7 +932,7 @@ def ws_ping_cb(data, remaining_calls): team.ws.ping() team.last_ping_time = time.time() except (WebSocketConnectionClosedException, socket.error) as e: - handle_socket_error(e, team, 'ping') + handle_socket_error(e, team, "ping") return w.WEECHAT_RC_OK @@ -840,14 +945,19 @@ def reconnect_callback(*args): @utf8_decode def buffer_renamed_cb(data, signal, current_buffer): channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer) - if isinstance(channel, SlackChannelCommon) and not channel.buffer_rename_in_progress: + if ( + isinstance(channel, SlackChannelCommon) + and not channel.buffer_rename_in_progress + ): if w.buffer_get_string(channel.channel_buffer, "old_full_name"): channel.label_full_drop_prefix = True channel.label_full = w.buffer_get_string(channel.channel_buffer, "name") else: channel.label_short_drop_prefix = True - channel.label_short = w.buffer_get_string(channel.channel_buffer, "short_name") + channel.label_short = w.buffer_get_string( + channel.channel_buffer, "short_name" + ) channel.rename() return w.WEECHAT_RC_OK @@ -871,13 +981,18 @@ def buffer_input_callback(signal, buffer_ptr, data): sending messages. """ if weechat_version < 0x2090000: - data = data.replace('\r', '\n') + data = data.replace("\r", "\n") 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(r"{}{}\s*$".format(REACTION_PREFIX_REGEX_STRING, EMOJI_CHAR_OR_NAME_REGEX_STRING), data) + reaction = re.match( + r"{}{}\s*$".format( + REACTION_PREFIX_REGEX_STRING, EMOJI_CHAR_OR_NAME_REGEX_STRING + ), + data, + ) substitute = re.match("{}?s/".format(MESSAGE_ID_REGEX_STRING), data) if reaction: emoji = reaction.group("emoji_char") or reaction.group("emoji_name") @@ -887,18 +1002,22 @@ def buffer_input_callback(signal, buffer_ptr, data): channel.send_remove_reaction(reaction.group("msg_id"), emoji) elif substitute: try: - old, new, flags = re.split(r'(?<!\\)/', data)[1:] + old, new, flags = re.split(r"(?<!\\)/", data)[1:] except ValueError: - print_error('Incomplete regex for changing a message, ' - 'it should be in the form s/old text/new text/') + print_error( + "Incomplete regex for changing a message, " + "it should be in the form s/old text/new text/" + ) else: # Replacement string in re.sub() is a string, not a regex, so get # rid of escapes. - new = new.replace(r'\/', '/') - old = old.replace(r'\/', '/') - channel.edit_nth_previous_message(substitute.group("msg_id"), old, new, flags) + new = new.replace(r"\/", "/") + old = old.replace(r"\/", "/") + channel.edit_nth_previous_message( + substitute.group("msg_id"), old, new, flags + ) else: - if data.startswith(('//', ' ')): + if data.startswith(("//", " ")): data = data[1:] channel.send_message(data) # this is probably wrong channel.mark_read(update_remote=True, force=True) @@ -913,7 +1032,7 @@ def buffer_input_callback(signal, buffer_ptr, data): def input_text_for_buffer_cb(data, modifier, current_buffer, string): if current_buffer not in EVENTROUTER.weechat_controller.buffers: return string - return re.sub('\r?\n', '\r', decode_from_utf8(string)) + return re.sub("\r?\n", "\r", decode_from_utf8(string)) @utf8_decode @@ -929,7 +1048,9 @@ def buffer_switch_callback(data, signal, current_buffer): if prev: prev.mark_read() - new_channel = EVENTROUTER.weechat_controller.get_channel_from_buffer_ptr(current_buffer) + new_channel = EVENTROUTER.weechat_controller.get_channel_from_buffer_ptr( + current_buffer + ) if new_channel: if not new_channel.got_history or new_channel.history_needs_update: new_channel.get_history() @@ -1002,7 +1123,7 @@ def typing_bar_item_cb(data, item, current_window, current_buffer, extra_info): if current_channel: # this try is mostly becuase server buffers don't implement is_someone_typing try: - if current_channel.type != 'im' and current_channel.is_someone_typing(): + if current_channel.type != "im" and current_channel.is_someone_typing(): typers += current_channel.get_typing_list() except: pass @@ -1027,16 +1148,16 @@ def typing_bar_item_cb(data, item, current_window, current_buffer, extra_info): def away_bar_item_cb(data, item, current_window, current_buffer, extra_info): channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer) if not channel: - return '' + return "" if channel.team.is_user_present(channel.team.myidentifier): - return '' + return "" else: - away_color = w.config_string(w.config_get('weechat.color.item_away')) - if channel.team.my_manual_presence == 'away': - return colorize_string(away_color, 'manual away') + away_color = w.config_string(w.config_get("weechat.color.item_away")) + if channel.team.my_manual_presence == "away": + return colorize_string(away_color, "manual away") else: - return colorize_string(away_color, 'auto away') + return colorize_string(away_color, "auto away") @utf8_decode @@ -1045,21 +1166,40 @@ def channel_completion_cb(data, completion_item, current_buffer, completion): Adds all channels on all teams to completion list """ current_channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer) - should_include_channel = lambda channel: channel.active and channel.type in ['channel', 'group', 'private', 'shared'] + should_include_channel = lambda channel: channel.active and channel.type in [ + "channel", + "group", + "private", + "shared", + ] - other_teams = [team for team in EVENTROUTER.teams.values() if not current_channel or team != current_channel.team] + other_teams = [ + team + for team in EVENTROUTER.teams.values() + if not current_channel or team != current_channel.team + ] for team in other_teams: for channel in team.channels.values(): if should_include_channel(channel): - w.hook_completion_list_add(completion, channel.name, 0, w.WEECHAT_LIST_POS_SORT) + w.hook_completion_list_add( + completion, channel.name, 0, w.WEECHAT_LIST_POS_SORT + ) if current_channel: - for channel in sorted(current_channel.team.channels.values(), key=lambda channel: channel.name, reverse=True): + for channel in sorted( + current_channel.team.channels.values(), + key=lambda channel: channel.name, + reverse=True, + ): if should_include_channel(channel): - w.hook_completion_list_add(completion, channel.name, 0, w.WEECHAT_LIST_POS_BEGINNING) + w.hook_completion_list_add( + completion, channel.name, 0, w.WEECHAT_LIST_POS_BEGINNING + ) if should_include_channel(current_channel): - w.hook_completion_list_add(completion, current_channel.name, 0, w.WEECHAT_LIST_POS_BEGINNING) + w.hook_completion_list_add( + completion, current_channel.name, 0, w.WEECHAT_LIST_POS_BEGINNING + ) return w.WEECHAT_RC_OK @@ -1070,8 +1210,10 @@ def dm_completion_cb(data, completion_item, current_buffer, completion): """ for team in EVENTROUTER.teams.values(): for channel in team.channels.values(): - if channel.active and channel.type in ['im', 'mpim']: - w.hook_completion_list_add(completion, channel.name, 0, w.WEECHAT_LIST_POS_SORT) + if channel.active and channel.type in ["im", "mpim"]: + w.hook_completion_list_add( + completion, channel.name, 0, w.WEECHAT_LIST_POS_SORT + ) return w.WEECHAT_RC_OK @@ -1085,7 +1227,7 @@ def nick_completion_cb(data, completion_item, current_buffer, completion): return w.WEECHAT_RC_OK base_command = w.hook_completion_get_string(completion, "base_command") - if base_command in ['invite', 'msg', 'query', 'whois']: + if base_command in ["invite", "msg", "query", "whois"]: members = current_channel.team.members else: members = current_channel.members @@ -1093,8 +1235,12 @@ def nick_completion_cb(data, completion_item, current_buffer, completion): for member in members: user = current_channel.team.users.get(member) if user and not user.deleted: - w.hook_completion_list_add(completion, user.name, 1, w.WEECHAT_LIST_POS_SORT) - w.hook_completion_list_add(completion, "@" + user.name, 1, w.WEECHAT_LIST_POS_SORT) + w.hook_completion_list_add( + completion, user.name, 1, w.WEECHAT_LIST_POS_SORT + ) + w.hook_completion_list_add( + completion, "@" + user.name, 1, w.WEECHAT_LIST_POS_SORT + ) return w.WEECHAT_RC_OK @@ -1112,7 +1258,9 @@ def emoji_completion_cb(data, completion_item, current_buffer, completion): prefix = reaction.group(0) if reaction else ":" for emoji in current_channel.team.emoji_completions: - w.hook_completion_list_add(completion, prefix + emoji + ":", 0, w.WEECHAT_LIST_POS_SORT) + w.hook_completion_list_add( + completion, prefix + emoji + ":", 0, w.WEECHAT_LIST_POS_SORT + ) return w.WEECHAT_RC_OK @@ -1122,14 +1270,18 @@ def thread_completion_cb(data, completion_item, current_buffer, completion): Adds all $-prefixed thread ids to completion list """ current_channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer) - if current_channel is None or not hasattr(current_channel, 'hashed_messages'): + if current_channel is None or not hasattr(current_channel, "hashed_messages"): return w.WEECHAT_RC_OK - threads = (x for x in current_channel.hashed_messages.items() if isinstance(x[0], str)) + threads = ( + x for x in current_channel.hashed_messages.items() if isinstance(x[0], str) + ) for thread_id, message_ts in sorted(threads, key=lambda item: item[1]): message = current_channel.messages.get(message_ts) if message and message.number_of_replies(): - w.hook_completion_list_add(completion, "$" + thread_id, 0, w.WEECHAT_LIST_POS_BEGINNING) + w.hook_completion_list_add( + completion, "$" + thread_id, 0, w.WEECHAT_LIST_POS_BEGINNING + ) return w.WEECHAT_RC_OK @@ -1144,8 +1296,8 @@ def topic_completion_cb(data, completion_item, current_buffer, completion): topic = current_channel.render_topic() channel_names = [channel.name for channel in current_channel.team.channels.values()] - if topic.split(' ', 1)[0] in channel_names: - topic = '{} {}'.format(current_channel.name, topic) + if topic.split(" ", 1)[0] in channel_names: + topic = "{} {}".format(current_channel.name, topic) w.hook_completion_list_add(completion, topic, 0, w.WEECHAT_LIST_POS_SORT) return w.WEECHAT_RC_OK @@ -1160,7 +1312,9 @@ def usergroups_completion_cb(data, completion_item, current_buffer, completion): if current_channel is None: return w.WEECHAT_RC_OK - subteam_handles = [subteam.handle for subteam in current_channel.team.subteams.values()] + subteam_handles = [ + subteam.handle for subteam in current_channel.team.subteams.values() + ] for group in subteam_handles + ["@channel", "@everyone", "@here"]: w.hook_completion_list_add(completion, group, 1, w.WEECHAT_LIST_POS_SORT) return w.WEECHAT_RC_OK @@ -1174,7 +1328,11 @@ def complete_next_cb(data, current_buffer, command): thing """ current_channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer) - if not hasattr(current_channel, 'members') or current_channel is None or current_channel.members is None: + if ( + not hasattr(current_channel, "members") + or current_channel is None + or current_channel.members is None + ): return w.WEECHAT_RC_OK line_input = w.buffer_get_string(current_buffer, "input") @@ -1184,12 +1342,16 @@ def complete_next_cb(data, current_buffer, command): word_start = 0 word_end = input_length # If we're on a non-word, look left for something to complete - while current_pos >= 0 and line_input[current_pos] != '@' and not line_input[current_pos].isalnum(): + while ( + current_pos >= 0 + and line_input[current_pos] != "@" + and not line_input[current_pos].isalnum() + ): current_pos = current_pos - 1 if current_pos < 0: current_pos = 0 for l in range(current_pos, 0, -1): - if line_input[l] != '@' and not line_input[l].isalnum(): + if line_input[l] != "@" and not line_input[l].isalnum(): word_start = l + 1 break for l in range(current_pos, input_length): @@ -1203,8 +1365,16 @@ def complete_next_cb(data, current_buffer, command): if user and user.name == word: # Here, we cheat. Insert a @ in front and rely in the @ # nicks being in the completion list - w.buffer_set(current_buffer, "input", line_input[:word_start] + "@" + line_input[word_start:]) - w.buffer_set(current_buffer, "input_pos", str(w.buffer_get_integer(current_buffer, "input_pos") + 1)) + w.buffer_set( + current_buffer, + "input", + line_input[:word_start] + "@" + line_input[word_start:], + ) + w.buffer_set( + current_buffer, + "input_pos", + str(w.buffer_get_integer(current_buffer, "input_pos") + 1), + ) return w.WEECHAT_RC_OK_EAT return w.WEECHAT_RC_OK @@ -1221,12 +1391,13 @@ def stop_talking_to_slack(): which triggers leaving the channel because of how close buffer is handled """ - if 'EVENTROUTER' in globals(): + if "EVENTROUTER" in globals(): EVENTROUTER.shutdown() for team in EVENTROUTER.teams.values(): team.ws.shutdown() return w.WEECHAT_RC_OK + ##### New Classes @@ -1236,7 +1407,16 @@ class SlackRequest(object): makes a SHA of the requst url and current time so we can re-tag this on the way back through. """ - def __init__(self, team, request, post_data=None, channel=None, metadata=None, retries=3, token=None): + def __init__( + self, + team, + request, + post_data=None, + channel=None, + metadata=None, + retries=3, + token=None, + ): if team is None and token is None: raise ValueError("Both team and token can't be None") self.team = team @@ -1248,17 +1428,28 @@ class SlackRequest(object): self.token = token if token else team.token self.tries = 0 self.start_time = time.time() - self.request_normalized = re.sub(r'\W+', '', request) - self.domain = 'api.slack.com' - self.post_data['token'] = self.token - self.url = 'https://{}/api/{}?{}'.format(self.domain, self.request, urlencode(encode_to_utf8(self.post_data))) - self.params = {'useragent': 'wee_slack {}'.format(SCRIPT_VERSION)} - self.response_id = sha1_hex('{}{}'.format(self.url, self.start_time)) + self.request_normalized = re.sub(r"\W+", "", request) + self.domain = "api.slack.com" + self.post_data["token"] = self.token + self.url = "https://{}/api/{}?{}".format( + self.domain, self.request, urlencode(encode_to_utf8(self.post_data)) + ) + self.params = {"useragent": "wee_slack {}".format(SCRIPT_VERSION)} + self.response_id = sha1_hex("{}{}".format(self.url, self.start_time)) def __repr__(self): - return ("SlackRequest(team={}, request='{}', post_data={}, retries={}, token='{}', " - "tries={}, start_time={})").format(self.team, self.request, self.post_data, - self.retries, token_for_print(self.token), self.tries, self.start_time) + return ( + "SlackRequest(team={}, request='{}', post_data={}, retries={}, token='{}', " + "tries={}, start_time={})" + ).format( + self.team, + self.request, + self.post_data, + self.retries, + token_for_print(self.token), + self.tries, + self.start_time, + ) def request_string(self): return "{}".format(self.url) @@ -1271,27 +1462,27 @@ class SlackRequest(object): return self.tries < self.retries def retry_ready(self): - return (self.start_time + (self.tries**2)) < time.time() + return (self.start_time + (self.tries ** 2)) < time.time() class SlackSubteam(object): - """ - Represents a slack group or subteam - """ + """ + Represents a slack group or subteam + """ - def __init__(self, originating_team_id, is_member, **kwargs): - self.handle = '@{}'.format(kwargs['handle']) - self.identifier = kwargs['id'] - self.name = kwargs['name'] - self.description = kwargs.get('description') - self.team_id = originating_team_id - self.is_member = is_member + def __init__(self, originating_team_id, is_member, **kwargs): + self.handle = "@{}".format(kwargs["handle"]) + self.identifier = kwargs["id"] + self.name = kwargs["name"] + self.description = kwargs.get("description") + self.team_id = originating_team_id + self.is_member = is_member - def __repr__(self): - return "Name:{} Identifier:{}".format(self.name, self.identifier) + def __repr__(self): + return "Name:{} Identifier:{}".format(self.name, self.identifier) - def __eq__(self, compare_str): - return compare_str == self.identifier + def __eq__(self, compare_str): + return compare_str == self.identifier class SlackTeam(object): @@ -1300,7 +1491,22 @@ class SlackTeam(object): Team object under which users and channels live.. Does lots. """ - def __init__(self, eventrouter, token, team_hash, websocket_url, team_info, subteams, nick, myidentifier, my_manual_presence, users, bots, channels, **kwargs): + def __init__( + self, + eventrouter, + token, + team_hash, + websocket_url, + team_info, + subteams, + nick, + myidentifier, + my_manual_presence, + users, + bots, + channels, + **kwargs + ): self.slack_api_translator = copy.deepcopy(SLACK_API_TRANSLATOR) self.identifier = team_info["id"] self.type = "team" @@ -1339,13 +1545,15 @@ class SlackTeam(object): self.got_history = True self.history_needs_update = False self.create_buffer() - self.set_muted_channels(kwargs.get('muted_channels', "")) - self.set_highlight_words(kwargs.get('highlight_words', "")) + self.set_muted_channels(kwargs.get("muted_channels", "")) + self.set_highlight_words(kwargs.get("highlight_words", "")) for c in self.channels.keys(): channels[c].set_related_server(self) channels[c].check_should_open() # Last step is to make sure my nickname is the set color - self.users[self.myidentifier].force_color(w.config_string(w.config_get('weechat.color.chat_nick_self'))) + 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.load_emoji_completions() @@ -1353,7 +1561,11 @@ class SlackTeam(object): return "domain={} nick={}".format(self.subdomain, self.nick) def __eq__(self, compare_str): - return compare_str == self.token or compare_str == self.domain or compare_str == self.subdomain + return ( + compare_str == self.token + or compare_str == self.domain + or compare_str == self.subdomain + ) @property def members(self): @@ -1383,10 +1595,14 @@ class SlackTeam(object): def create_buffer(self): if not self.channel_buffer: - self.channel_buffer = w.buffer_new(self.name, "buffer_input_callback", "EVENTROUTER", "", "") - self.eventrouter.weechat_controller.register_buffer(self.channel_buffer, self) + self.channel_buffer = w.buffer_new( + self.name, "buffer_input_callback", "EVENTROUTER", "", "" + ) + self.eventrouter.weechat_controller.register_buffer( + self.channel_buffer, self + ) w.buffer_set(self.channel_buffer, "input_multiline", "1") - w.buffer_set(self.channel_buffer, "localvar_set_type", 'server') + w.buffer_set(self.channel_buffer, "localvar_set_type", "server") w.buffer_set(self.channel_buffer, "localvar_set_slack_type", self.type) w.buffer_set(self.channel_buffer, "localvar_set_nick", self.nick) w.buffer_set(self.channel_buffer, "localvar_set_server", self.name) @@ -1394,8 +1610,8 @@ class SlackTeam(object): def buffer_merge(self, config_value=None): if not config_value: - config_value = w.config_string(w.config_get('irc.look.server_buffer')) - if config_value == 'merge_with_core': + config_value = w.config_string(w.config_get("irc.look.server_buffer")) + if config_value == "merge_with_core": w.buffer_merge(self.channel_buffer, w.buffer_search_main()) else: w.buffer_unmerge(self.channel_buffer, 0) @@ -1404,13 +1620,13 @@ class SlackTeam(object): pass def set_muted_channels(self, muted_str): - self.muted_channels = {x for x in muted_str.split(',') if x} + self.muted_channels = {x for x in muted_str.split(",") if x} for channel in self.channels.values(): channel.set_highlights() channel.rename() def set_highlight_words(self, highlight_str): - self.highlight_words = {x for x in highlight_str.split(',') if x} + self.highlight_words = {x for x in highlight_str.split(",") if x} for channel in self.channels.values(): channel.set_highlights() @@ -1428,7 +1644,8 @@ class SlackTeam(object): def find_channel_by_members(self, members, channel_type=None): for channel in self.channels.values(): if channel.members == members and ( - channel_type is None or channel.type == channel_type): + channel_type is None or channel.type == channel_type + ): return channel def get_channel_map(self): @@ -1449,7 +1666,7 @@ class SlackTeam(object): def is_user_present(self, user_id): user = self.users.get(user_id) - if user and user.presence == 'active': + if user and user.presence == "active": return True else: return False @@ -1466,16 +1683,39 @@ class SlackTeam(object): proxy = ProxyWrapper() timeout = config.slack_timeout / 1000 if proxy.has_proxy == True: - ws = create_connection(self.ws_url, timeout=timeout, sslopt=sslopt_ca_certs, http_proxy_host=proxy.proxy_address, http_proxy_port=proxy.proxy_port, http_proxy_auth=(proxy.proxy_user, proxy.proxy_password)) + ws = create_connection( + self.ws_url, + timeout=timeout, + sslopt=sslopt_ca_certs, + http_proxy_host=proxy.proxy_address, + http_proxy_port=proxy.proxy_port, + http_proxy_auth=(proxy.proxy_user, proxy.proxy_password), + ) else: - ws = create_connection(self.ws_url, timeout=timeout, sslopt=sslopt_ca_certs) - - self.hook = w.hook_fd(ws.sock.fileno(), 1, 0, 0, "receive_ws_callback", self.get_team_hash()) + ws = create_connection( + self.ws_url, timeout=timeout, sslopt=sslopt_ca_certs + ) + + self.hook = w.hook_fd( + ws.sock.fileno(), + 1, + 0, + 0, + "receive_ws_callback", + self.get_team_hash(), + ) ws.sock.setblocking(0) except: - w.prnt(self.channel_buffer, - 'Failed connecting to slack team {}, retrying.'.format(self.domain)) - dbg('connect failed with exception:\n{}'.format(format_exc_tb()), level=5) + w.prnt( + self.channel_buffer, + "Failed connecting to slack team {}, retrying.".format( + self.domain + ), + ) + dbg( + "connect failed with exception:\n{}".format(format_exc_tb()), + level=5, + ) return False finally: self.connecting_ws = False @@ -1486,15 +1726,20 @@ class SlackTeam(object): # The fast reconnect failed, so start over-ish for chan in self.channels: self.channels[chan].history_needs_update = True - s = initiate_connection(self.token, retries=999, team=self, reconnect=reconnect) + s = initiate_connection( + self.token, retries=999, team=self, reconnect=reconnect + ) self.eventrouter.receive(s) self.connecting_rtm = True def set_connected(self): self.connected = True self.last_pong_time = time.time() - self.buffer_prnt('Connected to Slack team {} ({}) with username {}'.format( - self.team_info["name"], self.domain, self.nick)) + self.buffer_prnt( + "Connected to Slack team {} ({}) with username {}".format( + self.team_info["name"], self.domain, self.nick + ) + ) dbg("connected to {}".format(self.domain)) if config.background_load_all_history: @@ -1502,8 +1747,13 @@ class SlackTeam(object): if channel.channel_buffer: channel.get_history(slow_queue=True) else: - current_channel = self.eventrouter.weechat_controller.buffers.get(w.current_buffer()) - if isinstance(current_channel, SlackChannelCommon) and current_channel.team == self: + current_channel = self.eventrouter.weechat_controller.buffers.get( + w.current_buffer() + ) + if ( + isinstance(current_channel, SlackChannelCommon) + and current_channel.team == self + ): current_channel.get_history(slow_queue=True) def set_disconnected(self): @@ -1526,7 +1776,7 @@ class SlackTeam(object): self.ws.send(encode_to_utf8(message)) dbg("Sent {}...".format(message[:100])) except (WebSocketConnectionClosedException, socket.error) as e: - handle_socket_error(e, self, 'send') + handle_socket_error(e, self, "send") def update_member_presence(self, user, presence): user.presence = presence @@ -1545,10 +1795,13 @@ class SlackTeam(object): users = list(self.users.keys())[:750] if self.myidentifier not in users: users.append(self.myidentifier) - self.send_to_websocket({ - "type": "presence_sub", - "ids": users, - }, expect_reply=False) + self.send_to_websocket( + { + "type": "presence_sub", + "ids": users, + }, + expect_reply=False, + ) class SlackChannelCommon(object): @@ -1559,7 +1812,9 @@ class SlackChannelCommon(object): self.label_short = None self.buffer_rename_in_progress = False - def prnt_message(self, message, history_message=False, no_log=False, force_render=False): + def prnt_message( + self, message, history_message=False, no_log=False, force_render=False + ): text = self.render(message, force_render) thread_channel = isinstance(self, SlackThreadChannel) @@ -1593,16 +1848,27 @@ class SlackChannelCommon(object): else: return - self.buffer_prnt(prefix, text, message.ts, tagset=tagset, - tag_nick=message.sender_plain, history_message=history_message, - no_log=no_log, extra_tags=extra_tags) + self.buffer_prnt( + prefix, + text, + message.ts, + tagset=tagset, + tag_nick=message.sender_plain, + history_message=history_message, + no_log=no_log, + extra_tags=extra_tags, + ) def print_getting_history(self): if self.channel_buffer: ts = SlackTS() w.buffer_set(self.channel_buffer, "print_hooks_enabled", "0") - w.prnt_date_tags(self.channel_buffer, ts.major, - tag(ts, backlog=True, no_log=True), '\tgetting channel history...') + w.prnt_date_tags( + self.channel_buffer, + ts.major, + tag(ts, backlog=True, no_log=True), + "\tgetting channel history...", + ) w.buffer_set(self.channel_buffer, "print_hooks_enabled", "1") def reprint_messages(self, history_message=False, no_log=True, force_render=False): @@ -1611,19 +1877,31 @@ class SlackChannelCommon(object): self.last_line_from = None for message in self.visible_messages.values(): self.prnt_message(message, history_message, no_log, force_render) - if (self.identifier in self.pending_history_requests or - config.thread_messages_in_channel and self.pending_history_requests): + if ( + self.identifier in self.pending_history_requests + or config.thread_messages_in_channel + and self.pending_history_requests + ): self.print_getting_history() def send_message(self, message, subtype=None, request_dict_ext={}): - if subtype == 'me_message': + if subtype == "me_message": message = linkify_text(message, self.team, escape_characters=False) - s = SlackRequest(self.team, "chat.meMessage", {"channel": self.identifier, "text": message}, channel=self) + s = SlackRequest( + self.team, + "chat.meMessage", + {"channel": self.identifier, "text": message}, + channel=self, + ) self.eventrouter.receive(s) else: message = linkify_text(message, self.team) - request = {"type": "message", "channel": self.identifier, - "text": message, "user": self.team.myidentifier} + request = { + "type": "message", + "channel": self.identifier, + "text": message, + "user": self.team.myidentifier, + } request.update(request_dict_ext) self.team.send_to_websocket(request) @@ -1647,18 +1925,28 @@ class SlackChannelCommon(object): else: method = "reactions.add" - data = {"channel": self.identifier, "timestamp": message.ts, "name": reaction_name} - s = SlackRequest(self.team, method, data, channel=self, metadata={'reaction': reaction}) + data = { + "channel": self.identifier, + "timestamp": message.ts, + "name": reaction_name, + } + s = SlackRequest( + self.team, method, data, channel=self, metadata={"reaction": reaction} + ) self.eventrouter.receive(s) def edit_nth_previous_message(self, msg_id, old, new, flags): - message_filter = lambda message: message.user_identifier == self.team.myidentifier + message_filter = ( + lambda message: message.user_identifier == self.team.myidentifier + ) message = self.message_from_hash_or_index(msg_id, message_filter) if message is None: if msg_id: - print_error("Invalid id given, must be an existing id to one of your " + - "messages or a number greater than 0 and less than the number " + - "of your messages in the channel") + print_error( + "Invalid id given, must be an existing id to one of your " + + "messages or a number greater than 0 and less than the number " + + "of your messages in the channel" + ) else: print_error("You don't have any messages in this channel") return @@ -1667,15 +1955,19 @@ class SlackChannelCommon(object): s = SlackRequest(self.team, "chat.delete", post_data, channel=self) self.eventrouter.receive(s) else: - num_replace = 0 if 'g' in flags else 1 + num_replace = 0 if "g" in flags else 1 f = re.UNICODE - f |= re.IGNORECASE if 'i' in flags else 0 - f |= re.MULTILINE if 'm' in flags else 0 - f |= re.DOTALL if 's' in flags else 0 + f |= re.IGNORECASE if "i" in flags else 0 + f |= re.MULTILINE if "m" in flags else 0 + f |= re.DOTALL if "s" in flags else 0 old_message_text = message.message_json["text"] new_message_text = re.sub(old, new, old_message_text, num_replace, f) if new_message_text != old_message_text: - post_data = {"channel": self.identifier, "ts": message.ts, "text": new_message_text} + post_data = { + "channel": self.identifier, + "ts": message.ts, + "text": new_message_text, + } s = SlackRequest(self.team, "chat.update", post_data, channel=self) self.eventrouter.receive(s) else: @@ -1694,14 +1986,16 @@ class SlackChannelCommon(object): return message def message_from_index(self, index, message_filter=None, reverse=True): - for ts in (reversed(self.visible_messages) if reverse else self.visible_messages): + for ts in reversed(self.visible_messages) if reverse else self.visible_messages: message = self.messages[ts] if not message_filter or message_filter(message): index -= 1 if index == 0: return message - def message_from_hash_or_index(self, hash_or_index=None, message_filter=None, reverse=True): + def message_from_hash_or_index( + self, hash_or_index=None, message_filter=None, reverse=True + ): message = self.message_from_hash(hash_or_index, message_filter) if not message: if not hash_or_index: @@ -1723,13 +2017,19 @@ class SlackChannelCommon(object): if text: m.change_text(text) - if (type(m) == SlackMessage or m.subtype == "thread_broadcast" - or config.thread_messages_in_channel): + if ( + type(m) == SlackMessage + or m.subtype == "thread_broadcast" + or config.thread_messages_in_channel + ): new_text = self.render(m, force=True) modify_buffer_line(self.channel_buffer, ts, new_text) if type(m) == SlackThreadMessage or m.thread_channel is not None: - thread_channel = (m.parent_message.thread_channel - if isinstance(m, SlackThreadMessage) else m.thread_channel) + thread_channel = ( + m.parent_message.thread_channel + if isinstance(m, SlackThreadMessage) + else m.thread_channel + ) if thread_channel and thread_channel.active: new_text = thread_channel.render(m, force=True) modify_buffer_line(thread_channel.channel_buffer, ts, new_text) @@ -1769,7 +2069,7 @@ class SlackChannel(SlackChannelCommon): for key, value in kwargs.items(): setattr(self, key, value) self.eventrouter = eventrouter - self.team = kwargs.get('team') + self.team = kwargs.get("team") self.identifier = kwargs["id"] self.type = channel_type self.set_name(kwargs["name"]) @@ -1787,14 +2087,18 @@ class SlackChannel(SlackChannelCommon): self.new_messages = False self.typing = {} # short name relates to the localvar we change for typing indication - self.set_members(kwargs.get('members', [])) + self.set_members(kwargs.get("members", [])) self.unread_count_display = 0 self.last_line_from = None self.buffer_name_needs_update = False self.last_refresh_typing = False 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"): + 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 @@ -1823,7 +2127,9 @@ class SlackChannel(SlackChannelCommon): self.buffer_rename_in_progress = True if typing is None: typing = self.is_someone_typing() - present = self.team.is_user_present(self.user) if self.type == "im" else None + present = ( + self.team.is_user_present(self.user) if self.type == "im" else None + ) name = self.formatted_name("long_default", typing, present) short_name = self.formatted_name("sidebar", typing, present) @@ -1878,7 +2184,12 @@ class SlackChannel(SlackChannelCommon): if self.label_short_drop_prefix: if show_typing: name = prepend + name[1:] - elif self.type == "im" and present and config.show_buflist_presence and name[0] == " ": + elif ( + self.type == "im" + and present + and config.show_buflist_presence + and name[0] == " " + ): name = prepend + name[1:] else: name = prepend + name @@ -1903,9 +2214,9 @@ class SlackChannel(SlackChannelCommon): return prepend + name def render_topic(self, fallback_to_purpose=False): - topic = self.topic['value'] + topic = self.topic["value"] if not topic and fallback_to_purpose: - topic = self.slack_purpose['value'] + topic = self.slack_purpose["value"] return unhtmlescape(unfurl_refs(topic)) def set_topic(self, value=None): @@ -1923,7 +2234,9 @@ class SlackChannel(SlackChannelCommon): if update_remote: join_method = self.team.slack_api_translator[self.type].get("join") if join_method: - s = SlackRequest(self.team, join_method, {"channel": self.identifier}, channel=self) + s = SlackRequest( + self.team, join_method, {"channel": self.identifier}, channel=self + ) self.eventrouter.receive(s) self.create_buffer() self.active = True @@ -1947,9 +2260,12 @@ class SlackChannel(SlackChannelCommon): self.team = team def highlights(self): - nick_highlights = {'@' + self.team.nick, self.team.myidentifier} - subteam_highlights = {subteam.handle for subteam in self.team.subteams.values() - if subteam.is_member} + nick_highlights = {"@" + self.team.nick, self.team.myidentifier} + subteam_highlights = { + subteam.handle + for subteam in self.team.subteams.values() + if subteam.is_member + } highlights = nick_highlights | subteam_highlights | self.team.highlight_words if self.muted and config.muted_channels_activity == "personal_highlights": return highlights @@ -1967,10 +2283,15 @@ class SlackChannel(SlackChannelCommon): w.buffer_set(self.channel_buffer, "notify", notify_level) else: buffer_full_name = w.buffer_get_string(self.channel_buffer, "full_name") - w.command(self.channel_buffer, "/mute /unset weechat.notify.{}".format(buffer_full_name)) + w.command( + self.channel_buffer, + "/mute /unset weechat.notify.{}".format(buffer_full_name), + ) if self.muted and config.muted_channels_activity == "none": - w.buffer_set(self.channel_buffer, "highlight_tags_restrict", "highlight_force") + w.buffer_set( + self.channel_buffer, "highlight_tags_restrict", "highlight_force" + ) else: w.buffer_set(self.channel_buffer, "highlight_tags_restrict", "") @@ -1983,15 +2304,29 @@ class SlackChannel(SlackChannelCommon): """ if not self.channel_buffer: self.active = True - self.channel_buffer = w.buffer_new(self.formatted_name(style="long_default"), "buffer_input_callback", "EVENTROUTER", "", "") - self.eventrouter.weechat_controller.register_buffer(self.channel_buffer, self) + self.channel_buffer = w.buffer_new( + self.formatted_name(style="long_default"), + "buffer_input_callback", + "EVENTROUTER", + "", + "", + ) + self.eventrouter.weechat_controller.register_buffer( + self.channel_buffer, self + ) w.buffer_set(self.channel_buffer, "input_multiline", "1") - w.buffer_set(self.channel_buffer, "localvar_set_type", get_localvar_type(self.type)) + w.buffer_set( + self.channel_buffer, "localvar_set_type", get_localvar_type(self.type) + ) w.buffer_set(self.channel_buffer, "localvar_set_slack_type", self.type) - w.buffer_set(self.channel_buffer, "localvar_set_channel", self.formatted_name()) + w.buffer_set( + self.channel_buffer, "localvar_set_channel", self.formatted_name() + ) w.buffer_set(self.channel_buffer, "localvar_set_nick", self.team.nick) self.buffer_rename_in_progress = True - w.buffer_set(self.channel_buffer, "short_name", self.formatted_name(style="sidebar")) + w.buffer_set( + self.channel_buffer, "short_name", self.formatted_name(style="sidebar") + ) self.buffer_rename_in_progress = False self.set_highlights() self.set_topic() @@ -2001,24 +2336,45 @@ class SlackChannel(SlackChannelCommon): info_method = self.team.slack_api_translator[self.type].get("info") if info_method: - s = SlackRequest(self.team, info_method, {"channel": self.identifier}, channel=self) + s = SlackRequest( + self.team, info_method, {"channel": self.identifier}, channel=self + ) self.eventrouter.receive(s) if self.type == "im": join_method = self.team.slack_api_translator[self.type].get("join") if join_method: - s = SlackRequest(self.team, join_method, {"users": self.user, "return_im": True}, channel=self) + s = SlackRequest( + self.team, + join_method, + {"users": self.user, "return_im": True}, + channel=self, + ) self.eventrouter.receive(s) def destroy_buffer(self, update_remote): super(SlackChannel, self).destroy_buffer(update_remote) self.messages = OrderedDict() if update_remote and not self.eventrouter.shutting_down: - s = SlackRequest(self.team, self.team.slack_api_translator[self.type]["leave"], - {"channel": self.identifier}, channel=self) + s = SlackRequest( + self.team, + self.team.slack_api_translator[self.type]["leave"], + {"channel": self.identifier}, + channel=self, + ) self.eventrouter.receive(s) - def buffer_prnt(self, nick, text, timestamp, tagset, tag_nick=None, history_message=False, no_log=False, extra_tags=None): + def buffer_prnt( + self, + nick, + text, + timestamp, + tagset, + tag_nick=None, + history_message=False, + no_log=False, + extra_tags=None, + ): data = "{}\t{}".format(format_nick(nick, self.last_line_from), text) self.last_line_from = nick ts = SlackTS(timestamp) @@ -2033,10 +2389,21 @@ class SlackChannel(SlackChannelCommon): no_log = no_log or history_message and backlog self_msg = tag_nick == self.team.nick - tags = tag(ts, tagset, user=tag_nick, self_msg=self_msg, backlog=backlog, no_log=no_log, extra_tags=extra_tags) + tags = tag( + ts, + tagset, + user=tag_nick, + self_msg=self_msg, + backlog=backlog, + no_log=no_log, + extra_tags=extra_tags, + ) - if (config.unhide_buffers_with_activity - and not self.is_visible() and not self.muted): + if ( + config.unhide_buffers_with_activity + and not self.is_visible() + and not self.muted + ): w.buffer_set(self.channel_buffer, "hidden", "0") if no_log: @@ -2058,9 +2425,12 @@ class SlackChannel(SlackChannelCommon): self.messages[message_to_store.ts] = message_to_store self.messages = OrderedDict(sorted(self.messages.items())) - max_history = w.config_integer(w.config_get("weechat.history.max_buffer_lines_number")) - messages_to_check = islice(self.messages.items(), - max(0, len(self.messages) - max_history)) + max_history = w.config_integer( + w.config_get("weechat.history.max_buffer_lines_number") + ) + messages_to_check = islice( + self.messages.items(), max(0, len(self.messages) - max_history) + ) messages_to_delete = [] for (ts, message) in messages_to_check: if ts == message_to_store.ts: @@ -2070,8 +2440,11 @@ class SlackChannel(SlackChannelCommon): if thread_channel is None or not thread_channel.active: messages_to_delete.append(ts) elif message.number_of_replies(): - if ((message.thread_channel is None or not message.thread_channel.active) and - not any(submessage in self.messages for submessage in message.submessages)): + if ( + message.thread_channel is None or not message.thread_channel.active + ) and not any( + submessage in self.messages for submessage in message.submessages + ): messages_to_delete.append(ts) else: messages_to_delete.append(ts) @@ -2097,8 +2470,13 @@ class SlackChannel(SlackChannelCommon): if self.got_history and self.messages and not full: post_data["oldest"] = next(reversed(self.messages)) - s = SlackRequest(self.team, self.team.slack_api_translator[self.type]["history"], - post_data, channel=self, metadata={"slow_queue": slow_queue, "no_log": no_log}) + s = SlackRequest( + self.team, + self.team.slack_api_translator[self.type]["history"], + post_data, + channel=self, + metadata={"slow_queue": slow_queue, "no_log": no_log}, + ) self.eventrouter.receive(s, slow_queue) self.got_history = True self.history_needs_update = False @@ -2114,11 +2492,18 @@ class SlackChannel(SlackChannelCommon): thread_channel.print_getting_history() self.pending_history_requests.add(thread_ts) - post_data = {"channel": self.identifier, "ts": thread_ts, - "limit": config.history_fetch_count} - s = SlackRequest(self.team, "conversations.replies", - post_data, channel=self, - metadata={"thread_ts": thread_ts, "no_log": no_log}) + post_data = { + "channel": self.identifier, + "ts": thread_ts, + "limit": config.history_fetch_count, + } + s = SlackRequest( + self.team, + "conversations.replies", + post_data, + channel=self, + metadata={"thread_ts": thread_ts, "no_log": no_log}, + ) self.eventrouter.receive(s, slow_queue) # Typing related @@ -2172,18 +2557,38 @@ class SlackChannel(SlackChannelCommon): 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 - here = w.nicklist_search_group(self.channel_buffer, '', NICK_GROUP_HERE) + 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) + here = w.nicklist_add_group( + self.channel_buffer, + "", + NICK_GROUP_HERE, + "weechat.color.nicklist_group", + 1, + ) + afk = w.nicklist_search_group(self.channel_buffer, "", NICK_GROUP_AWAY) if not afk: - afk = w.nicklist_add_group(self.channel_buffer, '', NICK_GROUP_AWAY, "weechat.color.nicklist_group", 1) + afk = w.nicklist_add_group( + self.channel_buffer, + "", + NICK_GROUP_AWAY, + "weechat.color.nicklist_group", + 1, + ) # Add External nicklist group only for shared channels - if self.type == 'shared': - external = w.nicklist_search_group(self.channel_buffer, '', NICK_GROUP_EXTERNAL) + if self.type == "shared": + external = w.nicklist_search_group( + self.channel_buffer, "", NICK_GROUP_EXTERNAL + ) if not external: - external = w.nicklist_add_group(self.channel_buffer, '', NICK_GROUP_EXTERNAL, 'weechat.color.nicklist_group', 2) + external = w.nicklist_add_group( + self.channel_buffer, + "", + NICK_GROUP_EXTERNAL, + "weechat.color.nicklist_group", + 2, + ) if user and len(self.members) < 1000: user = self.team.users.get(user) @@ -2200,7 +2605,15 @@ class SlackChannel(SlackChannelCommon): elif self.team.is_user_present(user.identifier): nick_group = here if user.identifier in self.members: - w.nicklist_add_nick(self.channel_buffer, nick_group, user.name, user.color_name, "", "", 1) + w.nicklist_add_nick( + self.channel_buffer, + nick_group, + user.name, + user.color_name, + "", + "", + 1, + ) # if we didn't get a user, build a complete list. this is expensive. else: @@ -2215,21 +2628,36 @@ class SlackChannel(SlackChannelCommon): nick_group = external elif self.team.is_user_present(user.identifier): nick_group = here - w.nicklist_add_nick(self.channel_buffer, nick_group, user.name, user.color_name, "", "", 1) + w.nicklist_add_nick( + self.channel_buffer, + nick_group, + user.name, + user.color_name, + "", + "", + 1, + ) except: - dbg("DEBUG: {} {} {}".format(self.identifier, self.name, format_exc_only())) + dbg( + "DEBUG: {} {} {}".format( + self.identifier, self.name, format_exc_only() + ) + ) else: w.nicklist_remove_all(self.channel_buffer) for fn in ["1| too", "2| many", "3| users", "4| to", "5| show"]: - w.nicklist_add_group(self.channel_buffer, '', fn, w.color('white'), 1) + w.nicklist_add_group( + self.channel_buffer, "", fn, w.color("white"), 1 + ) def render(self, message, force=False): text = message.render(force) if isinstance(message, SlackThreadMessage): thread_hash = self.hashed_messages[message.thread_ts] hash_str = colorize_string( - get_thread_color(str(thread_hash)), '[{}]'.format(thread_hash)) - return '{} {}'.format(hash_str, text) + get_thread_color(str(thread_hash)), "[{}]".format(thread_hash) + ) + return "{} {}".format(hash_str, text) return text @@ -2254,8 +2682,11 @@ class SlackChannelVisibleMessages(MappingReversible): return False message = self.get(ts) - if (type(message) == SlackThreadMessage and message.subtype != "thread_broadcast" and - not config.thread_messages_in_channel): + if ( + type(message) == SlackThreadMessage + and message.subtype != "thread_broadcast" + and not config.thread_messages_in_channel + ): return False return True @@ -2335,13 +2766,13 @@ class SlackDMChannel(SlackChannel): def set_related_server(self, team): super(SlackDMChannel, self).set_related_server(team) if self.user not in self.team.users: - s = SlackRequest(self.team, 'users.info', {'user': self.user}, channel=self) + s = SlackRequest(self.team, "users.info", {"user": self.user}, channel=self) self.eventrouter.receive(s) def create_buffer(self): if not self.channel_buffer: super(SlackDMChannel, self).create_buffer() - w.buffer_set(self.channel_buffer, "localvar_set_type", 'private') + w.buffer_set(self.channel_buffer, "localvar_set_type", "private") def update_color(self): if config.colorize_private_chats: @@ -2354,12 +2785,19 @@ class SlackDMChannel(SlackChannel): self.get_history() info_method = self.team.slack_api_translator[self.type].get("info") if info_method: - s = SlackRequest(self.team, info_method, {"name": self.identifier}, channel=self) + s = SlackRequest( + self.team, info_method, {"name": self.identifier}, channel=self + ) self.eventrouter.receive(s) if update_remote: join_method = self.team.slack_api_translator[self.type].get("join") if join_method: - s = SlackRequest(self.team, join_method, {"users": self.user, "return_im": True}, channel=self) + s = SlackRequest( + self.team, + join_method, + {"users": self.user, "return_im": True}, + channel=self, + ) self.eventrouter.receive(s) @@ -2385,7 +2823,12 @@ class SlackPrivateChannel(SlackGroupChannel): def get_history(self, slow_queue=False, full=False, no_log=False): # Fetch members since they aren't included in rtm.start - s = SlackRequest(self.team, 'conversations.members', {'channel': self.identifier}, channel=self) + s = SlackRequest( + self.team, + "conversations.members", + {"channel": self.identifier}, + channel=self, + ) self.eventrouter.receive(s) super(SlackPrivateChannel, self).get_history(slow_queue, full, no_log) @@ -2397,11 +2840,13 @@ class SlackMPDMChannel(SlackChannel): """ def __init__(self, eventrouter, team_users, myidentifier, **kwargs): - kwargs["name"] = ','.join(sorted( - getattr(team_users.get(user_id), 'name', user_id) + kwargs["name"] = ",".join( + sorted( + getattr(team_users.get(user_id), "name", user_id) for user_id in kwargs["members"] if user_id != myidentifier - )) + ) + ) super(SlackMPDMChannel, self).__init__(eventrouter, "mpim", **kwargs) def open(self, update_remote=True): @@ -2410,12 +2855,19 @@ class SlackMPDMChannel(SlackChannel): self.get_history() info_method = self.team.slack_api_translator[self.type].get("info") if info_method: - s = SlackRequest(self.team, info_method, {"channel": self.identifier}, channel=self) + s = SlackRequest( + self.team, info_method, {"channel": self.identifier}, channel=self + ) self.eventrouter.receive(s) if update_remote: join_method = self.team.slack_api_translator[self.type].get("join") if join_method: - s = SlackRequest(self.team, join_method, {'users': ','.join(self.members)}, channel=self) + s = SlackRequest( + self.team, + join_method, + {"users": ",".join(self.members)}, + channel=self, + ) self.eventrouter.receive(s) @@ -2425,7 +2877,12 @@ class SlackSharedChannel(SlackChannel): def get_history(self, slow_queue=False, full=False, no_log=False): # Fetch members since they aren't included in rtm.start - s = SlackRequest(self.team, 'conversations.members', {'channel': self.identifier, 'limit': 1000}, channel=self) + s = SlackRequest( + self.team, + "conversations.members", + {"channel": self.identifier, "limit": 1000}, + channel=self, + ) self.eventrouter.receive(s) super(SlackSharedChannel, self).get_history(slow_queue, full, no_log) @@ -2520,9 +2977,21 @@ class SlackThreadChannel(SlackChannelCommon): return args = {"thread_ts": self.thread_ts} args.update(post_data) - super(SlackThreadChannel, self).mark_read(ts=ts, update_remote=update_remote, force=force, post_data=args) + super(SlackThreadChannel, self).mark_read( + ts=ts, update_remote=update_remote, force=force, post_data=args + ) - def buffer_prnt(self, nick, text, timestamp, tagset, tag_nick=None, history_message=False, no_log=False, extra_tags=None): + def buffer_prnt( + self, + nick, + text, + timestamp, + tagset, + tag_nick=None, + history_message=False, + no_log=False, + extra_tags=None, + ): data = "{}\t{}".format(format_nick(nick, self.last_line_from), text) self.last_line_from = nick ts = SlackTS(timestamp) @@ -2534,7 +3003,15 @@ class SlackThreadChannel(SlackChannelCommon): no_log = no_log or history_message and backlog self_msg = tag_nick == self.team.nick - tags = tag(ts, tagset, user=tag_nick, self_msg=self_msg, backlog=backlog, no_log=no_log, extra_tags=extra_tags) + tags = tag( + ts, + tagset, + user=tag_nick, + self_msg=self_msg, + backlog=backlog, + no_log=no_log, + extra_tags=extra_tags, + ) if no_log: w.buffer_set(self.channel_buffer, "print_hooks_enabled", "0") @@ -2552,12 +3029,16 @@ class SlackThreadChannel(SlackChannelCommon): if not any_msg_is_none: self.reprint_messages(history_message=True, no_log=no_log) - if (full or any_msg_is_none or - len(self.parent_message.submessages) < self.parent_message.number_of_replies()): + if ( + full + or any_msg_is_none + or len(self.parent_message.submessages) + < self.parent_message.number_of_replies() + ): self.parent_channel.get_thread_history(self.thread_ts, slow_queue, no_log) def send_message(self, message, subtype=None, request_dict_ext={}): - if subtype == 'me_message': + if subtype == "me_message": w.prnt("", "ERROR: /me is not supported in threads") return w.WEECHAT_RC_ERROR @@ -2578,8 +3059,12 @@ class SlackThreadChannel(SlackChannelCommon): def rename(self): if self.channel_buffer: self.buffer_rename_in_progress = True - w.buffer_set(self.channel_buffer, "name", self.formatted_name(style="long_default")) - w.buffer_set(self.channel_buffer, "short_name", self.formatted_name(style="sidebar")) + w.buffer_set( + self.channel_buffer, "name", self.formatted_name(style="long_default") + ) + w.buffer_set( + self.channel_buffer, "short_name", self.formatted_name(style="sidebar") + ) self.buffer_rename_in_progress = False def set_highlights(self, highlight_string=None): @@ -2593,22 +3078,43 @@ class SlackThreadChannel(SlackChannelCommon): Creates the weechat buffer where the thread magic happens. """ if not self.channel_buffer: - self.channel_buffer = w.buffer_new(self.formatted_name(style="long_default"), "buffer_input_callback", "EVENTROUTER", "", "") - self.eventrouter.weechat_controller.register_buffer(self.channel_buffer, self) + self.channel_buffer = w.buffer_new( + self.formatted_name(style="long_default"), + "buffer_input_callback", + "EVENTROUTER", + "", + "", + ) + self.eventrouter.weechat_controller.register_buffer( + self.channel_buffer, self + ) w.buffer_set(self.channel_buffer, "input_multiline", "1") - w.buffer_set(self.channel_buffer, "localvar_set_type", get_localvar_type(self.parent_channel.type)) + w.buffer_set( + self.channel_buffer, + "localvar_set_type", + get_localvar_type(self.parent_channel.type), + ) w.buffer_set(self.channel_buffer, "localvar_set_slack_type", self.type) w.buffer_set(self.channel_buffer, "localvar_set_nick", self.team.nick) - w.buffer_set(self.channel_buffer, "localvar_set_channel", self.formatted_name()) + w.buffer_set( + self.channel_buffer, "localvar_set_channel", self.formatted_name() + ) w.buffer_set(self.channel_buffer, "localvar_set_server", self.team.name) self.buffer_rename_in_progress = True - w.buffer_set(self.channel_buffer, "short_name", self.formatted_name(style="sidebar")) + w.buffer_set( + self.channel_buffer, "short_name", self.formatted_name(style="sidebar") + ) self.buffer_rename_in_progress = False self.set_highlights() - time_format = w.config_string(w.config_get("weechat.look.buffer_time_format")) + time_format = w.config_string( + w.config_get("weechat.look.buffer_time_format") + ) parent_time = time.localtime(SlackTS(self.thread_ts).major) - topic = '{} {} | {}'.format(time.strftime(time_format, parent_time), - self.parent_message.sender, self.render(self.parent_message)) + topic = "{} {} | {}".format( + time.strftime(time_format, parent_time), + self.parent_message.sender, + self.render(self.parent_message), + ) w.buffer_set(self.channel_buffer, "title", topic) def destroy_buffer(self, update_remote): @@ -2634,7 +3140,10 @@ class SlackThreadChannelMessages(MappingReversible): return self.thread_channel.parent_message def __getitem__(self, key): - if key != self._parent_message.ts and key not in self._parent_message.submessages: + if ( + key != self._parent_message.ts + and key not in self._parent_message.submessages + ): raise KeyError(key) return self.thread_channel.parent_channel.messages[key] @@ -2664,8 +3173,9 @@ class SlackUser(object): self.profile = {} self.presence = kwargs.get("presence", "unknown") self.deleted = kwargs.get("deleted", False) - self.is_external = (not kwargs.get("is_bot") and - kwargs.get("team_id") != originating_team_id) + self.is_external = ( + not kwargs.get("is_bot") and kwargs.get("team_id") != originating_team_id + ) for key, value in kwargs.items(): setattr(self, key, value) @@ -2701,6 +3211,7 @@ class SlackBot(SlackUser): Basically the same as a user, but split out to identify and for future needs """ + def __init__(self, originating_team_id, **kwargs): super(SlackBot, self).__init__(originating_team_id, is_bot=True, **kwargs) @@ -2713,14 +3224,15 @@ class SlackMessage(object): Note: these can't be tied to a SlackUser object because users can be deleted, so we have to store sender in each one. """ + def __init__(self, subtype, message_json, channel): self.team = channel.team self.channel = channel self.subtype = subtype - self.user_identifier = message_json.get('user') + self.user_identifier = message_json.get("user") self.message_json = message_json self.submessages = [] - self.ts = SlackTS(message_json['ts']) + self.ts = SlackTS(message_json["ts"]) self.subscribed = message_json.get("subscribed", False) self.last_read = SlackTS(message_json.get("last_read", 0)) self.last_notify = SlackTS(0) @@ -2738,7 +3250,9 @@ class SlackMessage(object): def open_thread(self, switch=False): if not self.thread_channel or not self.thread_channel.active: - self.channel.thread_channels[self.ts] = SlackThreadChannel(EVENTROUTER, self.channel, self.ts) + self.channel.thread_channels[self.ts] = SlackThreadChannel( + EVENTROUTER, self.channel, self.ts + ) self.thread_channel.open() if switch: w.buffer_set(self.thread_channel.channel_buffer, "display", "1") @@ -2762,33 +3276,47 @@ class SlackMessage(object): else: text = self.message_json.get("text", "") - if self.message_json.get('mrkdwn', True): + if self.message_json.get("mrkdwn", True): text = render_formatting(text) - if (self.message_json.get('subtype') in ('channel_join', 'group_join') and - self.message_json.get('inviter')): - inviter_id = self.message_json.get('inviter') + if ( + self.message_json.get("subtype") + in ( + "channel_join", + "group_join", + ) + and self.message_json.get("inviter") + ): + inviter_id = self.message_json.get("inviter") text += " by invitation from <@{}>".format(inviter_id) text = unfurl_refs(text) - if (self.subtype == 'me_message' and - not self.message_json['text'].startswith(self.sender)): + if self.subtype == "me_message" and not self.message_json["text"].startswith( + self.sender + ): text = "{} {}".format(self.sender, text) if "edited" in self.message_json: - text += " " + colorize_string(config.color_edited_suffix, '(edited)') + text += " " + colorize_string(config.color_edited_suffix, "(edited)") text += unfurl_refs(unwrap_attachments(self.message_json, text)) text += unfurl_refs(unwrap_files(self.message_json, text)) text = unhtmlescape(text.lstrip().replace("\t", " ")) text += create_reactions_string( - self.message_json.get("reactions", ""), self.team.myidentifier) + self.message_json.get("reactions", ""), self.team.myidentifier + ) if self.number_of_replies(): - text += " " + colorize_string(get_thread_color(self.hash), "[ Thread: {} Replies: {}{} ]".format( - self.hash, self.number_of_replies(), " Subscribed" if self.subscribed else "")) + text += " " + colorize_string( + get_thread_color(self.hash), + "[ Thread: {} Replies: {}{} ]".format( + self.hash, + self.number_of_replies(), + " Subscribed" if self.subscribed else "", + ), + ) text = replace_string_with_emoji(text) @@ -2806,14 +3334,16 @@ class SlackMessage(object): if user.is_external: name += config.external_user_suffix return name - elif 'user_profile' in self.message_json: - nick = nick_from_profile(self.message_json['user_profile'], self.user_identifier) + elif "user_profile" in self.message_json: + nick = nick_from_profile( + self.message_json["user_profile"], self.user_identifier + ) color_name = get_nick_color(nick) name = nick if plain else colorize_string(color_name, nick) - if self.message_json.get('user_team') != self.message_json.get('team'): + if self.message_json.get("user_team") != self.message_json.get("team"): name += config.external_user_suffix return name - elif 'username' in self.message_json: + elif "username" in self.message_json: username = self.message_json["username"] if plain: return username @@ -2821,13 +3351,13 @@ class SlackMessage(object): return "{} :]".format(username) else: return "-{}-".format(username) - elif 'service_name' in self.message_json: + elif "service_name" in self.message_json: service_name = self.message_json["service_name"] if plain: return service_name else: return "-{}-".format(service_name) - elif self.message_json.get('bot_id') in self.team.bots: + elif self.message_json.get("bot_id") in self.team.bots: bot = self.team.bots[self.message_json["bot_id"]] name = bot.formatted_name(enable_color=not plain) if plain: @@ -2858,7 +3388,9 @@ class SlackMessage(object): else: if "reactions" not in self.message_json: self.message_json["reactions"] = [] - self.message_json["reactions"].append({"name": reaction_name, "users": [user]}) + self.message_json["reactions"].append( + {"name": reaction_name, "users": [user]} + ) def remove_reaction(self, reaction_name, user): reaction = self.get_reaction(reaction_name) @@ -2866,8 +3398,10 @@ class SlackMessage(object): reaction["users"].remove(user) def has_mention(self): - return w.string_has_highlight(unfurl_refs(self.message_json.get('text')), - ",".join(self.channel.highlights())) + return w.string_has_highlight( + unfurl_refs(self.message_json.get("text")), + ",".join(self.channel.highlights()), + ) def number_of_replies(self): return max(len(self.submessages), self.message_json.get("reply_count", 0)) @@ -2878,8 +3412,12 @@ class SlackMessage(object): return message = self.channel.messages.get(self.submessages[-1]) - if (self.thread_channel and self.thread_channel.active or - message.ts <= self.last_read or message.ts <= self.last_notify): + if ( + self.thread_channel + and self.thread_channel.active + or message.ts <= self.last_read + or message.ts <= self.last_notify + ): return if message.has_mention(): @@ -2894,17 +3432,26 @@ class SlackMessage(object): if config.auto_open_threads: self.open_thread() - if message.user_identifier != self.team.myidentifier and (config.notify_subscribed_threads == True or - config.notify_subscribed_threads == "auto" and not config.auto_open_threads and - not config.thread_messages_in_channel): - message = template.format(hash=self.hash, channel=self.channel.formatted_name()) + if message.user_identifier != self.team.myidentifier and ( + config.notify_subscribed_threads == True + or config.notify_subscribed_threads == "auto" + and not config.auto_open_threads + and not config.thread_messages_in_channel + ): + message = template.format( + hash=self.hash, channel=self.channel.formatted_name() + ) self.team.buffer_prnt(message, message=True) -class SlackThreadMessage(SlackMessage): +class SlackThreadMessage(SlackMessage): def __init__(self, parent_channel, thread_ts, message_json, *args): - subtype = message_json.get('subtype', - 'thread_broadcast' if message_json.get("reply_broadcast") else 'thread_message') + subtype = message_json.get( + "subtype", + "thread_broadcast" + if message_json.get("reply_broadcast") + else "thread_message", + ) super(SlackThreadMessage, self).__init__(subtype, message_json, *args) self.parent_channel = parent_channel self.thread_ts = thread_ts @@ -2916,20 +3463,19 @@ class SlackThreadMessage(SlackMessage): class Hdata(object): def __init__(self, w): - self.buffer = w.hdata_get('buffer') - self.line = w.hdata_get('line') - self.line_data = w.hdata_get('line_data') - self.lines = w.hdata_get('lines') + self.buffer = w.hdata_get("buffer") + self.line = w.hdata_get("line") + self.line_data = w.hdata_get("line_data") + self.lines = w.hdata_get("lines") class SlackTS(object): - def __init__(self, ts=None): if isinstance(ts, int): self.major = ts self.minor = 0 elif ts is not None: - self.major, self.minor = [int(x) for x in ts.split('.', 1)] + self.major, self.minor = [int(x) for x in ts.split(".", 1)] else: self.major = int(time.time()) self.minor = 0 @@ -2989,6 +3535,7 @@ class SlackTS(object): def minorstr(self): return str(self.minor) + ###### New handlers @@ -2999,11 +3546,18 @@ def handle_rtmstart(login_data, eventrouter, team, channel, metadata): metadata = login_data["wee_slack_request_metadata"] if not login_data["ok"]: - w.prnt("", "ERROR: Failed connecting to Slack with token {}: {}" - .format(token_for_print(metadata.token), login_data["error"])) + w.prnt( + "", + "ERROR: Failed connecting to Slack with token {}: {}".format( + token_for_print(metadata.token), login_data["error"] + ), + ) if not re.match(r"^xo\w\w(-\d+){3}-[0-9a-f]+$", metadata.token): - w.prnt("", "ERROR: Token does not look like a valid Slack token. " - "Ensure it is a valid token and not just a OAuth code.") + w.prnt( + "", + "ERROR: Token does not look like a valid Slack token. " + "Ensure it is a valid token and not just a OAuth code.", + ) return @@ -3015,22 +3569,25 @@ def handle_rtmstart(login_data, eventrouter, team, channel, metadata): self_nick = nick_from_profile(self_profile, login_data["self"]["name"]) # Let's reuse a team if we have it already. - th = SlackTeam.generate_team_hash(login_data['team']['id'], login_data['team']['domain']) + th = SlackTeam.generate_team_hash( + login_data["team"]["id"], login_data["team"]["domain"] + ) if not eventrouter.teams.get(th): users = {} for item in login_data["users"]: - users[item["id"]] = SlackUser(login_data['team']['id'], **item) + users[item["id"]] = SlackUser(login_data["team"]["id"], **item) bots = {} for item in login_data["bots"]: - bots[item["id"]] = SlackBot(login_data['team']['id'], **item) + bots[item["id"]] = SlackBot(login_data["team"]["id"], **item) subteams = {} for item in login_data["subteams"]["all"]: - is_member = item['id'] in login_data["subteams"]["self"] - subteams[item['id']] = SlackSubteam( - login_data['team']['id'], is_member=is_member, **item) + is_member = item["id"] in login_data["subteams"]["self"] + subteams[item["id"]] = SlackSubteam( + login_data["team"]["id"], is_member=is_member, **item + ) channels = {} for item in login_data["channels"]: @@ -3046,7 +3603,9 @@ def handle_rtmstart(login_data, eventrouter, team, channel, metadata): for item in login_data["groups"]: if item["is_mpim"]: - channels[item["id"]] = SlackMPDMChannel(eventrouter, users, login_data["self"]["id"], **item) + channels[item["id"]] = SlackMPDMChannel( + eventrouter, users, login_data["self"]["id"], **item + ) else: channels[item["id"]] = SlackGroupChannel(eventrouter, **item) @@ -3054,7 +3613,7 @@ def handle_rtmstart(login_data, eventrouter, team, channel, metadata): eventrouter, metadata.token, th, - login_data['url'], + login_data["url"], login_data["team"], subteams, self_nick, @@ -3072,25 +3631,35 @@ def handle_rtmstart(login_data, eventrouter, team, channel, metadata): t = eventrouter.teams.get(th) if t.myidentifier != login_data["self"]["id"]: print_error( - 'The Slack team {} has tokens for two different users, this is not supported. The ' - 'token {} is for user {}, and the token {} is for user {}. Please remove one of ' - 'them.'.format(t.team_info["name"], token_for_print(t.token), t.nick, - token_for_print(metadata.token), self_nick) + "The Slack team {} has tokens for two different users, this is not supported. The " + "token {} is for user {}, and the token {} is for user {}. Please remove one of " + "them.".format( + t.team_info["name"], + token_for_print(t.token), + t.nick, + token_for_print(metadata.token), + self_nick, + ) ) return - elif not metadata.metadata.get('reconnect'): + elif not metadata.metadata.get("reconnect"): print_error( - 'Ignoring duplicate Slack tokens for the same team ({}) and user ({}). The two ' - 'tokens are {} and {}.'.format(t.team_info["name"], t.nick, - token_for_print(t.token), token_for_print(metadata.token)), - warning=True + "Ignoring duplicate Slack tokens for the same team ({}) and user ({}). The two " + "tokens are {} and {}.".format( + t.team_info["name"], + t.nick, + token_for_print(t.token), + token_for_print(metadata.token), + ), + warning=True, ) return else: - t.set_reconnect_url(login_data['url']) + t.set_reconnect_url(login_data["url"]) t.connecting_rtm = False - t.connect(metadata.metadata['reconnect']) + t.connect(metadata.metadata["reconnect"]) + def handle_rtmconnect(login_data, eventrouter, team, channel, metadata): metadata = login_data["wee_slack_request_metadata"] @@ -3098,12 +3667,16 @@ def handle_rtmconnect(login_data, eventrouter, team, channel, metadata): team.connecting_rtm = False if not login_data["ok"]: - w.prnt("", "ERROR: Failed reconnecting to Slack with token {}: {}" - .format(token_for_print(metadata.token), login_data["error"])) + w.prnt( + "", + "ERROR: Failed reconnecting to Slack with token {}: {}".format( + token_for_print(metadata.token), login_data["error"] + ), + ) return - team.set_reconnect_url(login_data['url']) - team.connect(metadata.metadata['reconnect']) + team.set_reconnect_url(login_data["url"]) + team.connect(metadata.metadata["reconnect"]) def handle_emojilist(emoji_json, eventrouter, team, channel, metadata): @@ -3112,39 +3685,68 @@ def handle_emojilist(emoji_json, eventrouter, team, channel, metadata): def handle_channelsinfo(channel_json, eventrouter, team, channel, metadata): - channel.set_unread_count_display(channel_json['channel'].get('unread_count_display', 0)) - channel.set_members(channel_json['channel']['members']) + channel.set_unread_count_display( + channel_json["channel"].get("unread_count_display", 0) + ) + channel.set_members(channel_json["channel"]["members"]) def handle_groupsinfo(group_json, eventrouter, team, channel, metadatas): - channel.set_unread_count_display(group_json['group'].get('unread_count_display', 0)) - channel.set_members(group_json['group']['members']) + channel.set_unread_count_display(group_json["group"].get("unread_count_display", 0)) + channel.set_members(group_json["group"]["members"]) -def handle_conversationsopen(conversation_json, eventrouter, team, channel, metadata, object_name='channel'): +def handle_conversationsopen( + conversation_json, eventrouter, team, channel, metadata, object_name="channel" +): # Set unread count if the channel isn't new if channel: - unread_count_display = conversation_json[object_name].get('unread_count_display', 0) + unread_count_display = conversation_json[object_name].get( + "unread_count_display", 0 + ) channel.set_unread_count_display(unread_count_display) -def handle_mpimopen(mpim_json, eventrouter, team, channel, metadata, object_name='group'): - handle_conversationsopen(mpim_json, eventrouter, team, channel, metadata, object_name) +def handle_mpimopen( + mpim_json, eventrouter, team, channel, metadata, object_name="group" +): + handle_conversationsopen( + mpim_json, eventrouter, team, channel, metadata, object_name + ) -def handle_history(message_json, eventrouter, team, channel, metadata, includes_threads=True): +def handle_history( + message_json, eventrouter, team, channel, metadata, includes_threads=True +): channel.got_history = True channel.history_needs_update = False for message in reversed(message_json["messages"]): - message = process_message(message, eventrouter, team, channel, metadata, history_message=True) - if (not includes_threads and message and message.number_of_replies() and - (config.thread_messages_in_channel or message.subscribed and - SlackTS(message.message_json.get("latest_reply", 0)) > message.last_read)): - channel.get_thread_history(message.ts, metadata["slow_queue"], metadata["no_log"]) + message = process_message( + message, eventrouter, team, channel, metadata, history_message=True + ) + if ( + not includes_threads + and message + and message.number_of_replies() + and ( + config.thread_messages_in_channel + or message.subscribed + and SlackTS(message.message_json.get("latest_reply", 0)) + > message.last_read + ) + ): + channel.get_thread_history( + message.ts, metadata["slow_queue"], metadata["no_log"] + ) channel.pending_history_requests.discard(channel.identifier) - if channel.visible_messages.first_ts_to_display.major == 0 and message_json["messages"]: - channel.visible_messages.first_ts_to_display = SlackTS(message_json["messages"][-1]["ts"]) + if ( + channel.visible_messages.first_ts_to_display.major == 0 + and message_json["messages"] + ): + channel.visible_messages.first_ts_to_display = SlackTS( + message_json["messages"][-1]["ts"] + ) channel.reprint_messages(history_message=True, no_log=metadata["no_log"]) for thread_channel in channel.thread_channels.values(): thread_channel.reprint_messages(history_message=True, no_log=metadata["no_log"]) @@ -3156,15 +3758,19 @@ handle_imhistory = handle_history handle_mpimhistory = handle_history -def handle_conversationshistory(message_json, eventrouter, team, channel, metadata, includes_threads=True): +def handle_conversationshistory( + message_json, eventrouter, team, channel, metadata, includes_threads=True +): handle_history(message_json, eventrouter, team, channel, metadata, False) def handle_conversationsreplies(message_json, eventrouter, team, channel, metadata): - for message in message_json['messages']: - process_message(message, eventrouter, team, channel, metadata, history_message=True) - channel.pending_history_requests.discard(metadata.get('thread_ts')) - thread_channel = channel.thread_channels.get(metadata.get('thread_ts')) + for message in message_json["messages"]: + process_message( + message, eventrouter, team, channel, metadata, history_message=True + ) + channel.pending_history_requests.discard(metadata.get("thread_ts")) + thread_channel = channel.thread_channels.get(metadata.get("thread_ts")) if thread_channel and thread_channel.active: thread_channel.got_history = True thread_channel.history_needs_update = False @@ -3174,111 +3780,135 @@ def handle_conversationsreplies(message_json, eventrouter, team, channel, metada def handle_conversationsmembers(members_json, eventrouter, team, channel, metadata): - if members_json['ok']: - channel.set_members(members_json['members']) - unknown_users = set(members_json['members']) - set(team.users.keys()) + if members_json["ok"]: + channel.set_members(members_json["members"]) + unknown_users = set(members_json["members"]) - set(team.users.keys()) for user in unknown_users: - s = SlackRequest(team, 'users.info', {'user': user}, channel=channel) + s = SlackRequest(team, "users.info", {"user": user}, channel=channel) eventrouter.receive(s) else: - w.prnt(team.channel_buffer, '{}Couldn\'t load members for channel {}. Error: {}' - .format(w.prefix('error'), channel.name, members_json['error'])) + w.prnt( + team.channel_buffer, + "{}Couldn't load members for channel {}. Error: {}".format( + w.prefix("error"), channel.name, members_json["error"] + ), + ) def handle_usersinfo(user_json, eventrouter, team, channel, metadata): - user_info = user_json['user'] - if not metadata.get('user'): + user_info = user_json["user"] + if not metadata.get("user"): user = SlackUser(team.identifier, **user_info) - team.users[user_info['id']] = user + team.users[user_info["id"]] = user - if channel.type == 'shared': - channel.update_nicklist(user_info['id']) - elif channel.type == 'im': + if channel.type == "shared": + channel.update_nicklist(user_info["id"]) + elif channel.type == "im": channel.set_name(user.name) channel.set_topic(create_user_status_string(user.profile)) def handle_usergroupsuserslist(users_json, eventrouter, team, channel, metadata): - header = 'Users in {}'.format(metadata['usergroup_handle']) - users = [team.users[key] for key in users_json['users']] + header = "Users in {}".format(metadata["usergroup_handle"]) + users = [team.users[key] for key in users_json["users"]] return print_users_info(team, header, users) def handle_usersprofileset(json, eventrouter, team, channel, metadata): - if not json['ok']: - w.prnt('', 'ERROR: Failed to set profile: {}'.format(json['error'])) + if not json["ok"]: + w.prnt("", "ERROR: Failed to set profile: {}".format(json["error"])) def handle_conversationscreate(json, eventrouter, team, channel, metadata): metadata = json["wee_slack_request_metadata"] - if not json['ok']: + if not json["ok"]: name = metadata.post_data["name"] - print_error("Couldn't create channel {}: {}".format(name, json['error'])) + print_error("Couldn't create channel {}: {}".format(name, json["error"])) def handle_conversationsinvite(json, eventrouter, team, channel, metadata): - nicks = ', '.join(metadata['nicks']) - if json['ok']: - w.prnt(team.channel_buffer, 'Invited {} to {}'.format(nicks, channel.name)) + nicks = ", ".join(metadata["nicks"]) + if json["ok"]: + w.prnt(team.channel_buffer, "Invited {} to {}".format(nicks, channel.name)) else: - w.prnt(team.channel_buffer, 'ERROR: Couldn\'t invite {} to {}. Error: {}' - .format(nicks, channel.name, json['error'])) + w.prnt( + team.channel_buffer, + "ERROR: Couldn't invite {} to {}. Error: {}".format( + nicks, channel.name, json["error"] + ), + ) def handle_chatcommand(json, eventrouter, team, channel, metadata): - command = '{} {}'.format(metadata['command'], metadata['command_args']).rstrip() - response = unfurl_refs(json['response']) if 'response' in json else '' - if json['ok']: - response_text = 'Response: {}'.format(response) if response else 'No response' - w.prnt(team.channel_buffer, 'Ran command "{}". {}' .format(command, response_text)) + command = "{} {}".format(metadata["command"], metadata["command_args"]).rstrip() + response = unfurl_refs(json["response"]) if "response" in json else "" + if json["ok"]: + response_text = "Response: {}".format(response) if response else "No response" + w.prnt( + team.channel_buffer, 'Ran command "{}". {}'.format(command, response_text) + ) else: - response_text = '. Response: {}'.format(response) if response else '' - w.prnt(team.channel_buffer, 'ERROR: Couldn\'t run command "{}". Error: {}{}' - .format(command, json['error'], response_text)) + response_text = ". Response: {}".format(response) if response else "" + w.prnt( + team.channel_buffer, + 'ERROR: Couldn\'t run command "{}". Error: {}{}'.format( + command, json["error"], response_text + ), + ) def handle_chatdelete(json, eventrouter, team, channel, metadata): - if not json['ok']: - print_error("Couldn't delete message: {}".format(json['error'])) + if not json["ok"]: + print_error("Couldn't delete message: {}".format(json["error"])) def handle_chatupdate(json, eventrouter, team, channel, metadata): - if not json['ok']: - print_error("Couldn't change message: {}".format(json['error'])) + if not json["ok"]: + print_error("Couldn't change message: {}".format(json["error"])) def handle_reactionsadd(json, eventrouter, team, channel, metadata): - if not json['ok']: - print_error("Couldn't add reaction {}: {}".format(metadata['reaction'], json['error'])) + if not json["ok"]: + print_error( + "Couldn't add reaction {}: {}".format(metadata["reaction"], json["error"]) + ) def handle_reactionsremove(json, eventrouter, team, channel, metadata): - if not json['ok']: - print_error("Couldn't remove reaction {}: {}".format(metadata['reaction'], json['error'])) + if not json["ok"]: + print_error( + "Couldn't remove reaction {}: {}".format( + metadata["reaction"], json["error"] + ) + ) def handle_subscriptionsthreadmark(json, eventrouter, team, channel, metadata): if not json["ok"]: - if json['error'] == 'not_allowed_token_type': - team.slack_api_translator['thread']['mark'] = None + if json["error"] == "not_allowed_token_type": + team.slack_api_translator["thread"]["mark"] = None else: - print_error("Couldn't set thread read status: {}".format(json['error'])) + print_error("Couldn't set thread read status: {}".format(json["error"])) def handle_subscriptionsthreadadd(json, eventrouter, team, channel, metadata): if not json["ok"]: - if json['error'] == 'not_allowed_token_type': - print_error("Can only subscribe to a thread when using a session token, see the readme: https://github.com/wee-slack/wee-slack#4-add-your-slack-api-tokens") + if json["error"] == "not_allowed_token_type": + print_error( + "Can only subscribe to a thread when using a session token, see the readme: https://github.com/wee-slack/wee-slack#4-add-your-slack-api-tokens" + ) else: - print_error("Couldn't add thread subscription: {}".format(json['error'])) + print_error("Couldn't add thread subscription: {}".format(json["error"])) def handle_subscriptionsthreadremove(json, eventrouter, team, channel, metadata): if not json["ok"]: - if json['error'] == 'not_allowed_token_type': - print_error("Can only unsubscribe from a thread when using a session token, see the readme: https://github.com/wee-slack/wee-slack#4-add-your-slack-api-tokens") + if json["error"] == "not_allowed_token_type": + print_error( + "Can only unsubscribe from a thread when using a session token, see the readme: https://github.com/wee-slack/wee-slack#4-add-your-slack-api-tokens" + ) else: - print_error("Couldn't remove thread subscription: {}".format(json['error'])) + print_error("Couldn't remove thread subscription: {}".format(json["error"])) ###### New/converted process_ and subprocess_ methods @@ -3287,7 +3917,7 @@ def process_hello(message_json, eventrouter, team, channel, metadata): def process_reconnect_url(message_json, eventrouter, team, channel, metadata): - team.set_reconnect_url(message_json['url']) + team.set_reconnect_url(message_json["url"]) def process_presence_change(message_json, eventrouter, team, channel, metadata): @@ -3308,23 +3938,23 @@ def process_manual_presence_change(message_json, eventrouter, team, channel, met def process_pref_change(message_json, eventrouter, team, channel, metadata): - if message_json['name'] == 'muted_channels': - team.set_muted_channels(message_json['value']) - elif message_json['name'] == 'highlight_words': - team.set_highlight_words(message_json['value']) + if message_json["name"] == "muted_channels": + team.set_muted_channels(message_json["value"]) + elif message_json["name"] == "highlight_words": + team.set_highlight_words(message_json["value"]) else: - dbg("Preference change not implemented: {}\n".format(message_json['name'])) + dbg("Preference change not implemented: {}\n".format(message_json["name"])) def process_user_change(message_json, eventrouter, team, channel, metadata): """ Currently only used to update status, but lots here we could do. """ - user = metadata['user'] - profile = message_json['user']['profile'] + user = metadata["user"] + profile = message_json["user"]["profile"] if user: - user.update_status(profile.get('status_emoji'), profile.get('status_text')) - dmchannel = team.find_channel_by_members({user.identifier}, channel_type='im') + user.update_status(profile.get("status_emoji"), profile.get("status_text")) + dmchannel = team.find_channel_by_members({user.identifier}, channel_type="im") if dmchannel: dmchannel.set_topic(create_user_status_string(profile)) @@ -3336,7 +3966,7 @@ def process_user_typing(message_json, eventrouter, team, channel, metadata): def process_team_join(message_json, eventrouter, team, channel, metadata): - user = message_json['user'] + user = message_json["user"] team.users[user["id"]] = SlackUser(team.identifier, **user) @@ -3344,17 +3974,27 @@ def process_pong(message_json, eventrouter, team, channel, metadata): team.last_pong_time = time.time() -def process_message(message_json, eventrouter, team, channel, metadata, history_message=False): - if not history_message and "ts" in message_json and SlackTS(message_json["ts"]) in channel.messages: +def process_message( + message_json, eventrouter, team, channel, metadata, history_message=False +): + if ( + not history_message + and "ts" in message_json + and SlackTS(message_json["ts"]) in channel.messages + ): return subtype = message_json.get("subtype") subtype_functions = get_functions_with_prefix("subprocess_") if "thread_ts" in message_json and "reply_count" not in message_json: - message = subprocess_thread_message(message_json, eventrouter, team, channel, history_message) + message = subprocess_thread_message( + message_json, eventrouter, team, channel, history_message + ) elif subtype in subtype_functions: - message = subtype_functions[subtype](message_json, eventrouter, team, channel, history_message) + message = subtype_functions[subtype]( + message_json, eventrouter, team, channel, history_message + ) else: message = SlackMessage(subtype or "normal", message_json, channel) channel.store_message(message) @@ -3379,8 +4019,12 @@ def download_files(message_json, team): try: os.makedirs(download_location) except: - w.prnt('', 'ERROR: Failed to create directory at files_download_location: {}' - .format(format_exc_only())) + w.prnt( + "", + "ERROR: Failed to create directory at files_download_location: {}".format( + format_exc_only() + ), + ) def fileout_iter(path): yield path @@ -3388,27 +4032,32 @@ def download_files(message_json, team): for i in count(start=1): yield main + "-{}".format(i) + ext - for f in message_json.get('files', []): - if f.get('mode') == 'tombstone': + for f in message_json.get("files", []): + if f.get("mode") == "tombstone": continue - filetype = '' if f['title'].endswith(f['filetype']) else '.' + f['filetype'] - filename = '{}_{}{}'.format(team.name, f['title'], filetype) + filetype = "" if f["title"].endswith(f["filetype"]) else "." + f["filetype"] + filename = "{}_{}{}".format(team.name, f["title"], filetype) for fileout in fileout_iter(os.path.join(download_location, filename)): if os.path.isfile(fileout): continue w.hook_process_hashtable( - "url:" + f['url_private'], + "url:" + f["url_private"], { - 'file_out': fileout, - 'httpheader': 'Authorization: Bearer ' + team.token + "file_out": fileout, + "httpheader": "Authorization: Bearer " + team.token, }, - config.slack_timeout, "", "") + config.slack_timeout, + "", + "", + ) break -def subprocess_thread_message(message_json, eventrouter, team, channel, history_message): - parent_ts = SlackTS(message_json['thread_ts']) +def subprocess_thread_message( + message_json, eventrouter, team, channel, history_message +): + parent_ts = SlackTS(message_json["thread_ts"]) message = SlackThreadMessage(channel, parent_ts, message_json, channel) parent_message = message.parent_message @@ -3442,7 +4091,7 @@ def subprocess_channel_join(message_json, eventrouter, team, channel, history_me def subprocess_channel_leave(message_json, eventrouter, team, channel, history_message): - message = SlackMessage("leave", message_json, channel) + message = SlackMessage("leave", message_json, channel) channel.store_message(message) channel.user_left(message_json["user"]) return message @@ -3460,17 +4109,23 @@ subprocess_group_leave = subprocess_channel_leave subprocess_group_topic = subprocess_channel_topic -def subprocess_message_replied(message_json, eventrouter, team, channel, history_message): +def subprocess_message_replied( + message_json, eventrouter, team, channel, history_message +): pass -def subprocess_message_changed(message_json, eventrouter, team, channel, history_message): +def subprocess_message_changed( + message_json, eventrouter, team, channel, history_message +): new_message = message_json.get("message") channel.change_message(new_message["ts"], message_json=new_message) -def subprocess_message_deleted(message_json, eventrouter, team, channel, history_message): - message = colorize_string(config.color_deleted, '(deleted)') +def subprocess_message_deleted( + message_json, eventrouter, team, channel, history_message +): + message = colorize_string(config.color_deleted, "(deleted)") channel.change_message(message_json["deleted_ts"], text=message) @@ -3479,12 +4134,22 @@ def process_reply(message_json, eventrouter, team, channel, metadata): original_message_json = team.ws_replies.pop(reply_to, None) if original_message_json: dbg("REPLY {}".format(message_json)) - channel = team.channels[original_message_json.get('channel')] + channel = team.channels[original_message_json.get("channel")] if message_json["ok"]: original_message_json.update(message_json) - process_message(original_message_json, eventrouter, team=team, channel=channel, metadata={}) + process_message( + original_message_json, + eventrouter, + team=team, + channel=channel, + metadata={}, + ) else: - print_error("Couldn't send message to channel {}: {}".format(channel.name, message_json["error"])) + print_error( + "Couldn't send message to channel {}: {}".format( + channel.name, message_json["error"] + ) + ) else: dbg("Unexpected reply {}".format(message_json)) @@ -3509,7 +4174,8 @@ def process_thread_marked(message_json, eventrouter, team, channel, metadata): channel = team.channels.get(subscription.get("channel")) if ts and thread_ts and channel: thread_channel = channel.thread_channels.get(SlackTS(thread_ts)) - if thread_channel: thread_channel.mark_read(ts=ts, force=True, update_remote=False) + if thread_channel: + thread_channel.mark_read(ts=ts, force=True, update_remote=False) else: dbg("tried to mark something weird {}".format(message_json)) @@ -3521,21 +4187,21 @@ def process_channel_joined(message_json, eventrouter, team, channel, metadata): def process_channel_created(message_json, eventrouter, team, channel, metadata): item = message_json["channel"] - item['is_member'] = False + item["is_member"] = False channel = SlackChannel(eventrouter, team=team, **item) team.channels[item["id"]] = channel - team.buffer_prnt('Channel created: {}'.format(channel.name)) + team.buffer_prnt("Channel created: {}".format(channel.name)) def process_channel_rename(message_json, eventrouter, team, channel, metadata): - channel.set_name(message_json['channel']['name']) + channel.set_name(message_json["channel"]["name"]) def process_im_created(message_json, eventrouter, team, channel, metadata): item = message_json["channel"] channel = SlackDMChannel(eventrouter, team=team, users=team.users, **item) team.channels[item["id"]] = channel - team.buffer_prnt('IM channel created: {}'.format(channel.name)) + team.buffer_prnt("IM channel created: {}".format(channel.name)) def process_im_open(message_json, eventrouter, team, channel, metadata): @@ -3545,15 +4211,21 @@ def process_im_open(message_json, eventrouter, team, channel, metadata): def process_im_close(message_json, eventrouter, team, channel, metadata): if channel.channel_buffer: - w.prnt(team.channel_buffer, - 'IM {} closed by another client or the server'.format(channel.name)) - eventrouter.weechat_controller.unregister_buffer(channel.channel_buffer, False, True) + w.prnt( + team.channel_buffer, + "IM {} closed by another client or the server".format(channel.name), + ) + eventrouter.weechat_controller.unregister_buffer( + channel.channel_buffer, False, True + ) def process_group_joined(message_json, eventrouter, team, channel, metadata): item = message_json["channel"] if item["name"].startswith("mpdm-"): - channel = SlackMPDMChannel(eventrouter, team.users, team.myidentifier, team=team, **item) + channel = SlackMPDMChannel( + eventrouter, team.users, team.myidentifier, team=team, **item + ) else: channel = SlackGroupChannel(eventrouter, team=team, **item) team.channels[item["id"]] = channel @@ -3563,7 +4235,7 @@ def process_group_joined(message_json, eventrouter, team, channel, metadata): def process_reaction_added(message_json, eventrouter, team, channel, metadata): channel = team.channels.get(message_json["item"].get("channel")) if message_json["item"].get("type") == "message": - ts = SlackTS(message_json['item']["ts"]) + ts = SlackTS(message_json["item"]["ts"]) message = channel.messages.get(ts) if message: @@ -3576,7 +4248,7 @@ def process_reaction_added(message_json, eventrouter, team, channel, metadata): def process_reaction_removed(message_json, eventrouter, team, channel, metadata): channel = team.channels.get(message_json["item"].get("channel")) if message_json["item"].get("type") == "message": - ts = SlackTS(message_json['item']["ts"]) + ts = SlackTS(message_json["item"]["ts"]) message = channel.messages.get(ts) if message: @@ -3587,25 +4259,33 @@ def process_reaction_removed(message_json, eventrouter, team, channel, metadata) def process_subteam_created(subteam_json, eventrouter, team, channel, metadata): - subteam_json_info = subteam_json['subteam'] - is_member = team.myidentifier in subteam_json_info.get('users', []) + subteam_json_info = subteam_json["subteam"] + is_member = team.myidentifier in subteam_json_info.get("users", []) subteam = SlackSubteam(team.identifier, is_member=is_member, **subteam_json_info) - team.subteams[subteam_json_info['id']] = subteam + team.subteams[subteam_json_info["id"]] = subteam def process_subteam_updated(subteam_json, eventrouter, team, channel, metadata): - current_subteam_info = team.subteams[subteam_json['subteam']['id']] - is_member = team.myidentifier in subteam_json['subteam'].get('users', []) - new_subteam_info = SlackSubteam(team.identifier, is_member=is_member, **subteam_json['subteam']) - team.subteams[subteam_json['subteam']['id']] = new_subteam_info + current_subteam_info = team.subteams[subteam_json["subteam"]["id"]] + is_member = team.myidentifier in subteam_json["subteam"].get("users", []) + new_subteam_info = SlackSubteam( + team.identifier, is_member=is_member, **subteam_json["subteam"] + ) + team.subteams[subteam_json["subteam"]["id"]] = new_subteam_info if current_subteam_info.is_member != new_subteam_info.is_member: for channel in team.channels.values(): channel.set_highlights() - if config.notify_usergroup_handle_updated and current_subteam_info.handle != new_subteam_info.handle: - message = 'User group {old_handle} has updated its handle to {new_handle} in team {team}.'.format( - old_handle=current_subteam_info.handle, new_handle=new_subteam_info.handle, team=team.name) + if ( + config.notify_usergroup_handle_updated + and current_subteam_info.handle != new_subteam_info.handle + ): + message = "User group {old_handle} has updated its handle to {new_handle} in team {team}.".format( + old_handle=current_subteam_info.handle, + new_handle=new_subteam_info.handle, + team=team.name, + ) team.buffer_prnt(message, message=True) @@ -3639,16 +4319,22 @@ def process_thread_unsubscribed(message_json, eventrouter, team, channel, metada ###### New module/global methods def render_formatting(text): - text = re.sub(r'(^| )\*([^*\n`]+)\*(?=[^\w]|$)', - r'\1{}*\2*{}'.format(w.color(config.render_bold_as), - w.color('-' + config.render_bold_as)), - text, - flags=re.UNICODE) - text = re.sub(r'(^| )_([^_\n`]+)_(?=[^\w]|$)', - r'\1{}_\2_{}'.format(w.color(config.render_italic_as), - w.color('-' + config.render_italic_as)), - text, - flags=re.UNICODE) + text = re.sub( + r"(^| )\*([^*\n`]+)\*(?=[^\w]|$)", + r"\1{}*\2*{}".format( + w.color(config.render_bold_as), w.color("-" + config.render_bold_as) + ), + text, + flags=re.UNICODE, + ) + text = re.sub( + r"(^| )_([^_\n`]+)_(?=[^\w]|$)", + r"\1{}_\2_{}".format( + w.color(config.render_italic_as), w.color("-" + config.render_italic_as) + ), + text, + flags=re.UNICODE, + ) return text @@ -3659,17 +4345,19 @@ def linkify_text(message, team, only_users=False, escape_characters=True): channels = team.get_channel_map() usergroups = team.generate_usergroup_map() if escape_characters: - message = (message + message = ( + message # Replace IRC formatting chars with Slack formatting chars. - .replace('\x02', '*') - .replace('\x1D', '_') - .replace('\x1F', config.map_underline_to) + .replace("\x02", "*") + .replace("\x1D", "_") + .replace("\x1F", config.map_underline_to) # Escape chars that have special meaning to Slack. Note that we do not # (and should not) perform full HTML entity-encoding here. # See https://api.slack.com/docs/message-formatting for details. - .replace('&', '&') - .replace('<', '<') - .replace('>', '>')) + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + ) def linkify_word(match): word = match.group(0) @@ -3686,7 +4374,7 @@ def linkify_text(message, team, only_users=False, escape_characters=True): return "<#{}|{}>".format(channels[word], name) return word - linkify_regex = r'(?:^|(?<=\s))([@#])([\w\(\)\'.-]+)' + linkify_regex = r"(?:^|(?<=\s))([@#])([\w\(\)\'.-]+)" return re.sub(linkify_regex, linkify_word, message, flags=re.UNICODE) @@ -3705,15 +4393,23 @@ def unfurl_blocks(blocks): if element["type"] == "button": elements.append(unfurl_block_element(element["text"])) else: - elements.append(colorize_string(config.color_deleted, - '<<Unsupported block action type "{}">>'.format(element["type"]))) + elements.append( + colorize_string( + config.color_deleted, + '<<Unsupported block action type "{}">>'.format( + element["type"] + ), + ) + ) block_text.append(" | ".join(elements)) elif block["type"] == "call": block_text.append("Join via " + block["call"]["v1"]["join_url"]) elif block["type"] == "divider": block_text.append("---") elif block["type"] == "context": - block_text.append(" | ".join(unfurl_block_element(el) for el in block["elements"])) + block_text.append( + " | ".join(unfurl_block_element(el) for el in block["elements"]) + ) elif block["type"] == "image": if "title" in block: block_text.append(unfurl_block_element(block["title"])) @@ -3721,11 +4417,18 @@ def unfurl_blocks(blocks): elif block["type"] == "rich_text": continue else: - block_text.append(colorize_string(config.color_deleted, - '<<Unsupported block type "{}">>'.format(block["type"]))) + block_text.append( + colorize_string( + config.color_deleted, + '<<Unsupported block type "{}">>'.format(block["type"]), + ) + ) dbg('Unsupported block: "{}"'.format(json.dumps(block)), level=4) except Exception as e: - dbg("Failed to unfurl block ({}): {}".format(repr(e), json.dumps(block)), level=4) + dbg( + "Failed to unfurl block ({}): {}".format(repr(e), json.dumps(block)), + level=4, + ) return block_text @@ -3782,9 +4485,7 @@ def unfurl_refs(text): def unhtmlescape(text): - return text.replace("<", "<") \ - .replace(">", ">") \ - .replace("&", "&") + return text.replace("<", "<").replace(">", ">").replace("&", "&") def unwrap_attachments(message_json, text_before): @@ -3793,7 +4494,7 @@ def unwrap_attachments(message_json, text_before): a = message_json.get("attachments") if a: if text_before: - attachment_texts.append('') + attachment_texts.append("") for attachment in a: # Attachments should be rendered roughly like: # @@ -3801,54 +4502,76 @@ def unwrap_attachments(message_json, text_before): # $author: (if rest of line is non-empty) $title ($title_link) OR $from_url # $author: (if no $author on previous line) $text # $fields - if 'original_url' in attachment and not config.link_previews: - continue + if "original_url" in attachment and not config.link_previews: + continue t = [] - prepend_title_text = '' - if 'author_name' in attachment: - prepend_title_text = attachment['author_name'] + ": " - if 'pretext' in attachment: - t.append(attachment['pretext']) + prepend_title_text = "" + if "author_name" in attachment: + prepend_title_text = attachment["author_name"] + ": " + if "pretext" in attachment: + t.append(attachment["pretext"]) link_shown = False - title = attachment.get('title') - title_link = attachment.get('title_link', '') - if title_link and (title_link in text_before or title_link in text_before_unescaped): - title_link = '' + title = attachment.get("title") + title_link = attachment.get("title_link", "") + if title_link and ( + title_link in text_before or title_link in text_before_unescaped + ): + title_link = "" link_shown = True if title and title_link: - t.append('%s%s (%s)' % (prepend_title_text, title, title_link,)) - prepend_title_text = '' + t.append( + "%s%s (%s)" + % ( + prepend_title_text, + title, + title_link, + ) + ) + prepend_title_text = "" elif title and not title_link: - t.append('%s%s' % (prepend_title_text, title,)) - prepend_title_text = '' - from_url = attachment.get('from_url', '') - if (from_url not in text_before and from_url not in text_before_unescaped - and from_url != title_link): + t.append( + "%s%s" + % ( + prepend_title_text, + title, + ) + ) + prepend_title_text = "" + from_url = attachment.get("from_url", "") + if ( + from_url not in text_before + and from_url not in text_before_unescaped + and from_url != title_link + ): t.append(from_url) elif from_url: link_shown = True atext = attachment.get("text") if atext: - tx = re.sub(r' *\n[\n ]+', '\n', atext) + tx = re.sub(r" *\n[\n ]+", "\n", atext) t.append(prepend_title_text + tx) - prepend_title_text = '' + prepend_title_text = "" blocks = attachment.get("blocks", []) t.extend(unfurl_blocks(blocks)) - image_url = attachment.get('image_url', '') - if (image_url not in text_before and image_url not in text_before_unescaped - and image_url != from_url and image_url != title_link): + image_url = attachment.get("image_url", "") + if ( + image_url not in text_before + and image_url not in text_before_unescaped + and image_url != from_url + and image_url != title_link + ): t.append(image_url) elif image_url: link_shown = True for field in attachment.get("fields", []): - if field.get('title'): - t.append('{}: {}'.format(field['title'], field['value'])) + if field.get("title"): + t.append("{}: {}".format(field["title"], field["value"])) else: - t.append(field['value']) + t.append(field["value"]) files = unwrap_files(attachment, None) if files: @@ -3859,97 +4582,112 @@ def unwrap_attachments(message_json, text_before): ts = attachment.get("ts") if ts: ts_int = ts if type(ts) == int else SlackTS(ts).major - time_string = '' + time_string = "" if date.today() - date.fromtimestamp(ts_int) <= timedelta(days=1): - time_string = ' at {time}' - timestamp_formatted = resolve_ref('!date^{}^{{date_short_pretty}}{}' - .format(ts_int, time_string)).capitalize() - footer += ' | {}'.format(timestamp_formatted) + time_string = " at {time}" + timestamp_formatted = resolve_ref( + "!date^{}^{{date_short_pretty}}{}".format(ts_int, time_string) + ).capitalize() + footer += " | {}".format(timestamp_formatted) t.append(footer) fallback = attachment.get("fallback") if t == [] and fallback and not link_shown: t.append(fallback) if t: - lines = [line for part in t for line in part.strip().split("\n") if part] - prefix = '|' + lines = [ + line for part in t for line in part.strip().split("\n") if part + ] + prefix = "|" line_color = None - color = attachment.get('color') + color = attachment.get("color") if color and config.colorize_attachments != "none": - weechat_color = w.info_get("color_rgb2term", str(int(color.lstrip("#"), 16))) + weechat_color = w.info_get( + "color_rgb2term", str(int(color.lstrip("#"), 16)) + ) if config.colorize_attachments == "prefix": prefix = colorize_string(weechat_color, prefix) elif config.colorize_attachments == "all": line_color = weechat_color attachment_texts.extend( - colorize_string(line_color, "{} {}".format(prefix, line)) - for line in lines) + colorize_string(line_color, "{} {}".format(prefix, line)) + for line in lines + ) return "\n".join(attachment_texts) def unwrap_files(message_json, text_before): files_texts = [] - for f in message_json.get('files', []): - if f.get('mode', '') == 'tombstone': - text = colorize_string(config.color_deleted, '(This file was deleted.)') - elif f.get('mode', '') == 'hidden_by_limit': - text = colorize_string(config.color_deleted, '(This file is hidden because the workspace has passed its storage limit.)') - elif f.get('url_private', None) is not None and f.get('title', None) is not None: - text = '{} ({})'.format(f['url_private'], f['title']) + for f in message_json.get("files", []): + if f.get("mode", "") == "tombstone": + text = colorize_string(config.color_deleted, "(This file was deleted.)") + elif f.get("mode", "") == "hidden_by_limit": + text = colorize_string( + config.color_deleted, + "(This file is hidden because the workspace has passed its storage limit.)", + ) + elif ( + f.get("url_private", None) is not None and f.get("title", None) is not None + ): + text = "{} ({})".format(f["url_private"], f["title"]) else: - dbg('File {} has unrecognized mode {}'.format(f['id'], f['mode']), 5) - text = colorize_string(config.color_deleted, '(This file cannot be handled.)') + dbg("File {} has unrecognized mode {}".format(f["id"], f["mode"]), 5) + text = colorize_string( + config.color_deleted, "(This file cannot be handled.)" + ) files_texts.append(text) if text_before: - files_texts.insert(0, '') + files_texts.insert(0, "") return "\n".join(files_texts) def resolve_ref(ref): - if ref in ['!channel', '!everyone', '!group', '!here']: - return ref.replace('!', '@') + if ref in ["!channel", "!everyone", "!group", "!here"]: + return ref.replace("!", "@") for team in EVENTROUTER.teams.values(): - if ref.startswith('@'): + if ref.startswith("@"): user = team.users.get(ref[1:]) if user: - suffix = config.external_user_suffix if user.is_external else '' - return '@{}{}'.format(user.name, suffix) - elif ref.startswith('#'): + suffix = config.external_user_suffix if user.is_external else "" + return "@{}{}".format(user.name, suffix) + elif ref.startswith("#"): channel = team.channels.get(ref[1:]) if channel: return channel.name - elif ref.startswith('!subteam'): - _, subteam_id = ref.split('^') + elif ref.startswith("!subteam"): + _, subteam_id = ref.split("^") subteam = team.subteams.get(subteam_id) if subteam: return subteam.handle elif ref.startswith("!date"): - parts = ref.split('^') + parts = ref.split("^") ref_datetime = datetime.fromtimestamp(int(parts[1])) - link_suffix = ' ({})'.format(parts[3]) if len(parts) > 3 else '' + link_suffix = " ({})".format(parts[3]) if len(parts) > 3 else "" token_to_format = { - 'date_num': '%Y-%m-%d', - 'date': '%B %d, %Y', - 'date_short': '%b %d, %Y', - 'date_long': '%A, %B %d, %Y', - 'time': '%H:%M', - 'time_secs': '%H:%M:%S' + "date_num": "%Y-%m-%d", + "date": "%B %d, %Y", + "date_short": "%b %d, %Y", + "date_long": "%A, %B %d, %Y", + "time": "%H:%M", + "time_secs": "%H:%M:%S", } def replace_token(match): token = match.group(1) - if token.startswith('date_') and token.endswith('_pretty'): + if token.startswith("date_") and token.endswith("_pretty"): if ref_datetime.date() == date.today(): - return 'today' + return "today" elif ref_datetime.date() == date.today() - timedelta(days=1): - return 'yesterday' + return "yesterday" elif ref_datetime.date() == date.today() + timedelta(days=1): - return 'tomorrow' + return "tomorrow" else: - token = token.replace('_pretty', '') + token = token.replace("_pretty", "") if token in token_to_format: - return decode_from_utf8(ref_datetime.strftime(token_to_format[token])) + return decode_from_utf8( + ref_datetime.strftime(token_to_format[token]) + ) else: return match.group(0) @@ -3971,39 +4709,46 @@ def create_user_status_string(profile): def create_reaction_string(reaction, myidentifier): if config.show_reaction_nicks: - nicks = [resolve_ref('@{}'.format(user)) for user in reaction['users']] - users = '({})'.format(','.join(nicks)) + nicks = [resolve_ref("@{}".format(user)) for user in reaction["users"]] + users = "({})".format(",".join(nicks)) else: - users = len(reaction['users']) - reaction_string = ':{}:{}'.format(reaction['name'], users) - if myidentifier in reaction['users']: - return colorize_string(config.color_reaction_suffix_added_by_you, reaction_string, - reset_color=config.color_reaction_suffix) + users = len(reaction["users"]) + reaction_string = ":{}:{}".format(reaction["name"], users) + if myidentifier in reaction["users"]: + return colorize_string( + config.color_reaction_suffix_added_by_you, + reaction_string, + reset_color=config.color_reaction_suffix, + ) else: return reaction_string def create_reactions_string(reactions, myidentifier): - reactions_with_users = [r for r in reactions if len(r['users']) > 0] - reactions_string = ' '.join(create_reaction_string(r, myidentifier) for r in reactions_with_users) + reactions_with_users = [r for r in reactions if len(r["users"]) > 0] + reactions_string = " ".join( + create_reaction_string(r, myidentifier) for r in reactions_with_users + ) if reactions_string: - return ' ' + colorize_string(config.color_reaction_suffix, '[{}]'.format(reactions_string)) + return " " + colorize_string( + config.color_reaction_suffix, "[{}]".format(reactions_string) + ) else: - return '' + return "" def hdata_line_ts(line_pointer): - data = w.hdata_pointer(hdata.line, line_pointer, 'data') - for i in range(w.hdata_integer(hdata.line_data, data, 'tags_count')): - tag = w.hdata_string(hdata.line_data, data, '{}|tags_array'.format(i)) - if tag.startswith('slack_ts_'): + data = w.hdata_pointer(hdata.line, line_pointer, "data") + for i in range(w.hdata_integer(hdata.line_data, data, "tags_count")): + tag = w.hdata_string(hdata.line_data, data, "{}|tags_array".format(i)) + if tag.startswith("slack_ts_"): return SlackTS(tag[9:]) return None def modify_buffer_line(buffer_pointer, ts, new_text): - own_lines = w.hdata_pointer(hdata.buffer, buffer_pointer, 'own_lines') - line_pointer = w.hdata_pointer(hdata.lines, own_lines, 'last_line') + own_lines = w.hdata_pointer(hdata.buffer, buffer_pointer, "own_lines") + line_pointer = w.hdata_pointer(hdata.lines, own_lines, "last_line") # Find the last line with this ts is_last_line = True @@ -4022,60 +4767,70 @@ def modify_buffer_line(buffer_pointer, ts, new_text): return w.WEECHAT_RC_OK if is_last_line: - lines = new_text.split('\n') + lines = new_text.split("\n") extra_lines_count = len(lines) - len(pointers) if extra_lines_count > 0: - line_data = w.hdata_pointer(hdata.line, pointers[0], 'data') - tags_count = w.hdata_integer(hdata.line_data, line_data, 'tags_count') - tags = [w.hdata_string(hdata.line_data, line_data, '{}|tags_array'.format(i)) - for i in range(tags_count)] + line_data = w.hdata_pointer(hdata.line, pointers[0], "data") + tags_count = w.hdata_integer(hdata.line_data, line_data, "tags_count") + tags = [ + w.hdata_string(hdata.line_data, line_data, "{}|tags_array".format(i)) + for i in range(tags_count) + ] tags = tags_set_notify_none(tags) - tags_str = ','.join(tags) - last_read_line = w.hdata_pointer(hdata.lines, own_lines, 'last_read_line') + tags_str = ",".join(tags) + last_read_line = w.hdata_pointer(hdata.lines, own_lines, "last_read_line") should_set_unread = last_read_line == pointers[-1] # Insert new lines to match the number of lines in the message w.buffer_set(buffer_pointer, "print_hooks_enabled", "0") for _ in range(extra_lines_count): w.prnt_date_tags(buffer_pointer, ts.major, tags_str, " \t ") - pointers.append(w.hdata_pointer(hdata.lines, own_lines, 'last_line')) + pointers.append(w.hdata_pointer(hdata.lines, own_lines, "last_line")) if should_set_unread: w.buffer_set(buffer_pointer, "unread", "") w.buffer_set(buffer_pointer, "print_hooks_enabled", "1") else: # Split the message into at most the number of existing lines as we can't insert new lines - lines = new_text.split('\n', len(pointers) - 1) + lines = new_text.split("\n", len(pointers) - 1) # Replace newlines to prevent garbled lines in bare display mode - lines = [line.replace('\n', ' | ') for line in lines] + lines = [line.replace("\n", " | ") for line in lines] # Extend lines in case the new message is shorter than the old as we can't delete lines - lines += [''] * (len(pointers) - len(lines)) + lines += [""] * (len(pointers) - len(lines)) for pointer, line in zip(pointers, lines): - data = w.hdata_pointer(hdata.line, pointer, 'data') + data = w.hdata_pointer(hdata.line, pointer, "data") w.hdata_update(hdata.line_data, data, {"message": line}) return w.WEECHAT_RC_OK def nick_from_profile(profile, username): - full_name = profile.get('real_name') or username + full_name = profile.get("real_name") or username if config.use_full_names: nick = full_name else: - nick = profile.get('display_name') or full_name - return nick.replace(' ', '') + nick = profile.get("display_name") or full_name + return nick.replace(" ", "") def format_nick(nick, previous_nick=None): if nick == previous_nick: - nick = w.config_string(w.config_get('weechat.look.prefix_same_nick')) or nick - nick_prefix = w.config_string(w.config_get('weechat.look.nick_prefix')) - nick_prefix_color_name = w.config_string(w.config_get('weechat.color.chat_nick_prefix')) + nick = w.config_string(w.config_get("weechat.look.prefix_same_nick")) or nick + nick_prefix = w.config_string(w.config_get("weechat.look.nick_prefix")) + nick_prefix_color_name = w.config_string( + w.config_get("weechat.color.chat_nick_prefix") + ) - nick_suffix = w.config_string(w.config_get('weechat.look.nick_suffix')) - nick_suffix_color_name = w.config_string(w.config_get('weechat.color.chat_nick_prefix')) - return colorize_string(nick_prefix_color_name, nick_prefix) + nick + colorize_string(nick_suffix_color_name, nick_suffix) + nick_suffix = w.config_string(w.config_get("weechat.look.nick_suffix")) + nick_suffix_color_name = w.config_string( + w.config_get("weechat.color.chat_nick_prefix") + ) + return ( + colorize_string(nick_prefix_color_name, nick_prefix) + + nick + + colorize_string(nick_suffix_color_name, nick_suffix) + ) def tags_set_notify_none(tags): @@ -4085,7 +4840,15 @@ def tags_set_notify_none(tags): return tags -def tag(ts, tagset=None, user=None, self_msg=False, backlog=False, no_log=False, extra_tags=None): +def tag( + ts, + tagset=None, + user=None, + self_msg=False, + backlog=False, + no_log=False, + extra_tags=None, +): tagsets = { "team_info": ["no_highlight", "log3"], "team_message": ["irc_privmsg", "notify_message", "log1"], @@ -4107,14 +4870,16 @@ def tag(ts, tagset=None, user=None, self_msg=False, backlog=False, no_log=False, tags += ["logger_backlog"] if no_log: tags += ["no_log"] - tags = [tag for tag in tags if not tag.startswith("log") or tag == "logger_backlog"] + tags = [ + tag for tag in tags if not tag.startswith("log") or tag == "logger_backlog" + ] if extra_tags: tags += extra_tags return ",".join(OrderedDict.fromkeys(tags)) def set_own_presence_active(team): - slackbot = team.get_channel_map()['Slackbot'] + slackbot = team.get_channel_map()["Slackbot"] channel = team.channels[slackbot] request = {"type": "typing", "channel": channel.identifier} channel.team.send_to_websocket(request, expect_reply=False) @@ -4129,14 +4894,19 @@ def invite_command_cb(data, current_buffer, args): team = EVENTROUTER.weechat_controller.buffers[current_buffer].team split_args = args.split()[1:] if not split_args: - w.prnt('', 'Too few arguments for command "/invite" (help on command: /help invite)') + w.prnt( + "", + 'Too few arguments for command "/invite" (help on command: /help invite)', + ) return w.WEECHAT_RC_OK_EAT - if split_args[-1].startswith("#") or split_args[-1].startswith(config.group_name_prefix): + if split_args[-1].startswith("#") or split_args[-1].startswith( + config.group_name_prefix + ): nicks = split_args[:-1] channel = team.channels.get(team.get_channel_map().get(split_args[-1])) if not nicks or not channel: - w.prnt('', '{}: No such nick/channel'.format(split_args[-1])) + w.prnt("", "{}: No such nick/channel".format(split_args[-1])) return w.WEECHAT_RC_OK_EAT else: nicks = split_args @@ -4145,14 +4915,19 @@ def invite_command_cb(data, current_buffer, args): all_users = team.get_username_map() users = set() for nick in nicks: - user = all_users.get(nick.lstrip('@')) + user = all_users.get(nick.lstrip("@")) if not user: - w.prnt('', 'ERROR: Unknown user: {}'.format(nick)) + w.prnt("", "ERROR: Unknown user: {}".format(nick)) return w.WEECHAT_RC_OK_EAT users.add(user) - s = SlackRequest(team, "conversations.invite", {"channel": channel.identifier, "users": ",".join(users)}, - channel=channel, metadata={"nicks": nicks}) + s = SlackRequest( + team, + "conversations.invite", + {"channel": channel.identifier, "users": ",".join(users)}, + channel=channel, + metadata={"nicks": nicks}, + ) EVENTROUTER.receive(s) return w.WEECHAT_RC_OK_EAT @@ -4168,24 +4943,28 @@ def part_command_cb(data, current_buffer, args): channel = "".join(args[1:]) if channel in cmap: buffer_ptr = team.channels[cmap[channel]].channel_buffer - e.weechat_controller.unregister_buffer(buffer_ptr, update_remote=True, close_buffer=True) + e.weechat_controller.unregister_buffer( + buffer_ptr, update_remote=True, close_buffer=True + ) else: w.prnt(team.channel_buffer, "{}: No such channel".format(channel)) else: - e.weechat_controller.unregister_buffer(current_buffer, update_remote=True, close_buffer=True) + e.weechat_controller.unregister_buffer( + current_buffer, update_remote=True, close_buffer=True + ) return w.WEECHAT_RC_OK_EAT def parse_topic_command(command): - _, _, args = command.partition(' ') - if args.startswith('#'): - channel_name, _, topic_arg = args.partition(' ') + _, _, args = command.partition(" ") + if args.startswith("#"): + channel_name, _, topic_arg = args.partition(" ") else: channel_name = None topic_arg = args - if topic_arg == '-delete': - topic = '' + if topic_arg == "-delete": + topic = "" elif topic_arg: topic = topic_arg else: @@ -4214,11 +4993,17 @@ def topic_command_cb(data, current_buffer, command): return w.WEECHAT_RC_OK_EAT if topic is None: - w.prnt(channel.channel_buffer, - 'Topic for {} is "{}"'.format(channel.name, channel.render_topic())) + w.prnt( + channel.channel_buffer, + 'Topic for {} is "{}"'.format(channel.name, channel.render_topic()), + ) else: - s = SlackRequest(team, "conversations.setTopic", - {"channel": channel.identifier, "topic": linkify_text(topic, team)}, channel=channel) + s = SlackRequest( + team, + "conversations.setTopic", + {"channel": channel.identifier, "topic": linkify_text(topic, team)}, + channel=channel, + ) EVENTROUTER.receive(s) return w.WEECHAT_RC_OK_EAT @@ -4235,11 +5020,12 @@ def whois_command_cb(data, current_buffer, command): w.prnt(current_buffer, "Not enough arguments") return w.WEECHAT_RC_OK_EAT user = args[1] - if (user.startswith('@')): + if user.startswith("@"): user = user[1:] team = EVENTROUTER.weechat_controller.buffers[current_buffer].team u = team.users.get(team.get_username_map().get(user)) if u: + def print_profile(field): value = u.profile.get(field) if value: @@ -4254,10 +5040,10 @@ def whois_command_cb(data, current_buffer, command): team.buffer_prnt("[{}]: username: {}".format(user, u.username)) team.buffer_prnt("[{}]: id: {}".format(user, u.identifier)) - print_profile('title') - print_profile('email') - print_profile('phone') - print_profile('skype') + print_profile("title") + print_profile("email") + print_profile("phone") + print_profile("skype") else: team.buffer_prnt("[{}]: No such user".format(user)) return w.WEECHAT_RC_OK_EAT @@ -4267,8 +5053,8 @@ def whois_command_cb(data, current_buffer, command): @utf8_decode def me_command_cb(data, current_buffer, args): channel = EVENTROUTER.weechat_controller.buffers[current_buffer] - message = args.split(' ', 1)[1] - channel.send_message(message, subtype='me_message') + message = args.split(" ", 1)[1] + channel.send_message(message, subtype="me_message") return w.WEECHAT_RC_OK_EAT @@ -4296,7 +5082,9 @@ def command_register(data, current_buffer, args): else: nothirdparty = args == "-nothirdparty" code = "" if nothirdparty else args - redirect_uri = quote(REDIRECT_URI_NOTHIRDPARTY if nothirdparty else REDIRECT_URI_GITHUB, safe='') + redirect_uri = quote( + REDIRECT_URI_NOTHIRDPARTY if nothirdparty else REDIRECT_URI_GITHUB, safe="" + ) if not code: if nothirdparty: @@ -4305,17 +5093,23 @@ def command_register(data, current_buffer, args): else: nothirdparty_note = "\nNote that by default GitHub Pages will see a temporary code used to create your token (but not the token itself). If you're worried about this, you can use the -nothirdparty option, though the process will be a bit less user friendly." last_step = "The web page will show a command in the form `/slack register <code>`. Run this command in weechat." - message = textwrap.dedent(""" + message = ( + textwrap.dedent( + """ ### Connecting to a Slack team with OAuth ###{} 1) Paste this link into a browser: https://slack.com/oauth/authorize?client_id={}&scope=client&redirect_uri={} 2) Select the team you wish to access from wee-slack in your browser. If you want to add multiple teams, you will have to repeat this whole process for each team. 3) Click "Authorize" in the browser. If you get a message saying you are not authorized to install wee-slack, the team has restricted Slack app installation and you will have to request it from an admin. To do that, go to https://my.slack.com/apps/A1HSZ9V8E-wee-slack and click "Request to Install". 4) {} - """).strip().format(nothirdparty_note, CLIENT_ID, redirect_uri, last_step) + """ + ) + .strip() + .format(nothirdparty_note, CLIENT_ID, redirect_uri, last_step) + ) w.prnt("", "\n" + message) return w.WEECHAT_RC_OK_EAT - elif code.startswith('xox'): + elif code.startswith("xox"): add_token(code) return w.WEECHAT_RC_OK_EAT @@ -4323,64 +5117,79 @@ def command_register(data, current_buffer, args): "https://slack.com/api/oauth.access?" "client_id={}&client_secret={}&redirect_uri={}&code={}" ).format(CLIENT_ID, CLIENT_SECRET, redirect_uri, code) - params = {'useragent': 'wee_slack {}'.format(SCRIPT_VERSION)} - w.hook_process_hashtable('url:', params, config.slack_timeout, "", "") - w.hook_process_hashtable("url:{}".format(uri), params, config.slack_timeout, "register_callback", "") + params = {"useragent": "wee_slack {}".format(SCRIPT_VERSION)} + w.hook_process_hashtable("url:", params, config.slack_timeout, "", "") + w.hook_process_hashtable( + "url:{}".format(uri), params, config.slack_timeout, "register_callback", "" + ) return w.WEECHAT_RC_OK_EAT -command_register.completion = '-nothirdparty %-' + +command_register.completion = "-nothirdparty %-" @utf8_decode def register_callback(data, command, return_code, out, err): if return_code != 0: - w.prnt("", "ERROR: problem when trying to get Slack OAuth token. Got return code {}. Err: {}".format(return_code, err)) + w.prnt( + "", + "ERROR: problem when trying to get Slack OAuth token. Got return code {}. Err: {}".format( + return_code, err + ), + ) w.prnt("", "Check the network or proxy settings") return w.WEECHAT_RC_OK_EAT if len(out) <= 0: - w.prnt("", "ERROR: problem when trying to get Slack OAuth token. Got 0 length answer. Err: {}".format(err)) + w.prnt( + "", + "ERROR: problem when trying to get Slack OAuth token. Got 0 length answer. Err: {}".format( + err + ), + ) w.prnt("", "Check the network or proxy settings") return w.WEECHAT_RC_OK_EAT d = json.loads(out) if not d["ok"]: - w.prnt("", - "ERROR: Couldn't get Slack OAuth token: {}".format(d['error'])) + w.prnt("", "ERROR: Couldn't get Slack OAuth token: {}".format(d["error"])) return w.WEECHAT_RC_OK_EAT - add_token(d['access_token'], d['team_name']) + add_token(d["access_token"], d["team_name"]) return w.WEECHAT_RC_OK_EAT def add_token(token, team_name=None): - if config.is_default('slack_api_token'): - w.config_set_plugin('slack_api_token', token) + if config.is_default("slack_api_token"): + w.config_set_plugin("slack_api_token", token) else: # Add new token to existing set, joined by comma. - existing_tokens = config.get_string('slack_api_token') + existing_tokens = config.get_string("slack_api_token") if token in existing_tokens: - print_error('This token is already registered') + print_error("This token is already registered") return - w.config_set_plugin('slack_api_token', ','.join([existing_tokens, token])) + w.config_set_plugin("slack_api_token", ",".join([existing_tokens, token])) if team_name: - w.prnt("", "Success! Added team \"{}\"".format(team_name)) + w.prnt("", 'Success! Added team "{}"'.format(team_name)) else: w.prnt("", "Success! Added token") w.prnt("", "Please reload wee-slack with: /python reload slack") - w.prnt("", "If you want to add another team you can repeat this process from step 1 before reloading wee-slack.") + w.prnt( + "", + "If you want to add another team you can repeat this process from step 1 before reloading wee-slack.", + ) @slack_buffer_or_ignore @utf8_decode def msg_command_cb(data, current_buffer, args): aargs = args.split(None, 2) - who = aargs[1].lstrip('@') + who = aargs[1].lstrip("@") if who == "*": who = EVENTROUTER.weechat_controller.buffers[current_buffer].name else: - join_query_command_cb(data, current_buffer, '/query ' + who) + join_query_command_cb(data, current_buffer, "/query " + who) if len(aargs) > 2: message = aargs[2] @@ -4398,7 +5207,9 @@ def print_team_items_info(team, header, items, extra_info_function): max_name_length = max(len(item.name) for item in items) for item in sorted(items, key=lambda item: item.name.lower()): extra_info = extra_info_function(item) - team.buffer_prnt(" {:<{}}({})".format(item.name, max_name_length + 2, extra_info)) + team.buffer_prnt( + " {:<{}}({})".format(item.name, max_name_length + 2, extra_info) + ) return w.WEECHAT_RC_OK_EAT @@ -4406,6 +5217,7 @@ def print_users_info(team, header, users): def extra_info_function(user): external_text = ", external" if user.is_external else "" return user.presence + external_text + return print_team_items_info(team, header, users, extra_info_function) @@ -4430,7 +5242,12 @@ def command_channels(data, current_buffer, args): List the channels in the current team. """ team = EVENTROUTER.weechat_controller.buffers[current_buffer].team - channels = [channel for channel in team.channels.values() if channel.type not in ['im', 'mpim']] + channels = [ + channel + for channel in team.channels.values() + if channel.type not in ["im", "mpim"] + ] + def extra_info_function(channel): if channel.active: return "member" @@ -4438,6 +5255,7 @@ def command_channels(data, current_buffer, args): return "archived" else: return "not a member" + return print_team_items_info(team, "Channels", channels, extra_info_function) @@ -4465,20 +5283,29 @@ def command_usergroups(data, current_buffer, args): usergroup_key = usergroups.get(args) if usergroup_key: - s = SlackRequest(team, "usergroups.users.list", {"usergroup": usergroup_key}, - metadata={'usergroup_handle': args}) + s = SlackRequest( + team, + "usergroups.users.list", + {"usergroup": usergroup_key}, + metadata={"usergroup_handle": args}, + ) EVENTROUTER.receive(s) elif args: - w.prnt('', 'ERROR: Unknown usergroup handle: {}'.format(args)) + w.prnt("", "ERROR: Unknown usergroup handle: {}".format(args)) return w.WEECHAT_RC_ERROR else: + def extra_info_function(subteam): - is_member = 'member' if subteam.is_member else 'not a member' - return '{}, {}'.format(subteam.handle, is_member) - return print_team_items_info(team, "Usergroups", team.subteams.values(), extra_info_function) + is_member = "member" if subteam.is_member else "not a member" + return "{}, {}".format(subteam.handle, is_member) + + return print_team_items_info( + team, "Usergroups", team.subteams.values(), extra_info_function + ) return w.WEECHAT_RC_OK_EAT -command_usergroups.completion = '%(usergroups) %-' + +command_usergroups.completion = "%(usergroups) %-" @slack_buffer_required @@ -4489,21 +5316,26 @@ def command_talk(data, current_buffer, args): Open a chat with the specified user(s). """ if not args: - w.prnt('', 'Usage: /slack talk <user>[,<user2>[,<user3>...]]') + w.prnt("", "Usage: /slack talk <user>[,<user2>[,<user3>...]]") return w.WEECHAT_RC_ERROR - return join_query_command_cb(data, current_buffer, '/query ' + args) + return join_query_command_cb(data, current_buffer, "/query " + args) + -command_talk.completion = '%(nicks)' +command_talk.completion = "%(nicks)" @slack_buffer_or_ignore @utf8_decode def join_query_command_cb(data, current_buffer, args): team = EVENTROUTER.weechat_controller.buffers[current_buffer].team - split_args = args.split(' ', 1) + split_args = args.split(" ", 1) if len(split_args) < 2 or not split_args[1]: - w.prnt('', 'Too few arguments for command "{}" (help on command: /help {})' - .format(split_args[0], split_args[0].lstrip('/'))) + w.prnt( + "", + 'Too few arguments for command "{}" (help on command: /help {})'.format( + split_args[0], split_args[0].lstrip("/") + ), + ) return w.WEECHAT_RC_OK_EAT query = split_args[1] @@ -4512,33 +5344,37 @@ def join_query_command_cb(data, current_buffer, args): # If the channel doesn't exist, try finding a DM or MPDM instead if not channel: - if query.startswith('#'): - w.prnt('', 'ERROR: Unknown channel: {}'.format(query)) + if query.startswith("#"): + w.prnt("", "ERROR: Unknown channel: {}".format(query)) return w.WEECHAT_RC_OK_EAT # Get the IDs of the users all_users = team.get_username_map() users = set() - for username in query.split(','): - user = all_users.get(username.lstrip('@')) + for username in query.split(","): + user = all_users.get(username.lstrip("@")) if not user: - w.prnt('', 'ERROR: Unknown user: {}'.format(username)) + w.prnt("", "ERROR: Unknown user: {}".format(username)) return w.WEECHAT_RC_OK_EAT users.add(user) if users: if len(users) > 1: - channel_type = 'mpim' + channel_type = "mpim" # Add the current user since MPDMs include them as a member users.add(team.myidentifier) else: - channel_type = 'im' + channel_type = "im" channel = team.find_channel_by_members(users, channel_type=channel_type) # If the DM or MPDM doesn't exist, create it if not channel: - s = SlackRequest(team, team.slack_api_translator[channel_type]['join'], {'users': ','.join(users)}) + s = SlackRequest( + team, + team.slack_api_translator[channel_type]["join"], + {"users": ",".join(users)}, + ) EVENTROUTER.receive(s) if channel: @@ -4569,7 +5405,8 @@ def command_create(data, current_buffer, args): EVENTROUTER.receive(s) return w.WEECHAT_RC_OK_EAT -command_create.completion = '-private' + +command_create.completion = "-private" @slack_buffer_required @@ -4580,9 +5417,10 @@ def command_showmuted(data, current_buffer, args): List the muted channels in the current team. """ team = EVENTROUTER.weechat_controller.buffers[current_buffer].team - muted_channels = [team.channels[key].name - for key in team.muted_channels if key in team.channels] - team.buffer_prnt("Muted channels: {}".format(', '.join(muted_channels))) + muted_channels = [ + team.channels[key].name for key in team.muted_channels if key in team.channels + ] + team.buffer_prnt("Muted channels: {}".format(", ".join(muted_channels))) return w.WEECHAT_RC_OK_EAT @@ -4597,7 +5435,7 @@ def command_thread(data, current_buffer, args): channel = EVENTROUTER.weechat_controller.buffers[current_buffer] if not isinstance(channel, SlackChannelCommon): - print_error('/thread can not be used in the team buffer, only in a channel') + print_error("/thread can not be used in the team buffer, only in a channel") return w.WEECHAT_RC_ERROR message = channel.message_from_hash(args) @@ -4608,14 +5446,17 @@ def command_thread(data, current_buffer, args): if message: message.open_thread(switch=config.switch_buffer_on_join) elif args: - print_error("Invalid id given, must be an existing id or a number greater " + - "than 0 and less than the number of thread messages in the channel") + print_error( + "Invalid id given, must be an existing id or a number greater " + + "than 0 and less than the number of thread messages in the channel" + ) else: print_error("No threads found in channel") return w.WEECHAT_RC_OK_EAT -command_thread.completion = '%(threads) %-' + +command_thread.completion = "%(threads) %-" def subscribe_helper(current_buffer, args, usage, api): @@ -4633,7 +5474,11 @@ def subscribe_helper(current_buffer, args, usage, api): return w.WEECHAT_RC_OK_EAT last_read = next(reversed(message.submessages), message.ts) - post_data = {"channel": channel.identifier, "thread_ts": message.ts, "last_read": last_read} + post_data = { + "channel": channel.identifier, + "thread_ts": message.ts, + "last_read": last_read, + } s = SlackRequest(team, api, post_data, channel=channel) EVENTROUTER.receive(s) return w.WEECHAT_RC_OK_EAT @@ -4649,9 +5494,15 @@ def command_subscribe(data, current_buffer, args): This command only works when using a session token, see the readme: https://github.com/wee-slack/wee-slack#4-add-your-slack-api-tokens """ - return subscribe_helper(current_buffer, args, 'Usage: /slack subscribe <thread>', "subscriptions.thread.add") + return subscribe_helper( + current_buffer, + args, + "Usage: /slack subscribe <thread>", + "subscriptions.thread.add", + ) + -command_subscribe.completion = '%(threads) %-' +command_subscribe.completion = "%(threads) %-" @slack_buffer_required @@ -4665,9 +5516,15 @@ def command_unsubscribe(data, current_buffer, args): This command only works when using a session token, see the readme: https://github.com/wee-slack/wee-slack#4-add-your-slack-api-tokens """ - return subscribe_helper(current_buffer, args, 'Usage: /slack unsubscribe <thread>', "subscriptions.thread.remove") + return subscribe_helper( + current_buffer, + args, + "Usage: /slack unsubscribe <thread>", + "subscriptions.thread.remove", + ) + -command_unsubscribe.completion = '%(threads) %-' +command_unsubscribe.completion = "%(threads) %-" @slack_buffer_required @@ -4703,7 +5560,10 @@ def command_reply(data, current_buffer, args): try: msg_id, text = args.split(None, 1) except ValueError: - w.prnt('', 'Usage (when in a channel buffer): /reply [-alsochannel] <count/message_id> <message>') + w.prnt( + "", + "Usage (when in a channel buffer): /reply [-alsochannel] <count/message_id> <message>", + ) return w.WEECHAT_RC_OK_EAT message = channel.message_from_hash_or_index(msg_id) @@ -4716,10 +5576,13 @@ def command_reply(data, current_buffer, args): elif message: parent_id = str(message.ts) - channel.send_message(text, request_dict_ext={'thread_ts': parent_id, 'reply_broadcast': broadcast}) + channel.send_message( + text, request_dict_ext={"thread_ts": parent_id, "reply_broadcast": broadcast} + ) return w.WEECHAT_RC_OK_EAT -command_reply.completion = '%(threads)|-alsochannel %(threads)' + +command_reply.completion = "%(threads)|-alsochannel %(threads)" @slack_buffer_required @@ -4737,7 +5600,8 @@ def command_rehistory(data, current_buffer, args): channel.reprint_messages(force_render=True) return w.WEECHAT_RC_OK_EAT -command_rehistory.completion = '-remote' + +command_rehistory.completion = "-remote" @slack_buffer_required @@ -4748,7 +5612,7 @@ def command_hide(data, current_buffer, args): Hide the current channel if it is marked as distracting. """ channel = EVENTROUTER.weechat_controller.buffers[current_buffer] - name = channel.formatted_name(style='long_default') + name = channel.formatted_name(style="long_default") if name in config.distracting_channels: w.buffer_set(channel.channel_buffer, "hidden", "1") return w.WEECHAT_RC_OK_EAT @@ -4756,12 +5620,12 @@ def command_hide(data, current_buffer, args): @utf8_decode def slack_command_cb(data, current_buffer, args): - split_args = args.split(' ', 1) + split_args = args.split(" ", 1) cmd_name = split_args[0] - cmd_args = split_args[1] if len(split_args) > 1 else '' - cmd = EVENTROUTER.cmds.get(cmd_name or 'help') + cmd_args = split_args[1] if len(split_args) > 1 else "" + cmd = EVENTROUTER.cmds.get(cmd_name or "help") if not cmd: - w.prnt('', 'Command not found: ' + cmd_name) + w.prnt("", "Command not found: " + cmd_name) return w.WEECHAT_RC_OK return cmd(data, current_buffer, cmd_args) @@ -4777,18 +5641,24 @@ def command_help(data, current_buffer, args): if cmd: cmds = {args: cmd} else: - w.prnt('', 'Command not found: ' + args) + w.prnt("", "Command not found: " + args) return w.WEECHAT_RC_OK else: cmds = EVENTROUTER.cmds - w.prnt('', '\n{}'.format(colorize_string('bold', 'Slack commands:'))) + w.prnt("", "\n{}".format(colorize_string("bold", "Slack commands:"))) - script_prefix = '{0}[{1}python{0}/{1}slack{0}]{1}'.format(w.color('green'), w.color('reset')) + script_prefix = "{0}[{1}python{0}/{1}slack{0}]{1}".format( + w.color("green"), w.color("reset") + ) for _, cmd in sorted(cmds.items()): name, cmd_args, description = parse_help_docstring(cmd) - w.prnt('', '\n{} {} {}\n\n{}'.format( - script_prefix, colorize_string('white', name), cmd_args, description)) + w.prnt( + "", + "\n{} {} {}\n\n{}".format( + script_prefix, colorize_string("white", name), cmd_args, description + ), + ) return w.WEECHAT_RC_OK @@ -4806,7 +5676,7 @@ def command_distracting(data, current_buffer, args): config.distracting_channels.remove(fullname) else: config.distracting_channels.append(fullname) - w.config_set_plugin('distracting_channels', ','.join(config.distracting_channels)) + w.config_set_plugin("distracting_channels", ",".join(config.distracting_channels)) return w.WEECHAT_RC_OK_EAT @@ -4820,14 +5690,18 @@ def command_slash(data, current_buffer, args): channel = EVENTROUTER.weechat_controller.buffers[current_buffer] team = channel.team - split_args = args.split(' ', 1) + split_args = args.split(" ", 1) command = split_args[0] text = split_args[1] if len(split_args) > 1 else "" text_linkified = linkify_text(text, team, only_users=True) - s = SlackRequest(team, "chat.command", - {"command": command, "text": text_linkified, 'channel': channel.identifier}, - channel=channel, metadata={'command': command, 'command_args': text}) + s = SlackRequest( + team, + "chat.command", + {"command": command, "text": text_linkified, "channel": channel.identifier}, + channel=channel, + metadata={"command": command, "command_args": text}, + ) EVENTROUTER.receive(s) return w.WEECHAT_RC_OK_EAT @@ -4844,8 +5718,12 @@ def command_mute(data, current_buffer, args): team.muted_channels ^= {channel.identifier} muted_str = "Muted" if channel.identifier in team.muted_channels else "Unmuted" team.buffer_prnt("{} channel {}".format(muted_str, channel.name)) - s = SlackRequest(team, "users.prefs.set", - {"name": "muted_channels", "value": ",".join(team.muted_channels)}, channel=channel) + s = SlackRequest( + team, + "users.prefs.set", + {"name": "muted_channels", "value": ",".join(team.muted_channels)}, + channel=channel, + ) EVENTROUTER.receive(s) return w.WEECHAT_RC_OK_EAT @@ -4859,16 +5737,18 @@ def command_linkarchive(data, current_buffer, args): Use cursor or mouse mode to get the id. """ channel = EVENTROUTER.weechat_controller.buffers[current_buffer] - url = 'https://{}/'.format(channel.team.domain) + url = "https://{}/".format(channel.team.domain) if isinstance(channel, SlackChannelCommon): - url += 'archives/{}/'.format(channel.identifier) + url += "archives/{}/".format(channel.identifier) if args: message = channel.message_from_hash_or_index(args) if message: - url += 'p{}{:0>6}'.format(message.ts.majorstr(), message.ts.minorstr()) + url += "p{}{:0>6}".format(message.ts.majorstr(), message.ts.minorstr()) if isinstance(message, SlackThreadMessage): - url += "?thread_ts={}&cid={}".format(message.parent_message.ts, channel.identifier) + url += "?thread_ts={}&cid={}".format( + message.parent_message.ts, channel.identifier + ) else: print_message_not_found_error(args) return w.WEECHAT_RC_OK_EAT @@ -4876,7 +5756,8 @@ def command_linkarchive(data, current_buffer, args): w.command(current_buffer, "/input insert {}".format(url)) return w.WEECHAT_RC_OK_EAT -command_linkarchive.completion = '%(threads) %-' + +command_linkarchive.completion = "%(threads) %-" @utf8_decode @@ -4887,8 +5768,11 @@ def command_nodistractions(data, current_buffer, args): """ global hide_distractions hide_distractions = not hide_distractions - channels = [channel for channel in EVENTROUTER.weechat_controller.buffers.values() - if channel in config.distracting_channels] + channels = [ + channel + for channel in EVENTROUTER.weechat_controller.buffers.values() + if channel in config.distracting_channels + ] for channel in channels: w.buffer_set(channel.channel_buffer, "hidden", str(int(hide_distractions))) return w.WEECHAT_RC_OK_EAT @@ -4905,52 +5789,60 @@ def command_upload(data, current_buffer, args): weechat_dir = w.info_get("weechat_dir", "") file_path = os.path.join(weechat_dir, os.path.expanduser(args)) - if channel.type == 'team': - w.prnt('', "ERROR: Can't upload a file to the team buffer") + if channel.type == "team": + w.prnt("", "ERROR: Can't upload a file to the team buffer") return w.WEECHAT_RC_ERROR if not os.path.isfile(file_path): - unescaped_file_path = file_path.replace(r'\ ', ' ') + unescaped_file_path = file_path.replace(r"\ ", " ") if os.path.isfile(unescaped_file_path): file_path = unescaped_file_path else: - w.prnt('', 'ERROR: Could not find file: {}'.format(file_path)) + w.prnt("", "ERROR: Could not find file: {}".format(file_path)) return w.WEECHAT_RC_ERROR post_data = { - 'channels': channel.identifier, + "channels": channel.identifier, } if isinstance(channel, SlackThreadChannel): - post_data['thread_ts'] = channel.thread_ts + post_data["thread_ts"] = channel.thread_ts - url = SlackRequest(channel.team, 'files.upload', post_data, channel=channel).request_string() - options = [ - '-s', - '-Ffile=@{}'.format(file_path), - url - ] + url = SlackRequest( + channel.team, "files.upload", post_data, channel=channel + ).request_string() + options = ["-s", "-Ffile=@{}".format(file_path), url] proxy_string = ProxyWrapper().curl() if proxy_string: options.append(proxy_string) - options_hashtable = {'arg{}'.format(i + 1): arg for i, arg in enumerate(options)} - w.hook_process_hashtable('curl', options_hashtable, config.slack_timeout, 'upload_callback', '') + options_hashtable = {"arg{}".format(i + 1): arg for i, arg in enumerate(options)} + w.hook_process_hashtable( + "curl", options_hashtable, config.slack_timeout, "upload_callback", "" + ) return w.WEECHAT_RC_OK_EAT -command_upload.completion = '%(filename) %-' + +command_upload.completion = "%(filename) %-" @utf8_decode def upload_callback(data, command, return_code, out, err): if return_code != 0: - w.prnt("", "ERROR: Couldn't upload file. Got return code {}. Error: {}".format(return_code, err)) + w.prnt( + "", + "ERROR: Couldn't upload file. Got return code {}. Error: {}".format( + return_code, err + ), + ) return w.WEECHAT_RC_OK_EAT try: response = json.loads(out) except JSONDecodeError: - w.prnt("", "ERROR: Couldn't process response from file upload. Got: {}".format(out)) + w.prnt( + "", "ERROR: Couldn't process response from file upload. Got: {}".format(out) + ) return w.WEECHAT_RC_OK_EAT if not response["ok"]: @@ -4960,7 +5852,7 @@ def upload_callback(data, command, return_code, out, err): @utf8_decode def away_command_cb(data, current_buffer, args): - all_servers, message = re.match('^/away( -all)? ?(.*)', args).groups() + all_servers, message = re.match("^/away( -all)? ?(.*)", args).groups() if all_servers: team_buffers = [team.channel_buffer for team in EVENTROUTER.teams.values()] elif current_buffer in EVENTROUTER.weechat_controller.buffers: @@ -5002,9 +5894,12 @@ def command_status(data, current_buffer, args): split_args = args.split(" ", 1) if not split_args[0]: profile = team.users[team.myidentifier].profile - team.buffer_prnt("Status: {} {}".format( - replace_string_with_emoji(profile.get("status_emoji", "")), - profile.get("status_text", ""))) + team.buffer_prnt( + "Status: {} {}".format( + replace_string_with_emoji(profile.get("status_emoji", "")), + profile.get("status_text", ""), + ) + ) return w.WEECHAT_RC_OK emoji = "" if split_args[0] == "-delete" else split_args[0] @@ -5015,14 +5910,15 @@ def command_status(data, current_buffer, args): EVENTROUTER.receive(s) return w.WEECHAT_RC_OK + command_status.completion = "-delete|%(emoji) %-" @utf8_decode def line_event_cb(data, signal, hashtable): - tags = hashtable["_chat_line_tags"].split(',') + tags = hashtable["_chat_line_tags"].split(",") for tag in tags: - if tag.startswith('slack_ts_'): + if tag.startswith("slack_ts_"): ts = SlackTS(tag[9:]) break else: @@ -5054,7 +5950,9 @@ def line_event_cb(data, signal, hashtable): w.command(buffer_pointer, "/slack linkarchive {}".format(message_hash)) elif data == "reply": w.command(buffer_pointer, "/cursor stop") - w.command(buffer_pointer, "/input insert /reply {}\\x20".format(message_hash)) + w.command( + buffer_pointer, "/input insert /reply {}\\x20".format(message_hash) + ) elif data == "thread": w.command(buffer_pointer, "/cursor stop") w.command(buffer_pointer, "/thread {}".format(message_hash)) @@ -5100,6 +5998,7 @@ def command_label(data, current_buffer, args): channel.rename() return w.WEECHAT_RC_OK + command_label.completion = "-unset|-full -unset %-" @@ -5126,9 +6025,11 @@ class InvalidType(Exception): Raised when we do type checking to ensure objects of the wrong type are not used improperly. """ + def __init__(self, type_str): super(InvalidType, self).__init__(type_str) + ###### New but probably old and need to migrate @@ -5142,7 +6043,9 @@ def create_slack_debug_buffer(): global slack_debug, debug_string if slack_debug is None: debug_string = None - slack_debug = w.buffer_new("slack-debug", "", "", "closed_slack_debug_buffer_cb", "") + slack_debug = w.buffer_new( + "slack-debug", "", "", "closed_slack_debug_buffer_cb", "" + ) w.buffer_set(slack_debug, "print_hooks_enabled", "0") w.buffer_set(slack_debug, "notify", "0") w.buffer_set(slack_debug, "highlight_tags_restrict", "highlight_force") @@ -5150,24 +6053,34 @@ def create_slack_debug_buffer(): def load_emoji(): try: - weechat_dir = w.info_get('weechat_dir', '') - weechat_sharedir = w.info_get('weechat_sharedir', '') - local_weemoji, global_weemoji = ('{}/weemoji.json'.format(path) - for path in (weechat_dir, weechat_sharedir)) - path = (global_weemoji if os.path.exists(global_weemoji) and - not os.path.exists(local_weemoji) else local_weemoji) - with open(path, 'r') as ef: + weechat_dir = w.info_get("weechat_dir", "") + weechat_sharedir = w.info_get("weechat_sharedir", "") + local_weemoji, global_weemoji = ( + "{}/weemoji.json".format(path) for path in (weechat_dir, weechat_sharedir) + ) + path = ( + global_weemoji + if os.path.exists(global_weemoji) and not os.path.exists(local_weemoji) + else local_weemoji + ) + with open(path, "r") as ef: emojis = json.loads(ef.read()) - if 'emoji' in emojis: - print_error('The weemoji.json file is in an old format. Please update it.') + if "emoji" in emojis: + print_error( + "The weemoji.json file is in an old format. Please update it." + ) else: - emoji_unicode = {key: value['unicode'] for key, value in emojis.items()} + emoji_unicode = {key: value["unicode"] for key, value in emojis.items()} - emoji_skin_tones = {skin_tone['name']: skin_tone['unicode'] - for emoji in emojis.values() - for skin_tone in emoji.get('skinVariations', {}).values()} + emoji_skin_tones = { + skin_tone["name"]: skin_tone["unicode"] + for emoji in emojis.values() + for skin_tone in emoji.get("skinVariations", {}).values() + } - emoji_with_skin_tones = chain(emoji_unicode.items(), emoji_skin_tones.items()) + emoji_with_skin_tones = chain( + emoji_unicode.items(), emoji_skin_tones.items() + ) emoji_with_skin_tones_reverse = {v: k for k, v in emoji_with_skin_tones} return emoji_unicode, emoji_with_skin_tones_reverse except: @@ -5176,16 +6089,16 @@ def load_emoji(): def parse_help_docstring(cmd): - doc = textwrap.dedent(cmd.__doc__).strip().split('\n', 1) + doc = textwrap.dedent(cmd.__doc__).strip().split("\n", 1) cmd_line = doc[0].split(None, 1) - args = ''.join(cmd_line[1:]) + args = "".join(cmd_line[1:]) return cmd_line[0], args, doc[1].strip() def setup_hooks(): - w.bar_item_new('slack_typing_notice', '(extra)typing_bar_item_cb', '') - w.bar_item_new('away', '(extra)away_bar_item_cb', '') - w.bar_item_new('slack_away', '(extra)away_bar_item_cb', '') + w.bar_item_new("slack_typing_notice", "(extra)typing_bar_item_cb", "") + w.bar_item_new("away", "(extra)away_bar_item_cb", "") + w.bar_item_new("slack_away", "(extra)away_bar_item_cb", "") w.hook_timer(5000, 0, 0, "ws_ping_cb", "") w.hook_timer(1000, 0, 0, "typing_update_cb", "") @@ -5193,70 +6106,94 @@ def setup_hooks(): w.hook_timer(3000, 0, 0, "reconnect_callback", "EVENTROUTER") w.hook_timer(1000 * 60 * 5, 0, 0, "slack_never_away_cb", "") - w.hook_signal('buffer_closing', "buffer_closing_callback", "") - w.hook_signal('buffer_renamed', "buffer_renamed_cb", "") - w.hook_signal('buffer_switch', "buffer_switch_callback", "") - w.hook_signal('window_switch', "buffer_switch_callback", "") - w.hook_signal('quit', "quit_notification_callback", "") + w.hook_signal("buffer_closing", "buffer_closing_callback", "") + w.hook_signal("buffer_renamed", "buffer_renamed_cb", "") + w.hook_signal("buffer_switch", "buffer_switch_callback", "") + w.hook_signal("window_switch", "buffer_switch_callback", "") + w.hook_signal("quit", "quit_notification_callback", "") if config.send_typing_notice: - w.hook_signal('input_text_changed', "typing_notification_cb", "") + w.hook_signal("input_text_changed", "typing_notification_cb", "") - command_help.completion = '|'.join(EVENTROUTER.cmds.keys()) - completions = '||'.join( - '{} {}'.format(name, getattr(cmd, 'completion', '')) - for name, cmd in EVENTROUTER.cmds.items()) + command_help.completion = "|".join(EVENTROUTER.cmds.keys()) + completions = "||".join( + "{} {}".format(name, getattr(cmd, "completion", "")) + for name, cmd in EVENTROUTER.cmds.items() + ) w.hook_command( # Command name and description - 'slack', 'Plugin to allow typing notification and sync of read markers for slack.com', + "slack", + "Plugin to allow typing notification and sync of read markers for slack.com", # Usage - '<command> [<command options>]', + "<command> [<command options>]", # Description of arguments - 'Commands:\n' + - '\n'.join(sorted(EVENTROUTER.cmds.keys())) + - '\nUse /slack help <command> to find out more\n', + "Commands:\n" + + "\n".join(sorted(EVENTROUTER.cmds.keys())) + + "\nUse /slack help <command> to find out more\n", # Completions completions, # Function name - 'slack_command_cb', '') - - w.hook_command_run('/me', 'me_command_cb', '') - w.hook_command_run('/query', 'join_query_command_cb', '') - w.hook_command_run('/join', 'join_query_command_cb', '') - w.hook_command_run('/part', 'part_command_cb', '') - w.hook_command_run('/topic', 'topic_command_cb', '') - w.hook_command_run('/msg', 'msg_command_cb', '') - w.hook_command_run('/invite', 'invite_command_cb', '') + "slack_command_cb", + "", + ) + + w.hook_command_run("/me", "me_command_cb", "") + w.hook_command_run("/query", "join_query_command_cb", "") + w.hook_command_run("/join", "join_query_command_cb", "") + w.hook_command_run("/part", "part_command_cb", "") + w.hook_command_run("/topic", "topic_command_cb", "") + w.hook_command_run("/msg", "msg_command_cb", "") + w.hook_command_run("/invite", "invite_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_command_run('/whois', 'whois_command_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_command_run("/whois", "whois_command_cb", "") - for cmd_name in ['hide', 'label', 'rehistory', 'reply', 'thread']: + for cmd_name in ["hide", "label", "rehistory", "reply", "thread"]: cmd = EVENTROUTER.cmds[cmd_name] _, args, description = parse_help_docstring(cmd) - completion = getattr(cmd, 'completion', '') - w.hook_command(cmd_name, description, args, '', completion, 'command_' + cmd_name, '') + completion = getattr(cmd, "completion", "") + w.hook_command( + cmd_name, description, args, "", completion, "command_" + cmd_name, "" + ) - w.hook_completion("irc_channel_topic", "complete topic for slack", "topic_completion_cb", "") - w.hook_completion("irc_channels", "complete channels for slack", "channel_completion_cb", "") - w.hook_completion("irc_privates", "complete dms/mpdms for slack", "dm_completion_cb", "") + w.hook_completion( + "irc_channel_topic", "complete topic for slack", "topic_completion_cb", "" + ) + w.hook_completion( + "irc_channels", "complete channels for slack", "channel_completion_cb", "" + ) + w.hook_completion( + "irc_privates", "complete dms/mpdms for slack", "dm_completion_cb", "" + ) w.hook_completion("nicks", "complete @-nicks for slack", "nick_completion_cb", "") - w.hook_completion("threads", "complete thread ids for slack", "thread_completion_cb", "") - w.hook_completion("usergroups", "complete @-usergroups for slack", "usergroups_completion_cb", "") + w.hook_completion( + "threads", "complete thread ids for slack", "thread_completion_cb", "" + ) + w.hook_completion( + "usergroups", "complete @-usergroups for slack", "usergroups_completion_cb", "" + ) w.hook_completion("emoji", "complete :emoji: for slack", "emoji_completion_cb", "") - w.key_bind("mouse", { - "@chat(python.*):button2": "hsignal:slack_mouse", - }) - w.key_bind("cursor", { - "@chat(python.*):D": "hsignal:slack_cursor_delete", - "@chat(python.*):L": "hsignal:slack_cursor_linkarchive", - "@chat(python.*):M": "hsignal:slack_cursor_message", - "@chat(python.*):R": "hsignal:slack_cursor_reply", - "@chat(python.*):T": "hsignal:slack_cursor_thread", - }) + w.key_bind( + "mouse", + { + "@chat(python.*):button2": "hsignal:slack_mouse", + }, + ) + w.key_bind( + "cursor", + { + "@chat(python.*):D": "hsignal:slack_cursor_delete", + "@chat(python.*):L": "hsignal:slack_cursor_linkarchive", + "@chat(python.*):M": "hsignal:slack_cursor_message", + "@chat(python.*):R": "hsignal:slack_cursor_reply", + "@chat(python.*):T": "hsignal:slack_cursor_thread", + }, + ) w.hook_hsignal("slack_mouse", "line_event_cb", "auto") w.hook_hsignal("slack_cursor_delete", "line_event_cb", "delete") @@ -5270,6 +6207,7 @@ def setup_hooks(): # w.hook_signal('window_scrolled', "scrolled_cb", "") # w.hook_timer(3000, 0, 0, "slack_connection_persistence_cb", "") + ##### END NEW @@ -5282,10 +6220,10 @@ def dbg(message, level=0, main_buffer=False, fout=False): global debug_string message = "DEBUG: {}".format(message) if fout: - with open('/tmp/debug.log', 'a+') as log_file: - log_file.writelines(message + '\n') + with open("/tmp/debug.log", "a+") as log_file: + log_file.writelines(message + "\n") if main_buffer: - w.prnt("", "slack: " + message) + w.prnt("", "slack: " + message) else: if slack_debug and (not debug_string or debug_string in message): w.prnt(slack_debug, message) @@ -5293,7 +6231,7 @@ def dbg(message, level=0, main_buffer=False, fout=False): ###### Config code class PluginConfig(object): - Setting = collections.namedtuple('Setting', ['default', 'desc']) + Setting = collections.namedtuple("Setting", ["default", "desc"]) # Default settings. # These are, initially, each a (default, desc) tuple; the former is the # default value of the setting, in the (string) format that weechat @@ -5304,185 +6242,216 @@ class PluginConfig(object): # Following this procedure, the keys remain the same, but the values are # the real (python) values of the settings. default_settings = { - 'auto_open_threads': Setting( - default='false', - desc='Automatically open threads when mentioned or in' - 'response to own messages.'), - 'background_load_all_history': Setting( - default='true', - desc='Load the history for all channels in the background when the script is loaded,' - ' rather than waiting until the buffer is switched to. You can set this to false if' - ' you experience performance issues, however that causes some loss of functionality,' - ' see known issues in the readme.'), - 'channel_name_typing_indicator': Setting( - default='true', - desc='Change the prefix of a channel from # to > when someone is' - ' typing in it. Note that this will (temporarily) affect the sort' - ' order if you sort buffers by name rather than by number.'), - 'color_buflist_muted_channels': Setting( - default='darkgray', - desc='Color to use for muted channels in the buflist'), - 'color_deleted': Setting( - default='red', - desc='Color to use for deleted messages and files.'), - 'color_edited_suffix': Setting( - default='095', - desc='Color to use for (edited) suffix on messages that have been edited.'), - 'color_reaction_suffix': Setting( - default='darkgray', - desc='Color to use for the [:wave:(@user)] suffix on messages that' - ' have reactions attached to them.'), - 'color_reaction_suffix_added_by_you': Setting( - default='blue', - desc='Color to use for reactions that you have added.'), - 'color_thread_suffix': Setting( - default='lightcyan', - desc='Color to use for the [thread: XXX] suffix on messages that' + "auto_open_threads": Setting( + default="false", + desc="Automatically open threads when mentioned or in" + "response to own messages.", + ), + "background_load_all_history": Setting( + default="true", + desc="Load the history for all channels in the background when the script is loaded," + " rather than waiting until the buffer is switched to. You can set this to false if" + " you experience performance issues, however that causes some loss of functionality," + " see known issues in the readme.", + ), + "channel_name_typing_indicator": Setting( + default="true", + desc="Change the prefix of a channel from # to > when someone is" + " typing in it. Note that this will (temporarily) affect the sort" + " order if you sort buffers by name rather than by number.", + ), + "color_buflist_muted_channels": Setting( + default="darkgray", desc="Color to use for muted channels in the buflist" + ), + "color_deleted": Setting( + default="red", desc="Color to use for deleted messages and files." + ), + "color_edited_suffix": Setting( + default="095", + desc="Color to use for (edited) suffix on messages that have been edited.", + ), + "color_reaction_suffix": Setting( + default="darkgray", + desc="Color to use for the [:wave:(@user)] suffix on messages that" + " have reactions attached to them.", + ), + "color_reaction_suffix_added_by_you": Setting( + default="blue", desc="Color to use for reactions that you have added." + ), + "color_thread_suffix": Setting( + default="lightcyan", + desc="Color to use for the [thread: XXX] suffix on messages that" ' have threads attached to them. The special value "multiple" can' - ' be used to use a different color for each thread.'), - 'color_typing_notice': Setting( - default='yellow', - desc='Color to use for the typing notice.'), - 'colorize_attachments': Setting( - default='prefix', + " be used to use a different color for each thread.", + ), + "color_typing_notice": Setting( + default="yellow", desc="Color to use for the typing notice." + ), + "colorize_attachments": Setting( + default="prefix", desc='Whether to colorize attachment lines. Values: "prefix": Only colorize' - ' the prefix, "all": Colorize the whole line, "none": Don\'t colorize.'), - 'colorize_private_chats': Setting( - default='false', - desc='Whether to use nick-colors in DM windows.'), - 'debug_mode': Setting( - default='false', - desc='Open a dedicated buffer for debug messages and start logging' - ' to it. How verbose the logging is depends on log_level.'), - 'debug_level': Setting( - default='3', - desc='Show only this level of debug info (or higher) when' - ' debug_mode is on. Lower levels -> more messages.'), - 'distracting_channels': Setting( - default='', - desc='List of channels to hide.'), - 'external_user_suffix': Setting( - default='*', - desc='The suffix appended to nicks to indicate external users.'), - 'files_download_location': Setting( - default='', - desc='If set, file attachments will be automatically downloaded' + ' the prefix, "all": Colorize the whole line, "none": Don\'t colorize.', + ), + "colorize_private_chats": Setting( + default="false", desc="Whether to use nick-colors in DM windows." + ), + "debug_mode": Setting( + default="false", + desc="Open a dedicated buffer for debug messages and start logging" + " to it. How verbose the logging is depends on log_level.", + ), + "debug_level": Setting( + default="3", + desc="Show only this level of debug info (or higher) when" + " debug_mode is on. Lower levels -> more messages.", + ), + "distracting_channels": Setting(default="", desc="List of channels to hide."), + "external_user_suffix": Setting( + default="*", desc="The suffix appended to nicks to indicate external users." + ), + "files_download_location": Setting( + default="", + desc="If set, file attachments will be automatically downloaded" ' to this location. "%h" will be replaced by WeeChat home,' - ' "~/.weechat" by default. Requires WeeChat 2.2 or newer.'), - 'group_name_prefix': Setting( - default='&', - desc='The prefix of buffer names for groups (private channels).'), - 'history_fetch_count': Setting( - default='200', - desc='The number of messages to fetch for each channel when fetching' - ' history, between 1 and 1000.'), - 'link_previews': Setting( - default='true', - desc='Show previews of website content linked by teammates.'), - 'map_underline_to': Setting( - default='_', - desc='When sending underlined text to slack, use this formatting' + ' "~/.weechat" by default. Requires WeeChat 2.2 or newer.', + ), + "group_name_prefix": Setting( + default="&", + desc="The prefix of buffer names for groups (private channels).", + ), + "history_fetch_count": Setting( + default="200", + desc="The number of messages to fetch for each channel when fetching" + " history, between 1 and 1000.", + ), + "link_previews": Setting( + default="true", desc="Show previews of website content linked by teammates." + ), + "map_underline_to": Setting( + default="_", + desc="When sending underlined text to slack, use this formatting" ' character for it. The default ("_") sends it as italics. Use' - ' "*" to send bold instead.'), - 'muted_channels_activity': Setting( - default='personal_highlights', + ' "*" to send bold instead.', + ), + "muted_channels_activity": Setting( + default="personal_highlights", desc="Control which activity you see from muted channels, either" " none, personal_highlights, all_highlights or all. none: Don't" " show any activity. personal_highlights: Only show personal" " highlights, i.e. not @channel and @here. all_highlights: Show" " all highlights, but not other messages. all: Show all activity," - " like other channels."), - 'notify_subscribed_threads': Setting( - default='auto', + " like other channels.", + ), + "notify_subscribed_threads": Setting( + default="auto", desc="Control if you want to see a notification in the team buffer when a" " thread you're subscribed to receives a new message, either auto, true or" " false. auto means that you only get a notification if auto_open_threads" - " and thread_messages_in_channel both are false. Defaults to auto."), - 'notify_usergroup_handle_updated': Setting( - default='false', + " and thread_messages_in_channel both are false. Defaults to auto.", + ), + "notify_usergroup_handle_updated": Setting( + default="false", desc="Control if you want to see a notification in the team buffer when a" - "usergroup's handle has changed, either true or false."), - 'never_away': Setting( - default='false', - desc='Poke Slack every five minutes so that it never marks you "away".'), - 'record_events': Setting( - default='false', - desc='Log all traffic from Slack to disk as JSON.'), - 'render_bold_as': Setting( - default='bold', - desc='When receiving bold text from Slack, render it as this in weechat.'), - 'render_emoji_as_string': Setting( - default='false', + "usergroup's handle has changed, either true or false.", + ), + "never_away": Setting( + default="false", + desc='Poke Slack every five minutes so that it never marks you "away".', + ), + "record_events": Setting( + default="false", desc="Log all traffic from Slack to disk as JSON." + ), + "render_bold_as": Setting( + default="bold", + desc="When receiving bold text from Slack, render it as this in weechat.", + ), + "render_emoji_as_string": Setting( + default="false", desc="Render emojis as :emoji_name: instead of emoji characters. Enable this" " if your terminal doesn't support emojis, or set to 'both' if you want to" " see both renderings. Note that even though this is" " disabled by default, you need to place {}/blob/master/weemoji.json in your" - " weechat directory to enable rendering emojis as emoji characters." - .format(REPO_URL)), - 'render_italic_as': Setting( - 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' - ' will be used instead of the actual name of the slack (in buffer' - ' names, logging, etc). E.g `work:no_fun_allowed` would make your' - ' work slack show up as `no_fun_allowed` rather than `work.slack.com`.'), - 'shared_name_prefix': Setting( - default='%', - desc='The prefix of buffer names for shared channels.'), - 'short_buffer_names': Setting( - default='false', - desc='Use `foo.#channel` rather than `foo.slack.com.#channel` as the' - ' internal name for Slack buffers.'), - 'show_buflist_presence': Setting( - default='true', - desc='Display a `+` character in the buffer list for present users.'), - 'show_reaction_nicks': Setting( - default='false', - desc='Display the name of the reacting user(s) alongside each reactji.'), - 'slack_api_token': Setting( - default='INSERT VALID KEY HERE!', - desc='List of Slack API tokens, one per Slack instance you want to' - ' connect to. See the README for details on how to get these.'), - 'slack_timeout': Setting( - default='20000', - desc='How long (ms) to wait when communicating with Slack.'), - 'switch_buffer_on_join': Setting( - default='true', - desc='When /joining a channel, automatically switch to it as well.'), - 'thread_messages_in_channel': Setting( - default='false', - desc='When enabled shows thread messages in the parent channel.'), - 'unfurl_ignore_alt_text': Setting( - default='false', + " weechat directory to enable rendering emojis as emoji characters.".format( + REPO_URL + ), + ), + "render_italic_as": Setting( + 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" + " will be used instead of the actual name of the slack (in buffer" + " names, logging, etc). E.g `work:no_fun_allowed` would make your" + " work slack show up as `no_fun_allowed` rather than `work.slack.com`.", + ), + "shared_name_prefix": Setting( + default="%", desc="The prefix of buffer names for shared channels." + ), + "short_buffer_names": Setting( + default="false", + desc="Use `foo.#channel` rather than `foo.slack.com.#channel` as the" + " internal name for Slack buffers.", + ), + "show_buflist_presence": Setting( + default="true", + desc="Display a `+` character in the buffer list for present users.", + ), + "show_reaction_nicks": Setting( + default="false", + desc="Display the name of the reacting user(s) alongside each reactji.", + ), + "slack_api_token": Setting( + default="INSERT VALID KEY HERE!", + desc="List of Slack API tokens, one per Slack instance you want to" + " connect to. See the README for details on how to get these.", + ), + "slack_timeout": Setting( + default="20000", desc="How long (ms) to wait when communicating with Slack." + ), + "switch_buffer_on_join": Setting( + default="true", + desc="When /joining a channel, automatically switch to it as well.", + ), + "thread_messages_in_channel": Setting( + default="false", + desc="When enabled shows thread messages in the parent channel.", + ), + "unfurl_ignore_alt_text": Setting( + default="false", 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', + " 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' + " 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' - ' previously hidden (whether by the user or by the' - ' distracting_channels setting).'), - 'use_full_names': Setting( - default='false', - desc='Use full names as the nicks for all users. When this is' - ' false (the default), display names will be used if set, with a' - ' fallback to the full name if display name is not set.'), + " to display both.", + ), + "unhide_buffers_with_activity": Setting( + default="false", + desc="When activity occurs on a buffer, unhide it even if it was" + " previously hidden (whether by the user or by the" + " distracting_channels setting).", + ), + "use_full_names": Setting( + default="false", + desc="Use full names as the nicks for all users. When this is" + " false (the default), display names will be used if set, with a" + " fallback to the full name if display name is not set.", + ), } # Set missing settings to their defaults. Load non-missing settings from @@ -5504,7 +6473,9 @@ class PluginConfig(object): self.config_changed(None, None, None) def __str__(self): - return "".join([x + "\t" + str(self.settings[x]) + "\n" for x in self.settings.keys()]) + return "".join( + [x + "\t" + str(self.settings[x]) + "\n" for x in self.settings.keys()] + ) def config_changed(self, data, full_key, value): if full_key is None: @@ -5514,13 +6485,15 @@ class PluginConfig(object): key = full_key.replace(CONFIG_PREFIX + ".", "") self.settings[key] = self.fetch_setting(key) - if (full_key is None or full_key == CONFIG_PREFIX + ".debug_mode") and self.debug_mode: + if ( + full_key is None or full_key == CONFIG_PREFIX + ".debug_mode" + ) and self.debug_mode: create_slack_debug_buffer() return w.WEECHAT_RC_OK def fetch_setting(self, key): try: - return getattr(self, 'get_' + key)(key) + return getattr(self, "get_" + key)(key) except AttributeError: # Most settings are on/off, so make get_boolean the default return self.get_boolean(key) @@ -5570,15 +6543,15 @@ class PluginConfig(object): 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(',') if x] + return [x.strip() for x in w.config_get_plugin(key).split(",") if x] def get_server_aliases(self, key): alias_list = w.config_get_plugin(key) - return dict(item.split(":") for item in alias_list.split(",") if ':' in item) + return dict(item.split(":") for item in alias_list.split(",") if ":" in item) def get_slack_api_token(self, key): token = w.config_get_plugin("slack_api_token") - if token.startswith('${sec.data'): + if token.startswith("${sec.data"): return w.string_eval_expression(token, {}, {}, {}) else: return token @@ -5590,10 +6563,10 @@ class PluginConfig(object): return w.config_string_to_boolean(value) def get_notify_subscribed_threads(self, key): - return self.get_string_or_boolean(key, 'auto') + return self.get_string_or_boolean(key, "auto") def get_render_emoji_as_string(self, key): - return self.get_string_or_boolean(key, 'both') + return self.get_string_or_boolean(key, "both") def migrate(self): """ @@ -5625,16 +6598,16 @@ def config_server_buffer_cb(data, key, value): def setup_trace(): global f now = time.time() - f = open('{}/{}-trace.json'.format(RECORD_DIR, now), 'w') + f = open("{}/{}-trace.json".format(RECORD_DIR, now), "w") def trace_calls(frame, event, arg): global f - if event != 'call': + if event != "call": return co = frame.f_code func_name = co.co_name - if func_name == 'write': + if func_name == "write": # Ignore write() calls from print statements return func_line_no = frame.f_lineno @@ -5642,38 +6615,57 @@ def trace_calls(frame, event, arg): caller = frame.f_back caller_line_no = caller.f_lineno caller_filename = caller.f_code.co_filename - print('Call to %s on line %s of %s from line %s of %s' % \ - (func_name, func_line_no, func_filename, - caller_line_no, caller_filename), file=f) + print( + "Call to %s on line %s of %s from line %s of %s" + % (func_name, func_line_no, func_filename, caller_line_no, caller_filename), + file=f, + ) f.flush() return def initiate_connection(token, retries=3, team=None, reconnect=False): - return SlackRequest(team, - 'rtm.{}'.format('connect' if team else 'start'), - {"batch_presence_aware": 1}, - retries=retries, - token=token, - metadata={'reconnect': reconnect}) + return SlackRequest( + team, + "rtm.{}".format("connect" if team else "start"), + {"batch_presence_aware": 1}, + retries=retries, + token=token, + metadata={"reconnect": reconnect}, + ) if __name__ == "__main__": w = WeechatWrapper(weechat) - if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, - SCRIPT_DESC, "script_unloaded", ""): + if w.register( + SCRIPT_NAME, + SCRIPT_AUTHOR, + SCRIPT_VERSION, + SCRIPT_LICENSE, + SCRIPT_DESC, + "script_unloaded", + "", + ): weechat_version = int(w.info_get("version_number", "") or 0) weechat_upgrading = w.info_get("weechat_upgrading", "") if weechat_version < 0x1030000: - w.prnt("", "\nERROR: Weechat version 1.3+ is required to use {}.\n\n".format(SCRIPT_NAME)) + w.prnt( + "", + "\nERROR: Weechat version 1.3+ is required to use {}.\n\n".format( + SCRIPT_NAME + ), + ) elif weechat_upgrading == "1": - w.prnt("", "NOTE: wee-slack will not work after running /upgrade until it's" + w.prnt( + "", + "NOTE: wee-slack will not work after running /upgrade until it's" " reloaded. Please run `/python reload slack` to continue using it. You" - " will not receive any new messages in wee-slack buffers until doing this.") + " will not receive any new messages in wee-slack buffers until doing this.", + ) else: global EVENTROUTER @@ -5707,9 +6699,13 @@ if __name__ == "__main__": auto_connect = weechat.info_get("auto_connect", "") != "0" if auto_connect: - tokens = [token.strip() for token in config.slack_api_token.split(',')] - w.prnt('', 'Connecting to {} slack team{}.' - .format(len(tokens), '' if len(tokens) == 1 else 's')) + tokens = [token.strip() for token in config.slack_api_token.split(",")] + w.prnt( + "", + "Connecting to {} slack team{}.".format( + len(tokens), "" if len(tokens) == 1 else "s" + ), + ) for t in tokens: s = initiate_connection(t) EVENTROUTER.receive(s) |