move from bot
This commit is contained in:
		
						commit
						30f7203c5f
					
				
					 9 changed files with 749 additions and 0 deletions
				
			
		
							
								
								
									
										4
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | # Emacs | ||||||
|  | *~ | ||||||
|  | \#*# | ||||||
|  | 
 | ||||||
							
								
								
									
										19
									
								
								LICENSE
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								LICENSE
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | Zlib License | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2021 Er2 <er2@dismail.de> | ||||||
|  | 
 | ||||||
|  | This software is provided 'as-is', without any express or implied | ||||||
|  | warranty. In no event will the authors be held liable for any damages | ||||||
|  | arising from the use of this software. | ||||||
|  | 
 | ||||||
|  | Permission is granted to anyone to use this software for any purpose, | ||||||
|  | including commercial applications, and to alter it and redistribute it | ||||||
|  | freely, subject to the following restrictions: | ||||||
|  | 
 | ||||||
|  | 1. The origin of this software must not be misrepresented; you must not | ||||||
|  |    claim that you wrote the original software. If you use this software | ||||||
|  |    in a product, an acknowledgement in the product documentation would be | ||||||
|  |    appreciated but is not required. | ||||||
|  | 2. Altered source versions must be plainly marked as such, and must not be | ||||||
|  |    misrepresented as being the original software. | ||||||
|  | 3. This notice may not be removed or altered from any source distribution. | ||||||
							
								
								
									
										137
									
								
								api.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								api.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,137 @@ | ||||||
|  | --[[ API Library | ||||||
|  |   -- (c) Er2 2021 <er2@dismail.de> | ||||||
|  |   -- Zlib License | ||||||
|  | --]] | ||||||
|  | 
 | ||||||
|  | local tools  = require 'api.tools' | ||||||
|  | local json   = require 'json' | ||||||
|  | local events = require 'events' | ||||||
|  | local api    = { | ||||||
|  |   request    = function(s, ...) return tools.request(s.token, ...) end | ||||||
|  | } | ||||||
|  | api.__index = api -- Make class | ||||||
|  | events(api)       -- inheritance | ||||||
|  | 
 | ||||||
|  | -- parse arguments | ||||||
|  | local function argp(cid, rmp, pmod, dwp) | ||||||
|  |   return | ||||||
|  |     type(cid) == 'table' and cid.chat.id or cid, | ||||||
|  |     type(rmp) == 'table' and json.encode(rmp) or rmp, | ||||||
|  |     (type(pmod) == 'boolean' and pmod == true) and 'MarkdownV2' or pmod, | ||||||
|  |     dwp == nil and true or dwp | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | -- Getters without params | ||||||
|  | 
 | ||||||
|  | function api:getMe() return self:request 'getMe' end | ||||||
|  | function api:getMyCommands() return self:request 'getMyCommands' end | ||||||
|  | 
 | ||||||
|  | -- Getters with params | ||||||
|  | 
 | ||||||
|  | function api:getChat(cid) return self:request('getChat', {chat_id = cid}) end | ||||||
|  | 
 | ||||||
|  | -- Setters | ||||||
|  | 
 | ||||||
|  | function api:send(msg, txt, pmod, dwp, dnot, rtmid, rmp) | ||||||
|  |   msg, rmp, pmod, dwp = argp(msg, rmp, pmod, dwp) | ||||||
|  | 
 | ||||||
|  |   if txt and #txt >= 4096 then | ||||||
|  |     txt = txt:sub(0, 4092) .. '...' | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   return self:request('sendMessage', { | ||||||
|  |     chat_id = msg, | ||||||
|  |     text = txt, | ||||||
|  |     parse_mode = pmod, | ||||||
|  |     disable_web_page_preview = dwp, | ||||||
|  |     disable_notification = dnot, | ||||||
|  |     reply_to_message_id = rtmid, | ||||||
|  |     reply_markup = rmp, | ||||||
|  |   }) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function api:reply(msg, txt, pmod, dwp, rmp, dnot) | ||||||
|  |   _, rmp, pmod, dwp = argp(msg, rmp, pmod, dwp) | ||||||
|  |   return self:request('sendMessage', { | ||||||
|  |     chat_id = msg.chat.id, | ||||||
|  |     text = txt, | ||||||
|  |     parse_mode = pmod, | ||||||
|  |     disable_web_page_preview = dwp, | ||||||
|  |     disable_notification = dnot, | ||||||
|  |     reply_to_message_id = msg.message_id, | ||||||
|  |     reply_markup = rmp, | ||||||
|  |   }) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function api:forward(cid, frcid, mid, dnot) | ||||||
|  |   return self:request('forwardMessage', { | ||||||
|  |     chat_id = cid, | ||||||
|  |     from_chat_id = frcid, | ||||||
|  |     disable_notification = dnot, | ||||||
|  |     message_id = mid, | ||||||
|  |   }) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function api:sendPhoto(cid, f, cap, pmod, dnot, rtmid, rmp) | ||||||
|  |   cid, rmp, pmod = argp(cid, rmp, pmod) | ||||||
|  |   return self:request('sendPhoto', { | ||||||
|  |     chat_id = cid, | ||||||
|  |     caption = cap, | ||||||
|  |     parse_mode = pmod, | ||||||
|  |     disable_notification = dnot, | ||||||
|  |     reply_to_message_id = rtmid, | ||||||
|  |     reply_markup = rmp, | ||||||
|  |   }, { photo = f }) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function api:sendDocument(cid, f, cap, pmod, dnot, rtmid, rmp) | ||||||
|  |   cid, rmp, pmod = argp(cid, rmp, pmod) | ||||||
|  |   return self:request('sendDocument', { | ||||||
|  |     chat_id = cid, | ||||||
|  |     caption = cap, | ||||||
|  |     parse_mode = pmod, | ||||||
|  |     disable_notification = dnot, | ||||||
|  |     reply_to_message_id = rtmid, | ||||||
|  |     reply_markup = rmp, | ||||||
|  |   }, { document = f }) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function api:sendPoll(cid, q, opt, anon, ptype, mansw, coptid, expl, pmode, oper, cdate, closed, dnot, rtmid, rmp) | ||||||
|  |   cid, rmp, pmode = argp(cid, rmp, pmode) | ||||||
|  |   opt = type(opt) == 'string' and opt or json.encode(opt) | ||||||
|  |   anon = type(anon) == 'boolean' and anon or false | ||||||
|  |   mansw = type(mansw) == 'boolean' and mansw or false | ||||||
|  |   return self:request('sendPoll', { | ||||||
|  |     chat_id = cid, | ||||||
|  |     question = q, | ||||||
|  |     options = opt, | ||||||
|  |     is_anonymous = anon, | ||||||
|  |     type = ptype, | ||||||
|  |     allows_multiple_answers = mansw, | ||||||
|  |     correct_option_id = coptid, | ||||||
|  |     explanation = expl, | ||||||
|  |     explanation_parse_mode = pmode, | ||||||
|  |     open_period = oper, | ||||||
|  |     close_date = cdate, | ||||||
|  |     is_closed = closed, | ||||||
|  |     disable_notification = dnot, | ||||||
|  |     reply_to_message_id = rtmid, | ||||||
|  |     reply_markup = rmp, | ||||||
|  |   }) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function api:answerCallback(id, txt, alrt, url, ctime) | ||||||
|  |   return self:request('answerCallbackQuery', { | ||||||
|  |     callback_query_id = id, | ||||||
|  |     text = txt, | ||||||
|  |     show_alert = alrt, | ||||||
|  |     url = url, | ||||||
|  |     cache_time = ctime, | ||||||
|  |   }) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function api:setMyCommands(cmds) | ||||||
|  |   return self:request('setMyCommands', { commands = json.encode(cmds) }) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | return api | ||||||
							
								
								
									
										133
									
								
								core.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								core.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,133 @@ | ||||||
|  | --[[ Core file | ||||||
|  |   -- (c) Er2 2021 <er2@dismail.de> | ||||||
|  |   -- Zlib License | ||||||
|  | --]] | ||||||
|  | 
 | ||||||
