add file sending, request refactor, bugfixes

This commit is contained in:
Er2 2021-08-14 20:24:14 +03:00
parent 81e2a7cad7
commit a303c6d0d0
7 changed files with 236 additions and 32 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Emacs
*~
\#*#

View file

@ -7,7 +7,7 @@ local tools =require 'etc.api.tools'
local json = require 'etc.json'
local events=require 'etc.events'
local api = {
request = function(s, ...) return tools.request(s.token, ...) end
request = function(s, ...) return tools.request(s.token, ...) end,
}
api.__index = api -- Make class

View file

@ -10,6 +10,7 @@ local tools = {
local json = tools.json
local https = require 'ssl.https'
local ltn12 = require 'ltn12'
local mp = require 'etc.multipart'
function tools.fetchCmd(text)
return
@ -17,30 +18,23 @@ function tools.fetchCmd(text)
text:match '/[%w_]+@([%w_]+)' -- to
end
-- https://gist.github.com/liukun/f9ce7d6d14fa45fe9b924a3eed5c3d99
function tools.urlencode(url)
if url == nil then return end
url = url:gsub("\n", "\r\n")
url = url:gsub("([^%w_%- . ~])", function(c) return string.format("%%%02X", string.byte(c)) end)
url = url:gsub(" ", "+")
return url
end
function tools.req(url, par)
if type(par) == 'table' then
url = url .. '?'
for k,v in pairs(par) do
url = url ..'&'.. k ..'='.. tools.urlencode(tostring(v))
end
end
function tools._req(url, meth, data, ctype)
assert(url, 'Provide URL!')
assert(meth, 'Provide method!')
local resp = {}
local succ, res = https.request {
local head = {
url = url,
method = 'GET',
method = meth,
headers = {
['Content-Type'] = ctype,
['Content-Length'] = #(data or ''),
},
source = ltn12.source.string(data),
sink = ltn12.sink.table(resp),
}
local succ, res = https.request(head)
if not succ then
print('Connection error [' .. res .. ']')
return nil, false
@ -48,22 +42,61 @@ function tools.req(url, par)
return resp[1], true
end
function tools.requ(url, par, dbg)
local res, succ = tools.req(url, par)
if dbg then print(url, succ, res) end
function tools.preq(url, par)
par = par or {}
local body, bound = mp.encode(par)
return tools._req(
url,
'POST',
body,
'multipart/form-data; boundary=' .. bound
)
end
function tools.greq(url, par, f)
par = json.encode(par)
return tools._req(url, 'GET', par, 'application/json')
end
function tools.req(url, par, f, dbg)
local res, succ
par = par or {}
-- files
if f and next(f) ~= nil then
par = par or {}
for k, v in pairs(par) do par[k] = tostring(v) end
local ft, fn = next(f)
local fr = io.open(fn, 'r')
if fr then
par[ft] = {
filename = fn,
data = fr:read '*a'
}
fr:close()
else par[ft] = fn end
res, succ = tools.preq(url, par)
else -- text
res, succ = tools.greq(url, par)
end
if dbg then print(url, succ, res, par)
-- dump(par))
end
res = json.decode(res or '{}')
if not succ or not res then return {}, false end
return res, true
end
function tools.request(token, endpoint, param, dbg)
function tools.request(token, endpoint, param, f, dbg)
assert(token, 'Provide token!')
assert(endpoint, 'Provide endpoint!')
local url = 'https://api.telegram.org/bot' ..token.. '/' ..endpoint
-- dbg = true
local resp = tools.requ(url, param, dbg)
local resp = tools.req(url, param, f, dbg)
return resp, resp.ok or false
end

167
etc/multipart.lua Normal file
View file

@ -0,0 +1,167 @@
-- based on https://github.com/catwell/lua-multipart-post
-- MIT License
local ltn12 = require 'ltn12'
local mp = {
CHARSET = 'UTF-8',
LANGUAGE = ''
}
-- https://gist.github.com/liukun/f9ce7d6d14fa45fe9b924a3eed5c3d99
local function urlencode(url)
if url == nil then return end
url = url:gsub("\n", "\r\n")
url = url:gsub("([^%w_%- . ~])", function(c) return string.format("%%%02X", string.byte(c)) end)
url = url:gsub(" ", "+")
return url
end
mp.urlencode = urlencode
local function fmt(p, ...)
if select('#', ...) == 0 then
return p
end
return string.format(p, ...)
end
local function tprintf(t, p, ...)
t[#t+1] = fmt(p, ...)
end
local function section_header(r, k, extra)
tprintf(r, 'content-disposition: form-data; name="%s"', k)
if extra.filename then
tprintf(r, '; filename="%s"', extra.filename)
tprintf(
r, "; filename*=%s'%s'%s",
mp.CHARSET, mp.LANGUAGE, urlencode(extra.filename)
)
end
if extra.content_type then
tprintf(r, '\r\ncontent-type: %s', extra.content_type)
end
if extra.content_transfer_encoding then
tprintf(
r, '\r\ncontent-transfer-encoding: %s',
extra.content_transfer_encoding
)
end
tprintf(r, '\r\n\r\n')
end
function mp.boundary()
local t = {"BOUNDARY-"}
for i=2,17 do t[i] = string.char(math.random(65, 90)) end
t[18] = "-BOUNDARY"
return table.concat(t)
end
local function encode_header_to_table(r, k, v, boundary)
local _t = type(v)
tprintf(r, "--%s\r\n", boundary)
if _t == "string" then
section_header(r, k, {})
elseif _t == "table" then
assert(v.data, "invalid input")
local extra = {
filename = v.filename or v.name,
content_type = v.content_type or v.mimetype
or "application/octet-stream",
content_transfer_encoding = v.content_transfer_encoding
or "binary",
}
section_header(r, k, extra)
else
error(string.format("unexpected type %s", _t))
end
end
local function encode_header_as_source(k, v, boundary, ctx)
local r = {}
encode_header_to_table(r, k, v, boundary, ctx)
local s = table.concat(r)
if ctx then
ctx.headers_length = ctx.headers_length + #s
end
return ltn12.source.string(s)
end
local function data_len(d)
local _t = type(d)
if _t == "string" then
return string.len(d)
elseif _t == "table" then
if type(d.data) == "string" then
return string.len(d.data)
end
if d.len then return d.len end
error("must provide data length for non-string datatypes")
end
end
local function content_length(t, boundary, ctx)
local r = ctx and ctx.headers_length or 0
for k, v in pairs(t) do
if not ctx then
local tmp = {}
encode_header_to_table(tmp, k, v, boundary)
r = r + #table.concat(tmp)
end
r = r + data_len(v) + 2 -- `\r\n`
end
return r + #boundary + 6 -- `--BOUNDARY--\r\n`
end
local function get_data_src(v)
local _t = type(v)
if v.source then
return v.source
elseif _t == "string" then
return ltn12.source.string(v)
elseif _t == "table" then
_t = type(v.data)
if _t == "string" then
return ltn12.source.string(v.data)
elseif _t == "table" then
return ltn12.source.table(v.data)
elseif _t == "userdata" then
return ltn12.source.file(v.data)
elseif _t == "function" then
return v.data
end
end
error("invalid input")
end
local function set_ltn12_blksz(sz)
assert(type(sz) == "number", "set_ltn12_blksz expects a number")
ltn12.BLOCKSIZE = sz
end
mp.set_ltn12_blksz = set_ltn12_blksz
local function source(t, boundary, ctx)
local sources, n = {}, 1
for k, v in pairs(t) do
sources[n] = encode_header_as_source(k, v, boundary, ctx)
sources[n+1] = get_data_src(v)
sources[n+2] = ltn12.source.string("\r\n")
n = n + 3
end
sources[n] = ltn12.source.string(string.format("--%s--\r\n", boundary))
return ltn12.source.cat(table.unpack(sources))
end
mp.source = source
function mp.encode(t, boundary)
boundary = boundary or mp.boundary()
local r = {}
assert(ltn12.pump.all(
(source(t, boundary)),
(ltn12.sink.table(r))
))
return table.concat(r), boundary
end
return mp

View file

@ -10,8 +10,9 @@ local rub = {
}
function rub:course(wants)
local resp, succ = self.tools.requ(self.url)
local resp, succ = self.tools._req(self.url, 'GET')
if not succ then return 'err' end
resp = self.tools.json.decode(resp or '{}')
resp = resp.ValCurs
table.insert(resp.Valute, {

View file

@ -22,7 +22,7 @@
local cid = C.config.owner
api:forward(cid, msg.chat.id, msg.message_id, false)
api:send(cid, err)
api:reply(msg, msg.locale.error.not_suc)
api:reply(msg, C.locale:get('error', 'not_suc', l))
end
end
end

View file

@ -2,7 +2,7 @@
"error": {
"inv_cmd": "Указана неизвестная команда.",
"adm_cmd": "Вы не можете исполнять админские команды!",
"cmd_run": "Это команда не мжет быть выполнена сейчас.",
"cmd_run": "Это команда не может быть выполнена сейчас.",
"not_suc": "Произошла ошибка, которая уже отправлена создателю.",
"unk_err": "Неизвестная ошибка.",
"req_err": "Ошибка запроса."