Move the weechat command setup and some commands.

This commit is contained in:
poljar (Damir Jelić) 2018-01-26 17:43:07 +01:00
parent be48b73395
commit 27c0836eb5
7 changed files with 707 additions and 633 deletions

View file

@ -2,6 +2,8 @@
from __future__ import unicode_literals
# pylint: disable=redefined-builtin
from builtins import str
from collections import namedtuple
import webcolors

445
matrix/commands.py Normal file
View file

@ -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-name> <hostname>[:<port>] ||'
'server delete|list|listfull <server-name> ||'
'connect <server-name> ||'
'disconnect <server-name> ||'
'reconnect <server-name> ||'
'debug <debug-type> ||'
'help <matrix-command>'
),
# 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-number>[:<"message-part">] [<reason>]'
),
# 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

96
matrix/globals.py Normal file
View file

@ -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

View file

@ -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

72
matrix/socket.py Normal file
View file

@ -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

46
matrix/utils.py Normal file
View file

@ -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

View file

@ -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ć <poljar@termina.org.uk>" # 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-name> <hostname>[:<port>] ||'
'server delete|list|listfull <server-name> ||'
'connect <server-name> ||'
'disconnect <server-name> ||'
'reconnect <server-name> ||'
'debug <debug-type> ||'
'help <matrix-command>'
),
# 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-number>[:<"message-part">] [<reason>]'
),
# 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", "")