|  | local tools = require 'api.tools' | ||||||
|  | local api   = require 'api.api' | ||||||
|  | api.__index = api | ||||||
|  | 
 | ||||||
|  | function api:_ev(t, i, name, ...) | ||||||
|  |   local v = t[i] | ||||||
|  |   if v.name == name then | ||||||
|  |     v.fn(self, ...) | ||||||
|  |     if v.type == 'once' then table.remove(t, i) end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | -- UPDATES -- | ||||||
|  | function api:getUpdates(lim, offs, tout, allowed) | ||||||
|  |   allowed = type(allowed) == 'table' and tools.json.encode(allowed) or allowed | ||||||
|  |   return self:request('getUpdates', { | ||||||
|  |     timeout = tout, | ||||||
|  |     offset = offs, | ||||||
|  |     limit = lim, | ||||||
|  |     allowed_updates = allowed | ||||||
|  |   }) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | local function receiveUpdate(self, update) | ||||||
|  |   if update.message then | ||||||
|  |     local msg = update.message | ||||||
|  |     local cmd, to = tools.fetchCmd(msg.text or '') | ||||||
|  |     if cmd and (not to or to == self.info.username) then | ||||||
|  |       -- need /cmd@bot in groups | ||||||
|  |       if (msg.chat.type == 'group' or msg.chat.type == 'supergroup') | ||||||
|  |        and not to then return end | ||||||
|  | 
 | ||||||
