diff --git a/matrix/colors.py b/matrix/colors.py index 8a5c12a..0a672a8 100644 --- a/matrix/colors.py +++ b/matrix/colors.py @@ -2,6 +2,8 @@ from __future__ import unicode_literals +# pylint: disable=redefined-builtin +from builtins import str from collections import namedtuple import webcolors diff --git a/matrix/commands.py b/matrix/commands.py new file mode 100644 index 0000000..95ea287 --- /dev/null +++ b/matrix/commands.py @@ -0,0 +1,445 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +import re + +import matrix.globals + +from matrix.utf import utf8_decode +from matrix.api import MatrixMessage, MessageType +from matrix.utils import key_from_value, tags_from_line_data +from matrix.socket import send_or_queue + + +W = matrix.globals.W +GLOBAL_OPTIONS = matrix.globals.OPTIONS +SERVERS = matrix.globals.SERVERS + + +def hook_commands(): + W.hook_completion( + "matrix_server_commands", + "Matrix server completion", + "server_command_completion_cb", + "" + ) + + W.hook_completion( + "matrix_servers", + "Matrix server completion", + "matrix_server_completion_cb", + "" + ) + + W.hook_completion( + "matrix_commands", + "Matrix command completion", + "matrix_command_completion_cb", + "" + ) + + W.hook_completion( + "matrix_messages", + "Matrix message completion", + "matrix_message_completion_cb", + "" + ) + + W.hook_completion( + "matrix_debug_types", + "Matrix debugging type completion", + "matrix_debug_completion_cb", + "" + ) + + W.hook_command( + # Command name and short description + 'matrix', 'Matrix chat protocol command', + # Synopsis + ( + 'server add [:] ||' + 'server delete|list|listfull ||' + 'connect ||' + 'disconnect ||' + 'reconnect ||' + 'debug ||' + 'help ' + ), + # Description + ( + ' server: list, add, or remove Matrix servers\n' + ' connect: connect to Matrix servers\n' + 'disconnect: disconnect from one or all Matrix servers\n' + ' reconnect: reconnect to server(s)\n\n' + ' help: show detailed command help\n\n' + ' debug: enable or disable debugging\n\n' + 'Use /matrix help [command] to find out more\n' + ), + # Completions + ( + 'server %(matrix_server_commands)|%* ||' + 'connect %(matrix_servers) ||' + 'disconnect %(matrix_servers) ||' + 'reconnect %(matrix_servers) ||' + 'debug %(matrix_debug_types) ||' + 'help %(matrix_commands)' + ), + # Function name + 'matrix_command_cb', '') + + W.hook_command( + # Command name and short description + 'redact', 'redact messages', + # Synopsis + ( + '[:<"message-part">] []' + ), + # Description + ( + "message-number: number of the message to redact (message numbers" + "\n start from the last recieved as " + "1 and count up)\n" + " message-part: a shortened part of the message\n" + " reason: the redaction reason\n" + ), + # Completions + ( + '%(matrix_messages)' + ), + # Function name + 'matrix_redact_command_cb', '') + + W.hook_command_run('/topic', 'matrix_command_topic_cb', '') + W.hook_command_run('/buffer clear', 'matrix_command_buf_clear_cb', '') + W.hook_command_run('/join', 'matrix_command_join_cb', '') + W.hook_command_run('/part', 'matrix_command_part_cb', '') + W.hook_command_run('/invite', 'matrix_command_invite_cb', '') + + if GLOBAL_OPTIONS.enable_backlog: + hook_page_up() + + +def matrix_fetch_old_messages(server, room_id): + room = server.rooms[room_id] + prev_batch = room.prev_batch + + if not prev_batch: + return + + message = MatrixMessage(server, GLOBAL_OPTIONS, MessageType.ROOM_MSG, + room_id=room_id, extra_id=prev_batch) + + send_or_queue(server, message) + + return + + +def hook_page_up(): + GLOBAL_OPTIONS.page_up_hook = W.hook_command_run( + '/window page_up', + 'matrix_command_pgup_cb', + '' + ) + + +@utf8_decode +def matrix_debug_completion_cb(data, completion_item, buffer, completion): + for debug_type in ["messaging", "network", "timing"]: + W.hook_completion_list_add( + completion, + debug_type, + 0, + W.WEECHAT_LIST_POS_SORT) + return W.WEECHAT_RC_OK + + +@utf8_decode +def matrix_command_buf_clear_cb(data, buffer, command): + for server in SERVERS.values(): + if buffer in server.buffers.values(): + room_id = key_from_value(server.buffers, buffer) + server.rooms[room_id].prev_batch = server.next_batch + + return W.WEECHAT_RC_OK + + return W.WEECHAT_RC_OK + + +@utf8_decode +def matrix_command_pgup_cb(data, buffer, command): + # TODO the highlight status of a line isn't allowed to be updated/changed + # via hdata, therefore the highlight status of a messages can't be + # reoredered this would need to be fixed in weechat + # TODO we shouldn't fetch and print out more messages than + # max_buffer_lines_number or older messages than max_buffer_lines_minutes + for server in SERVERS.values(): + if buffer in server.buffers.values(): + window = W.window_search_with_buffer(buffer) + + first_line_displayed = bool( + W.window_get_integer(window, "first_line_displayed") + ) + + if first_line_displayed: + room_id = key_from_value(server.buffers, buffer) + matrix_fetch_old_messages(server, room_id) + + return W.WEECHAT_RC_OK + + return W.WEECHAT_RC_OK + + +@utf8_decode +def matrix_command_join_cb(data, buffer, command): + def join(server, args): + split_args = args.split(" ", 1) + + # TODO handle join for non public rooms + if len(split_args) != 2: + message = ("{prefix}Error with command \"/join\" (help on " + "command: /help join)").format( + prefix=W.prefix("error")) + W.prnt("", message) + return + + _, room_id = split_args + message = MatrixMessage( + server, + GLOBAL_OPTIONS, + MessageType.JOIN, + room_id=room_id + ) + send_or_queue(server, message) + + for server in SERVERS.values(): + if buffer in server.buffers.values(): + join(server, command) + return W.WEECHAT_RC_OK_EAT + elif buffer == server.server_buffer: + join(server, command) + return W.WEECHAT_RC_OK_EAT + + return W.WEECHAT_RC_OK + + +@utf8_decode +def matrix_command_part_cb(data, buffer, command): + def part(server, buffer, args): + rooms = [] + + split_args = args.split(" ", 1) + + if len(split_args) == 1: + if buffer == server.server_buffer: + message = ("{prefix}Error with command \"/part\" (help on " + "command: /help part)").format( + prefix=W.prefix("error")) + W.prnt("", message) + return + + rooms = [key_from_value(server.buffers, buffer)] + + else: + _, rooms = split_args + rooms = rooms.split(" ") + + for room_id in rooms: + message = MatrixMessage( + server, + GLOBAL_OPTIONS, + MessageType.PART, + room_id=room_id + ) + send_or_queue(server, message) + + for server in SERVERS.values(): + if buffer in server.buffers.values(): + part(server, buffer, command) + return W.WEECHAT_RC_OK_EAT + elif buffer == server.server_buffer: + part(server, buffer, command) + return W.WEECHAT_RC_OK_EAT + + return W.WEECHAT_RC_OK + + +@utf8_decode +def matrix_command_invite_cb(data, buffer, command): + def invite(server, buf, args): + split_args = args.split(" ", 1) + + # TODO handle join for non public rooms + if len(split_args) != 2: + message = ("{prefix}Error with command \"/invite\" (help on " + "command: /help invite)").format( + prefix=W.prefix("error")) + W.prnt("", message) + return + + _, invitee = split_args + room_id = key_from_value(server.buffers, buf) + + body = {"user_id": invitee} + + message = MatrixMessage( + server, + GLOBAL_OPTIONS, + MessageType.INVITE, + room_id=room_id, + data=body + ) + send_or_queue(server, message) + + for server in SERVERS.values(): + if buffer in server.buffers.values(): + invite(server, buffer, command) + return W.WEECHAT_RC_OK_EAT + + return W.WEECHAT_RC_OK + + +def event_id_from_line(buf, target_number): + # type: (weechat.buffer, int) -> str + own_lines = W.hdata_pointer(W.hdata_get('buffer'), buf, 'own_lines') + if own_lines: + line = W.hdata_pointer( + W.hdata_get('lines'), + own_lines, + 'last_line' + ) + + line_number = 1 + + while line: + line_data = W.hdata_pointer( + W.hdata_get('line'), + line, + 'data' + ) + + if line_data: + tags = tags_from_line_data(line_data) + + # Only count non redacted user messages + if ("matrix_message" in tags + and 'matrix_redacted' not in tags + and "matrix_new_redacted" not in tags): + + if line_number == target_number: + for tag in tags: + if tag.startswith("matrix_id"): + event_id = tag[10:] + return event_id + + line_number += 1 + + line = W.hdata_move(W.hdata_get('line'), line, -1) + + return "" + + +@utf8_decode +def matrix_redact_command_cb(data, buffer, args): + for server in SERVERS.values(): + if buffer in server.buffers.values(): + body = {} + + room_id = key_from_value(server.buffers, buffer) + + matches = re.match(r"(\d+)(:\".*\")? ?(.*)?", args) + + if not matches: + message = ("{prefix}matrix: Invalid command arguments (see " + "the help for the command /help redact)").format( + prefix=W.prefix("error")) + W.prnt("", message) + return W.WEECHAT_RC_ERROR + + line_string, _, reason = matches.groups() + line = int(line_string) + + if reason: + body = {"reason": reason} + + event_id = event_id_from_line(buffer, line) + + if not event_id: + message = ("{prefix}matrix: No such message with number " + "{number} found").format( + prefix=W.prefix("error"), + number=line) + W.prnt("", message) + return W.WEECHAT_RC_OK + + message = MatrixMessage( + server, + GLOBAL_OPTIONS, + MessageType.REDACT, + data=body, + room_id=room_id, + extra_id=event_id + ) + send_or_queue(server, message) + + return W.WEECHAT_RC_OK + + elif buffer == server.server_buffer: + message = ("{prefix}matrix: command \"redact\" must be " + "executed on a Matrix channel buffer").format( + prefix=W.prefix("error")) + W.prnt("", message) + return W.WEECHAT_RC_OK + + return W.WEECHAT_RC_OK + + +@utf8_decode +def matrix_message_completion_cb(data, completion_item, buffer, completion): + own_lines = W.hdata_pointer(W.hdata_get('buffer'), buffer, 'own_lines') + if own_lines: + line = W.hdata_pointer( + W.hdata_get('lines'), + own_lines, + 'last_line' + ) + + line_number = 1 + + while line: + line_data = W.hdata_pointer( + W.hdata_get('line'), + line, + 'data' + ) + + if line_data: + message = W.hdata_string(W.hdata_get('line_data'), line_data, + 'message') + + tags = tags_from_line_data(line_data) + + # Only add non redacted user messages to the completion + if (message + and 'matrix_message' in tags + and 'matrix_redacted' not in tags): + + if len(message) > GLOBAL_OPTIONS.redaction_comp_len + 2: + message = ( + message[:GLOBAL_OPTIONS.redaction_comp_len] + + '..') + + item = ("{number}:\"{message}\"").format( + number=line_number, + message=message) + + W.hook_completion_list_add( + completion, + item, + 0, + W.WEECHAT_LIST_POS_END) + line_number += 1 + + line = W.hdata_move(W.hdata_get('line'), line, -1) + + return W.WEECHAT_RC_OK diff --git a/matrix/globals.py b/matrix/globals.py new file mode 100644 index 0000000..bda4324 --- /dev/null +++ b/matrix/globals.py @@ -0,0 +1,96 @@ +# pylint: disable=import-error +import sys + +from matrix.utf import WeechatWrapper +from matrix.config import PluginOptions, Option + +import weechat + + +def init_matrix_config(): + config_file = W.config_new("matrix", "matrix_config_reload_cb", "") + + look_options = [ + Option( + "redactions", "integer", + "strikethrough|notice|delete", 0, 0, + "strikethrough", + ( + "Only notice redactions, strike through or delete " + "redacted messages" + ) + ), + Option( + "server_buffer", "integer", + "merge_with_core|merge_without_core|independent", + 0, 0, "merge_with_core", "Merge server buffers" + ) + ] + + network_options = [ + Option( + "max_initial_sync_events", "integer", + "", 1, 10000, + "30", + ( + "How many events to fetch during the initial sync" + ) + ), + Option( + "max_backlog_sync_events", "integer", + "", 1, 100, + "10", + ( + "How many events to fetch during backlog fetching" + ) + ), + Option( + "fetch_backlog_on_pgup", "boolean", + "", 0, 0, + "on", + ( + "Fetch messages in the backlog on a window page up event" + ) + ) + ] + + def add_global_options(section, options): + for option in options: + OPTIONS.options[option.name] = W.config_new_option( + config_file, section, option.name, + option.type, option.description, option.string_values, + option.min, option.max, option.value, option.value, 0, "", + "", "matrix_config_change_cb", "", "", "") + + section = W.config_new_section(config_file, "color", 0, 0, "", "", "", "", + "", "", "", "", "", "") + + # TODO color options + + section = W.config_new_section(config_file, "look", 0, 0, "", "", "", "", + "", "", "", "", "", "") + + add_global_options(section, look_options) + + section = W.config_new_section(config_file, "network", 0, 0, "", "", "", + "", "", "", "", "", "", "") + + add_global_options(section, network_options) + + W.config_new_section( + config_file, "server", + 0, 0, + "matrix_config_server_read_cb", + "", + "matrix_config_server_write_cb", + "", "", "", "", "", "", "" + ) + + return config_file + + +W = weechat if sys.hexversion >= 0x3000000 else WeechatWrapper(weechat) + +OPTIONS = PluginOptions() # type: PluginOptions +SERVERS = dict() # type: Dict[str, MatrixServer] +CONFIG = None # type: weechat.config diff --git a/matrix/server.py b/matrix/server.py index 2f7a293..e2036d8 100644 --- a/matrix/server.py +++ b/matrix/server.py @@ -5,10 +5,8 @@ from __future__ import unicode_literals import ssl from collections import deque - from http_parser.pyparser import HttpParser - from matrix.config import Option diff --git a/matrix/socket.py b/matrix/socket.py new file mode 100644 index 0000000..2b6f2a0 --- /dev/null +++ b/matrix/socket.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +import time + +from builtins import bytes, str + +import matrix.globals +from matrix.config import DebugType +from matrix.utils import prnt_debug, server_buffer_prnt + + +W = matrix.globals.W + + +def disconnect(server): + # type: (MatrixServer) -> None + if server.fd_hook: + W.unhook(server.fd_hook) + + server.fd_hook = None + server.socket = None + server.connected = False + + server_buffer_prnt(server, "Disconnected") + + +def send_or_queue(server, message): + # type: (MatrixServer, MatrixMessage) -> None + if not send(server, message): + prnt_debug(DebugType.MESSAGING, server, + ("{prefix} Failed sending message of type {t}. " + "Adding to queue").format( + prefix=W.prefix("error"), + t=message.type)) + server.send_queue.append(message) + + +def send(server, message): + # type: (MatrixServer, MatrixMessage) -> bool + + request = message.request.request + payload = message.request.payload + + prnt_debug(DebugType.MESSAGING, server, + "{prefix} Sending message of type {t}.".format( + prefix=W.prefix("error"), + t=message.type)) + + try: + start = time.time() + + # TODO we probably shouldn't use sendall here. + server.socket.sendall(bytes(request, 'utf-8')) + if payload: + server.socket.sendall(bytes(payload, 'utf-8')) + + end = time.time() + message.send_time = end + send_time = (end - start) * 1000 + prnt_debug(DebugType.NETWORK, server, + ("Message done sending ({t}ms), putting message in the " + "receive queue.").format(t=send_time)) + + server.receive_queue.append(message) + return True + + except OSError as error: + disconnect(server) + server_buffer_prnt(server, str(error)) + return False diff --git a/matrix/utils.py b/matrix/utils.py new file mode 100644 index 0000000..4e3ee27 --- /dev/null +++ b/matrix/utils.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +import time + +import matrix.globals + + +W = matrix.globals.W +GLOBAL_OPTIONS = matrix.globals.OPTIONS + + +def key_from_value(dictionary, value): + # type: (Dict[str, Any], Any) -> str + return list(dictionary.keys())[list(dictionary.values()).index(value)] + + +def prnt_debug(debug_type, server, message): + if debug_type in GLOBAL_OPTIONS.debug: + W.prnt(server.server_buffer, message) + + +def server_buffer_prnt(server, string): + # type: (MatrixServer, str) -> None + assert server.server_buffer + buffer = server.server_buffer + now = int(time.time()) + W.prnt_date_tags(buffer, now, "", string) + + +def tags_from_line_data(line_data): + # type: (weechat.hdata) -> List[str] + tags_count = W.hdata_get_var_array_size( + W.hdata_get('line_data'), + line_data, + 'tags_array') + + tags = [ + W.hdata_string( + W.hdata_get('line_data'), + line_data, + '%d|tags_array' % i + ) for i in range(tags_count)] + + return tags diff --git a/weechat-matrix.py b/weechat-matrix.py index e3d4edb..67eb969 100644 --- a/weechat-matrix.py +++ b/weechat-matrix.py @@ -8,11 +8,9 @@ import ssl import time import datetime import pprint -import re -import sys # pylint: disable=redefined-builtin -from builtins import bytes, str +from builtins import str from operator import itemgetter @@ -20,20 +18,48 @@ from operator import itemgetter from typing import (List, Set, Dict, Tuple, Text, Optional, AnyStr, Deque, Any) from matrix import colors -from matrix.utf import WeechatWrapper, utf8_decode +from matrix.utf import utf8_decode from matrix.http import HttpResponse from matrix.api import MatrixMessage, MessageType from matrix.server import MatrixServer +from matrix.socket import disconnect, send_or_queue, send + + +# Weechat searches for the registered callbacks in the global scope, import the +# callbacks here so weechat can find them. +from matrix.commands import ( + hook_commands, + hook_page_up, + matrix_command_join_cb, + matrix_command_part_cb, + matrix_command_invite_cb, + matrix_command_pgup_cb, + matrix_redact_command_cb, + matrix_command_buf_clear_cb, + matrix_debug_completion_cb, + matrix_message_completion_cb +) + +from matrix.utils import ( + key_from_value, + server_buffer_prnt, + prnt_debug, + tags_from_line_data +) + from matrix.config import ( - PluginOptions, - Option, DebugType, RedactType, ServerBufferType ) -# pylint: disable=import-error -import weechat +import matrix.globals + +W = matrix.globals.W +GLOBAL_OPTIONS = matrix.globals.OPTIONS +CONFIG = matrix.globals.CONFIG +SERVERS = matrix.globals.SERVERS + WEECHAT_SCRIPT_NAME = "matrix" # type: str WEECHAT_SCRIPT_DESCRIPTION = "matrix chat plugin" # type: str @@ -41,15 +67,6 @@ WEECHAT_SCRIPT_AUTHOR = "Damir Jelić " # type: str WEECHAT_SCRIPT_VERSION = "0.1" # type: str WEECHAT_SCRIPT_LICENSE = "MIT" # type: str -SERVERS = dict() # type: Dict[str, MatrixServer] -CONFIG = None # type: weechat.config -GLOBAL_OPTIONS = None # type: PluginOptions - - -def prnt_debug(debug_type, server, message): - if debug_type in GLOBAL_OPTIONS.debug: - W.prnt(server.server_buffer, message) - class MatrixUser: def __init__(self, name, display_name): @@ -73,11 +90,6 @@ class MatrixRoom: self.encrypted = False # type: bool -def key_from_value(dictionary, value): - # type: (Dict[str, Any], Any) -> str - return list(dictionary.keys())[list(dictionary.values()).index(value)] - - @utf8_decode def server_config_change_cb(server_name, option): # type: (str, weechat.config_option) -> int @@ -363,8 +375,8 @@ def date_from_age(age): def color_for_tags(color): if color == "weechat.color.chat_nick_self": - option = weechat.config_get(color) - return weechat.config_string(option) + option = W.config_get(color) + return W.config_string(option) return color @@ -964,52 +976,6 @@ def matrix_login(server): send_or_queue(server, message) -def send_or_queue(server, message): - # type: (MatrixServer, MatrixMessage) -> None - if not send(server, message): - prnt_debug(DebugType.MESSAGING, server, - ("{prefix} Failed sending message of type {t}. " - "Adding to queue").format( - prefix=W.prefix("error"), - t=message.type)) - server.send_queue.append(message) - - -def send(server, message): - # type: (MatrixServer, MatrixMessage) -> bool - - request = message.request.request - payload = message.request.payload - - prnt_debug(DebugType.MESSAGING, server, - "{prefix} Sending message of type {t}.".format( - prefix=W.prefix("error"), - t=message.type)) - - try: - start = time.time() - - # TODO we probably shouldn't use sendall here. - server.socket.sendall(bytes(request, 'utf-8')) - if payload: - server.socket.sendall(bytes(payload, 'utf-8')) - - end = time.time() - message.send_time = end - send_time = (end - start) * 1000 - prnt_debug(DebugType.NETWORK, server, - ("Message done sending ({t}ms), putting message in the " - "receive queue.").format(t=send_time)) - - server.receive_queue.append(message) - return True - - except socket.error as error: - disconnect(server) - server_buffer_prnt(server, str(error)) - return False - - @utf8_decode def receive_cb(server_name, file_descriptor): server = SERVERS[server_name] @@ -1081,26 +1047,6 @@ def close_socket(server): server.socket.close() -def disconnect(server): - # type: (MatrixServer) -> None - if server.fd_hook: - W.unhook(server.fd_hook) - - server.fd_hook = None - server.socket = None - server.connected = False - - server_buffer_prnt(server, "Disconnected") - - -def server_buffer_prnt(server, string): - # type: (MatrixServer, str) -> None - assert server.server_buffer - buffer = server.server_buffer - now = int(time.time()) - W.prnt_date_tags(buffer, now, "", string) - - def server_buffer_set_title(server): # type: (MatrixServer) -> None if server.numeric_address: @@ -1421,7 +1367,7 @@ def matrix_config_change_cb(data, option): if GLOBAL_OPTIONS.enable_backlog: if not GLOBAL_OPTIONS.page_up_hook: - hook_page_up() + hook_page_up(CONFIG) else: if GLOBAL_OPTIONS.page_up_hook: W.unhook(GLOBAL_OPTIONS.page_up_hook) @@ -1430,96 +1376,14 @@ def matrix_config_change_cb(data, option): return 1 -def init_matrix_config(): - config_file = W.config_new("matrix", "matrix_config_reload_cb", "") - - look_options = [ - Option( - "redactions", "integer", - "strikethrough|notice|delete", 0, 0, - "strikethrough", - ( - "Only notice redactions, strike through or delete " - "redacted messages" - ) - ), - Option( - "server_buffer", "integer", - "merge_with_core|merge_without_core|independent", - 0, 0, "merge_with_core", "Merge server buffers" - ) - ] - - network_options = [ - Option( - "max_initial_sync_events", "integer", - "", 1, 10000, - "30", - ( - "How many events to fetch during the initial sync" - ) - ), - Option( - "max_backlog_sync_events", "integer", - "", 1, 100, - "10", - ( - "How many events to fetch during backlog fetching" - ) - ), - Option( - "fetch_backlog_on_pgup", "boolean", - "", 0, 0, - "on", - ( - "Fetch messages in the backlog on a window page up event" - ) - ) - ] - - def add_global_options(section, options): - for option in options: - GLOBAL_OPTIONS.options[option.name] = W.config_new_option( - config_file, section, option.name, - option.type, option.description, option.string_values, - option.min, option.max, option.value, option.value, 0, "", - "", "matrix_config_change_cb", "", "", "") - - section = W.config_new_section(config_file, "color", 0, 0, "", "", "", "", - "", "", "", "", "", "") - - # TODO color options - - section = W.config_new_section(config_file, "look", 0, 0, "", "", "", "", - "", "", "", "", "", "") - - add_global_options(section, look_options) - - section = W.config_new_section(config_file, "network", 0, 0, "", "", "", - "", "", "", "", "", "", "") - - add_global_options(section, network_options) - - W.config_new_section( - config_file, "server", - 0, 0, - "matrix_config_server_read_cb", - "", - "matrix_config_server_write_cb", - "", "", "", "", "", "", "" - ) - - return config_file - - def read_matrix_config(): # type: () -> bool return_code = W.config_read(CONFIG) - if return_code == weechat.WEECHAT_CONFIG_READ_OK: + if return_code == W.WEECHAT_CONFIG_READ_OK: return True - elif return_code == weechat.WEECHAT_CONFIG_READ_MEMORY_ERROR: + elif return_code == W.WEECHAT_CONFIG_READ_MEMORY_ERROR: return False - elif return_code == weechat.WEECHAT_CONFIG_READ_FILE_NOT_FOUND: + elif return_code == W.WEECHAT_CONFIG_READ_FILE_NOT_FOUND: return True return False @@ -2051,13 +1915,13 @@ def add_servers_to_completion(completion): completion, server_name, 0, - weechat.WEECHAT_LIST_POS_SORT + W.WEECHAT_LIST_POS_SORT ) @utf8_decode def server_command_completion_cb(data, completion_item, buffer, completion): - buffer_input = weechat.buffer_get_string(buffer, "input").split() + buffer_input = W.buffer_get_string(buffer, "input").split() args = buffer_input[1:] commands = ['add', 'delete', 'list', 'listfull'] @@ -2068,7 +1932,7 @@ def server_command_completion_cb(data, completion_item, buffer, completion): completion, command, 0, - weechat.WEECHAT_LIST_POS_SORT + W.WEECHAT_LIST_POS_SORT ) if len(args) == 1: @@ -2109,7 +1973,7 @@ def matrix_command_completion_cb(data, completion_item, buffer, completion): completion, command, 0, - weechat.WEECHAT_LIST_POS_SORT) + W.WEECHAT_LIST_POS_SORT) return W.WEECHAT_RC_OK @@ -2190,348 +2054,6 @@ def matrix_command_topic_cb(data, buffer, command): return W.WEECHAT_RC_OK -def matrix_fetch_old_messages(server, room_id): - room = server.rooms[room_id] - prev_batch = room.prev_batch - - if not prev_batch: - return - - message = MatrixMessage(server, GLOBAL_OPTIONS, MessageType.ROOM_MSG, - room_id=room_id, extra_id=prev_batch) - - send_or_queue(server, message) - - return - - -@utf8_decode -def matrix_command_buf_clear_cb(data, buffer, command): - for server in SERVERS.values(): - if buffer in server.buffers.values(): - room_id = key_from_value(server.buffers, buffer) - server.rooms[room_id].prev_batch = server.next_batch - - return W.WEECHAT_RC_OK - - return W.WEECHAT_RC_OK - - -@utf8_decode -def matrix_command_pgup_cb(data, buffer, command): - # TODO the highlight status of a line isn't allowed to be updated/changed - # via hdata, therefore the highlight status of a messages can't be - # reoredered this would need to be fixed in weechat - # TODO we shouldn't fetch and print out more messages than - # max_buffer_lines_number or older messages than max_buffer_lines_minutes - for server in SERVERS.values(): - if buffer in server.buffers.values(): - window = W.window_search_with_buffer(buffer) - - first_line_displayed = bool( - W.window_get_integer(window, "first_line_displayed") - ) - - if first_line_displayed: - room_id = key_from_value(server.buffers, buffer) - matrix_fetch_old_messages(server, room_id) - - return W.WEECHAT_RC_OK - - return W.WEECHAT_RC_OK - - -@utf8_decode -def matrix_command_join_cb(data, buffer, command): - def join(server, args): - split_args = args.split(" ", 1) - - # TODO handle join for non public rooms - if len(split_args) != 2: - message = ("{prefix}Error with command \"/join\" (help on " - "command: /help join)").format( - prefix=W.prefix("error")) - W.prnt("", message) - return - - _, room_id = split_args - message = MatrixMessage( - server, - GLOBAL_OPTIONS, - MessageType.JOIN, - room_id=room_id - ) - send_or_queue(server, message) - - for server in SERVERS.values(): - if buffer in server.buffers.values(): - join(server, command) - return W.WEECHAT_RC_OK_EAT - elif buffer == server.server_buffer: - join(server, command) - return W.WEECHAT_RC_OK_EAT - - return W.WEECHAT_RC_OK - - -@utf8_decode -def matrix_command_part_cb(data, buffer, command): - def part(server, buffer, args): - rooms = [] - - split_args = args.split(" ", 1) - - if len(split_args) == 1: - if buffer == server.server_buffer: - message = ("{prefix}Error with command \"/part\" (help on " - "command: /help part)").format( - prefix=W.prefix("error")) - W.prnt("", message) - return - - rooms = [key_from_value(server.buffers, buffer)] - - else: - _, rooms = split_args - rooms = rooms.split(" ") - - for room_id in rooms: - message = MatrixMessage( - server, - GLOBAL_OPTIONS, - MessageType.PART, - room_id=room_id - ) - send_or_queue(server, message) - - for server in SERVERS.values(): - if buffer in server.buffers.values(): - part(server, buffer, command) - return W.WEECHAT_RC_OK_EAT - elif buffer == server.server_buffer: - part(server, buffer, command) - return W.WEECHAT_RC_OK_EAT - - return W.WEECHAT_RC_OK - - -@utf8_decode -def matrix_command_invite_cb(data, buffer, command): - def invite(server, buf, args): - split_args = args.split(" ", 1) - - # TODO handle join for non public rooms - if len(split_args) != 2: - message = ("{prefix}Error with command \"/invite\" (help on " - "command: /help invite)").format( - prefix=W.prefix("error")) - W.prnt("", message) - return - - _, invitee = split_args - room_id = key_from_value(server.buffers, buf) - - body = {"user_id": invitee} - - message = MatrixMessage( - server, - GLOBAL_OPTIONS, - MessageType.INVITE, - room_id=room_id, - data=body - ) - send_or_queue(server, message) - - for server in SERVERS.values(): - if buffer in server.buffers.values(): - invite(server, buffer, command) - return W.WEECHAT_RC_OK_EAT - - return W.WEECHAT_RC_OK - - -def tags_from_line_data(line_data): - # type: (weechat.hdata) -> List[str] - tags_count = W.hdata_get_var_array_size( - W.hdata_get('line_data'), - line_data, - 'tags_array') - - tags = [ - W.hdata_string( - W.hdata_get('line_data'), - line_data, - '%d|tags_array' % i - ) for i in range(tags_count)] - - return tags - - -def event_id_from_line(buf, target_number): - # type: (weechat.buffer, int) -> str - own_lines = W.hdata_pointer(W.hdata_get('buffer'), buf, 'own_lines') - if own_lines: - line = W.hdata_pointer( - W.hdata_get('lines'), - own_lines, - 'last_line' - ) - - line_number = 1 - - while line: - line_data = W.hdata_pointer( - W.hdata_get('line'), - line, - 'data' - ) - - if line_data: - tags = tags_from_line_data(line_data) - - # Only count non redacted user messages - if ("matrix_message" in tags - and 'matrix_redacted' not in tags - and "matrix_new_redacted" not in tags): - - if line_number == target_number: - for tag in tags: - if tag.startswith("matrix_id"): - event_id = tag[10:] - return event_id - - line_number += 1 - - line = W.hdata_move(W.hdata_get('line'), line, -1) - - return "" - - -@utf8_decode -def matrix_redact_command_cb(data, buffer, args): - for server in SERVERS.values(): - if buffer in server.buffers.values(): - body = {} - - room_id = key_from_value(server.buffers, buffer) - - matches = re.match(r"(\d+)(:\".*\")? ?(.*)?", args) - - if not matches: - message = ("{prefix}matrix: Invalid command arguments (see " - "the help for the command /help redact)").format( - prefix=W.prefix("error")) - W.prnt("", message) - return W.WEECHAT_RC_ERROR - - line_string, _, reason = matches.groups() - line = int(line_string) - - if reason: - body = {"reason": reason} - - event_id = event_id_from_line(buffer, line) - - if not event_id: - message = ("{prefix}matrix: No such message with number " - "{number} found").format( - prefix=W.prefix("error"), - number=line) - W.prnt("", message) - return W.WEECHAT_RC_OK - - message = MatrixMessage( - server, - GLOBAL_OPTIONS, - MessageType.REDACT, - data=body, - room_id=room_id, - extra_id=event_id - ) - send_or_queue(server, message) - - return W.WEECHAT_RC_OK - - elif buffer == server.server_buffer: - message = ("{prefix}matrix: command \"redact\" must be " - "executed on a Matrix channel buffer").format( - prefix=W.prefix("error")) - W.prnt("", message) - return W.WEECHAT_RC_OK - - return W.WEECHAT_RC_OK - - -@utf8_decode -def matrix_debug_completion_cb(data, completion_item, buffer, completion): - for debug_type in ["messaging", "network", "timing"]: - W.hook_completion_list_add( - completion, - debug_type, - 0, - weechat.WEECHAT_LIST_POS_SORT) - return W.WEECHAT_RC_OK - - -@utf8_decode -def matrix_message_completion_cb(data, completion_item, buffer, completion): - own_lines = W.hdata_pointer(W.hdata_get('buffer'), buffer, 'own_lines') - if own_lines: - line = W.hdata_pointer( - W.hdata_get('lines'), - own_lines, - 'last_line' - ) - - line_number = 1 - - while line: - line_data = W.hdata_pointer( - W.hdata_get('line'), - line, - 'data' - ) - - if line_data: - message = W.hdata_string(W.hdata_get('line_data'), line_data, - 'message') - - tags = tags_from_line_data(line_data) - - # Only add non redacted user messages to the completion - if (message - and 'matrix_message' in tags - and 'matrix_redacted' not in tags): - - if len(message) > GLOBAL_OPTIONS.redaction_comp_len + 2: - message = ( - message[:GLOBAL_OPTIONS.redaction_comp_len] - + '..') - - item = ("{number}:\"{message}\"").format( - number=line_number, - message=message) - - W.hook_completion_list_add( - completion, - item, - 0, - weechat.WEECHAT_LIST_POS_END) - line_number += 1 - - line = W.hdata_move(W.hdata_get('line'), line, -1) - - return W.WEECHAT_RC_OK - - -def hook_page_up(): - GLOBAL_OPTIONS.page_up_hook = W.hook_command_run( - '/window page_up', - 'matrix_command_pgup_cb', - '' - ) - - @utf8_decode def matrix_bar_item_plugin(data, item, window, buffer, extra_info): # pylint: disable=unused-argument @@ -2576,109 +2098,6 @@ def matrix_bar_item_name(data, item, window, buffer, extra_info): return "" -def init_hooks(): - W.hook_completion( - "matrix_server_commands", - "Matrix server completion", - "server_command_completion_cb", - "" - ) - - W.hook_completion( - "matrix_servers", - "Matrix server completion", - "matrix_server_completion_cb", - "" - ) - - W.hook_completion( - "matrix_commands", - "Matrix command completion", - "matrix_command_completion_cb", - "" - ) - - W.hook_completion( - "matrix_messages", - "Matrix message completion", - "matrix_message_completion_cb", - "" - ) - - W.hook_completion( - "matrix_debug_types", - "Matrix debugging type completion", - "matrix_debug_completion_cb", - "" - ) - - W.hook_command( - # Command name and short description - 'matrix', 'Matrix chat protocol command', - # Synopsis - ( - 'server add [:] ||' - 'server delete|list|listfull ||' - 'connect ||' - 'disconnect ||' - 'reconnect ||' - 'debug ||' - 'help ' - ), - # Description - ( - ' server: list, add, or remove Matrix servers\n' - ' connect: connect to Matrix servers\n' - 'disconnect: disconnect from one or all Matrix servers\n' - ' reconnect: reconnect to server(s)\n\n' - ' help: show detailed command help\n\n' - ' debug: enable or disable debugging\n\n' - 'Use /matrix help [command] to find out more\n' - ), - # Completions - ( - 'server %(matrix_server_commands)|%* ||' - 'connect %(matrix_servers) ||' - 'disconnect %(matrix_servers) ||' - 'reconnect %(matrix_servers) ||' - 'debug %(matrix_debug_types) ||' - 'help %(matrix_commands)' - ), - # Function name - 'matrix_command_cb', '') - - W.hook_command( - # Command name and short description - 'redact', 'redact messages', - # Synopsis - ( - '[:<"message-part">] []' - ), - # Description - ( - "message-number: number of the message to redact (message numbers" - "\n start from the last recieved as " - "1 and count up)\n" - " message-part: a shortened part of the message\n" - " reason: the redaction reason\n" - ), - # Completions - ( - '%(matrix_messages)' - ), - # Function name - 'matrix_redact_command_cb', '') - - W.hook_command_run('/topic', 'matrix_command_topic_cb', '') - W.hook_command_run('/buffer clear', 'matrix_command_buf_clear_cb', '') - W.hook_command_run('/join', 'matrix_command_join_cb', '') - W.hook_command_run('/part', 'matrix_command_part_cb', '') - W.hook_command_run('/invite', 'matrix_command_invite_cb', '') - - if GLOBAL_OPTIONS.enable_backlog: - hook_page_up() - - def autoconnect(servers): for server in servers.values(): if server.autoconnect: @@ -2686,8 +2105,6 @@ def autoconnect(servers): if __name__ == "__main__": - W = weechat if sys.hexversion >= 0x3000000 else WeechatWrapper(weechat) - if W.register(WEECHAT_SCRIPT_NAME, WEECHAT_SCRIPT_AUTHOR, WEECHAT_SCRIPT_VERSION, @@ -2696,13 +2113,11 @@ if __name__ == "__main__": 'matrix_unload_cb', ''): - GLOBAL_OPTIONS = PluginOptions() - # TODO if this fails we should abort and unload the script. - CONFIG = init_matrix_config() + CONFIG = matrix.globals.init_matrix_config() read_matrix_config() - init_hooks() + hook_commands() W.bar_item_new("(extra)buffer_plugin", "matrix_bar_item_plugin", "") W.bar_item_new("(extra)buffer_name", "matrix_bar_item_name", "")