Rework of matrix message handling.

This commit is contained in:
poljar (Damir Jelić) 2018-01-10 17:09:02 +01:00
parent 1bc6a6b1ed
commit fd7934c228

View file

@ -138,7 +138,7 @@ class HttpRequest:
user_agent='weechat-matrix/{version}'.format( user_agent='weechat-matrix/{version}'.format(
version=WEECHAT_SCRIPT_VERSION) version=WEECHAT_SCRIPT_VERSION)
): ):
# type: (unicode, int, unicode, Dict[Any, Any]) -> None # type: (unicode, int, unicode, Dict[unicode, Any], unicode) -> None
# TODO we need to handle PUT as well # TODO we need to handle PUT as well
host_string = ':'.join([host, str(port)]) host_string = ':'.join([host, str(port)])
@ -200,6 +200,11 @@ class MatrixRoom:
self.join_rule = join_rule # type: unicode self.join_rule = join_rule # type: unicode
def key_from_value(dictionary, value):
# type: (Dict[unicode, Any], Any) -> unicode
return list(dictionary.keys())[list(dictionary.values()).index(value)]
@utf8_decode @utf8_decode
def server_config_change_cb(server_name, option): def server_config_change_cb(server_name, option):
server = SERVERS[server_name] server = SERVERS[server_name]
@ -208,15 +213,7 @@ def server_config_change_cb(server_name, option):
# The function config_option_get_string() is used to get differing # The function config_option_get_string() is used to get differing
# properties from a config option, sadly it's only available in the plugin # properties from a config option, sadly it's only available in the plugin
# API of weechat. # API of weechat.
# TODO we already have a function to get a key from a value out of a dict option_name = key_from_value(server.options, option)
for name, server_option in server.options.items():
if server_option == option:
option_name = name
break
if not option_name:
# TODO print error here, can this happen?
return 0
if option_name == "address": if option_name == "address":
value = W.config_string(option) value = W.config_string(option)
@ -376,7 +373,7 @@ def handle_http_response(server, message):
if status_code == 200: if status_code == 200:
# TODO json.loads can fail # TODO json.loads can fail
response = json.loads(message.response.body, encoding='utf-8') response = json.loads(message.response.body, encoding='utf-8')
handle_matrix_message(server, message.type, response) matrix_handle_message(server, message.type, response)
else: else:
server_buffer_prnt( server_buffer_prnt(
server, server,
@ -389,150 +386,185 @@ def handle_http_response(server, message):
return return
def handle_room_info(server, room_info): def strip_matrix_server(string):
# type: (MatrixServer, Dict) -> None # type: (unicode) -> unicode
def create_buffer(roomd_id, alias=None): return string.rsplit(":", 1)[0]
if not alias:
alias = "#{id}".format(id=room_id)
buf = W.buffer_new(
alias,
"room_input_cb",
server.name,
"room_close_cb",
server.name
)
# TODO set the buffer type dynamically def matrix_create_room_buffer(server, room_id):
W.buffer_set(buf, "localvar_set_type", 'channel') # type: (MatrixServer, unicode) -> None
W.buffer_set(buf, "type", 'formated') buf = W.buffer_new(
W.buffer_set(buf, "localvar_set_channel", alias) room_id,
"room_input_cb",
server.name,
"room_close_cb",
server.name
)
# TODO set the nick dynamically # TODO set the buffer type dynamically
W.buffer_set(buf, "localvar_set_nick", 'poljar') W.buffer_set(buf, "localvar_set_type", 'channel')
W.buffer_set(buf, "type", 'formated')
W.buffer_set(buf, "localvar_set_channel", room_id)
W.buffer_set(buf, "localvar_set_server", "matrix.org") W.buffer_set(buf, "localvar_set_nick", server.user)
# TODO put this in a function W.buffer_set(buf, "localvar_set_server", server.name)
short_name = alias.rsplit(":", 1)[0]
W.buffer_set(buf, "short_name", short_name)
server.buffers[room_id] = buf short_name = strip_matrix_server(room_id)
W.buffer_set(buf, "short_name", short_name)
def handle_aliases(room_id, event): W.nicklist_add_group(
if room_id not in server.buffers: buf,
alias = event['content']['aliases'][-1] '',
create_buffer(room_id, alias) NICK_GROUP_HERE,
"weechat.color.nicklist_group",
1
)
def handle_members(room_id, event): W.buffer_set(buf, "nicklist", "1")
if event['membership'] == 'join': W.buffer_set(buf, "nicklist_display_groups", "0")
try:
buf = server.buffers[room_id]
except KeyError:
event_queue.append(event)
return
W.buffer_set(buf, "nicklist", "1") server.buffers[room_id] = buf
W.buffer_set(buf, "nicklist_display_groups", "0")
# create nicklists for the current channel if they don't exist
# if they do, use the existing pointer
# TODO move this into the buffer creation
here = W.nicklist_search_group(buf, '', NICK_GROUP_HERE)
nick = event['content']['displayname']
if not here:
here = W.nicklist_add_group(
buf,
'',
NICK_GROUP_HERE,
"weechat.color.nicklist_group",
1
)
def matrix_handle_room_aliases(server, room_id, event):
# type: (MatrixServer, unicode, Dict[unicode, Any]) -> None
buf = server.buffers[room_id]
alias = event['content']['aliases'][-1]
if not alias:
return
short_name = strip_matrix_server(alias)
W.buffer_set(buf, "name", alias)
W.buffer_set(buf, "short_name", short_name)
W.buffer_set(buf, "localvar_set_channel", alias)
def matrix_handle_room_members(server, room_id, event):
# type: (MatrixServer, unicode, Dict[unicode, Any]) -> None
buf = server.buffers[room_id]
here = W.nicklist_search_group(buf, '', NICK_GROUP_HERE)
if event['membership'] == 'join':
# TODO do we wan't to use the displayname here?
nick = event['content']['displayname']
nick_pointer = W.nicklist_search_nick(buf, "", nick)
if not nick_pointer:
W.nicklist_add_nick(buf, here, nick, "", "", "", 1) W.nicklist_add_nick(buf, here, nick, "", "", "", 1)
def handle_room_state(state_events): elif event['membership'] == 'leave':
for event in state_events: nick = event['content']['displayname']
if event['type'] == 'm.room.aliases': nick_pointer = W.nicklist_search_nick(buf, "", nick)
handle_aliases(room_id, event) if nick_pointer:
elif event['type'] == 'm.room.member': W.nicklist_remove_nick(buf, nick_pointer)
handle_members(room_id, event)
elif event['type'] == 'm.room.message':
message_queue.append(event)
def handle_room_timeline(timeline_events):
for event in timeline_events:
if event['type'] == 'm.room.aliases':
handle_aliases(room_id, event)
elif event['type'] == 'm.room.member':
handle_members(room_id, event)
elif event['type'] == 'm.room.message':
message_queue.append(event)
def handle_text_message(room_id, event): def date_from_age(age):
msg = event['content']['body'] # type: (float) -> int
now = time.time()
date = int(now - (age / 1000))
return date
# TODO put this in a function or lambda
msg_author = event['sender'].rsplit(":", 1)[0][1:]
data = "{author}\t{msg}".format(author=msg_author, msg=msg) def matrix_handle_room_text_message(server, room_id, event):
# type: (MatrixServer, unicode, Dict[unicode, Any]) -> None
msg = event['content']['body']
event_id = event['event_id'] msg_author = strip_matrix_server(event['sender'])[1:]
event_id = "matrix_id_{id}".format(id=event_id)
msg_age = event['unsigned']['age'] data = "{author}\t{msg}".format(author=msg_author, msg=msg)
now = time.time()
msg_date = int(now - (msg_age / 1000))
buf = server.buffers[room_id]
# TODO if this is an initial sync tag the messages as backlog event_id = event['event_id']
tag = "nick_{a},{event_id},irc_privmsg,notify_message".format( event_id = "matrix_id_{id}".format(id=event_id)
a=msg_author, event_id=event_id)
W.prnt_date_tags(buf, msg_date, tag, data) msg_date = date_from_age(event['unsigned']['age'])
# TODO if this is an initial sync tag the messages as backlog
tag = "nick_{a},{event_id},irc_privmsg,notify_message".format(
a=msg_author, event_id=event_id)
buf = server.buffers[room_id]
W.prnt_date_tags(buf, msg_date, tag, data)
def matrix_handle_redacted_message(server, room_id, event):
censor = strip_matrix_server(event['unsigned']['redacted_by'])
msg = ("(Message redacted by: {censor}{reason}").format(
censor=censor,
reason=")")
msg_author = strip_matrix_server(event['sender'])[1:]
data = "{author}\t{msg}".format(author=msg_author, msg=msg)
event_id = event['event_id']
event_id = "matrix_id_{id}".format(id=event_id)
msg_date = date_from_age(event['unsigned']['age'])
tag = ("nick_{a},{event_id},irc_privmsg,matrix_redactedmsg,"
"notify_message").format(a=msg_author, event_id=event_id)
buf = server.buffers[room_id]
W.prnt_date_tags(buf, msg_date, tag, data)
def matrix_handle_room_messages(server, room_id, event):
# type: (MatrixServer, unicode, Dict[unicode, Any]) -> None
if event['type'] == 'm.room.message':
if 'redacted_by' in event['unsigned']:
matrix_handle_redacted_message(server, room_id, event)
return
if event['content']['msgtype'] == 'm.text':
matrix_handle_room_text_message(server, room_id, event)
# TODO handle different content types here
else:
message = ("{prefix}Handling of content type "
"{type} not implemented").format(
type=event['content']['type'],
prefix=W.prefix("error"))
W.prnt(server.server_buffer, message)
def matrix_handle_room_events(server, room_id, room_events):
# type: (MatrixServer, unicode, Dict[Any, Any]) -> None
for event in room_events:
if event['type'] == 'm.room.aliases':
matrix_handle_room_aliases(server, room_id, event)
elif event['type'] == 'm.room.member':
matrix_handle_room_members(server, room_id, event)
elif event['type'] == 'm.room.message':
matrix_handle_room_messages(server, room_id, event)
else:
message = ("{prefix}Handling of message type "
"{type} not implemented").format(
type=event['type'],
prefix=W.prefix("error"))
W.prnt(server.server_buffer, message)
def matrix_handle_room_info(server, room_info):
# type: (MatrixServer, Dict) -> None
for room_id, room in room_info['join'].iteritems(): for room_id, room in room_info['join'].iteritems():
# TODO do we need these queues or can we just rename the buffer if and if not room_id:
# when we get an alias dynamically? continue
event_queue = deque() # type: Deque[Dict]
message_queue = deque() # type: Deque[Dict]
handle_room_state(room['state']['events'])
handle_room_timeline(room['timeline']['events'])
# The room doesn't have an alias, create it now using the room id
if room_id not in server.buffers: if room_id not in server.buffers:
create_buffer(room_id) matrix_create_room_buffer(server, room_id)
# TODO we don't need a separate event/message queue here matrix_handle_room_events(server, room_id, room['state']['events'])
while event_queue: matrix_handle_room_events(server, room_id, room['timeline']['events'])
event = event_queue.popleft()
if event['type'] == 'm.room.member':
handle_members(room_id, event)
else:
assert "Wrong event type in event queue"
while message_queue:
event = message_queue.popleft()
if event['type'] == 'm.room.message':
# TODO print out that there was an redacted message here
if 'redacted_by' in event['unsigned']:
continue
if event['content']['msgtype'] == 'm.text':
handle_text_message(room_id, event)
# TODO handle different content types here
else:
message = (
"Handling of content type {type} not implemented"
).format(type=event['content']['type'])
server_buffer_prnt(server, message)
def handle_matrix_message(server, message_type, response): def matrix_handle_message(server, message_type, response):
# type: (MatrixServer, MessageType, Dict[Any, Any]) -> None # type: (MatrixServer, MessageType, Dict[unicode, Any]) -> None
if message_type is MessageType.LOGIN: if message_type is MessageType.LOGIN:
server.access_token = response["access_token"] server.access_token = response["access_token"]
@ -547,7 +579,7 @@ def handle_matrix_message(server, message_type, response):
return return
room_info = response['rooms'] room_info = response['rooms']
handle_room_info(server, room_info) matrix_handle_room_info(server, room_info)
server.next_batch = next_batch server.next_batch = next_batch
@ -559,21 +591,27 @@ def handle_matrix_message(server, message_type, response):
def generate_matrix_request(server, message_type, room_id=None, data=None): def generate_matrix_request(server, message_type, room_id=None, data=None):
# type: (MatrixServer, MessageType, unicode, Dict[Any, Any]) -> MatrixMessage # type: (MatrixServer, MessageType, unicode, Dict[unicode, Any]) -> MatrixMessage
# TODO clean this up
if message_type == MessageType.LOGIN:
path = '/_matrix/client/r0/login'
post_data = {"type": "m.login.password",
"user": server.user,
"password": server.password}
request = HttpRequest(server.address, server.port, path, post_data) if message_type == MessageType.LOGIN:
path = ("{api}/login").format(api=MATRIX_API_PATH)
request = HttpRequest(server.address, server.port, path, data)
return MatrixMessage(MessageType.LOGIN, request, None) return MatrixMessage(MessageType.LOGIN, request, None)
elif message_type == MessageType.SYNC: elif message_type == MessageType.SYNC:
path = '/_matrix/client/r0/sync?access_token={access_token}'.format( # TODO the limit should be configurable matrix.network.sync_limit
access_token=server.access_token) sync_filter = {
"room": {
"timeline": {"limit": 1000}
}
}
path = ("{api}/sync?access_token={access_token}&"
"filter={sync_filter}").format(
api=MATRIX_API_PATH,
access_token=server.access_token,
sync_filter=json.dumps(sync_filter, separators=(',', ':')))
if server.next_batch: if server.next_batch:
path = path + '&since={next_batch}'.format( path = path + '&since={next_batch}'.format(
@ -584,7 +622,12 @@ def generate_matrix_request(server, message_type, room_id=None, data=None):
return MatrixMessage(MessageType.SYNC, request, None) return MatrixMessage(MessageType.SYNC, request, None)
elif message_type == MessageType.POST_MSG: elif message_type == MessageType.POST_MSG:
path = '/_matrix/client/r0/rooms/{room}/send/m.room.message?access_token={access_token}'.format(room=room_id, access_token=server.access_token) path = ("{api}/rooms/{room}/send/m.room.message?"
"access_token={access_token}").format(
api=MATRIX_API_PATH,
room=room_id,
access_token=server.access_token)
request = HttpRequest(server.address, server.port, path, data) request = HttpRequest(server.address, server.port, path, data)
return MatrixMessage(MessageType.POST_MSG, request, None) return MatrixMessage(MessageType.POST_MSG, request, None)
@ -596,7 +639,15 @@ def generate_matrix_request(server, message_type, room_id=None, data=None):
def matrix_login(server): def matrix_login(server):
# type: (MatrixServer) -> None # type: (MatrixServer) -> None
message = generate_matrix_request(server, MessageType.LOGIN) post_data = {"type": "m.login.password",
"user": server.user,
"password": server.password}
message = generate_matrix_request(
server,
MessageType.LOGIN,
data=post_data
)
send_or_queue(server, message) send_or_queue(server, message)
@ -712,9 +763,8 @@ def create_server_buffer(server):
"" ""
) )
# TODO the nick and server name should be dynamic
W.buffer_set(server.server_buffer, "localvar_set_type", 'server') W.buffer_set(server.server_buffer, "localvar_set_type", 'server')
W.buffer_set(server.server_buffer, "localvar_set_nick", 'poljar') W.buffer_set(server.server_buffer, "localvar_set_nick", server.user)
W.buffer_set(server.server_buffer, "localvar_set_server", server.name) W.buffer_set(server.server_buffer, "localvar_set_server", server.name)
W.buffer_set(server.server_buffer, "localvar_set_channel", server.name) W.buffer_set(server.server_buffer, "localvar_set_channel", server.name)
@ -862,9 +912,9 @@ def room_input_cb(server_name, buffer, input_data):
W.prnt(buffer, message) W.prnt(buffer, message)
return W.WEECHAT_RC_ERROR return W.WEECHAT_RC_ERROR
# TODO put this in a function room_id = key_from_value(server.buffers, buffer)
room_id = list(server.buffers.keys())[list(server.buffers.values()).index(buffer)]
body = {"msgtype": "m.text", "body": input_data} body = {"msgtype": "m.text", "body": input_data}
message = generate_matrix_request(server, MessageType.POST_MSG, message = generate_matrix_request(server, MessageType.POST_MSG,
data=body, room_id=room_id) data=body, room_id=room_id)
send_or_queue(server, message) send_or_queue(server, message)