This avoids the potential issue in which the status code would be "nil" or "false", which HTTP client libraries may choose to signal an error condition.
275 lines
8.1 KiB
Lua
275 lines
8.1 KiB
Lua
#! /usr/bin/env lua
|
|
--
|
|
-- api.lua
|
|
-- Copyright (C) 2016 Adrian Perez <aperez@igalia.com>
|
|
--
|
|
-- Distributed under terms of the MIT license.
|
|
--
|
|
|
|
local json = require "cjson"
|
|
|
|
local function noprintf(...) end
|
|
local function eprintf(fmt, ...)
|
|
io.stderr:write("[http] ")
|
|
io.stderr:write(fmt:format(...))
|
|
io.stderr:write("\n")
|
|
io.stderr:flush()
|
|
end
|
|
|
|
local function get_debug_log_function()
|
|
local env_value = os.getenv("MATRIX_DEBUG_LOG")
|
|
if env_value and #env_value > 0 and env_value ~= "0" then
|
|
return eprintf
|
|
else
|
|
return noprintf
|
|
end
|
|
end
|
|
|
|
|
|
local API = {}
|
|
API.__name = "matrix.api"
|
|
API.__index = API
|
|
|
|
setmetatable(API, { __call = function (self, base_url, token, http_factory)
|
|
return setmetatable({
|
|
base_url = base_url,
|
|
token = token,
|
|
txn_id = 0,
|
|
api_path = "/_matrix/client/r0", -- TODO: De-hardcode
|
|
_log = get_debug_log_function(),
|
|
_http = require("matrix.factory." .. (http_factory or "chttp"))(),
|
|
}, API)
|
|
end })
|
|
|
|
function API:__tostring()
|
|
return self.__name .. "{" .. self.base_url .. "}"
|
|
end
|
|
|
|
|
|
function API:initial_sync(limit)
|
|
return self:_send("GET", "/initialSync", { limit = limit or 1 })
|
|
end
|
|
|
|
function API:register(login_type, params)
|
|
return self:_send_with_params("POST", "/register", nil,
|
|
{ type = login_type }, params)
|
|
end
|
|
|
|
function API:login(login_type, params)
|
|
return self:_send_with_params("POST", "/login", nil,
|
|
{ type = login_type }, params)
|
|
end
|
|
|
|
function API:logout()
|
|
return self:_send("POST", "/logout")
|
|
end
|
|
|
|
function API:refresh_token(refresh_token)
|
|
return self:_send("POST", "/tokenrefresh", nil, { refresh_token = refresh_token })
|
|
end
|
|
|
|
function API:set_password(new_password, params)
|
|
return self:_send_with_params("POST", "/account/password",
|
|
{ new_password = new_password }, params)
|
|
end
|
|
|
|
function API:get_3pids()
|
|
local data = self:_send("GET", "/account/3pid")
|
|
return data.threepids
|
|
end
|
|
|
|
function API:set_3pids(threepids, bind)
|
|
return self:_send("POST", "/account/3pid", nil,
|
|
{ three_pid_creds = threepids, bind = (bind and true or false) })
|
|
end
|
|
|
|
----
|
|
-- | Option | Type | Default Value |
|
|
-- |:==========|:=========|===============|
|
|
-- | alias | string | nil |
|
|
-- | public | boolean | false |
|
|
-- | invite | {string} | {} |
|
|
----
|
|
function API:create_room(options)
|
|
local params = {
|
|
visibility = options.public and "public" or "private",
|
|
room_alias_name = options.alias,
|
|
invite = options.invite,
|
|
}
|
|
return self:_send("POST", "/createRoom", nil, params)
|
|
end
|
|
|
|
function API:join_room(room_id_or_alias)
|
|
return self:_send("POST", "/join/" .. self._http.quote(room_id_or_alias))
|
|
end
|
|
|
|
function API:event_stream(from_token, timeout)
|
|
return self:_send("GET", "/events", { from = from_token, timeout = timeout or 30000 })
|
|
end
|
|
|
|
function API:send_state_event(room_id, event_type, content, state_key)
|
|
local path = "/rooms/" .. self._http.quote(room_id) ..
|
|
"/state/" .. self._http.quote(event_type)
|
|
if state_key then
|
|
path = path .. "/" .. self._http.quote(state_key)
|
|
end
|
|
return self:_send("PUT", path, nil, content)
|
|
end
|
|
|
|
function API:send_message_event(room_id, event_type, content, txn_id)
|
|
if not txn_id then
|
|
txn_id = self.txn_id
|
|
self.txn_id = self.txn_id + 1
|
|
end
|
|
local path = "/rooms/" .. self._http.quote(room_id) .. "/send/" ..
|
|
self._http.quote(event_type) .. "/" ..
|
|
self._http.quote(tostring(txn_id))
|
|
return self:_send("PUT", path, nil, content)
|
|
end
|
|
|
|
function API:send_content(room_id, item_url, item_name, msg_type, extra_info)
|
|
return self:send_message_event(room_id, "m.room.message",
|
|
{ url = item_url, msgtype = msg_type, body = item_name, info = extra_info })
|
|
end
|
|
|
|
function API:send_message(room_id, text_content, msg_type)
|
|
return self:send_message_event(room_id, "m.room.message",
|
|
self:get_text_body(text_content, msg_type or "m.text"))
|
|
end
|
|
|
|
function API:send_emote(room_id, text_content)
|
|
return self:send_message_event(room_id, "m.room.message",
|
|
self:get_emote_body(text_content))
|
|
end
|
|
|
|
function API:send_notice(room_id, text_content)
|
|
return self:send_message_event(room_id, "m.room.message",
|
|
{ msgtype = "m.notice", body = text_content })
|
|
end
|
|
|
|
function API:get_room_name(room_id)
|
|
return self:_send("GET", "/rooms/" .. self._http.quote(room_id) .. "/state/m.room.name")
|
|
end
|
|
|
|
function API:get_room_topic(room_id)
|
|
return self:_send("GET", "/rooms/" .. self._http.quote(room_id) .. "/state/m.room.topic")
|
|
end
|
|
|
|
function API:leave_room(room_id)
|
|
return self:_send("POST", "/rooms/" .. self._http.quote(room_id) .. "/leave")
|
|
end
|
|
|
|
function API:invite_user(room_id, user_id)
|
|
return self:_send("POST", "/rooms/" .. self._http.quote(room_id) .. "/invite", nil,
|
|
{ user_id = user_id })
|
|
end
|
|
|
|
function API:kick_user(room_id, user_id, reason)
|
|
return self:set_membership(room_id, user_id, "leave", reason)
|
|
end
|
|
|
|
function API:set_membership(room_id, user_id, membership, reason)
|
|
local path = "/rooms/" .. self._http.quote(room_id) ..
|
|
"/state/m.room.member/" .. self._http.quote(user_id)
|
|
return self:_send("PUT", path, nil, { membership = membership, reason = reason or "" })
|
|
end
|
|
|
|
function API:ban_user(room_id, user_id, reason)
|
|
return self:_send("POST", "/rooms/" .. self._http.quote(room_id) .. "/ban", nil,
|
|
{ user_id = user_id, reason = reason or "" })
|
|
end
|
|
|
|
function API:get_room_state(room_id)
|
|
return self:_send("GET", "/rooms/" .. self._http.quote(room_id) .. "/state")
|
|
end
|
|
|
|
function API:get_text_body(text, msg_type)
|
|
return { msgtype = msg_type or "m.text", body = text }
|
|
end
|
|
|
|
function API:get_emote_body(text)
|
|
return { msgtype = "m.emote", body = text }
|
|
end
|
|
|
|
function API:media_upload(content, content_type)
|
|
-- TODO: De-harcode media API path
|
|
return self:_send("POST", "", nil, content,
|
|
{ ["content-type"] = content_type },
|
|
"/_matrix/media/r0/upload")
|
|
end
|
|
|
|
function API:get_display_name(user_id)
|
|
local data = self:_send("GET", "/profile/" .. self._http.quote(user_id) .. "/displayname")
|
|
return data.displayname
|
|
end
|
|
|
|
function API:set_display_name(user_id, display_name)
|
|
return self:_send("PUT", "/profile/" .. self._http.quote(user_id) .. "/displayname",
|
|
nil, { displayname = display_name })
|
|
end
|
|
|
|
function API:get_avatar_url(user_id)
|
|
local data = self:_send("GET", "/profile/" .. self._http.quote(user_id) .. "/avatar_url")
|
|
return data.avatar_url
|
|
end
|
|
|
|
function API:set_avatar_url(user_id, avatar_url)
|
|
return self:_send("PUT", "/profile/" .. self._http.quote(user_id) .. "/avatar_url",
|
|
nil, { avatar_url = avatar_url })
|
|
end
|
|
|
|
function API:get_download_url(mxc_url)
|
|
if mxc_url:sub(1, #"mxc://") == "mxc://" then
|
|
-- TODO: De-hardcode API version
|
|
return self.base_url .. "/_matrix/media/r0/download/" .. mxc_url:sub(7)
|
|
end
|
|
error("no mxc: scheme in URL: " .. mxc_url)
|
|
end
|
|
|
|
function API:_send_with_params(method, path, query_args, params, extra_params)
|
|
for name, value in pairs(extra_params) do
|
|
params[name] = value
|
|
end
|
|
return self:_send(method, path, query_args, params)
|
|
end
|
|
|
|
function API:_send(method, path, query_args, body, headers, api_path)
|
|
-- Ensure that there is a Content-Type header.
|
|
if not headers then
|
|
headers = {}
|
|
end
|
|
if not headers["content-type"] then
|
|
headers["content-type"] = "application/json"
|
|
end
|
|
|
|
-- Encode the request body, if necessary.
|
|
if headers["content-type"] == "application/json" then
|
|
body = body and json.encode(body) or "{}"
|
|
elseif not body then
|
|
body = ""
|
|
end
|
|
|
|
-- Copy the parameters, adding the access token.
|
|
local params = { access_token = self.token }
|
|
if query_args then
|
|
for name, value in pairs(query_args) do
|
|
params[name] = tostring(value)
|
|
end
|
|
end
|
|
|
|
-- Call the HTTP library.
|
|
local code, headers, body = self._http:request(self._log, method:upper(),
|
|
self.base_url .. (api_path or self.api_path) .. path, params, body, headers)
|
|
if code == 200 then
|
|
if headers["content-type"] == "application/json" then
|
|
body = json.decode(body)
|
|
end
|
|
return body
|
|
else
|
|
return error("HTTP " .. tostring(code) .. " - " .. body)
|
|
end
|
|
end
|
|
|
|
|
|
return API
|