diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a4464c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Emacs +*~ +\#*# \ No newline at end of file diff --git a/etc/api/api.lua b/etc/api/api.lua index e72bc79..63fbce5 100644 --- a/etc/api/api.lua +++ b/etc/api/api.lua @@ -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 diff --git a/etc/api/tools.lua b/etc/api/tools.lua index 56b2db5..277d9b5 100644 --- a/etc/api/tools.lua +++ b/etc/api/tools.lua @@ -7,9 +7,10 @@ local tools = { json = require 'etc.json', } -local json = tools.json +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, meth, data, ctype) + assert(url, 'Provide URL!') + assert(meth, 'Provide method!') -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 succ, res = https.request { - url = url, - method = 'GET', - sink = ltn12.sink.table(resp), + local head = { + url = url, + 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 + 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 diff --git a/etc/multipart.lua b/etc/multipart.lua new file mode 100644 index 0000000..361470b --- /dev/null +++ b/etc/multipart.lua @@ -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 diff --git a/src/cmds/rub.lua b/src/cmds/rub.lua index 21304b7..4a5f5aa 100644 --- a/src/cmds/rub.lua +++ b/src/cmds/rub.lua @@ -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, { diff --git a/src/events/command.lua b/src/events/command.lua index 3b24a28..38d9da9 100644 --- a/src/events/command.lua +++ b/src/events/command.lua @@ -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 \ No newline at end of file +end diff --git a/src/locales/ru.json b/src/locales/ru.json index 82fe87c..5efe927 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -2,7 +2,7 @@ "error": { "inv_cmd": "Указана неизвестная команда.", "adm_cmd": "Вы не можете исполнять админские команды!", - "cmd_run": "Это команда не мжет быть выполнена сейчас.", + "cmd_run": "Это команда не может быть выполнена сейчас.", "not_suc": "Произошла ошибка, которая уже отправлена создателю.", "unk_err": "Неизвестная ошибка.", "req_err": "Ошибка запроса."