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 json = require 'etc.json'
local events=require 'etc.events' local events=require 'etc.events'
local api = { 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 api.__index = api -- Make class

View File

@ -7,9 +7,10 @@ local tools = {
json = require 'etc.json', json = require 'etc.json',
} }
local json = tools.json local json = tools.json
local https = require 'ssl.https' local https = require 'ssl.https'
local ltn12 = require 'ltn12' local ltn12 = require 'ltn12'
local mp = require 'etc.multipart'
function tools.fetchCmd(text) function tools.fetchCmd(text)
return return
@ -17,30 +18,23 @@ function tools.fetchCmd(text)
text:match '/[%w_]+@([%w_]+)' -- to text:match '/[%w_]+@([%w_]+)' -- to
end end
-- https://gist.github.com/liukun/f9ce7d6d14fa45fe9b924a3eed5c3d99 function tools._req(url, meth, data, ctype)
function tools.urlencode(url) assert(url, 'Provide URL!')
if url == nil then return end assert(meth, 'Provide method!')
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
local resp = {} local resp = {}
local succ, res = https.request { local head = {
url = url, url = url,
method = 'GET', method = meth,
sink = ltn12.sink.table(resp), 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 if not succ then
print('Connection error [' .. res .. ']') print('Connection error [' .. res .. ']')
return nil, false return nil, false
@ -48,22 +42,61 @@ function tools.req(url, par)
return resp[1], true return resp[1], true
end end
function tools.requ(url, par, dbg) function tools.preq(url, par)
local res, succ = tools.req(url, par) par = par or {}
if dbg then print(url, succ, res) end
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 '{}') res = json.decode(res or '{}')
if not succ or not res then return {}, false end if not succ or not res then return {}, false end
return res, true return res, true
end end
function tools.request(token, endpoint, param, dbg) function tools.request(token, endpoint, param, f, dbg)
assert(token, 'Provide token!') assert(token, 'Provide token!')
assert(endpoint, 'Provide endpoint!') assert(endpoint, 'Provide endpoint!')
local url = 'https://api.telegram.org/bot' .. token .. '/' .. endpoint local url = 'https://api.telegram.org/bot' ..token.. '/' ..endpoint
-- dbg = true -- dbg = true
local resp = tools.requ(url, param, dbg) local resp = tools.req(url, param, f, dbg)
return resp, resp.ok or false return resp, resp.ok or false
end 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) 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 if not succ then return 'err' end
resp = self.tools.json.decode(resp or '{}')
resp = resp.ValCurs resp = resp.ValCurs
table.insert(resp.Valute, { table.insert(resp.Valute, {

View File

@ -22,7 +22,7 @@
local cid = C.config.owner local cid = C.config.owner
api:forward(cid, msg.chat.id, msg.message_id, false) api:forward(cid, msg.chat.id, msg.message_id, false)
api:send(cid, err) 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 end
end end

View File

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