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