bling/helpers/requests.lua

175 lines
4.9 KiB
Lua

local lgi = require("lgi")
local Soup = lgi.require("Soup", "3.0")
local Gio = lgi.require("Gio", "2.0")
local GLib = lgi.require("GLib", "2.0")
local bit = require("bit")
local gears = require("gears")
---@class Response
---@field url string
---@field status_code number
---@field ok boolean
---@field reason_phrase string
---@field stream userdata
---@field text string
---@field bytes GLib.Bytes
local Response = {}
-- ---@return table
-- Response.json = function(self)
-- local json = require("lib.json")
-- return json.decode(self.text)
-- end
function Response.new(url, status_code, ok, reason_phrase, input_stream)
local self = setmetatable({}, Response)
self.url = url
self.status_code = status_code
self.ok = ok
self.reason_phrase = reason_phrase
self.stream = input_stream
self.text = ""
self.bytes = nil
return self
end
---@param params table<string, any>
---@param parent_key string | nil
---@return string
local function encode_query_params(params, parent_key)
local encoded_params = {}
local encoded_val, encoded_key
for key, value in pairs(params) do
local full_key = parent_key and (parent_key .. "[" .. key .. "]") or key
encoded_key = GLib.Uri.escape_string(tostring(full_key), nil, true)
if type(value) == "table" then
table.insert(encoded_params, encode_query_params(value, full_key))
else
encoded_val = GLib.Uri.escape_string(tostring(value), nil, true)
table.insert(encoded_params, encoded_key .. "=" .. encoded_val)
end
end
return table.concat(encoded_params, "&")
end
---@class request
local requests = {}
---@alias request_args string | { url: string, params: table<string, any>, headers: table<string, string>, body: GLib.Bytes | string}
---@param method "GET" | "POST" | "PUT" | "DELETE" | "PATCH"
---@param args request_args
---@param callback fun(response: Response): nil
---@return nil
function requests.request(method, args, callback)
if type(args) == "string" then
args = gears.table.crush(
{ url = "", params = {} },
{ url = args },
false
)
else
args = gears.table.crush({ url = "", params = {} }, args, false)
end
local session, message, status_code, input_stream, ok, r, output_stream
session = Soup.Session.new()
if args.params then
args.url =
string.format("%s?%s", args.url, encode_query_params(args.params))
end
message = Soup.Message.new_from_uri(
method,
GLib.Uri.parse(args.url, GLib.UriFlags.NONE)
)
if args.headers then
for header_name, header_value in pairs(args.headers) do
message:get_request_headers():append(header_name, header_value)
end
end
if type(args.body) == "string" then
message.set_request_body_from_bytes(
nil,
GLib.Bytes.new({
string.byte(args.body, 1, #args.body),
})
)
end
return session:send_async(
message,
GLib.PRIORITY_DEFAULT,
nil,
function(_, task)
input_stream = session:send_finish(task)
status_code = message.status_code
ok = status_code >= 200 and status_code < 300
r = Response.new(
message.uri:to_string(),
status_code,
ok,
message.reason_phrase,
input_stream
)
output_stream = Gio.MemoryOutputStream.new_resizable()
return output_stream:splice_async(
r.stream,
bit.bor(
Gio.OutputStreamSpliceFlags.CLOSE_SOURCE,
Gio.OutputStreamSpliceFlags.CLOSE_TARGET
),
GLib.PRIORITY_DEFAULT,
nil,
function(_, t)
output_stream:splice_finish(t)
r.bytes = output_stream:steal_as_bytes()
r.text = r.bytes:get_data()
output_stream:flush_async(GLib.PRIORITY_DEFAULT, nil)
return callback(r)
end
)
end
)
end
---@param args request_args
---@param callback fun(response:Response): nil
---@return nil
function requests.get(args, callback)
return requests.request("GET", args, callback)
end
---@param args request_args
---@param callback fun(response:Response): nil
---@return nil
function requests.post(args, callback)
return requests.request("POST", args, callback)
end
---@param args request_args
---@param callback fun(response:Response): nil
---@return nil
function requests.put(args, callback)
return requests.request("PUT", args, callback)
end
---@param args request_args
---@param callback fun(response:Response): nil
---@return nil
function requests.delete(args, callback)
return requests.request("DELETE", args, callback)
end
return requests