add file sending, request refactor, bugfixes
This commit is contained in:
		
							parent
							
								
									81e2a7cad7
								
							
						
					
					
						commit
						a303c6d0d0
					
				
					 7 changed files with 236 additions and 32 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | # Emacs | ||||||
|  | *~ | ||||||
|  | \#*# | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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
									
								
							
							
						
						
									
										167
									
								
								etc/multipart.lua
									
										
									
									
									
										Normal 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 | ||||||
|  | @ -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, { | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  | @ -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": "Ошибка запроса." | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue