about summary refs log tree commit diff
path: root/weechat/.weechat
diff options
context:
space:
mode:
authorBen Harris <ben@tilde.team>2018-12-17 10:35:24 -0500
committerBen Harris <ben@tilde.team>2018-12-17 10:35:24 -0500
commit5f580c20f376ca6a5cb863b7d079187b65f2c4c1 (patch)
treeb337279099864005f185a8ecdb9d296ec60a9c44 /weechat/.weechat
parent3fae26d1ba3b09f1aae18f97b1dffcdcd103ef6b (diff)
i thought i removed matrix.lua?
Diffstat (limited to 'weechat/.weechat')
-rw-r--r--weechat/.weechat/irc.conf10
l---------weechat/.weechat/lua/autoload/matrix.lua1
-rw-r--r--weechat/.weechat/lua/matrix.lua3455
3 files changed, 5 insertions, 3461 deletions
diff --git a/weechat/.weechat/irc.conf b/weechat/.weechat/irc.conf
index cec52be..de7e07d 100644
--- a/weechat/.weechat/irc.conf
+++ b/weechat/.weechat/irc.conf
@@ -180,7 +180,7 @@ tilde.local_hostname
 tilde.usermode
 tilde.command = "/oper root ${sec.data.tildenetoper}; /msg operserv login ${sec.data.pass}"
 tilde.command_delay
-tilde.autojoin = "#chaos,#secret-sudoers,#opers,#meta,#team,#sudoers,#YourTilde,#bots,#music,#share,#projects,#politics,#dnd,#journal,#shitposting,#quotes,#gopher,#tildeverse,#idlerpg,#tilderadio,#zaphod,#modded,#minecraft,#minetest,#bbj,#tildelinux,#tildetheater,#slbr,#dadjokes,#cosmic,#institute,#.tilde 57:60,secretsudoteam,youneedtoknowthepassword"
+tilde.autojoin = "#opers,#secret-sudoers,#meta,#team,#sudoers,#YourTilde,#chaos,#bots,#music,#share,#projects,#politics,#dnd,#journal,#shitposting,#quotes,#gopher,#tildeverse,#idlerpg,#tilderadio,#zaphod,#modded,#minecraft,#minetest,#bbj,#tildelinux,#tildetheater,#slbr,#dadjokes,#cosmic,#institute,#.tilde,#adventofcode,#unkt,#counting,#trivia,#solid,#tildetel,#bsd youneedtoknowthepassword,secretsudoteam"
 tilde.autorejoin
 tilde.autorejoin_delay
 tilde.connection_timeout
@@ -262,7 +262,7 @@ town.local_hostname
 town.usermode
 town.command
 town.command_delay
-town.autojoin = "#tildetown,#bots,#dumpsterfire,#projects,#madlibs,#share,#tildemush,#counting,#team,#aaa,#anarkiddies,#movienight,#counting-meta,#heavy"
+town.autojoin = "#tildetown,#bots,#dumpsterfire,#projects,#madlibs,#share,#tildemush,#counting,#team,#aaa,#anarkiddies,#movienight,#counting-meta,#heavy,#aoc,#adventofcode,#ooer"
 town.autorejoin
 town.autorejoin_delay
 town.connection_timeout
@@ -344,7 +344,7 @@ sdf.local_hostname
 sdf.usermode
 sdf.command
 sdf.command_delay
-sdf.autojoin = "#sdf,#gopher,#anonradio,#helpdesk"
+sdf.autojoin = "#helpdesk,#anonradio,#gopher,#sdf"
 sdf.autorejoin
 sdf.autorejoin_delay
 sdf.connection_timeout
@@ -467,7 +467,7 @@ freenode.local_hostname
 freenode.usermode
 freenode.command
 freenode.command_delay
-freenode.autojoin = "#freenode,#weechat,##oodnet,#alacritty,#disroot,#fediverse,#irc.net,#lobsters,#lobsters-boil,#lxcontainers,#thelounge,#gitea,#mastodon,#pleroma,#pleroma-offtopic,#pixelfed,#pixelfed-offtopic,#oragono,#weechat-android,#git,#thunix,#nginx,#devuan,#tilde.team,#sr.ht,#cmpwn,#tildeverse,#opennic #freenode-overflow,6:10"
+freenode.autojoin = "#weechat,##oodnet,#alacritty,#disroot,#fediverse,#irc.net,#lobsters,#lobsters-boil,#lxcontainers,#thelounge,#gitea,#mastodon,#pleroma,#pleroma-offtopic,#pixelfed,#pixelfed-offtopic,#oragono,#weechat-android,#git,#thunix,#nginx,#tilde.team,#sr.ht,#cmpwn,#tildeverse,#opennic,#lobsters-advent,#termux 6:10"
 freenode.autorejoin
 freenode.autorejoin_delay
 freenode.connection_timeout
@@ -672,7 +672,7 @@ gitter.local_hostname
 gitter.usermode
 gitter.command
 gitter.command_delay
-gitter.autojoin
+gitter.autojoin = "#solid/node-solid-server,#solid/chat"
 gitter.autorejoin
 gitter.autorejoin_delay
 gitter.connection_timeout
diff --git a/weechat/.weechat/lua/autoload/matrix.lua b/weechat/.weechat/lua/autoload/matrix.lua
deleted file mode 120000
index 90c1627..0000000
--- a/weechat/.weechat/lua/autoload/matrix.lua
+++ /dev/null
@@ -1 +0,0 @@
-/home/ben/.weechat/lua/matrix.lua
\ No newline at end of file
diff --git a/weechat/.weechat/lua/matrix.lua b/weechat/.weechat/lua/matrix.lua
deleted file mode 100644
index 60b5e5f..0000000
--- a/weechat/.weechat/lua/matrix.lua
+++ /dev/null
@@ -1,3455 +0,0 @@
--- WeeChat Matrix.org Client
--- vim: expandtab:ts=4:sw=4:sts=4
--- luacheck: globals weechat command_help command_connect matrix_command_cb matrix_away_command_run_cb configuration_changed_cb real_http_cb matrix_unload http_cb upload_cb send buffer_input_cb poll polltimer_cb cleartyping otktimer_cb join_command_cb part_command_cb leave_command_cb me_command_cb topic_command_cb upload_command_cb query_command_cb create_command_cb createalias_command_cb invite_command_cb list_command_cb op_command_cb voice_command_cb devoice_command_cbtow.config_get_plugin('timeout')) kick_command_cb deop_command_cb nick_command_cb whois_command_cb notice_command_cb msg_command_cb encrypt_command_cb public_command_cb names_command_cb more_command_cb roominfo_command_cb name_command_cb closed_matrix_buffer_cb closed_matrix_room_cb typing_notification_cb buffer_switch_cb typing_bar_item_cb devoice_command_cb
--- lots of shadowing here, just ignore it
--- luacheck: ignore current_buffer
-
---[[
- Author: xt <xt@xt.gg>
- Thanks to Ryan Huber of wee_slack.py for some ideas and inspiration.
-
- This script is considered alpha quality as only the bare minimal of
- functionality is in place and it is not very well tested.
-
- It is known to be able to crash WeeChat in certain scenarioes so all
- usage of this script is at your own risk.
-
- If at any point there seems to be problem, make sure you update to
- the latest version of this script. You can also try reloading the
- script using /lua reload matrix to refresh all the state.
-
-Power Levels
-------------
-
-A default Matrix room has power level between 0 to 100.
-This script maps this as follows:
-
- ~ Room creator
- & Power level 100
- @ Power level 50
- + Power level > 0
-
- TODO
- ----
- /ban
- Giving people arbitrary power levels
- Lazyload messages instead of HUGE initialSync
- Dynamically fetch more messages in backlog when user reaches the
- oldest message using pgup
- Need a way to change room join rule
- Fix broken state after failed initial connect
- Fix parsing of multiple join messages
- Friendlier error message on bad user/password
- Parse some HTML and turn into color/bold/etc
- Support weechat.look.prefix_same_nick
-
-]]
-
-local json = require 'cjson' -- apt-get install lua-cjson
-local olmstatus, olm = pcall(require, 'olm') -- LuaJIT olm FFI binding ln -s ~/olm/olm.lua /usr/local/share/lua/5.1
-local w = weechat
-
-local SCRIPT_NAME = "matrix"
-local SCRIPT_AUTHOR = "xt <xt@xt.gg>"
-local SCRIPT_VERSION = "3"
-local SCRIPT_LICENSE = "MIT"
-local SCRIPT_DESC = "Matrix.org chat plugin"
-local SCRIPT_COMMAND = SCRIPT_NAME
-
-local WEECHAT_VERSION
-
-local SERVER
-local STDOUT = {}
-local OUT = {}
-local BUFFER
-local Room
-local MatrixServer
-local Olm
-local DEBUG = false
--- How many seconds to timeout if nothing happened on the server. If something
--- happens before it will return sooner.
--- default Nginx proxy timeout is 60s, so we go slightly lower
-local POLL_INTERVAL = 55
-
--- Time in seconds until a connection is assumed to be timed out.
--- Floating values like 0.4 should work too.
-local timeout = 5*1000 -- overriden by w.config_get_plugin later
-
-local current_buffer
-
-local default_color = w.color('default')
--- Cache error variables so we don't have to look them up for every error
--- message, a normal user will not change these ever anyway.
-local errprefix
-local errprefix_c
-
-local HOMEDIR
-local OLM_ALGORITHM = 'm.olm.v1.curve25519-aes-sha2'
-local OLM_KEY = 'secr3t' -- TODO configurable using weechat sec data
-local v2_api_ns = '_matrix/client/v2_alpha'
-
-local function tprint(tbl, indent, out)
-    if not indent then indent = 0 end
-    if not out then out = BUFFER end
-    for k, v in pairs(tbl) do
-        local formatting = string.rep("  ", indent) .. k .. ": "
-        if type(v) == "table" then
-            w.print(out, formatting)
-            tprint(v, indent+1, out)
-        elseif type(v) == 'boolean' then
-            w.print(out, formatting .. tostring(v))
-        elseif type(v) == 'userdata' then
-            w.print(out, formatting .. tostring(v))
-        else
-            w.print(out, formatting .. v)
-        end
-    end
-end
-
-local function mprint(message)
-    -- Print message to matrix buffer
-    if type(message) == 'table' then
-        tprint(message)
-    else
-        message = tostring(message)
-        w.print(BUFFER, message)
-    end
-end
-
-local function perr(message)
-    if message == nil then return end
-    -- Print error message to the matrix "server" buffer using WeeChat styled
-    -- error message
-    mprint(
-        errprefix_c ..
-        errprefix ..
-        '\t' ..
-        default_color ..
-        tostring(message)
-        )
-end
-
-local function dbg(message)
-    perr('- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -')
-    if type(message) == 'table' then
-        tprint(message)
-    else
-        message = ("DEBUG\t%s"):format(tostring(message))
-        mprint(BUFFER, message)
-    end
-end
-
-local dtraceback = debug.traceback
--- luacheck: ignore debug
-debug.traceback = function (...)
-    if select('#', ...) >= 1 then
-        local err, lvl = ...
-        local trace = dtraceback(err, (lvl or 2)+1)
-        perr(trace)
-    end
-    -- direct call to debug.traceback: return the original.
-    -- debug.traceback(nil, level) doesn't work in Lua 5.1
-    -- (http://lua-users.org/lists/lua-l/2011-06/msg00574.html), so
-    -- simply remove first frame from the stack trace
-    return (dtraceback(...):gsub("(stack traceback:\n)[^\n]*\n", "%1"))
-end
-
-local function weechat_eval(text)
-    if WEECHAT_VERSION >= 0x00040200 then
-        return w.string_eval_expression(text,{},{},{})
-    end
-    return text
-end
-
-local urllib = {}
-urllib.quote = function(str)
-    if not str then return '' end
-    if type(str) == 'number' then return str end
-    return str:gsub(
-    '([^%w ])',
-    function (c)
-        return string.format ("%%%02X", string.byte(c))
-    end
-    ):gsub(' ', '+')
-end
-urllib.urlencode = function(tbl)
-    local out = {}
-    for k, v in pairs(tbl) do
-        table.insert(out, urllib.quote(k)..'='..urllib.quote(v))
-    end
-    return table.concat(out, '&')
-end
-
-local function accesstoken_redact(str)
-    return (str:gsub('access.*token=[0-9a-zA-Z%%]*', 'access_token=[redacted]'))
-end
-
-local transaction_id_counter = 0
-local function get_next_transaction_id()
-    transaction_id_counter = transaction_id_counter + 1
-    return transaction_id_counter
-end
-
---[[
--- Function for signing json, unused for now, we hand craft the required
--- signed json in the encryption function. But I think this function will be
--- needed in the future so leaving it here in a commented version
-local function sign_json(json_object, signing_key, signing_name)
-    -- See: https://github.com/matrix-org/matrix-doc/blob/master/specification/31_event_signing.rst
-    -- Maybe use:http://regex.info/code/JSON.lua which sorts keys
-    local signatures = json_object.signatures or {}
-    json_object.signatures = nil
-
-    local unsigned = json_object.unsigned or nil
-    json_object.unsigned = nil
-
-    -- TODO ensure canonical json
-    local signed = signing_key:sign(json.encode(json_object))
-    local signature_base64 = encode_base64(signed.signature)
-
-    local key_id = ("%s:%s"):format(signing_key.agl, signing_key.version)
-    signatures[signing_name] = {[key_id] = signature_base64}
-
-    json_object.signatures = signatures
-    if unsigned then
-        json_object.unsigned = unsigned
-    end
-
-    return json_object
-end
---]]
-
-local function split_args(args)
-    local function_name, arg = args:match('^(.-) (.*)$')
-    return function_name, arg
-end
-
-local function byte_to_tag(s, byte, open_tag, close_tag)
-    if s:match(byte) then
-        local inside = false
-        local open_tags = 0
-        local htmlbody = s:gsub(byte, function(c)
-            if inside then
-                inside = false
-                return close_tag
-            end
-            inside = true
-            open_tags = open_tags + 1
-            return open_tag
-        end)
-        local _, count = htmlbody:gsub(close_tag, '')
-        -- Ensure we close tags
-        if count < open_tags then
-            htmlbody = htmlbody .. close_tag
-        end
-        return htmlbody
-    end
-    return s
-end
-
-local function irc_formatting_to_html(s)
-    -- TODO, support foreground and background?
-    local ct = {'white','black','blue','green','red','maroon','purple',
-        'orange','yellow','lightgreen','teal','cyan', 'lightblue',
-        'fuchsia', 'gray', 'lightgray'}
-
-    s = byte_to_tag(s, '\02', '<strong>', '</strong>')
-    s = byte_to_tag(s, '\029', '<em>', '</em>')
-    s = byte_to_tag(s, '\031', '<u>', '</u>')
-    -- First do full color strings with reset.
-    -- Iterate backwards to catch long colors before short
-    for i=#ct,1,-1 do
-        s = s:gsub(
-            '\003'..tostring(i-1)..'(.-)\003',
-            '<font color="'..ct[i]..'">%1</font>')
-    end
-
-    -- Then replace unmatch colors
-    -- Iterate backwards to catch long colors before short
-    for i=#ct,1,-1 do
-        local c = ct[i]
-        s = byte_to_tag(s, '\003'..tostring(i-1),
-            '<font color="'..c..'">', '</font>')
-    end
-    return s
-end
-
-local function strip_irc_formatting(s)
-    if not s then return '' end
-    return (s
-        :gsub("\02", "")
-        :gsub("\03%d%d?,%d%d?", "")
-        :gsub("\03%d%d?", "")
-        :gsub("\03", "")
-        :gsub("\15", "")
-        :gsub("\17", "")
-        :gsub("\18", "")
-        :gsub("\22", "")
-        :gsub("\29", "")
-        :gsub("\31", ""))
-end
-
-local function irc_formatting_to_weechat_color(s)
-    -- TODO, support foreground and background?
-    -- - is atribute to remove formatting
-    -- | is to keep formatting during color changes
-    s = byte_to_tag(s, '\02', w.color'bold', w.color'-bold')
-    s = byte_to_tag(s, '\029', w.color'italic', w.color'-italic')
-    s = byte_to_tag(s, '\031', w.color'underline', w.color'-underline')
-    -- backwards to catch long numbers before short
-    for i=16,1,-1 do
-        i = tostring(i)
-        s = byte_to_tag(s, '\003'..i,
-            w.color("|"..i), w.color("-"..i))
-    end
-    return s
-end
-
-function matrix_unload()
-    w.print('', 'matrix: Unloading')
-    -- Clear/free olm memory if loaded
-    if olmstatus then
-        w.print('', 'matrix: Saving olm state')
-        SERVER.olm:save()
-        w.print('', 'matrix: Clearing olm state from memory')
-        SERVER.olm.account:clear()
-        --SERVER.olm = nil
-    end
-    w.print('', 'matrix: done cleaning up!')
-    return w.WEECHAT_RC_OK
-end
-
-local function wconf(optionname)
-    return w.config_string(w.config_get(optionname))
-end
-
-local function wcolor(optionname)
-    return w.color(wconf(optionname))
-end
-
-function command_help(current_buffer, args)
-    if args then
-         local help_cmds = {args=args}
-         if not help_cmds then
-             w.print("", "Command not found: " .. args)
-             return
-         end
-         for cmd, helptext in pairs(help_cmds) do
-             w.print('', w.color("bold") .. cmd)
-             w.print('', (helptext or 'No help text').strip())
-             w.print('', '')
-        end
-    end
-
-end
-
-function command_connect(current_buffer, args)
-    if not SERVER.connected then
-        SERVER:connect()
-    end
-    return w.WEECHAT_RC_OK
-end
-
-function matrix_command_cb(data, current_buffer, args)
-    if args == 'connect' then
-        return command_connect(current_buffer)
-    elseif args == 'debug' then
-        if DEBUG then
-            DEBUG = false
-            w.print('', SCRIPT_NAME..': debugging messages disabled')
-        else
-            DEBUG = true
-            w.print('', SCRIPT_NAME..': debugging messages enabled')
-        end
-    elseif args:match('^msg ') then
-        local _
-        _, args = split_args(args) -- remove cmd
-        local roomarg, msg = split_args(args)
-        local room
-        for id, r in pairs(SERVER.rooms) do
-            -- Send /msg to a ID
-            if id == roomarg then
-                room = r
-                break
-            elseif roomarg == r.name then
-                room = r
-                break
-            elseif roomarg == r.roomname then
-                room = r
-                break
-            end
-        end
-
-        if room then
-            room:Msg(msg)
-            return w.WEECHAT_RC_OK_EAT
-        end
-    else
-        perr("Command not found: " .. args)
-    end
-
-    return w.WEECHAT_RC_OK
-end
-
-function matrix_away_command_run_cb(data, buffer, args)
-    -- Callback for command /away -all
-    local _
-    _, args = split_args(args) -- remove cmd
-    local msg
-    _, msg = split_args(args)
-    w.buffer_set(BUFFER, "localvar_set_away", msg)
-    for id, room in pairs(SERVER.rooms) do
-        if msg and msg ~= '' then
-            w.buffer_set(room.buffer, "localvar_set_away", msg)
-        else
-            -- Delete takes empty string, and not nil
-            w.buffer_set(room.buffer, "localvar_del_away", '')
-        end
-    end
-    if msg and msg ~= '' then
-        SERVER:SendPresence('unavailable', msg)
-        mprint 'You have been marked as unavailable'
-    else
-        SERVER:SendPresence('online', nil)
-        mprint 'You have been marked as online'
-    end
-    return w.WEECHAT_RC_OK
-end
-
-function configuration_changed_cb(data, option, value)
-    if option == 'plugins.var.lua.matrix.timeout' then
-        timeout = tonumber(value)*1000
-    elseif option == 'plugins.var.lua.matrix.debug' then
-        if value == 'on' then
-            DEBUG = true
-            w.print('', SCRIPT_NAME..': debugging messages enabled')
-        else
-            DEBUG = false
-            w.print('', SCRIPT_NAME..': debugging messages disabled')
-        end
-    end
-end
-
-local function http(url, post, cb, h_timeout, extra, api_ns)
-    if not post then
-        post = {}
-    end
-    if not cb then
-        cb = 'http_cb'
-    end
-    if not h_timeout then
-        h_timeout = 60*1000
-    end
-    if not extra then
-        extra = nil
-    end
-    if not api_ns then
-        api_ns = "_matrix/client/r0"
-    end
-
-    -- Add accept encoding by default if it's not already there
-    if not post.accept_encoding then
-        post.accept_encoding = 'application/json'
-    end
-    if not post.header then
-        post.header = 1 -- Request http headers in the response
-    end
-
-    if not url:match'https?://' then
-        local homeserver_url = w.config_get_plugin('homeserver_url')
-        homeserver_url = homeserver_url .. api_ns
-        url = homeserver_url .. url
-    end
-
-    if DEBUG then
-        dbg{request={
-            url=accesstoken_redact(url),
-            post=post,extra=extra}
-        }
-    end
-    w.hook_process_hashtable('url:' .. url, post, h_timeout, cb, extra)
-end
-
-local function parse_http_statusline(line)
-    -- Attempt to match HTTP/1.0 or HTTP/1.1
-    local httpversion, status_code, reason_phrase = line:match("^HTTP/(1%.[01]) (%d%d%d) (.-)\r?\n")
-    if not httpversion then
-        -- Attempt to match HTTP/1 or HTTP/2 if previous match fell through
-        httpversion, status_code, reason_phrase = line:match("^HTTP/([12]) (%d%d%d) (.-)\r?\n")
-        if not httpversion then
-            return
-        end
-    end
-    return httpversion, tonumber(status_code), reason_phrase
-end
-
-function real_http_cb(extra, command, rc, stdout, stderr)
-    if DEBUG then
-        dbg{reply={
-            command=accesstoken_redact(command),
-            extra=extra,rc=rc,stdout=stdout,stderr=stderr}
-        }
-    end
-
-    if stderr and stderr ~= '' then
-        mprint(('error: %s'):format(accesstoken_redact(stderr)))
-        return w.WEECHAT_RC_OK
-    end
-
-    -- Because of a bug in WeeChat sometimes the stdout gets prepended by
-    -- any number of BEL chars (hex 07). Let's have a nasty workaround and
-    -- just replace them away.
-    if WEECHAT_VERSION < 0x01030000 then -- fixed in 1.3
-        stdout = (stdout:gsub('^\007*', ''))
-    end
-
-    if stdout ~= '' then
-        if not STDOUT[command] then
-            STDOUT[command] = {}
-        end
-        table.insert(STDOUT[command], stdout)
-    end
-
-    if tonumber(rc) >= 0 then
-        stdout = table.concat(STDOUT[command] or {})
-        STDOUT[command] = nil
-        local httpversion, status_code, reason_phrase = parse_http_statusline(stdout)
-        if not httpversion then
-            perr(('Invalid http request: %s'):format(stdout))
-            return w.WEECHAT_RC_OK
-        end
-        if status_code == 504 and command:find'/sync' then -- keep hammering to try to get in as the server will keep slowly generating the response
-            SERVER:initial_sync()
-            return w.WEECHAT_RC_OK
-        end
-        if status_code >= 500 then
-            perr(('HTTP API returned error. Code: %s, reason: %s'):format(status_code, reason_phrase))
-            return w.WEECHAT_RC_OK
-        end
-        -- Skip to data
-        stdout = (stdout:match('.-\r?\n\r?\n(.*)'))
-        -- Protected call in case of JSON errors
-        local success, js = pcall(json.decode, stdout)
-        if not success then
-            mprint(('error\t%s during json load: %s'):format(js, stdout))
-            return w.WEECHAT_RC_OK
-        end
-        if js['errcode'] or js['error'] then
-            if command:find'login' then
-                w.print('', ('matrix: Error code during login: %s, code: %s'):format(
-                    js['error'], js['errcode']))
-                w.print('', 'matrix: Please verify your username and password')
-            else
-                perr('API call returned error: '..js['error'] .. '('..tostring(js.errcode)..')')
-            end
-            return w.WEECHAT_RC_OK
-        end
-        -- Get correct handler
-        if command:find('login') then
-            for k, v in pairs(js) do
-                SERVER[k] = v
-            end
-            SERVER.connected = true
-            SERVER:initial_sync()
-        elseif command:find'/rooms/.*/initialSync' then
-            local myroom = SERVER:addRoom(js)
-            for _, chunk in ipairs(js['presence']) do
-                myroom:ParseChunk(chunk, true, 'presence')
-            end
-            for _, chunk in ipairs(js['messages']['chunk']) do
-                myroom:ParseChunk(chunk, true, 'messages')
-            end
-        elseif command:find'/sync' then
-            SERVER.end_token = js.next_batch
-
-            -- We have a new end token, which means we safely can release the
-            -- poll lock
-            SERVER.poll_lock = false
-
-            local backlog = false
-            local initial = false
-            if extra == 'initial' then
-                initial = true
-                backlog = true
-            end
-
-            -- Start with setting the global presence variable on the server
-            -- so when the nicks get added to the room they can get added to
-            -- the correct nicklist group according to if they have presence
-            -- or not
-            for _, e in ipairs(js.presence.events) do
-                SERVER:UpdatePresence(e)
-            end
-            for membership, rooms in pairs(js['rooms']) do
-                -- If we left the room, simply ignore it
-                if membership ~= 'leave' or (membership == 'leave' and (not backlog)) then
-                    for identifier, room in pairs(rooms) do
-                        -- Monkey patch it to look like v1 object
-                        room.room_id = identifier
-                        local myroom
-                        if initial then
-                            myroom = SERVER:addRoom(room)
-                        else
-                            myroom = SERVER.rooms[identifier]
-                            -- Chunk for non-existing room
-                            if not myroom then
-                                myroom = SERVER:addRoom(room)
-                                if not membership == 'invite' then
-                                    perr('Event for unknown room')
-                                end
-                            end
-                        end
-                        -- First of all parse invite states.
-                        local inv_states = room.invite_state
-                        if inv_states then
-                            local chunks = room.invite_state.events or {}
-                            for _, chunk in ipairs(chunks) do
-                                myroom:ParseChunk(chunk, backlog, 'states')
-                            end
-                        end
-                        -- Parse states before messages so we can add nicks and stuff
-                        -- before messages start appearing
-                        local states = room.state
-                        if states then
-                            local chunks = room.state.events or {}
-                            for _, chunk in ipairs(chunks) do
-                                myroom:ParseChunk(chunk, backlog, 'states')
-                            end
-                        end
-                        local timeline = room.timeline
-                        if timeline then
-                            -- Save the prev_batch on the initial message so we
-                            -- know for later when we picked up the sync
-                            if initial then
-                                myroom.prev_batch = timeline.prev_batch
-                            end
-                            local chunks = timeline.events or {}
-                            for _, chunk in ipairs(chunks) do
-                                myroom:ParseChunk(chunk, backlog, 'messages')
-                            end
-                        end
-                        local ephemeral = room.ephemeral
-                        -- Ignore Ephemeral Events during initial sync
-                        if (extra and extra ~= 'initial') and ephemeral then
-                            local chunks = ephemeral.events or {}
-                            for _, chunk in ipairs(chunks) do
-                                myroom:ParseChunk(chunk, backlog, 'states')
-                            end
-                        end
-                        local account_data = room.account_data
-                        if account_data then
-                            -- looks for m.fully_read event
-                            local chunks = account_data.events or {}
-                            for _, chunk in ipairs(chunks) do
-                                myroom:ParseChunk(chunk, backlog, 'account_data')
-                            end
-                        end
-                        if backlog then
-                            -- All the state should be done. Try to get a good name for the room now.
-                            myroom:SetName(myroom.identifier)
-                        end
-                    end
-                end
-            end
-            -- Now we have created rooms and can go over the rooms and update
-            -- the presence for each nick
-            for _, e in pairs(js.presence.events) do
-                SERVER:UpdatePresence(e)
-            end
-            if initial then
-                SERVER:post_initial_sync()
-            end
-            SERVER:poll()
-        elseif command:find'messages' then
-            local identifier = extra
-            local myroom = SERVER.rooms[identifier]
-            myroom.prev_batch = js['end']
-            -- Freeze buffer
-            myroom:Freeze()
-            -- Clear buffer
-            myroom:Clear()
-            -- We request backwards direction, so iterate backwards
-            for i=#js.chunk,1,-1 do
-                local chunk = js.chunk[i]
-                myroom:ParseChunk(chunk, true, 'messages')
-            end
-            -- Thaw!
-            myroom:Thaw()
-        elseif command:find'/join/' then
-            -- We came from a join command, fecth some messages
-            local found = false
-            for id, _ in pairs(SERVER.rooms) do
-                if id == js.room_id then
-                    found = true
-                    -- this is a false positive for example when getting
-                    -- invited. need to investigate more
-                    --mprint('error\tJoined room, but already in it.')
-                    break
-                end
-            end
-            if not found then
-                local data = urllib.urlencode({
-                    access_token= SERVER.access_token,
-                    --limit= w.config_get_plugin('backlog_lines'),
-                    limit = 10,
-                })
-                http(('/rooms/%s/initialSync?%s'):format(
-                    urllib.quote(js.room_id), data))
-            end
-        elseif command:find'leave' then
-            -- We store room_id in extra
-            local room_id = extra
-            SERVER:delRoom(room_id)
-        elseif command:find'/keys/claim' then
-            local count = 0
-            for user_id, v in pairs(js.one_time_keys or {}) do
-                for device_id, keys in pairs(v or {}) do
-                    for key_id, key in pairs(keys or {}) do
-                        SERVER.olm.otks[user_id..':'..device_id] = {[device_id]=key}
-                        perr(('olm: Recieved OTK for user %s for device id %s'):format(user_id, device_id))
-                        count = count + 1
-                        SERVER.olm:create_session(user_id, device_id)
-                    end
-                end
-            end
-        elseif command:find'/keys/query' then
-            for k, v in pairs(js.device_keys or {}) do
-                SERVER.olm.device_keys[k] = v
-
-                -- Claim keys for all only if missing session
-                for device_id, device_data in pairs(v) do
-                    -- First try to create session from saved data
-                    -- if that doesn't success we will download otk
-                    local device_key = device_data.keys['curve25519:'..device_id]
-                    local sessions = SERVER.olm:get_sessions(device_key)
-                    if #sessions == 0 then
-                        perr('olm: Downloading otk for user '..k..', and device_id: '..device_id)
-                        SERVER.olm:claim(k, device_id)
-                    else
-                        perr('olm: Reusing existing session for user '..k)
-                    end
-                end
-            end
-        elseif command:find'/keys/upload' then
-            local key_count = 0
-            local sensible_number_of_keys = 20
-            for algo, count in pairs(js.one_time_key_counts) do
-                key_count = count
-                SERVER.olm.key_count = key_count
-            end
-            if DEBUG then
-                perr('olm: Number of own OTKs uploaded to server: '..key_count)
-            end
-            -- TODO make endless loop prevention in case of server error
-            if key_count < sensible_number_of_keys then
-                SERVER.olm:upload_keys()
-            end
-        elseif command:find'upload' then
-            -- We store room_id in extra
-            local room_id = extra
-            if js.content_uri then
-                SERVER:Msg(room_id, js.content_uri)
-            end
-        -- luacheck: ignore 542
-        elseif command:find'/typing/' then
-            -- either it errs or it is empty
-        elseif command:find'/state/' then
-            -- TODO errorcode: M_FORBIDDEN
-            -- either it errs or it is empty
-            --dbg({state= js})
-        elseif command:find'/send/' then
-            -- XXX Errorhandling
-            -- TODO save event id to use for localecho
-            local event_id = js.event_id
-            local room_id = extra
-            -- When using relay client, WeeChat doesn't get any buffer_switch
-            -- signals, and thus cannot know when the relay client reads any
-            -- messages. https://github.com/weechat/weechat/issues/180
-            -- As a better than nothing approach we send read receipt when
-            -- user sends a message, since most likely the user has read
-            -- messages in that room if sending messages to it.
-            SERVER:SendReadMarker(room_id, event_id)
-        elseif command:find'createRoom' then
-            -- We get join events, so we don't have to do anything
-        elseif command:find'/publicRooms' then
-            mprint 'Public rooms:'
-            mprint '\tUsers\tName\tTopic\tAliases'
-            table.sort(js.chunk, function(a, b)
-                return a.num_joined_members > b.num_joined_members
-            end)
-            for _, r in ipairs(js.chunk) do
-                local name = ''
-                if r.name and r.name ~= json.null then
-                    name = r.name:gsub('\n', '')
-                end
-                local topic = ''
-                if r.topic and r.topic ~= json.null then
-                    topic = r.topic:gsub('\n', '')
-                end
-                mprint(('%s %s %s %s')
-                    :format(
-                        r.num_joined_members or '',
-                        name or '',
-                        topic or '',
-                        table.concat(r.aliases or {}, ', ')))
-            end
-        -- luacheck: ignore 542
-        elseif command:find'/invite' then
-        elseif command:find'receipt' then
-            -- we don't care about receipts for now
-        elseif command:find'read_markers' then
-            -- we don't care about read markers for now
-        elseif command:find'directory/room' then
-            --- XXX: parse result
-            mprint 'Created new alias for room'
-        elseif command:match'presence/.*/status' then
-            -- Return of SendPresence which we don't have to handle because
-            -- it will be sent back to us as an event
-        else
-            dbg{['error'] = {msg='Unknown command in http cb', command=accesstoken_redact(command),
-                js=js}}
-        end
-    end
-
-    if tonumber(rc) == -2 then -- -2 == WEECHAT_HOOK_PROCESS_ERROR
-        perr(('Call to API errored in command %s, maybe timeout?'):format(
-            accesstoken_redact(command)))
-        -- Empty cache in case of errors
-        if STDOUT[command] then
-            STDOUT[command] = nil
-        end
-        -- Release poll lock in case of errors
-        SERVER.poll_lock = false
-    end
-
-    return w.WEECHAT_RC_OK
-end
-
-function http_cb(data, command, rc, stdout, stderr)
-    local status, result = pcall(real_http_cb, data, command, rc, stdout, stderr)
-    if not status then
-        perr('Error in http_cb: ' .. tostring(result))
-        perr(debug.traceback())
-    end
-    return result
-end
-
-function upload_cb(data, command, rc, stdout, stderr)
-    local success, js = pcall(json.decode, stdout)
-    if not success then
-        mprint(('error\t%s when getting uploaded URI: %s'):format(js, stdout))
-        return w.WEECHAT_RC_OK
-    end
-
-    local uri = js.content_uri
-    if not uri then
-        mprint(('error\tNo content_uri after upload. Stdout: %s, stderr: %s'):format(stdout, stderr))
-        return w.WEECHAT_RC_OK
-    end
-
-    local room_id = data
-    local body = 'Image'
-    local msgtype = 'm.image'
-    SERVER:Msg(room_id, body, msgtype, uri)
-
-    return w.WEECHAT_RC_OK
-end
-
-Olm = {}
-Olm.__index = Olm
-Olm.create = function()
-    local olmdata = {}
-    setmetatable(olmdata, Olm)
-    if not olmstatus then
-        w.print('', SCRIPT_NAME .. ': Unable to load olm encryption library. Not enabling encryption. Please see documentation (README.md) for information on how to enable.')
-        return
-    end
-
-    local account = olm.Account.new()
-    olmdata.account = account
-    olmdata.sessions = {}
-    olmdata.device_keys = {}
-    olmdata.otks = {}
-    -- Try to read account from filesystem, if not generate a new account
-    local fd = io.open(HOMEDIR..'account.olm', 'rb')
-    local pickled = ''
-    if fd then
-        pickled = fd:read'*all'
-        fd:close()
-    end
-    if pickled == '' then
-        account:create()
-        local _, err = account:generate_one_time_keys(5)
-        perr(err)
-    else
-        local _, err = account:unpickle(OLM_KEY, pickled)
-        perr(err)
-    end
-    local identity = json.decode(account:identity_keys())
-    -- TODO figure out what device id is supposed to be
-    olmdata.device_id = identity.ed25519:match'%w*' -- problems with nonalfanum
-    olmdata.device_key = identity.curve25519
-    w.print('', 'matrix: Encryption loaded. To send encrypted messages in a room, use command /encrypt on with a room as active current buffer')
-    if DEBUG then
-        dbg{olm={
-            'Loaded identity:',
-            json.decode(account:identity_keys())
-        }}
-    end
-    return olmdata
-end
-
-function Olm:save()
-    -- Save account and every pickled session
-    local pickle, err = self.account:pickle(OLM_KEY)
-    perr(err)
-    local fd = io.open(HOMEDIR..'account.olm', 'wb')
-    fd:write(pickle)
-    fd:close()
-    --for key, pickled in pairs(self.sessions) do
-    --    local user_id, device_id = key:match('(.*):(.+)')
-    --    self.write_session_to_file(pickled, user_id, device_id)
-    --end
-end
-
-function Olm:query(user_ids) -- Query keys from other user_id
-    if DEBUG then
-        perr('olm: querying user_ids')
-        tprint(user_ids)
-    end
-    local auth = urllib.urlencode{access_token=SERVER.access_token}
-    local data = {
-        device_keys = {}
-    }
-    for _, uid in pairs(user_ids) do
-        data.device_keys[uid] = {false}
-    end
-    http('/keys/query/?'..auth,
-        {postfields=json.encode(data)},
-        'http_cb',
-        timeout, nil,
-        v2_api_ns
-    )
-end
-
-function Olm:check_server_keycount()
-    local data = urllib.urlencode{access_token=SERVER.access_token}
-    http('/keys/upload/'..self.device_id..'?'..data,
-        {},
-        'http_cb', timeout, nil, v2_api_ns
-    )
-end
-
-function Olm:upload_keys()
-    if DEBUG then
-        perr('olm: Uploading keys')
-    end
-    local id_keys = json.decode(self.account:identity_keys())
-    local user_id = SERVER.user_id
-    local one_time_keys = {}
-    local otks = json.decode(self.account:one_time_keys())
-    local keyCount = 0
-    for id, k in pairs(otks.curve25519) do
-        keyCount = keyCount + 1
-    end
-    perr('olm: keycount: '..tostring(keyCount))
-    if keyCount < 5 then -- try to always have 5 keys
-        perr('olm: newly generated keys: '..tostring(tonumber(
-        self.account:generate_one_time_keys(5 - keyCount))))
-        otks = json.decode(self.account:one_time_keys())
-    end
-
-    for id, key in pairs(otks.curve25519) do
-        one_time_keys['curve25519:'..id] = key
-        keyCount = keyCount + 1
-    end
-
-    -- Construct JSON manually so it's ready for signing
-    local keys_json = '{"algorithms":["' .. OLM_ALGORITHM .. '"]'
-    .. ',"device_id":"' .. self.device_id .. '"'
-    .. ',"keys":'
-    .. '{"ed25519:' .. self.device_id .. '":"'
-    .. id_keys.ed25519
-    .. '","curve25519:' .. self.device_id .. '":"'
-    .. id_keys.curve25519
-    .. '"}'
-    .. ',"user_id":"' .. user_id
-    .. '"}'
-
-    local success, key_data = pcall(json.decode, keys_json)
-    -- TODO save key data to device_keys so we don't have to download
-    -- our own keys from the servers?
-    if not success then
-        perr(('olm: upload_keys: %s when converting to json: %s')
-        :format(key_data, keys_json))
-    end
-
-    local msg = {
-        device_keys = key_data,
-        one_time_keys = one_time_keys
-    }
-    msg.device_keys.signatures = {
-        [user_id] = {
-            ["ed25519:"..self.device_id] = self.account:sign(keys_json)
-        }
-    }
-    local data = urllib.urlencode{
-        access_token = SERVER.access_token
-    }
-    http('/keys/upload/'..self.device_id..'?'..data, {
-        postfields = json.encode(msg)
-    }, 'http_cb', timeout, nil, v2_api_ns)
-
-    self.account:mark_keys_as_published()
-
-end
-
-function Olm:claim(user_id, device_id) -- Fetch one time keys
-    if DEBUG then
-        perr(('olm: Claiming OTK for user: %s and device: %s'):format(user_id, device_id))
-    end
-    -- TODO take a list of ids for batch downloading
-    local auth = urllib.urlencode{ access_token = SERVER.access_token }
-    local data = {
-        one_time_keys = {
-            [user_id] = {
-                [device_id] = 'curve25519'
-            }
-        }
-    }
-    http('/keys/claim?'..auth,
-        {postfields=json.encode(data)},
-        'http_cb', timeout, nil, v2_api_ns
-    )
-end
-
-function Olm:create_session(user_id, device_id)
-    perr(('olm: creating session for user: %s, and device: %s'):format(user_id, device_id))
-    local device_data = self.device_keys[user_id][device_id]
-    if not device_data then
-        perr(('olm: missing device data for user: %s, and device: %s'):format(user_id, device_id))
-        return
-    end
-    local device_key = device_data.keys['curve25519:'..device_id]
-    if not device_key then
-        perr("olm: Missing key for user: "..user_id.." and device: "..device_id.."")
-        return
-    end
-    local sessions = self:get_sessions(device_key)
-    if not sessions[device_key] then
-        perr(('olm: creating NEW session for: %s, and device: %s'):format(user_id, device_id))
-        local session = olm.Session.new()
-        local otk = self.otks[user_id..':'..device_id]
-        if not otk then
-            perr("olm: Missing OTK for user: "..user_id.." and device: "..device_id.."")
-        else
-            otk = otk[device_id]
-        end
-        if otk then
-            session:create_outbound(self.account, device_key, otk)
-            local session_id = session:session_id()
-            perr('olm: Session ID:'..tostring(session_id))
-            self:store_session(device_key, session)
-        end
-        session:clear()
-    end
-end
-
-function Olm:get_sessions(device_key)
-    if DEBUG then
-        perr("olm: get_sessions: device: "..device_key.."")
-    end
-    local sessions = self.sessions[device_key]
-    if not sessions then
-        sessions = self:read_session(device_key)
-    end
-    return sessions
-end
-
-function Olm:read_session(device_key)
-    local session_filename = HOMEDIR..device_key..'.session.olm'
-    local fd, err = io.open(session_filename, 'rb')
-    if fd then
-        perr(('olm: reading saved session device: %s'):format(device_key))
-        local sessions = fd:read'*all'
-        sessions = json.decode(sessions)
-        self.sessions[device_key] = sessions
-        fd:close()
-        return sessions
-    else
-        perr(('olm: Error: %s, reading saved session device: %s'):format(err, device_key))
-    end
-    return {}
-end
-
-function Olm:store_session(device_key, session)
-    local session_id = session:session_id()
-    if DEBUG then
-        perr("olm: store_session: device: "..device_key..", Session ID: "..session_id)
-    end
-    local sessions = self.sessions[device_key] or {}
-    local pickled = session:pickle(OLM_KEY)
-    sessions[session_id] = pickled
-    self.sessions[device_key] = sessions
-    self:write_session_to_file(sessions, device_key)
-end
-
-function Olm:write_session_to_file(sessions, device_key)
-    local session_filename = HOMEDIR..device_key..'.session.olm'
-    local fd, err = io.open(session_filename, 'wb')
-    if fd then
-        fd:write(json.encode(sessions))
-        fd:close()
-    else
-        perr('olm: error saving session: '..tostring(err))
-    end
-end
-
-MatrixServer = {}
-MatrixServer.__index = MatrixServer
-
-MatrixServer.create = function()
-     local server = {}
-     setmetatable(server, MatrixServer)
-     server.nick = nil
-     server.connecting = false
-     server.connected = false
-     server.rooms = {}
-     -- Store user presences here since they are not local to the rooms
-     server.presence = {}
-     server.avatars = {}
-     server.end_token = 'END'
-     server.typing_time = os.time()
-     if w.config_get_plugin('presence_filter') ~= 'on' then
-         server.typingtimer = w.hook_timer(10*1000, 0, 0, "cleartyping", "")
-     end
-
-     -- Use a lock to prevent multiple simul poll with same end token, which
-     -- could lead to duplicate messages
-     server.poll_lock = false
-     server.olm = Olm.create()
-     if server.olm then -- might not be available
-         -- Run save so we do not lose state. Create might create new account,
-         -- new keys, etc.
-         server.olm:save()
-     end
-     return server
-end
-
-function MatrixServer:UpdatePresence(c)
-    local user_id = c.sender or c.content.user_id
-    self.presence[user_id] = c.content.presence
-    for id, room in pairs(self.rooms) do
-        room:UpdatePresence(c.sender, c.content.presence)
-    end
-end
-
-function MatrixServer:findRoom(buffer_ptr)
-    for id, room in pairs(self.rooms) do
-        if room.buffer == buffer_ptr then
-            return room
-        end
-    end
-end
-
-function MatrixServer:connect()
-    if not self.connecting then
-        local user = weechat_eval(w.config_get_plugin('user'))
-        local password = weechat_eval(w.config_get_plugin('password'))
-        if user == '' or password == '' then
-            w.print('', 'Please set your username and password using the settings system and then type /matrix connect')
-            return
-        end
-
-        self.connecting = true
-        w.print('', 'matrix: Connecting to homeserver URL: '..
-            w.config_get_plugin('homeserver_url'))
-        local pattern = "^[%w.]+@%w+%.%w+$"
-        local medium
-        if string.match(user, pattern) then
-            medium = "email"
-        else
-            medium = "user"
-        end
-        local post = {
-            ["user"]=user,
-            ["identifier.address"]=user,
-            ["identifier.medium"]=medium,
-            ["identifier.type"]="m.id.thirdparty",
-            ["initial_device_display_name"]="WeeMatrix",
-            ["medium"]=medium,
-            ["password"]=password,
-            ["type"]="m.login.password"
-        }
-        http('/login', {
-            postfields = json.encode(post)
-        }, 'http_cb', timeout)
-    end
-end
-
-function MatrixServer:initial_sync()
-    BUFFER = w.buffer_new(SCRIPT_NAME, "", "", "closed_matrix_buffer_cb", "")
-    w.buffer_set(BUFFER, "short_name", SCRIPT_NAME)
-    w.buffer_set(BUFFER, "name", SCRIPT_NAME)
-    w.buffer_set(BUFFER, "localvar_set_type", "server")
-    w.buffer_set(BUFFER, "localvar_set_server", SCRIPT_NAME)
-    w.buffer_set(BUFFER, "title", ("Matrix: %s"):format(
-        w.config_get_plugin'homeserver_url'))
-    if w.config_string(w.config_get('irc.look.server_buffer')) == 'merge_with_core' then
-        w.buffer_merge(BUFFER, w.buffer_search_main())
-    end
-    w.buffer_set(BUFFER, "display", "auto")
-    local data = urllib.urlencode({
-        access_token = self.access_token,
-        timeout = 1000*POLL_INTERVAL,
-        full_state = 'true',
-        filter = json.encode({ -- timeline filter
-            room = {
-                timeline = {
-                    limit = tonumber(w.config_get_plugin('backlog_lines'))
-                }
-            },
-            presence = {
-                not_types = {'*'}, -- dont want presence
-            }
-        })
-    })
-    local extra = 'initial'
-    -- New v2 sync API is slow. Until we can easily ignore archived rooms
-    -- let's increase the timer for the initial login
-    local login_timer = 60*5*1000
-    http('/sync?'..data, nil, 'http_cb', login_timer, extra)
-end
-
-function MatrixServer:post_initial_sync()
-    -- Timer used in cased of errors to restart the polling cycle
-    -- During normal operation the polling should re-invoke itself
-    SERVER.polltimer = w.hook_timer(POLL_INTERVAL*1000, 0, 0, "polltimer_cb", "")
-    if olmstatus then
-        -- timer that checks number of otks available on the server
-        SERVER.otktimer = w.hook_timer(5*60*1000, 0, 0, "otktimer_cb", "")
-        SERVER.olm:query{SERVER.user_id}
-        --SERVER.olm.upload_keys()
-        SERVER.olm:check_server_keycount()
-    end
-end
-
-function MatrixServer:getMessages(room_id, dir, from, limit)
-    if not dir then dir = 'b' end
-    if not from then from = 'END' end
-    if not limit then limit = w.config_get_plugin('backlog_lines') end
-    local data = urllib.urlencode({
-        access_token = self.access_token,
-        dir = dir,
-        from = from,
-        limit = limit,
-    })
-    http(('/rooms/%s/messages?%s')
-        :format(urllib.quote(room_id), data), nil, nil, nil, room_id)
-end
-
-function MatrixServer:Join(room)
-    if not self.connected then
-        --XXX'''
-        return
-    end
-
-    mprint('\tJoining room '..room)
-    room = urllib.quote(room)
-    http('/join/' .. room,
-        {postfields = "access_token="..self.access_token})
-end
-
-function MatrixServer:part(room)
-    if not self.connected then
-        --XXX'''
-        return
-    end
-
-    local id = urllib.quote(room.identifier)
-    local data = urllib.urlencode({
-        access_token= self.access_token,
-    })
-    http(('/rooms/%s/leave?%s'):format(id, data), {postfields = "{}"},
-        'http_cb', timeout, room.identifier)
-end
-
-function MatrixServer:poll()
-    if self.connected == false then
-        return
-    end
-    if self.poll_lock then
-        return
-    end
-    self.poll_lock = true
-    self.polltime = os.time()
-    local filter = {}
-    if w.config_get_plugin('presence_filter') == 'on' then
-        filter = { -- timeline filter
-            presence = {
-                not_types = {'*'}, -- dont want presence
-            },
-            room = {
-                ephemeral = {
-                    not_types = {'*'}, -- dont want read receipt and typing notices
-                }
-            }
-        }
-    end
-    local data = urllib.urlencode({
-        access_token = self.access_token,
-        timeout = 1000*POLL_INTERVAL,
-        full_state = 'false',
-        filter = json.encode(filter),
-        since = self.end_token
-    })
-    http('/sync?'..data, nil, 'http_cb', (POLL_INTERVAL+10)*1000)
-end
-
-function MatrixServer:addRoom(room)
-    -- Just in case, we check for duplicates here
-    if self.rooms[room['room_id']] then
-        return self.rooms[room['room_id']]
-    end
-    local myroom = Room.create(room)
-    myroom:create_buffer()
-    self.rooms[room['room_id']] = myroom
-    return myroom
-end
-
-function MatrixServer:delRoom(room_id)
-    for id, room in pairs(self.rooms) do
-        if id == room_id then
-            mprint('\tLeft room '..room.name)
-            room:destroy()
-            self.rooms[id] = nil
-            break
-        end
-    end
-end
-
-function MatrixServer:SendReadMarker(room_id, event_id)
-    -- Send read marker and read receipt too.
-    -- Read receipt is a federated event, read marker is only visible by the
-    -- user to by used by clients.
-    --
-    -- TODO: prevent sending multiple identical read receipts
-    local auth = urllib.urlencode{access_token=self.access_token}
-    local url = '/rooms/'..room_id..'/read_markers?'..auth
-    local data = {
-        customrequest = 'POST',
-        postfields = {}
-    }
-    data.postfields['m.fully_read'] = event_id
-
-    if w.config_get_plugin('read_receipts') == 'on' then
-        data.postfields['m.read'] = event_id
-    end
-
-    data.postfields = json.encode(data.postfields)
-    http(url,
-      data,
-      'http_cb',
-      timeout
-    )
-end
-
-function MatrixServer:Msg(room_id, body, msgtype, url)
-    -- check if there's an outgoing message timer already
-    self:ClearSendTimer()
-
-    if not msgtype then
-        msgtype = 'm.text'
-    end
-
-    if not OUT[room_id] then
-        OUT[room_id] = {}
-    end
-    -- Add message to outgoing queue of messages for this room
-    table.insert(OUT[room_id], {msgtype, body, url})
-
-    self:StartSendTimer()
-end
-
-function MatrixServer:StartSendTimer()
-    local send_delay = 50 -- Wait this long for paste detection
-    self.sendtimer = w.hook_timer(send_delay, 0, 1, "send", "")
-end
-
-function MatrixServer:ClearSendTimer()
-    -- Clear timer if it exists
-    if self.sendtimer then
-        w.unhook(self.sendtimer)
-    end
-    self.sendtimer = nil
-end
-
-function send(cbdata, calls)
-    SERVER:ClearSendTimer()
-    -- Find the room
-    local room
-
-    for id, msgs in pairs(OUT) do
-        -- Clear message
-        OUT[id] = nil
-        local body = {}
-        local htmlbody = {}
-        local msgtype
-        local url
-
-        local ishtml = false
-
-        for _, r in pairs(SERVER.rooms) do
-            if r.identifier == id then
-                room = r
-                break
-            end
-        end
-
-        for _, msg in pairs(msgs) do
-            -- last msgtype will override any other for simplicity's sake
-            msgtype = msg[1]
-            local html = irc_formatting_to_html(msg[2])
-            if html ~= msg[2] then
-                ishtml = true
-            end
-            table.insert(htmlbody, html )
-            table.insert(body, msg[2] )
-            if msg[3] then -- Primarily image upload
-                url = msg[3]
-            end
-        end
-        body = table.concat(body, '\n')
-
-        -- Run IRC modifiers (XXX: maybe run out1 also?
-        body = w.hook_modifier_exec('irc_out1_PRIVMSG', '', body)
-
-        if w.config_get_plugin('local_echo') == 'on' or
-            room.encrypted then
-            -- Generate local echo
-            local color = default_color
-            if msgtype == 'm.text' then
-                --- XXX: no localecho for encrypted messages?
-                local tags = 'notify_none,localecho,no_highlight'
-                if room.encrypted then
-                    tags = tags .. ',no_log'
-                    color = w.color(w.config_get_plugin(
-                        'encrypted_message_color'))
-                end
-                w.print_date_tags(room.buffer, nil,
-                    tags, ("%s\t%s%s"):format(
-                        room:formatNick(SERVER.user_id),
-                        color,
-                        irc_formatting_to_weechat_color(body)
-                        )
-                    )
-            elseif msgtype == 'm.emote' then
-                local prefix_c = wcolor'weechat.color.chat_prefix_action'
-                local prefix = wconf'weechat.look.prefix_action'
-                local tags = 'notify_none,localecho,irc_action,no_highlight'
-                if room.encrypted then
-                    tags = tags .. ',no_log'
-                    color = w.color(w.config_get_plugin(
-                        'encrypted_message_color'))
-                end
-                w.print_date_tags(room.buffer, nil,
-                    tags, ("%s%s\t%s%s%s %s"):format(
-                        prefix_c,
-                        prefix,
-                        w.color('chat_nick_self'),
-                        room.users[SERVER.user_id],
-                        color,
-                        irc_formatting_to_weechat_color(body)
-                        )
-                    )
-            end
-        end
-
-        local data = {
-            postfields = {
-                msgtype = msgtype,
-                body = body,
-                url = url,
-        }}
-
-        if ishtml then
-            htmlbody = table.concat(htmlbody, '\n')
-            data.postfields.body = strip_irc_formatting(body)
-            data.postfields.format = 'org.matrix.custom.html'
-            data.postfields.formatted_body = htmlbody
-        end
-
-        local api_event_function = 'm.room.message'
-
-        if olmstatus and room.encrypted then
-            api_event_function = 'm.room.encrypted'
-            local olmd = SERVER.olm
-
-            data.postfields.algorithm = OLM_ALGORITHM
-            data.postfields.sender_key = olmd.device_key
-            data.postfields.ciphertext = {}
-
-            -- Count number of devices we are sending to
-            local recipient_count = 0
-
-            for user_id, _ in pairs(room.users) do
-                for device_id, device_data in pairs(olmd.device_keys[user_id] or {}) do -- FIXME check for missing keys?
-
-                    local device_key
-                    -- TODO save this better somehow?
-                    for key_id, key_data in pairs(device_data.keys) do
-                        if key_id:match('^curve25519') then
-                            device_key = key_data
-                        end
-                    end
-                    local sessions = olmd:get_sessions(device_key)
-                    -- Use the session with the lowest ID
-                    -- TODO: figure out how to pick session better?
-                    table.sort(sessions)
-                    local pickled = next(sessions)
-                    if pickled then
-                        local session = olm.Session.new()
-                        session:unpickle(OLM_KEY, pickled)
-                        local session_id = session:session_id()
-                        perr(('Session ID: %s, user_id: %s, device_id: %s'):
-                            format(session_id, user_id, device_id))
-                        local payload = {
-                            room_id = room.identifier,
-                            ['type'] = "m.room.message",
-                            fingerprint = "", -- TODO: Olm:sha256 participants
-                            sender_device = olmd.device_id,
-                            content = {
-                                msgtype = msgtype,
-                                body = data.postfields.body or '',
-                                url = url
-                            }
-                        }
-                        -- encrypt body
-                        local mtype, e_body = session:encrypt(json.encode(payload))
-                        local ciphertext = {
-                            ["type"] = mtype,
-                            body = e_body
-                        }
-                        data.postfields.ciphertext[device_key] = ciphertext
-                        recipient_count = recipient_count + 1
-
-                        -- Save session
-                        olmd:store_session(device_key, session)
-                        session:clear()
-                    end
-                end
-            end
-            -- remove cleartext from original msg
-            data.postfields.body = nil
-            data.postfields.formatted_body = nil
-
-            if recipient_count == 0 then
-                perr('Aborted sending of encrypted message: could not find any valid recipients')
-                return
-            end
-        end
-
-        data.postfields = json.encode(data.postfields)
-        data.customrequest = 'PUT'
-
-        http(('/rooms/%s/send/%s/%s?access_token=%s')
-            :format(
-              urllib.quote(id),
-              api_event_function,
-              get_next_transaction_id(),
-              urllib.quote(SERVER.access_token)
-            ),
-            data,
-            nil,
-            nil,
-            id -- send room id to extra
-        )
-    end
-end
-
-function MatrixServer:emote(room_id, body)
-    self:Msg(room_id, body, 'm.emote')
-end
-
-function MatrixServer:notice(room_id, body)
-    self:Msg(room_id, body, 'm.notice')
-end
-
-function MatrixServer:state(room_id, key, data)
-    http(('/rooms/%s/state/%s?access_token=%s')
-        :format(urllib.quote(room_id),
-          urllib.quote(key),
-          urllib.quote(self.access_token)),
-        {customrequest = 'PUT',
-         postfields = json.encode(data),
-        })
-end
-
-function MatrixServer:set_membership(room_id, userid, data)
-    http(('/rooms/%s/state/m.room.member/%s?access_token=%s')
-        :format(urllib.quote(room_id),
-          urllib.quote(userid),
-          urllib.quote(self.access_token)),
-        {customrequest = 'PUT',
-         postfields = json.encode(data),
-        })
-end
-
-function MatrixServer:SendPresence(p, status_msg)
-    -- One of: ["online", "offline", "unavailable", "free_for_chat"]
-    local data = {
-        presence = p,
-        status_msg = status_msg
-    }
-    http(('/presence/%s/status?access_token=%s')
-        :format(
-          urllib.quote(self.user_id),
-          urllib.quote(self.access_token)),
-        {customrequest = 'PUT',
-         postfields = json.encode(data),
-        })
-end
-
-function MatrixServer:SendTypingNotice(room_id)
-    local data = {
-        typing = true,
-        timeout = 4*1000
-    }
-    http(('/rooms/%s/typing/%s?access_token=%s')
-        :format(urllib.quote(room_id),
-          urllib.quote(self.user_id),
-          urllib.quote(self.access_token)),
-        {customrequest = 'PUT',
-         postfields = json.encode(data),
-        })
-end
-
-function MatrixServer:Upload(room_id, filename)
-    local content_type = 'image/jpeg'
-    if filename:match'%.[Pp][nN][gG]$' then
-        content_type = 'image/png'
-    end
-    local url = w.config_get_plugin('homeserver_url') ..
-        ('_matrix/media/r0/upload?access_token=%s')
-        :format( urllib.quote(SERVER.access_token) )
-    w.hook_process_hashtable('curl', {
-        arg1 = '--data-binary', -- no encoding of data
-        arg2 = '@'..filename, -- @means curl will load the filename
-        arg3 = '-XPOST', -- HTTP POST method
-        arg4 = '-H', -- header
-        arg5 = 'Content-Type: '..content_type,
-        arg6 = '-s', -- silent
-        arg7 = url,
-    }, 30*1000, 'upload_cb', room_id)
-end
-
-function MatrixServer:CreateRoom(public, alias, invites)
-    local data = {}
-    if alias then
-        data.room_alias_name = alias
-    end
-    if public then
-        data.visibility = 'public'
-    else
-        data.visibility = 'private'
-    end
-    if invites then
-        data.invite = invites
-    end
-    http(('/createRoom?access_token=%s')
-        :format(urllib.quote(self.access_token)),
-        {customrequest = 'POST',
-         postfields = json.encode(data),
-        })
-end
-
-function MatrixServer:CreateRoomAlias(room_id, alias)
-    local data = {room_id = room_id}
-    alias = urllib.quote(alias)
-    http(('/directory/room/%s?access_token=%s')
-            :format(alias, urllib.quote(self.access_token)),
-        {customrequest = 'PUT',
-         postfields = json.encode(data),
-    })
-end
-
-function MatrixServer:ListRooms(arg)
-    local apipart = ('/publicRooms?access_token=%s'):format(urllib.quote(self.access_token))
-    if arg then
-        local url = 'https://' .. arg .. "/_matrix/client/r0"
-        http(url..apipart)
-    else
-        http(apipart)
-    end
-end
-
-function MatrixServer:Invite(room_id, user_id)
-    local data = {
-        user_id = user_id
-    }
-    http(('/rooms/%s/invite?access_token=%s')
-        :format(urllib.quote(room_id),
-          urllib.quote(self.access_token)),
-        {customrequest = 'POST',
-         postfields = json.encode(data),
-        })
-end
-
-function MatrixServer:Nick(displayname)
-    local data = {
-        displayname = displayname,
-    }
-    http(('/profile/%s/displayname?access_token=%s')
-        :format(
-          urllib.quote(self.user_id),
-          urllib.quote(self.access_token)),
-        {customrequest = 'PUT',
-         postfields = json.encode(data),
-        })
-end
-
-function buffer_input_cb(b, buffer, data)
-    for r_id, room in pairs(SERVER.rooms) do
-        if buffer == room.buffer then
-            data = data:gsub('^//', '/')
-            SERVER:Msg(r_id, data)
-            break
-        end
-    end
-    return w.WEECHAT_RC_OK
-end
-
-Room = {}
-Room.__index = Room
-Room.create = function(obj)
-    local room = {}
-    setmetatable(room, Room)
-    room.buffer = nil
-    room.identifier = obj['room_id']
-    local _, server = room.identifier:match('^(.*):(.+)$')
-    room.server = server
-    room.member_count = 0
-    -- Cache users for presence/nicklist
-    room.users = {}
-    -- Table of ids currently typing
-    room.typing_ids = {}
-    -- Cache the rooms power levels state
-    room.power_levels = {users={}, users_default=0}
-    -- Encryption status of room
-    room.encrypted = false
-    room.visibility = 'public'
-    room.join_rule = nil
-    room.roomname = nil -- m.room.name
-    room.aliases = nil -- aliases
-    room.canonical_alias = nil
-
-    -- We might not be a member yet
-    local state_events = obj.state or {}
-    for _, state in ipairs(state_events) do
-        if state['type'] == 'm.room.aliases' then
-            local name = state.content.aliases[1]
-            if name then
-                room.name, _ = name:match('(.+):(.+)')
-            end
-        end
-    end
-    if not room.name then
-        room.name = room.identifier
-    end
-    if not room.server then
-        room.server = 'matrix'
-    end
-
-    room.visibility = obj.visibility
-    if not obj['visibility'] then
-        room.visibility = 'public'
-    end
-
-    return room
-end
-
-function Room:SetName(name)
-    if not name or name == '' or name == json.null then
-        return
-    end
-    -- override hierarchy
-    if self.roomname and self.roomname ~= '' then
-        name = self.roomname
-    elseif self.canonical_alias then
-        name = self.canonical_alias
-        local short_name, _ = self.canonical_alias:match('^(.-):(.+)$')
-        if short_name then
-            name = short_name
-        end
-    elseif self.aliases then
-        local alias = self.aliases[1]
-        if name and alias then
-            local _
-            name, _ = alias:match('(.+):(.+)')
-        end
-    else
-        -- NO names. Set dynamic name based on members
-        local new = {}
-        for id, nick in pairs(self.users) do
-            -- Set the name to the other party
-            if id ~= SERVER.user_id then
-                new[#new+1] = nick
-            end
-        end
-        name = table.concat(new, ',')
-    end
-
-    if not name or name == '' or name == json.null then
-        return
-    end
-
-    -- Replace spaces with _, since weechat has poor support for names with
-    -- spaces.
-    -- (see weechat/weechat#937 https://github.com/weechat/weechat/issues/937)
-    -- name = name:gsub(" ", "_")
-
-    -- Check for dupe
-    local buffer_name = w.buffer_get_string(self.buffer, 'name')
-    if buffer_name == name then
-        return
-    end
-
-
-    w.buffer_set(self.buffer, "short_name", name)
-    w.buffer_set(self.buffer, "name", name)
-    -- Doesn't work
-    w.buffer_set(self.buffer, "plugin", "matrix")
-    w.buffer_set(self.buffer, "full_name",
-        self.server.."."..name)
-    w.buffer_set(self.buffer, "localvar_set_channel", name)
-end
-
-function Room:Topic(topic)
-    SERVER:state(self.identifier, 'm.room.topic', {topic=topic})
-end
-
-function Room:Name(name)
-    SERVER:state(self.identifier, 'm.room.name', {name=name})
-end
-
-function Room:public()
-    SERVER:state(self.identifier, 'm.room.join_rules', {join_rule='public'})
-end
-
-function Room:Upload(filename)
-    SERVER:Upload(self.identifier, filename)
-end
-
-function Room:Msg(msg)
-    SERVER:Msg(self.identifier, msg)
-end
-
-function Room:emote(msg)
-    SERVER:emote(self.identifier, msg)
-end
-
-function Room:Notice(msg)
-    SERVER:notice(self.identifier, msg)
-end
-
-function Room:SendTypingNotice()
-    SERVER:SendTypingNotice(self.identifier)
-end
-
-function Room:create_buffer()
-    --local buffer = w.buffer_search("", ("%s.%s"):format(self.server, self.name))
-    self.buffer = w.buffer_new(("%s.%s")
-        :format(self.server, self.name), "buffer_input_cb",
-        self.name, "closed_matrix_room_cb", "")
-    -- Needs to correspond with return values from Room:GetNickGroup()
-    -- We will use 5 nick groups:
-    -- 1: Ops
-    -- 2: Half-ops
-    -- 3: Voice
-    -- 4: People with presence
-    -- 5: People without presence
-    self.nicklist_groups = {
-        -- Emulate OPs
-        w.nicklist_add_group(self.buffer,
-            '', "000|o", "weechat.color.nicklist_group", 1),
-        w.nicklist_add_group(self.buffer,
-            '', "001|h", "weechat.color.nicklist_group", 1),
-        -- Emulate half-op
-        w.nicklist_add_group(self.buffer,
-            '', "002|v", "weechat.color.nicklist_group", 1),
-        -- Defined in weechat's irc-nick.h
-        w.nicklist_add_group(self.buffer,
-            '', "998|...", "weechat.color.nicklist_group", 1),
-        w.nicklist_add_group(self.buffer,
-            '', "999|...", "weechat.color.nicklist_group", 1),
-    }
-    w.buffer_set(self.buffer, "nicklist", "1")
-    -- Set to 1 for easier debugging of nick groups
-    w.buffer_set(self.buffer, "nicklist_display_groups", "0")
-    w.buffer_set(self.buffer, "localvar_set_server", self.server)
-    w.buffer_set(self.buffer, "localvar_set_roomid", self.identifier)
-    self:SetName(self.name)
-    if self.membership == 'invite' then
-        self:addNick(self.inviter)
-        if w.config_get_plugin('autojoin_on_invite') ~= 'on' then
-            w.print_date_tags(
-                self.buffer,
-                nil,
-                'notify_message',
-                ('You have been invited to join room %s by %s. Type /join in this buffer to join.')
-                    :format(
-                      self.name,
-                      self.inviter,
-                      self.identifier)
-            )
-        end
-    end
-end
-
-function Room:Freeze()
-    -- Function that saves all the lines in a buffer in a cache to be thawed
-    -- later. Used to redraw buffer when user requests more lines. Since
-    -- WeeChat can only render lines in order this is the workaround
-    local freezer = {}
-    local lines = w.hdata_pointer(w.hdata_get('buffer'), self.buffer, 'own_lines')
-    if lines == '' then return end
-    -- Start at top
-    local line = w.hdata_pointer(w.hdata_get('lines'), lines, 'first_line')
-    if line == '' then return end
-    local hdata_line = w.hdata_get('line')
-    local hdata_line_data = w.hdata_get('line_data')
-    while #line > 0 do
-        local data = w.hdata_pointer(hdata_line, line, 'data')
-        local tags = {}
-        local tag_count = w.hdata_integer(hdata_line_data, data, "tags_count")
-        if tag_count > 0 then
-            for i = 0, tag_count-1 do
-                local tag = w.hdata_string(hdata_line_data, data, i .. "|tags_array")
-                -- Skip notify tags since this is backlog
-                if not tag:match'^notify' then
-                    tags[#tags+1] = tag
-                end
-            end
-        end
-        tags[#tags+1] = 'no_log'
-        freezer[#freezer+1] = {
-            time = w.hdata_integer(hdata_line_data, data, 'time'),
-            tags = tags,
-            prefix = w.hdata_string(hdata_line_data, data, 'prefix'),
-            message = w.hdata_string(hdata_line_data, data, 'message'),
-        }
-        -- Move forward since we start at top
-        line = w.hdata_move(hdata_line, line, 1)
-    end
-    self.freezer = freezer
-end
-
-function Room:Thaw()
-    for _,l in ipairs(self.freezer) do
-        w.print_date_tags(
-            self.buffer,
-            l.time,
-            table.concat(l.tags, ','),
-            l.prefix .. '\t' .. l.message
-        )
-    end
-    -- Clear old data
-    self.freezer = nil
-end
-
-function Room:Clear()
-    w.buffer_clear(self.buffer)
-end
-
-function Room:destroy()
-    w.buffer_close(self.buffer)
-end
-
-function Room:_nickListChanged()
-    -- Check the user count, if it's 2 or less then we decide this buffer
-    -- is a "private" one like IRC's query type
-    if self.member_count == 3 then
-        w.buffer_set(self.buffer, "localvar_set_type", 'channel')
-        self.buffer_type = 'channel'
-    elseif self.member_count == 2 then
-        -- At the point where we reach two nicks, set the buffer name to be
-        -- the display name of the other guy that is not our self since it's
-        -- in effect a query, but the matrix protocol doesn't have such
-        -- a concept
-        w.buffer_set(self.buffer, "localvar_set_type", 'private')
-        self.buffer_type = 'query'
-    elseif self.member_count == 1 then
-        if not self.roomname and not self.aliases then
-            -- Set the name to ourselves
-            self:SetName(self.users[SERVER.user_id])
-        end
-    end
-end
-
-function Room:addNick(user_id, displayname)
-    local newnick = false
-    -- Sanitize displaynames a bit
-    if not displayname
-        or displayname == json.null
-        or displayname == ''
-        or w.config_get_plugin('nick_style') == 'uid'
-        or displayname:match'^%s+$' then
-        displayname = user_id:match('@(.*):.+')
-    end
-    if not self.users[user_id] then
-        self.member_count = self.member_count + 1
-        newnick = true
-    end
-
-    if self.users[user_id] ~= displayname then
-        self.users[user_id] = displayname
-    end
-
-    local nick_c = self:GetPresenceNickColor(user_id, SERVER.presence[user_id])
-    -- Check if this is ourselves
-    if user_id == SERVER.user_id then
-        w.buffer_set(self.buffer, "highlight_words", displayname)
-        w.buffer_set(self.buffer, "localvar_set_nick", displayname)
-    end
-
-    local ngroup, nprefix, nprefix_color = self:GetNickGroup(user_id)
-    -- Check if nick already exists
-    --local nick_ptr = w.nicklist_search_nick(self.buffer, '', displayname)
-    --if nick_ptr == '' then
-    local nick_ptr = w.nicklist_add_nick(self.buffer,
-        self.nicklist_groups[ngroup],
-        displayname,
-        nick_c, nprefix, nprefix_color, 1)
-    --else
-    --    -- TODO CHANGE nickname here
-    --end
-    if nick_ptr == '' then
-        -- Duplicate nick names :(
-        -- We just add the full id to the nicklist so atleast it will show
-        -- but we should probably assign something new and track the state
-        -- so we can print msgs with non-conflicting nicks too
-        w.nicklist_add_nick(self.buffer,
-            self.nicklist_groups[ngroup],
-            user_id,
-            nick_c, nprefix, nprefix_color, 1)
-        -- Since we can't allow duplicate displaynames, we just use the
-        -- user_id straight up. Maybe we could invent some clever
-        -- scheme here, like user(homeserver), user (2) or something
-        self.users[user_id] = user_id
-    end
-
-    if newnick then -- run this after nick been added so it can be used
-        self:_nickListChanged()
-    end
-
-    return displayname
-end
-
-function Room:GetNickGroup(user_id)
-    -- TODO, cache
-    local ngroup = 5
-    local nprefix = ' '
-    local nprefix_color = ''
-    if self:GetPowerLevel(user_id) >= 100 then
-        ngroup = 1
-        nprefix = '&'
-        nprefix_color = 'lightgreen'
-        if user_id == self.creator then
-            nprefix = '~'
-            nprefix_color = 'lightred'
-        end
-    elseif self:GetPowerLevel(user_id) >= 50 then
-        ngroup = 2
-        nprefix = '@'
-        nprefix_color = 'lightgreen'
-    elseif self:GetPowerLevel(user_id) > 0 then
-        ngroup = 3
-        nprefix = '+'
-        nprefix_color = 'yellow'
-    elseif SERVER.presence[user_id] then
-        -- User has a presence, put him in group3
-        ngroup = 4
-    end
-    return ngroup, nprefix, nprefix_color
-end
-
-function Room:GetPowerLevel(user_id)
-    return tonumber(self.power_levels.users[user_id] or self.power_levels.users_default or 0)
-end
-
-function Room:ClearTyping()
-    for user_id, nick in pairs(self.users) do
-        local _, nprefix, nprefix_color = self:GetNickGroup(user_id)
-        self:UpdateNick(user_id, 'prefix', nprefix)
-        self:UpdateNick(user_id, 'prefix_color', nprefix_color)
-    end
-end
-
-function Room:GetPresenceNickColor(user_id, presence)
-    local nick = self.users[user_id]
-    local nick_c
-    if user_id == SERVER.user_id then
-        -- Always use correct color for self
-        nick_c = 'weechat.color.chat_nick_self'
-    elseif presence == 'online' then
-        nick_c =  w.info_get('irc_nick_color_name', nick)
-    elseif presence == 'unavailable' then
-        nick_c = 'weechat.color.nicklist_away'
-    elseif presence == 'offline' then
-        nick_c = 'red'
-    elseif presence == nil then
-        nick_c = 'bar_fg'
-    else
-        dbg{err='unknown presence type',presence=presence}
-    end
-    return nick_c
-end
-
-function Room:UpdatePresence(user_id, presence)
-    if presence == 'typing' then
-        self:UpdateNick(user_id, 'prefix', '!')
-        self:UpdateNick(user_id, 'prefix_color', 'magenta')
-        return
-    end
-    local nick_c = self:GetPresenceNickColor(user_id, presence)
-    self:UpdateNick(user_id, 'color', nick_c)
-end
-
-function Room:UpdateNick(user_id, key, val)
-    local nick = self.users[user_id]
-    if not nick then return end
-    local nick_ptr = w.nicklist_search_nick(self.buffer, '', nick)
-
-    if nick_ptr ~= '' and key and val then
-        -- Check if we need to move the nick into another group
-        local group_ptr = w.nicklist_nick_get_pointer(self.buffer, nick_ptr,
-            'group')
-        local ngroup, nprefix, nprefix_color = self:GetNickGroup(user_id)
-        if group_ptr ~= self.nicklist_groups[ngroup] then
-            local nick_c = w.nicklist_nick_get_string(self.buffer, nick_ptr,
-                'color')
-            -- No WeeChat API for changing a nick's group so we will have to
-            -- delete the nick from the old nicklist and add it to the correct
-            -- nicklist group
-            w.nicklist_remove_nick(self.buffer, nick_ptr)
-            -- TODO please check if this call fails, if it does it means the
-            -- WeeChat version is old and has a bug so it can't remove nicks
-            -- and so it needs some workaround
-            nick_ptr = w.nicklist_add_nick(self.buffer,
-                self.nicklist_groups[ngroup],
-                nick,
-                nick_c, nprefix, nprefix_color, 1)
-        end
-        -- Check if we are clearing a typing notice, and don't issue updates
-        -- if we are, because it spams the API so much, including potential
-        -- relay clients
-        if key == 'prefix' and val == ' ' then
-            -- TODO check existing values like + and @ too
-            local prefix = w.nicklist_nick_get_string(self.buffer, nick_ptr,
-                key)
-            if prefix == '!' then
-                w.nicklist_nick_set(self.buffer, nick_ptr, key, val)
-            end
-        elseif key == 'prefix_color' then
-            local prefix_color = w.nicklist_nick_get_string(self.buffer,
-                nick_ptr, key)
-            if prefix_color ~= val then
-                w.nicklist_nick_set(self.buffer, nick_ptr, key, val)
-            end
-        else
-            -- Check if we are actually updating something, so there's less
-            -- updates issued (I think WeeChat sends all changes as nicklist
-            -- diffs to both UI code and to relay clients
-            local existing = w.nicklist_nick_get_string(self.buffer, nick_ptr, key)
-            if val ~= existing then
-                w.nicklist_nick_set(self.buffer, nick_ptr, key, val)
-            end
-        end
-    end
-end
-
-function Room:delNick(id)
-    if self.users[id] then
-        local nick = self.users[id]
-        local nick_ptr = w.nicklist_search_nick(self.buffer, '', nick)
-        if nick_ptr ~= '' then
-            w.nicklist_remove_nick(self.buffer, nick_ptr)
-        end
-        self.users[id] = nil
-        self.member_count = self.member_count - 1
-        self:_nickListChanged()
-        return true
-    end
-end
-
-function Room:UpdateLine(id, message)
-    if not id then return end
-    local lines = w.hdata_pointer(w.hdata_get('buffer'), self.buffer, 'own_lines')
-    if lines == '' then return end
-    local line = w.hdata_pointer(w.hdata_get('lines'), lines, 'last_line')
-    if line == '' then return end
-    local hdata_line = w.hdata_get('line')
-    local hdata_line_data = w.hdata_get('line_data')
-    while #line > 0 do
-        local needsupdate = false
-        local data = w.hdata_pointer(hdata_line, line, 'data')
-        local tags = {}
-        local tag_count = w.hdata_integer(hdata_line_data, data, "tags_count")
-        if tag_count > 0 then
-            for i = 0, tag_count-1 do
-                local tag = w.hdata_string(hdata_line_data, data, i .. "|tags_array")
-                tags[#tags+1] = tag
-                if tag:match(id) then
-                    needsupdate = true
-                end
-            end
-            if needsupdate then
-                w.hdata_update(hdata_line_data, data, {
-                    prefix = nil,
-                    message = message,
-                    tags_array = table.concat(tags, ','),
-                    })
-                return true
-            end
-        end
-        line = w.hdata_move(hdata_line, line, -1)
-    end
-    return false
-end
-
-function Room:formatNick(user_id)
-    -- Turns a nick name into a weechat-styled nickname. This means giving
-    -- it colors, and proper prefix and suffix
-    local nick = self.users[user_id]
-    if not nick then
-        return user_id
-    end
-    -- Remove nasty white space
-    nick = nick:gsub('[\n\t]', '')
-    local color
-    if user_id == SERVER.user_id then
-        color = w.color('chat_nick_self')
-    else
-        color = w.info_get('irc_nick_color', nick)
-    end
-    local _, nprefix, nprefix_c = self:GetNickGroup(user_id)
-    local prefix = wconf('weechat.look.nick_prefix')
-    local prefix_c = wcolor('weechat.color.chat_nick_prefix')
-    local suffix = wconf('weechat.look.nick_suffix')
-    local suffix_c = wcolor('weechat.color.chat_nick_suffix')
-    local nick_f = prefix_c
-        .. prefix
-        .. wcolor(nprefix_c)
-        .. nprefix
-        .. color
-        .. nick
-        .. suffix_c
-        .. suffix
-    return nick_f
-end
-
-function Room:decryptChunk(chunk)
-    -- vector client doesn't provide this
-    chunk.content.msgtype = 'm.text'
-
-    if not olmstatus then
-        chunk.content.body = 'encrypted message, unable to decrypt'
-        return chunk
-    end
-
-    chunk.content.body = 'encrypted message, unable to decrypt'
-    local device_key = chunk.content.sender_key
-    -- Find our id
-    local ciphertexts = chunk.content.ciphertext
-    local ciphertext
-    if not ciphertexts then
-        chunk.content.body = 'Recieved an encrypted message, but could not find ciphertext array'
-    else
-        ciphertext = ciphertexts[SERVER.olm.device_key]
-    end
-    if not ciphertext then
-        chunk.content.body = 'Recieved an encrypted message, but could not find cipher for ourselves from the sender.'
-        return chunk
-    end
-
-    local session
-    local decrypted
-    local err
-    local found_session = false
-    local sessions = SERVER.olm:get_sessions(device_key)
-    for id, pickle in pairs(sessions) do
-        -- Check if we already successfully decrypted with a sesssion, if that
-        -- is the case we break the loop
-        if decrypted then
-            break
-        end
-        session = olm.Session.new()
-        session:unpickle(OLM_KEY, pickle)
-        local matches_inbound = session:matches_inbound(ciphertext.body)
-        ---if ciphertext.type == 0 and matches_inbound then
-        if matches_inbound then
-            found_session = true
-        end
-        local cleartext
-        cleartext, err = session:decrypt(ciphertext.type, ciphertext.body)
-        if not err then
-            if DEBUG then
-                perr(('olm: Able to decrypt with an existing session %s'):format(session:session_id()))
-            end
-            decrypted = cleartext
-            SERVER.olm:store_session(device_key, session)
-        else
-            chunk.content.body = "Decryption error: "..err
-            if DEBUG then
-                perr(('olm: Unable to decrypt with an existing session: %s. Session-ID: %s'):format(err, session:session_id()))
-            end
-        end
-        session:clear()
-    end
-    if ciphertext.type == 0 and not found_session and not decrypted then
-        session = olm.Session.new()
-        local _
-        _, err = session:create_inbound_from(
-            SERVER.olm.account, device_key, ciphertext.body)
-        if err then
-            session:clear()
-            chunk.content.body = "Decryption error: create inbound "..err
-            return chunk
-        end
-        decrypted, err = session:decrypt(ciphertext.type, ciphertext.body)
-        if err then
-            session:clear()
-            chunk.content.body = "Decryption error: "..err
-            return chunk
-        end
-        -- TODO SERVER.olm.account:remove_one_time_keys(session)
-        local session_id = session:session_id()
-        perr(('Session ID: %s, user_id: %s, device_id: %s'):
-        format(session_id, SERVER.user_id, SERVER.olm.device_id))
-        SERVER.olm:store_session(device_key, session)
-        session:clear()
-        if err then
-            chunk.content.body = "Decryption error: "..err
-            return chunk
-        end
-    end
-
-    if decrypted then
-        local success, payload = pcall(json.decode, decrypted)
-        if not success then
-            chunk.content.body = "Payload error: "..payload
-            return chunk
-        end
-        -- TODO use the room id from payload for security
-        chunk.content.msgtype = payload.content.msgtype
-        -- Style the message so user can tell if it's
-        -- an encrypted message or not
-        local color = w.color(w.config_get_plugin(
-            'encrypted_message_color'))
-        chunk.content.body = color .. payload.content.body
-    end
-
-    return chunk
-end
-
--- Parses a chunk of json meant for a room
-function Room:ParseChunk(chunk, backlog, chunktype)
-    local taglist = {}
-    local tag = function(tag)
-        -- Helper function to add tags
-        if type(tag) == 'table' then
-            for _, t in ipairs(tag) do
-                taglist[t] = true
-            end
-        else
-            taglist[tag] = true
-        end
-    end
-    local tags = function()
-        -- Helper for returning taglist for this message
-        local out = {}
-        for k, v in pairs(taglist) do
-            table.insert(out, k)
-        end
-        return table.concat(out, ',')
-    end
-    if not backlog then
-        backlog = false
-    end
-
-    if backlog then
-        tag{'no_highlight','notify_none','no_log'}
-    end
-
-    local is_self = false
-    local was_decrypted = false
-
-    -- Sender of chunk, used to be chunk.user_id, v2 uses chunk.sender
-    local sender = chunk.sender or chunk.user_id
-    -- Check if own message
-    if sender == SERVER.user_id then
-        is_self = true
-        tag{'no_highlight','notify_none'}
-    end
-    -- Add Event ID to each line so can use it later to match on for things
-    -- like redactions and localecho, etc
-    tag{chunk.event_id}
-
-    -- Some messages are missing ts
-    local origin_server_ts = chunk['origin_server_ts'] or 0
-    local time_int = origin_server_ts/1000
-
-    if chunk['type'] == 'm.room.message' or chunk['type'] == 'm.room.encrypted' then
-        if chunk['type'] == 'm.room.encrypted'  then
-            tag{'no_log'} -- Don't log encrypted message
-            chunk = self:decryptChunk(chunk)
-            was_decrypted = true
-        end
-
-        if not backlog and not is_self then
-            tag'notify_message'
-            if self.buffer_type == 'query' then
-                tag'notify_private'
-            end
-        end
-
-        local color = default_color
-        local content = chunk['content']
-        local body = content['body']
-
-        if not content['msgtype'] then
-            -- We don't support redactions
-            return
-        end
-
-        -- If it has transaction id, it is from this client.
-        local is_from_this_client = false
-        if chunk.unsigned and chunk.unsigned.transaction_id then
-            is_from_this_client = true
-        end
-
-        -- luacheck: ignore 542
-        if content['msgtype'] == 'm.text' then
-            -- TODO
-            -- Parse HTML here:
-            -- content.format = 'org.matrix.custom.html'
-            -- fontent.formatted_body...
-        elseif content['msgtype'] == 'm.image' then
-            local url = content['url']
-            if type(url) ~= 'string' then
-                url = ''
-            end
-            url = url:gsub('mxc://',
-                w.config_get_plugin('homeserver_url')
-                .. '_matrix/media/v1/download/')
-            -- Synapse homeserver supports arbitrary file endings, so we put
-            -- filename at the end to make it nicer for URL "sniffers" to
-            -- realise it's a image URL
-            body = url .. '/' .. content.body
-        elseif content.msgtype == 'm.file' or content.msgtype == 'm.video' or
-            content.msgtype == 'm.audio' then
-            local url = content['url'] or ''
-            url = url:gsub('mxc://',
-                w.config_get_plugin('homeserver_url')
-                .. '_matrix/media/v1/download/')
-            body = 'File upload: ' ..
-                   tostring(content['body'])
-                   .. ' ' .. url
-        elseif content['msgtype'] == 'm.notice' then
-            color = wcolor('irc.color.notice')
-            body = content['body']
-        elseif content['msgtype'] == 'm.emote' then
-            local nick_c
-            local nick = self.users[sender] or sender
-            if is_self then
-                nick_c = w.color('chat_nick_self')
-            else
-                nick_c = w.info_get('irc_nick_color', nick)
-            end
-            tag"irc_action"
-            local prefix_c = wcolor'weechat.color.chat_prefix_action'
-            local prefix = wconf'weechat.look.prefix_action'
-            body = ("%s%s %s%s"):format(
-                nick_c, nick, color, content['body']
-            )
-            prefix = prefix_c .. prefix
-            local data = ("%s\t%s"):format(prefix, body)
-            if not backlog and is_self and is_from_this_client and
-              (   w.config_get_plugin('local_echo') == 'on'
-                  or was_decrypted -- local echo for encryption
-              )
-              then
-                -- We have already locally echoed this line
-                return
-            else
-                return w.print_date_tags(self.buffer, time_int, tags(), data)
-            end
-        else
-            -- Unknown content type, but if it contains an URL we will print
-            -- URL and body
-            local url = content['url']
-            if url ~= nil then
-                url = url:gsub('mxc://',
-                    w.config_get_plugin('homeserver_url')
-                    .. '_matrix/media/v1/download/')
-                body = content['body'] .. ' ' .. url
-            end
-            dbg {
-                warning='Warning: unknown/unhandled content type',
-                event=content
-            }
-        end
-        if not backlog and is_self and is_from_this_client
-          -- TODO better check, to work for multiple weechat clients
-          and (
-              w.config_get_plugin('local_echo') == 'on'
-              or was_decrypted -- local echo for encrypted messages
-            )
-          and (-- we don't generate local echo for files and images
-              content.msgtype == 'm.text'
-          )
-          then
-            -- We have already locally echoed this line
-            return
-        end
-        local data = ("%s\t%s%s"):format(
-                self:formatNick(sender),
-                color,
-                body)
-        w.print_date_tags(self.buffer, time_int, tags(), data)
-    elseif chunk['type'] == 'm.room.topic' then
-        local title = chunk['content']['topic']
-        if not title then
-            title = ''
-        end
-        w.buffer_set(self.buffer, "title", title)
-        local color = wcolor("irc.color.topic_new")
-        local nick = self.users[sender] or sender
-        local data = ('--\t%s%s has changed the topic to "%s%s%s"'):format(
-                nick,
-                default_color,
-                color,
-                title,
-                default_color
-              )
-        w.print_date_tags(self.buffer, chunk.origin_server_ts, tags(),
-            data)
-    elseif chunk['type'] == 'm.room.name' then
-        local name = chunk['content']['name']
-        if name ~= '' or name ~= json.null then
-            self.roomname = name
-            self:SetName(name)
-        end
-    elseif chunk['type'] == 'm.room.member' then
-        if chunk['content']['membership'] == 'join' then
-            tag"irc_join"
-            --- FIXME shouldn't be neccessary adding all the time
-            local name = self.users[sender] or self:addNick(sender, chunk.content.displayname)
-            if not name or name == json.null or name == '' then
-                name = sender
-            end
-            SERVER.avatars[name] = chunk.content.avatar_url
-            -- Check if the chunk has prev_content or not
-            -- if there is prev_content there wasn't a join but a nick change
-            -- or duplicate join
-            local prev_content = chunk.unsigned and chunk.unsigned.prev_content
-            if prev_content
-                    and prev_content.membership == 'join'
-                    and chunktype == 'messages' then
-                local nick = chunk.content.displayname or sender
-                if not nick or nick == json.null or nick == '' then
-                    nick = sender
-                end
-                local oldnick = prev_content.displayname
-                if not oldnick or oldnick == json.null then
-                    oldnick = sender
-                end
-
-                if oldnick == nick then
-                    -- Maybe they changed their avatar or something else
-                    -- that we don't care about (or multiple joins)
-                    return
-                end
-
-                self:delNick(sender)
-                self:addNick(sender, nick)
-
-                local pcolor = wcolor'weechat.color.chat_prefix_network'
-                tag'irc_nick'
-                local data = ('%s--\t%s%s%s is now known as %s%s'):format(
-                    pcolor,
-                    w.info_get('irc_nick_color', oldnick),
-                    oldnick,
-                    default_color,
-                    w.info_get('irc_nick_color', name),
-                    nick)
-                w.print_date_tags(self.buffer, time_int, tags(), data)
-            elseif chunktype == 'messages' then
-                tag"irc_smart_filter"
-                local data = ('%s%s\t%s%s%s (%s%s%s) joined the room.'):format(
-                    wcolor('weechat.color.chat_prefix_join'),
-                    wconf('weechat.look.prefix_join'),
-                    w.info_get('irc_nick_color', name),
-                    name,
-                    wcolor('irc.color.message_join'),
-                    wcolor'weechat.color.chat_host',
-                    sender,
-                    wcolor('irc.color.message_join')
-                )
-                w.print_date_tags(self.buffer, time_int, tags(), data)
-                -- if this is an encrypted room, also download key
-                if olmstatus and self.encrypted then
-                    SERVER.olm:query{sender}
-                end
-            end
-        elseif chunk['content']['membership'] == 'leave' then
-            if chunktype == 'messages' then
-                local nick = self.users[chunk.state_key] or sender
-                local prev = chunk.unsigned.prev_content
-                if (prev and
-                        prev.displayname and
-                        prev.displayname ~= json.null) then
-                    nick = prev.displayname
-                end
-                if sender ~= chunk.state_key then -- Kick
-                    tag{"irc_quit","irc_kick","irc_smart_filter"}
-                    local reason = chunk.content.reason or ''
-                    local sender_nick = self.users[chunk.sender]
-                    local data = ('%s%s\t%s%s%s has kicked %s%s%s (%s).'):format(
-                        wcolor('weechat.color.chat_prefix_quit'),
-                        wconf('weechat.look.prefix_quit'),
-                        w.info_get('irc_nick_color', sender_nick),
-                        sender_nick,
-                        wcolor('irc.color.message_quit'),
-                        w.info_get('irc_nick_color', nick),
-                        nick,
-                        default_color,
-                        reason
-                    )
-                    w.print_date_tags(self.buffer, time_int, tags(), data)
-                else
-                    tag{"irc_quit","irc_smart_filter"}
-                    local data = ('%s%s\t%s%s%s left the room.'):format(
-                        wcolor('weechat.color.chat_prefix_quit'),
-                        wconf('weechat.look.prefix_quit'),
-                        w.info_get('irc_nick_color', nick),
-                        nick,
-                        wcolor('irc.color.message_quit')
-                    )
-                    w.print_date_tags(self.buffer, time_int, tags(), data)
-                end
-            end
-            self:delNick(chunk.state_key)
-        elseif chunk['content']['membership'] == 'invite' then
-            -- Check if we were the one being invited
-            if chunk.state_key == SERVER.user_id and (
-                  (not backlog and chunktype == 'messages') or
-                  chunktype == 'states') then
-                --self:addNick(sender)
-                if w.config_get_plugin('autojoin_on_invite') == 'on' then
-                    SERVER:Join(self.identifier)
-                    mprint(('%s invited you'):format(sender))
-                else
-                    mprint(('You have been invited to join room %s by %s. Type /join %s to join.')
-                        :format(
-                          self.name,
-                          sender,
-                          self.identifier))
-                end
-            end
-            if chunktype == 'messages' then
-                tag"irc_invite"
-                local prefix_c = wcolor'weechat.color.chat_prefix_action'
-                local prefix = wconf'weechat.look.prefix_action'
-                local data = ("%s%s\t%s invited %s to join"):format(
-                    prefix_c,
-                    prefix,
-                    self.users[sender] or sender,
-                    self.users[chunk.state_key] or chunk.state_key
-                )
-                w.print_date_tags(self.buffer, time_int, tags(), data)
-            end
-        elseif chunk['content']['membership'] == 'ban' then
-            if chunktype == 'messages' then
-                tag"irc_ban"
-                local prefix_c = wcolor'weechat.color.chat_prefix_action'
-                local prefix = wconf'weechat.look.prefix_action'
-                local data = ("%s%s\t%s banned %s"):format(
-                    prefix_c,
-                    prefix,
-                    self.users[sender] or sender,
-                    self.users[chunk.state_key] or chunk.state_key
-                )
-                w.print_date_tags(self.buffer, time_int, tags(), data)
-            end
-        else
-            dbg{err= 'unknown membership type in ParseChunk', chunk= chunk}
-        end
-        -- if it's backlog this is done at the end from the caller place
-        if not backlog then
-            -- Run SetName on each member change in case we need to update room name
-            self:SetName(self.identifier)
-        end
-    elseif chunk['type'] == 'm.room.create' then
-        self.creator = chunk.content.creator
-    elseif chunk['type'] == 'm.room.power_levels' then
-        if chunk.content.users then
-            self.power_levels = chunk.content
-            for user_id, lvl in pairs(self.power_levels.users) do
-                -- TODO
-                -- calculate changes here and generate message lines
-                -- describing the change
-            end
-            for user_id, lvl in pairs(self.power_levels.users) do
-                local _, nprefix, nprefix_color = self:GetNickGroup(user_id)
-                self:UpdateNick(user_id, 'prefix', nprefix)
-                self:UpdateNick(user_id, 'prefix_color', nprefix_color)
-            end
-        end
-    elseif chunk['type'] == 'm.room.join_rules' then
-        -- TODO: parse join_rules events --
-        self.join_rules = chunk.content
-    elseif chunk['type'] == 'm.typing' then
-        -- Store the typing ids in a table that the bar item can use
-        local typing_ids = {}
-        for _, id in ipairs(chunk.content.user_ids) do
-            self:UpdatePresence(id, 'typing')
-            typing_ids[#typing_ids+1] = self.users[id]
-        end
-        self.typing_ids = typing_ids
-        w.bar_item_update('matrix_typing_notice')
-    elseif chunk['type'] == 'm.presence' then
-        SERVER:UpdatePresence(chunk)
-    elseif chunk['type'] == 'm.room.aliases' then
-        -- Use first alias, weechat doesn't really support multiple  aliases
-        self.aliases = chunk.content.aliases
-        self:SetName(chunk.content.aliases[1])
-    elseif chunk['type'] == 'm.room.canonical_alias' then
-        self.canonical_alias = chunk.content.alias
-        self:SetName(self.canonical_alias)
-    elseif chunk['type'] == 'm.room.redaction' then
-        local redact_id = chunk.redacts
-        --perr('Redacting message ' .. redact_id)
-        local result = self:UpdateLine(redact_id, w.color'darkgray'..'(redacted)')
-        if not result and not backlog then
-            -- backlog doesn't send original message
-            perr(('Could not find message to redact :(. Redaction ID is: %s'):format(redact_id))
-        end
-    elseif chunk['type'] == 'm.room.history_visibility' then
-        self.history_visibility = chunk.content.history_visibility
-    -- luacheck: ignore 542
-    elseif chunk['type'] == 'm.receipt' then
-        -- TODO: figure out if we can do something sensible with read receipts
-    elseif chunk['type'] == 'm.fully_read' and self.buffer ~= current_buffer then
-        -- we don't want to update read line for the current buffer
-        -- TODO: check if read marker correspond to the last event in the room
-        w.buffer_set(self.buffer, "unread", "")
-        w.buffer_set(self.buffer, "hotlist", "-1")
-    else
-        if DEBUG then
-            perr(('Unknown event type %s%s%s in room %s%s%s'):format(
-                w.color'bold',
-                chunk.type,
-                default_color,
-                w.color'bold',
-                self.name,
-                default_color))
-                dbg{chunk=chunk}
-        end
-    end
-end
-
-function Room:Op(nick)
-    for id, name in pairs(self.users) do
-        if name == nick then
-            -- patch the locally cached power levels
-            self.power_levels.users[id] = 99
-            SERVER:state(self.identifier, 'm.room.power_levels',
-                self.power_levels)
-            break
-        end
-    end
-end
-
-function Room:Voice(nick)
-    for id, name in pairs(self.users) do
-        if name == nick then
-            -- patch the locally cached power levels
-            self.power_levels.users[id] = 25
-            SERVER:state(self.identifier, 'm.room.power_levels',
-                self.power_levels)
-            break
-        end
-    end
-end
-
-function Room:Devoice(nick)
-    for id, name in pairs(self.users) do
-        if name == nick then
-            -- patch the locally cached power levels
-            self.power_levels.users[id] = 0
-            SERVER:state(self.identifier, 'm.room.power_levels',
-                self.power_levels)
-            break
-        end
-    end
-end
-
-function Room:Deop(nick)
-    for id, name in pairs(self.users) do
-        if name == nick then
-            -- patch the locally cached power levels
-            self.power_levels.users[id] = 0
-            SERVER:state(self.identifier, 'm.room.power_levels',
-                self.power_levels)
-            break
-        end
-    end
-end
-
-function Room:Kick(nick, reason)
-    for id, name in pairs(self.users) do
-        if name == nick then
-            local data = {
-                membership = 'leave',
-                reason = 'Kicked by '..SERVER.user_id
-            }
-            SERVER:set_membership(self.identifier, id, data)
-            break
-        end
-    end
-end
-
-function Room:Whois(nick)
-    for id, name in pairs(self.users) do
-        if name == nick then
-            local pcolor = wcolor'weechat.color.chat_prefix_network'
-            local data = ('%s--\t%s%s%s has user id %s%s'):format(
-                pcolor,
-                w.info_get('irc_nick_color', nick),
-                nick,
-                default_color,
-                w.info_get('irc_nick_color', id),
-                id)
-            w.print_date_tags(self.buffer, nil, 'notify_message', data)
-            local pdata = ('%s--\t%s%s%s has presence %s%s'):format(
-                pcolor,
-                w.info_get('irc_nick_color', nick),
-                nick,
-                default_color,
-                pcolor,
-                SERVER.presence[id] or 'offline')
-            w.print_date_tags(self.buffer, nil, 'notify_message', pdata)
-
-            local avatar_url = SERVER.avatars[nick]
-            if avatar_url ~= nil then
-                avatar_url = avatar_url:gsub('mxc://',
-                    w.config_get_plugin('homeserver_url')
-                    .. '_matrix/media/v1/download/')
-                local avatar_line = ('%s--\t%s%s%s has avatar %s'):format(
-                    pcolor,
-                    w.info_get('irc_nick_color', nick),
-                    nick,
-                    default_color,
-                    avatar_url)
-                w.print_date_tags(self.buffer, nil, 'notify_message', avatar_line)
-            end
-            -- TODO support printing status_msg field in presence data here
-            break
-        end
-    end
-end
-
-function Room:Invite(id)
-    SERVER:Invite(self.identifier, id)
-end
-
-function Room:Encrypt()
-    self.encrypted = true
-    -- Download keys for all members
-    self:Download_keys()
-    -- Create sessions
-    -- Pickle.
-    -- Save
-end
-function Room:Download_keys()
-    for id, name in pairs(self.users) do
-        -- TODO enable batch downloading of keys here when synapse can handle it
-        SERVER.olm:query({id})
-    end
-end
-
-function Room:MarkAsRead()
-    -- Get event id from tag of last line in buffer
-    local lines = w.hdata_pointer(w.hdata_get('buffer'), self.buffer, 'own_lines')
-    if lines == '' then return end
-    local line = w.hdata_pointer(w.hdata_get('lines'), lines, 'last_line')
-    if line == '' then return end
-    local hdata_line = w.hdata_get('line')
-    local hdata_line_data = w.hdata_get('line_data')
-    local data = w.hdata_pointer(hdata_line, line, 'data')
-    local tag_count = w.hdata_integer(hdata_line_data, data, "tags_count")
-    if tag_count > 0 then
-        for i = 0, tag_count-1 do
-            local tag = w.hdata_string(hdata_line_data, data, i .. "|tags_array")
-            -- Event ids are like $142533663810152bfUKc:matrix.org
-            if tag:match'^%$.*:' then
-                SERVER:SendReadMarker(self.identifier, tag)
-                break
-            end
-        end
-    end
-end
-
-function poll(a, b)
-    SERVER:poll()
-    return w.WEECHAT_RC_OK
-end
-
-function polltimer_cb(a, b)
-    local now = os.time()
-    if (now - SERVER.polltime) > POLL_INTERVAL+10 then
-        -- Release the poll lock
-        SERVER.poll_lock = false
-        SERVER:poll()
-    end
-    return w.WEECHAT_RC_OK
-end
-
-function otktimer_cb(a, b)
-    SERVER.olm:check_server_keycount()
-    return w.WEECHAT_RC_OK
-end
-
-function cleartyping(a, b)
-    for id, room in pairs(SERVER.rooms) do
-        room:ClearTyping()
-    end
-    return w.WEECHAT_RC_OK
-end
-
-function join_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if current_buffer == BUFFER or room then
-        local _, alias = split_args(args)
-        if not alias then
-            -- To support running /join on a invited room without args
-            SERVER:Join(room.identifier)
-        else
-            SERVER:Join(alias)
-        end
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function part_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        SERVER:part(room)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function leave_command_cb(data, current_buffer, args)
-    return part_command_cb(data, current_buffer, args)
-end
-
-function me_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, message = split_args(args)
-        room:emote(message or '')
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function topic_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, topic = split_args(args)
-        room:Topic(topic)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function upload_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, upload = split_args(args)
-        room:Upload(upload)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function query_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, query = split_args(args)
-        for id, displayname in pairs(room.users) do
-            if displayname == query then
-                -- Create a new room and invite the guy
-                SERVER:CreateRoom(false, nil, {id})
-                return w.WEECHAT_RC_OK_EAT
-            end
-        end
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function create_command_cb(data, current_buffer, args)
-    local command, arg = split_args(args)
-    local room = SERVER:findRoom(current_buffer)
-    if (room or current_buffer == BUFFER) and command == '/create' then
-        if arg then
-            -- Room names are supposed to be without # and homeserver, so
-            -- we try to help the user out here
-            local alias = arg:match'#?(.*):?'
-            -- Create a non-public room with argument as alias
-            SERVER:CreateRoom(false, alias, nil)
-        else
-            mprint 'Use /create room-name'
-        end
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function createalias_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, alias = split_args(args)
-        SERVER:CreateRoomAlias(room.identifier, alias)
-        return w.WEECHAT_RC_OK_EAT
-    elseif current_buffer == BUFFER then
-        mprint 'Use /createalias #alias:homeserver.domain from a room'
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function invite_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, invitee = split_args(args)
-        room:Invite(invitee)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function list_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room or current_buffer == BUFFER then
-        local _, target = split_args(args)
-        SERVER:ListRooms(target)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function op_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, target = split_args(args)
-        room:Op(target)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function voice_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, target = split_args(args)
-        room:Voice(target)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function devoice_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, target = split_args(args)
-        room:Devoice(target)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-function deop_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, target = split_args(args)
-        room:Deop(target)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function kick_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, target = split_args(args)
-        room:Kick(target)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function nick_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room or current_buffer == BUFFER then
-        local _, nick = split_args(args)
-        SERVER:Nick(nick)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function whois_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, nick = split_args(args)
-        room:Whois(nick)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function notice_command_cb(data, current_buffer, args)
-    -- TODO sending from matrix buffer given a room name
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, msg = split_args(args)
-        room:Notice(msg)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function msg_command_cb(data, current_buffer, args)
-    local _, msgmask = split_args(args)
-    local mask, msg = split_args(msgmask)
-    local room
-    -- WeeChat uses * as a mask for current buffer
-    if mask == '*' then
-        room = SERVER:findRoom(current_buffer)
-    else
-        for id, r in pairs(SERVER.rooms) do
-            -- Send /msg to a ID
-            if id == mask then
-                room = r
-                break
-            elseif mask == r.name then
-                room = r
-                break
-            end
-        end
-    end
-
-    if room then
-        room:Msg(msg)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function encrypt_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, arg = split_args(args)
-        if arg == 'on' then
-            mprint('Enabling encryption for outgoing messages in room ' .. tostring(room.name))
-            room:Encrypt()
-        elseif arg == 'off' then
-            mprint('Disabling encryption for outgoing messages in room ' .. tostring(room.name))
-            room.encrypted = false
-        else
-            w.print(current_buffer, 'Use /encrypt on or /encrypt off to turn encryption on or off')
-        end
-        return w.WEECHAT_RC_OK_EAT
-    else
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function public_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        mprint('Marking room as public: ' .. tostring(room.name))
-        room:public()
-        return w.WEECHAT_RC_OK_EAT
-    else
-        mprint('Run command from a room')
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function names_command_cb(cbdata, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local nrcolor = function(nr)
-            return wcolor'weechat.color.chat_channel'
-                .. tostring(nr)
-                .. default_color
-        end
-        local buffer_name = nrcolor(w.buffer_get_string(room.buffer, 'name'))
-        local delim_c = wcolor'weechat.color.chat_delimiters'
-        local tags = 'no_highlight,no_log,irc_names'
-        local pcolor = wcolor'weechat.color.chat_prefix_network'
-        local ngroups = {}
-        local nicks = {}
-        for id, name in pairs(room.users) do
-            local ncolor
-            if id == SERVER.user_id then
-                ncolor = w.color('chat_nick_self')
-            else
-                ncolor = w.info_get('irc_nick_color', name)
-            end
-            local ngroup, nprefix, nprefix_color = room:GetNickGroup(id)
-            if nprefix == ' ' then nprefix = '' end
-            nicks[#nicks+1] = ('%s%s%s%s'):format(
-                w.color(nprefix_color),
-                nprefix,
-                ncolor,
-                name
-            )
-            if not ngroups[ngroup] then
-                ngroups[ngroup] = 0
-            end
-            ngroups[ngroup] = ngroups[ngroup] + 1
-        end
-        local line1 = ('%s--\tNicks %s: %s[%s%s]'):format(
-            pcolor,
-            buffer_name,
-            delim_c,
-            table.concat(nicks, ' '),
-            delim_c
-        )
-        w.print_date_tags(room.buffer, 0, tags, line1)
-        local line2 = (
-            '%s--\tChannel %s: %s nicks %s(%s%s ops, %s voice, %s normals%s)'
-            ):format(
-                pcolor,
-                buffer_name,
-                nrcolor(room.member_count),
-                delim_c,
-                default_color,
-                nrcolor((ngroups[1] or 0) + (ngroups[2] or 0)),
-                nrcolor(ngroups[3] or 0),
-                nrcolor((ngroups[4] or 0) + (ngroups[5] or 0)),
-                delim_c
-            )
-        w.print_date_tags(room.buffer, 0, tags, line2)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        perr('Could not find room')
-        return w.WEECHAT_RC_OK
-    end
-end
-
-function more_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        SERVER:getMessages(room.identifier, 'b', room.prev_batch, 120)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        perr('/more Could not find room')
-    end
-    return w.WEECHAT_RC_OK
-end
-
-function roominfo_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        dbg{room=room}
-        return w.WEECHAT_RC_OK_EAT
-    else
-        perr('/roominfo Could not find room')
-    end
-    return w.WEECHAT_RC_OK
-end
-
-function name_command_cb(data, current_buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        local _, name = split_args(args)
-        room:Name(name)
-        return w.WEECHAT_RC_OK_EAT
-    else
-        perr('/name Could not find room')
-    end
-    return w.WEECHAT_RC_OK
-end
-
-function closed_matrix_buffer_cb(data, buffer)
-    BUFFER = nil
-    return w.WEECHAT_RC_OK
-end
-
-function closed_matrix_room_cb(data, buffer)
-    -- WeeChat closed our room
-    local room = SERVER:findRoom(buffer)
-    if room then
-        room.buffer = nil
-        perr('Room got closed: '..room.name)
-        SERVER.rooms[room.identifier] = nil
-        return w.WEECHAT_RC_OK
-    end
-    return w.WEECHAT_RC_ERR
-end
-
-function typing_notification_cb(signal, sig_type, data)
-    -- Ignore commands
-    if data:match'^/' then
-        return w.WEECHAT_RC_OK
-    end
-    -- Is this signal coming from a matrix buffer?
-    local room = SERVER:findRoom(data)
-    if room then
-        local input = w.buffer_get_string(data, "input")
-        -- Start sending when it reaches > 4 and doesn't start with command
-        if #input > 4 and not input:match'^/' then
-            local now = os.time()
-            -- Generate typing events every 4th second
-            if SERVER.typing_time + 4 < now then
-                SERVER.typing_time = now
-                room:SendTypingNotice()
-            end
-        end
-    end
-
-    return w.WEECHAT_RC_OK
-end
-
-function buffer_switch_cb(data, signal, sig_type)
-    -- Update bar item
-    w.bar_item_update('matrix_typing_notice')
-    if current_buffer then
-        local room = SERVER:findRoom(current_buffer)
-        if room then
-            room:MarkAsRead()
-        end
-    end
-
-    current_buffer = w.current_buffer()
-    local room = SERVER:findRoom(current_buffer)
-    if room then
-        room:MarkAsRead()
-    end
-    return w.WEECHAT_RC_OK
-end
-
-function typing_bar_item_cb(data, buffer, args)
-    local room = SERVER:findRoom(current_buffer)
-    if not room then return '' end
-    local typing_ids = table.concat(room.typing_ids, ' ')
-    if #typing_ids > 0 then
-        return "Typing: ".. typing_ids
-    end
-    return ''
-end
-
-if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "matrix_unload", "UTF-8") then
-    -- Save WeeChat version to a global so other functionality can see it
-    local version = w.info_get('version_number', '') or 0
-    WEECHAT_VERSION = tonumber(version)
-    local settings = {
-        homeserver_url= {'https://matrix.org/', 'Full URL including port to your homeserver (including trailing slash) or use default matrix.org'},
-        user= {'', 'Your homeserver username'},
-        password= {'', 'Your homeserver password'},
-        backlog_lines= {'120', 'Number of lines to fetch from backlog upon connecting'},
-        presence_filter = {'off', 'Filter presence messages and ephemeral events (for performance)'},
-        autojoin_on_invite = {'on', 'Automatically join rooms you are invited to'},
-        typing_notices = {'on', 'Send typing notices when you type'},
-        local_echo = {'on', 'Print lines locally instead of waiting for return from server'},
-        debug = {'off', 'Print a lot of extra information to help with finding bugs and other problems.'},
-        encrypted_message_color = {'lightgreen', 'Print encrypted mesages with this color'},
-        --olm_secret = {'', 'Password used to secure olm stores'},
-        timeout = {'5', 'Time in seconds until a connection is assumed to be timed out'},
-        nick_style = {'nick', 'Show nicknames or user IDs in chat (\'nick\' or \'uid\')'},
-        read_receipts = {'on', 'Send read receipts. Note that not sending them will prevent a room to be marked as read in Riot clients.'}
-    }
-    -- set default settings
-    for option, value in pairs(settings) do
-        if w.config_is_set_plugin(option) ~= 1 then
-            w.config_set_plugin(option, value[1])
-        end
-        if WEECHAT_VERSION >= 0x00030500 then
-            w.config_set_desc_plugin(option, ('%s (default: "%s")'):format(
-                     value[2], value[1]))
-        end
-    end
-    timeout = tonumber(w.config_get_plugin('timeout'))*1000
-    errprefix = wconf'weechat.look.prefix_error'
-    errprefix_c = wcolor'weechat.color.chat_prefix_error'
-    HOMEDIR = w.info_get('weechat_dir', '') .. '/'
-    local commands = {
-        'join', 'part', 'leave', 'me', 'topic', 'upload', 'query', 'list',
-        'op', 'voice', 'deop', 'devoice', 'kick', 'create', 'createalias', 'invite', 'nick',
-        'whois', 'notice', 'msg', 'encrypt', 'public', 'names', 'more',
-        'roominfo', 'name'
-    }
-    for _, c in pairs(commands) do
-        w.hook_command_run('/'..c, c..'_command_cb', '')
-    end
-
-    if w.config_get_plugin('typing_notices') == 'on' then
-        w.hook_signal('input_text_changed', "typing_notification_cb", '')
-    end
-
-    if w.config_get_plugin('debug') == 'on' then
-        DEBUG = true
-    end
-
-    w.hook_config('plugins.var.lua.matrix.debug', 'configuration_changed_cb', '')
-    w.hook_config('plugins.var.lua.matrix.timeout', 'configuration_changed_cb', '')
-
-    local cmds = {'help', 'connect', 'debug', 'msg'}
-    w.hook_command(SCRIPT_COMMAND, 'Plugin for matrix.org chat protocol',
-        '[command] [command options]',
-        'Commands:\n' ..table.concat(cmds, '\n') ..
-        '\nUse /matrix help [command] to find out more\n' ..
-        '\nSupported slash commands (i.e. /commands):\n' ..
-        table.concat(commands, ', '),
-        -- Completions
-        table.concat(cmds, '|'),
-        'matrix_command_cb', '')
-
-    w.hook_command_run('/away -all*', 'matrix_away_command_run_cb', '')
-    SERVER = MatrixServer.create()
-
-    if WEECHAT_VERSION < 0x01040000 then
-       perr(SCRIPT_NAME .. ': Please upgrade your WeeChat before using this script. Using this script on older WeeChat versions may lead to crashes. Many bugs have been fixed in newer versions of WeeChat.')
-       perr(SCRIPT_NAME .. ': Refusing to automatically connect you. If you insist, type /'..SCRIPT_COMMAND..' connect, and do not act surprised if it crashes :-)')
-    else
-        SERVER:connect()
-    end
-
-    w.hook_signal('buffer_switch', "buffer_switch_cb", "")
-    w.bar_item_new('matrix_typing_notice', 'typing_bar_item_cb', '')
-end