|  |       local args = {} | ||||||
|  |       msg.text = msg.text:sub(#cmd + #(to or '') + 3) | ||||||
|  |       for s in msg.text:gmatch '%S+' do table.insert(args, s) end | ||||||
|  | 
 | ||||||
|  |       msg.cmd  = cmd | ||||||
|  |       msg.args = args | ||||||
|  | 
 | ||||||
|  |       return self:emit('command', msg) | ||||||
|  |     elseif cmd then return end | ||||||
|  | 
 | ||||||
|  |     self:emit('message', msg) | ||||||
|  | 
 | ||||||
|  |   elseif update.edited_message then | ||||||
|  |     self:emit('messageEdit', update.edited_message) | ||||||
|  | 
 | ||||||
|  |   elseif update.channel_post then self:emit('channelPost', update.channel_post) | ||||||
|  |   elseif update.edited_channel_post then self:emit('channelPostEdit', update.edited_channel_post) | ||||||
|  | 
 | ||||||
|  |   elseif update.poll then self:emit('poll', update.poll) | ||||||
|  |   elseif update.poll_answer then self:emit('pollAnswer', update.poll_answer) | ||||||
|  | 
 | ||||||
|  |   elseif update.callback_query then self:emit('callbackQuery', update.callback_query) | ||||||
|  |   elseif update.inline_query then self:emit('inlineQuery', update.inline_query) | ||||||
|  |   elseif update.shipping_query then self:emit('shippingQuery', update.shipping_query) | ||||||
|  |   elseif update.pre_checkout_query then self:emit('preCheckoutQuery', update.pre_checkout_query) | ||||||
|  | 
 | ||||||
|  |   elseif update.chosen_inline_result then self:emit('inlineResult', update.chosen_inline_result) | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function api:_getUpd(lim, offs, ...) | ||||||
|  |   local u, ok = self:getUpdates(lim, offs, ...) | ||||||
|  |   if not ok or not u or (u and type(u) ~= 'table') or not u.result then return end | ||||||
|  |   for _, v in pairs(u.result) do | ||||||
|  |     offs = v.update_id + 1 | ||||||
|  |     if type(v) == 'table' then | ||||||
|  |       self:emit('update', v) | ||||||
|  |       receiveUpdate(self, v) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |   return offs | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function api:_loop(lim, offs, ...) | ||||||
|  |   while api.runs do | ||||||
|  |     local o = self:_getUpd(lim, offs, ...) | ||||||
|  |     offs = o and o or offs | ||||||
|  |   end | ||||||
|  |   self:getUpdates(lim, offs, ...) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | -- RUN -- | ||||||
|  | function api:run(lim, offs, tout, al) | ||||||
|  |   lim = tonumber(lim) or 1 | ||||||
|  |   offs = tonumber(offs) or 0 | ||||||
|  |   tout = tonumber(tout) or 0 | ||||||
|  | 
 | ||||||
|  |   self.runs = true | ||||||
|  |   self:emit 'ready' | ||||||
|  | 
 | ||||||
|  |   self.co = coroutine.create(api._loop) | ||||||
|  |   coroutine.resume(self.co, self, lim, tout, offs, al) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function api:destroy() self.runs = false end | ||||||
|  | 
 | ||||||
|  | function api:login(token, thn) | ||||||
|  |   self.token = assert(token or self.token, 'Provide token!') | ||||||
|  | 
 | ||||||
|  |   repeat | ||||||
|  |     local r, o = self:getMe() | ||||||
|  |     if o and r then self.info = r end | ||||||
|  |   until (self.info or {}).result | ||||||
|  | 
 | ||||||
|  |   self.info = self.info.result | ||||||
|  |   self.info.name = self.info.first_name | ||||||
|  | 
 | ||||||
|  |   if type(thn) == 'function' then thn(self) end | ||||||
|  | 
 | ||||||
|  |   if not self.nr then self:run() end | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | return function(opts) | ||||||
|  |   if not token or type(token) ~= 'string' then token = nil end | ||||||
|  | 
 | ||||||
|  |   local self = setmetatable({}, api) | ||||||
|  |   if type(opts) == 'table' then | ||||||
|  |     if opts.token then self.token = opts.token end | ||||||
|  |     if opts.norun then self.nr = true end | ||||||
|  |     if not opts.noinl then | ||||||
|  |       self.inline = require('etc.api.inline')(self) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   return self | ||||||
|  | end | ||||||
							
								
								
									
										1
									
								
								init.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								init.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | return require 'api.core' | ||||||
							
								
								
									
										156
									
								
								inline.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								inline.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,156 @@ | ||||||
|  | --[[ Inline query library | ||||||
|  |   -- (c) Er2 2021 <er2@dismail.de> | ||||||
|  |   -- Zlib License | ||||||
|  | --]] | ||||||
|  | 
 | ||||||
|  | local inline   = {} | ||||||
|  | inline.__index = inline -- Make class | ||||||
|  | 
 | ||||||
|  | function inline.query(id, from, q, off, ct, loc) | ||||||
|  |   return { | ||||||
|  |     id = tostring(id), | ||||||
|  |     from = from, | ||||||
|  |     query = q, | ||||||
|  |     offset = off, | ||||||
|  |     chat_type = ct, | ||||||
|  |     location = loc, | ||||||
|  |   } | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function inline.result(type, id, ...) | ||||||
|  |   type = tostring(type) | ||||||
|  |   local t = setmetatable({ | ||||||
|  |     type = type, | ||||||
|  |     id = tostring(tonumber(id) or 1), | ||||||
|  |   }, inline) | ||||||
|  |   local a = {...} | ||||||
|  |   if t.type == 'article' then t.title, t.url, t.hide_url, t.description = table.unpack(a) | ||||||
|  | 
 | ||||||
|  |   elseif t.type == 'photo' then | ||||||
|  |     t.photo_url, t.photo_width, t.photo_height, t.title, t.description, | ||||||
|  |     t.caption, t.parse_mode, t.caption_entities | ||||||
|  |       = table.unpack(a) | ||||||
|  | 
 | ||||||
|  |   elseif t.type == 'gif' or t.type == 'mpeg4_gif' then | ||||||
|  |     local url, width, height, duration | ||||||
|  |     url, width, height, duration, t.title, t.caption, t.parse_mode, t.caption_entities | ||||||
|  |       = table.unpack(a) | ||||||
|  | 
 | ||||||
|  |     if t.type == 'gif' then | ||||||
|  |       t.gif_url, t.gif_width, t.gif_height, t.gif_duration | ||||||
|  |       = url, width, height, duration | ||||||
|  |     else | ||||||
|  |       t.mpeg4_url, t.mpeg4_width, t.mpeg4_height, t.mpeg4_duration | ||||||
|  |       = url, width, height, duration | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |   elseif t.type == 'video' then | ||||||
|  |     t.video_url, t.mime_type, t.title, t.caption, t.parse_mode, | ||||||
|  |     t.caption_entities, t.video_width, t.video_height, t.video_duration, t.description | ||||||
|  |       = table.unpack(a) | ||||||
|  | 
 | ||||||
|  |   elseif t.type == 'audio' or t.type == 'voice' then | ||||||
|  |     t.title, t.caption, t.parse_mode, t.caption_entities = table.unpack(a, 2) | ||||||
|  | 
 | ||||||
|  |     if t.type == 'audio' then | ||||||
|  |       t.audio_url, t.performer, t.audio_duration = a[1], a[6], a[7] | ||||||
|  |     else | ||||||
|  |       t.voice_url, t.voice_duration = a[1], a[6] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |   elseif t.type == 'document' then | ||||||
|  |     t.title, t.caption, t.parse_mode, t.caption_entities, t.document_url, | ||||||
|  |     t.mime_type, t.description = table.unpack(a) | ||||||
|  | 
 | ||||||
|  |   elseif t.type == 'location' or t.type == 'venue' then | ||||||
|  |     t.latitude, t.longitude, t.title = table.unpack(a, 1, 3) | ||||||
|  | 
 | ||||||
|  |     if t.type ~= 'venue' then | ||||||
|  |       t.horizontal_accurancy, t.live_period, t.heading, t.proximity_alert_radius | ||||||
|  |       = table.unpack(a, 4, 7) | ||||||
|  |     else | ||||||
|  |       t.address, t.foursquare_id, t.foursquare_type, t.google_place_id, t.google_place_type | ||||||
|  |       = table.unpack(a, 4, 8) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |   elseif t.type == 'contact' then | ||||||
|  |     t.phone_number, t.first_name, t.last_name, t.vcard, | ||||||
|  |     t.reply_markup, t.input_message_content | ||||||
|  |       = table.unpack(a) | ||||||
|  | 
 | ||||||
|  |   elseif t.type == 'game' then t.game_short_name = a[1] | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   return t | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function inline:thumb(url, width, height, mime) | ||||||
|  |   if self.type == 'audio' | ||||||
|  |   or self.type == 'voice' | ||||||
|  |   or self.type == 'game' | ||||||
|  |   then return self end | ||||||
|  | 
 | ||||||
|  |   self.thumb_url = tostring(url) | ||||||
|  | 
 | ||||||
|  |   if  width and height and ( | ||||||
|  |      self.type == 'article' | ||||||
|  |   or self.type == 'document' | ||||||
|  |   or self.type == 'contact' | ||||||
|  |   or self.type == 'location' | ||||||
|  |   or self.type == 'venue' | ||||||
|  |   ) then | ||||||
|  |     self.thumb_width  = tonumber(width) | ||||||
|  |     self.thumb_height = tonumber(height) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   if mime and ( | ||||||
|  |      self.type == 'gif' | ||||||
|  |   or self.type == 'mpeg4_gif' | ||||||
|  |   ) then self.thumb_mime_type = mime end | ||||||
|  | 
 | ||||||
|  |   return self | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function inline:keyboard(...) | ||||||
|  |   if not self.type then return self end | ||||||
|  |   local k = {} | ||||||
|  | 
 | ||||||
|  |   for _, v in pairs {...} do | ||||||
|  |     if type(v) == 'table' then | ||||||
|  |       table.insert(k, v) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |   self.reply_markup = k | ||||||
|  | 
 | ||||||
|  |   return self | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | -- Author itself not understands why this funciton needed | ||||||
|  | -- so not recommends to use it | ||||||
|  | function inline:messCont(a) | ||||||
|  |   if self.type == 'game' or self.type == 'article' then | ||||||
|  |     self.input_message_content = a | ||||||
|  |   end | ||||||
|  |   return self | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function inline:answer(id, res, ctime, per, noff, pmt, pmp) | ||||||
|  |    print(dump(res)) | ||||||
|  |   if res.id then res = {res} end  | ||||||
|  |   return self:request('answerInlineQuery', { | ||||||
|  |     inline_query_id = id, | ||||||
|  |     results = res, | ||||||
|  |     cache_time = ctime, | ||||||
|  |     is_personal = per, | ||||||
|  |     next_offset = noff, | ||||||
|  |     switch_pm_text = pmt, | ||||||
|  |     switch_pm_parameter = pmp, | ||||||
|  |   }) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | return function(api) | ||||||
|  |   local self = setmetatable({ | ||||||
|  |     request = function(_, ...) api:request(...) end | ||||||
|  |   }, inline) | ||||||
|  |   return self | ||||||
|  | end | ||||||
							
								
								
									
										167
									
								
								multipart.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								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 | ||||||
							
								
								
									
										29
									
								
								readme.org
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								readme.org
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | ||||||
|  | * Telegram API | ||||||
|  | 
 | ||||||
|  | This is bindings of Telegram API on Lua, part of [[https://gitdab.com/er2/comp-tg][my bot]]. | ||||||
|  | 
 | ||||||
|  | * Installation | ||||||
|  | 
 | ||||||
|  | We recommended to use *.gitmodules* file for that. | ||||||
|  | To include, add to gitmodules this: | ||||||
|  | 
 | ||||||
|  | #+begin_src | ||||||
|  | [submodule "tg-api"] | ||||||
|  | 	path = # enter your path | ||||||
|  | 	url = https://gitdab.com/er2/tg-api-lua | ||||||
|  | 
 | ||||||
|  | #+end_src | ||||||
|  | 
 | ||||||
|  | Also you need to: | ||||||
|  |   + Install *LuaSec* for https requests. | ||||||
|  | 
 | ||||||
|  |   + Execute this code *before* include this API: | ||||||
|  | 
 | ||||||
|  |     #+begin_src lua | ||||||
|  |       package.path = 'yourpath/?.lua;yourpath/?/init.lua;' .. package.path | ||||||
|  |     #+end_src | ||||||
|  | 
 | ||||||
|  |   + Copy to yourpath +/api+ files *event.lua* and *json.lua* | ||||||
|  | 
 | ||||||
|  |     They can be taken from [[https://gitdab.com/er2/comp-tg/src/branch/main/etc][here]]. | ||||||
|  | 
 | ||||||
							
								
								
									
										103
									
								
								tools.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								tools.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,103 @@ | ||||||
|  | --[[ Additional tools | ||||||
|  |   -- (c) Er2 2021 <er2@dismail.de> | ||||||
|  |   -- Zlib license | ||||||
|  | --]] | ||||||
|  | 
 | ||||||
|  | local tools = { | ||||||
|  |   json = require 'json', | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | local json  = tools.json | ||||||
|  | local https = require 'ssl.https' | ||||||
|  | local ltn12 = require 'ltn12' | ||||||
|  | local mp    = require 'api.multipart' | ||||||
|  | 
 | ||||||
|  | function tools.fetchCmd(text) | ||||||
|  |   return | ||||||
|  |     text:match '/([%w_]+)',       -- cmd | ||||||
|  |     text:match '/[%w_]+@([%w_]+)' -- to | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function tools._req(url, meth, data, ctype) | ||||||
|  |   assert(url,  'Provide URL!') | ||||||
|  |   assert(meth, 'Provide method!') | ||||||
|  | 
 | ||||||
|  |   local 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 | ||||||
|  |   end | ||||||
|  |   return resp[1], true | ||||||
|  | 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, f) | ||||||
|  |   assert(token, 'Provide token!') | ||||||
|  |   assert(endpoint, 'Provide endpoint!') | ||||||
|  | 
 | ||||||
|  |   local url = 'https://api.telegram.org/bot' ..token.. '/' ..endpoint | ||||||
|  | 
 | ||||||
|  |   dbg = true | ||||||
|  |   local resp = tools.req(url, param, f, dbg) | ||||||
|  |   return resp, resp.ok or false | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | return tools | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue