Initial import: low-level API wrapper
This commit is contained in:
269
matrix/api.lua
Normal file
269
matrix/api.lua
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
#! /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.__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:_quote(string)
|
||||||
|
return self._http:quote(string)
|
||||||
|
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:_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:_quote(room_id) .. "/state/" .. self:_quote(event_type)
|
||||||
|
if state_key then
|
||||||
|
path = path .. "/" .. self:_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:_quote(room_id) .. "/send/" ..
|
||||||
|
self:_quote(event_type) .. "/" .. self:_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:_quote(room_id) .. "/state/m.room.name")
|
||||||
|
end
|
||||||
|
|
||||||
|
function API:get_room_topic(room_id)
|
||||||
|
return self:_send("GET", "/rooms/" .. self:_quote(room_id) .. "/state/m.room.topic")
|
||||||
|
end
|
||||||
|
|
||||||
|
function API:leave_room(room_id)
|
||||||
|
return self:_send("POST", "/rooms/" .. self:_quote(room_id) .. "/leave")
|
||||||
|
end
|
||||||
|
|
||||||
|
function API:invite_user(room_id, user_id)
|
||||||
|
return self:_send("POST", "/rooms/" .. self:_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:_quote(room_id) .. "/state/m.room.member/" .. self:_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:_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:_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:_quote(user_id) .. "/displayname")
|
||||||
|
return data.displayname
|
||||||
|
end
|
||||||
|
|
||||||
|
function API:set_display_name(user_id, display_name)
|
||||||
|
return self:_send("PUT", "/profile/" .. self:_quote(user_id) .. "/displayname",
|
||||||
|
nil, { displayname = display_name })
|
||||||
|
end
|
||||||
|
|
||||||
|
function API:get_avatar_url(user_id)
|
||||||
|
local data = self:_send("GET", "/profile/" .. self:_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:_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 " .. code .. " - " .. body)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return API
|
||||||
70
matrix/factory/chttp.lua
Normal file
70
matrix/factory/chttp.lua
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#! /usr/bin/env lua
|
||||||
|
--
|
||||||
|
-- chttp.lua
|
||||||
|
-- Copyright (C) 2016 Adrian Perez <aperez@igalia.com>
|
||||||
|
--
|
||||||
|
-- Distributed under terms of the MIT license.
|
||||||
|
--
|
||||||
|
|
||||||
|
local request = require "http.request"
|
||||||
|
local headers = require "http.headers"
|
||||||
|
local util = require "http.util"
|
||||||
|
|
||||||
|
local encodeURI, decodeURI = util.encodeURI, util.decodeURI
|
||||||
|
local dict_to_query = util.dict_to_query
|
||||||
|
|
||||||
|
|
||||||
|
local CqHttpClient = {
|
||||||
|
quote = function (self, text) return encodeURI(text) end,
|
||||||
|
unquote = function (self, text) return decodeURI(text) end,
|
||||||
|
}
|
||||||
|
CqHttpClient.__index = CqHttpClient
|
||||||
|
|
||||||
|
|
||||||
|
local function headers_to_dict(h)
|
||||||
|
local headers = {}
|
||||||
|
for name, value in pairs(h) do
|
||||||
|
if name:sub(1, 1) ~= ":" then
|
||||||
|
headers[name] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return headers
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function CqHttpClient:request(log, method, url, query_args, body, headers)
|
||||||
|
do
|
||||||
|
local qs = dict_to_query(query_args)
|
||||||
|
if #qs > 0 then
|
||||||
|
url = url .. "?" .. qs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log(">~> %s %s", method, url)
|
||||||
|
log(">>> %s", body)
|
||||||
|
|
||||||
|
local req = request.new_from_uri(url)
|
||||||
|
for name, value in pairs(headers) do
|
||||||
|
req.headers:append(name, value)
|
||||||
|
end
|
||||||
|
req.headers:upsert(":method", method)
|
||||||
|
if body then
|
||||||
|
req:set_body(body)
|
||||||
|
end
|
||||||
|
local h, s = req:go()
|
||||||
|
if not h then
|
||||||
|
log("<!< error: %s", s)
|
||||||
|
return 0, {}, s
|
||||||
|
end
|
||||||
|
local status = tonumber(h:get(":status"))
|
||||||
|
local response = s:get_body_as_string()
|
||||||
|
|
||||||
|
log("<~< %d", status)
|
||||||
|
log("<<< %s", response)
|
||||||
|
|
||||||
|
return status, headers_to_dict(h), response
|
||||||
|
end
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
return setmetatable({}, CqHttpClient)
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user