Files
lua-matrix/matrix/api.lua
Adrian Perez de Castro 28477c4db7 Always stringigy returned HTTP status code for logging
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.
2016-06-28 23:21:04 +03:00

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