First beta
This commit is contained in:
		
						commit
						134c1a519d
					
				
					 14 changed files with 888 additions and 0 deletions
				
			
		
							
								
								
									
										19
									
								
								LICENSE
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								LICENSE
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| zlib License | ||||
| 
 | ||||
| Copyright (c) 2020 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. | ||||
							
								
								
									
										50
									
								
								cmds/eval.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								cmds/eval.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | |||
| local function prind(...) | ||||
|   local t = {...} | ||||
|   local s = '' | ||||
|   for i = 1, #t do | ||||
|     if i > 1 then s = s..'\t' end | ||||
|     s = s .. tostring(t[i] or 'nil') | ||||
|   end | ||||
|   return s .. '\n' | ||||
| end | ||||
| 
 | ||||
| local env = { | ||||
|   assert = assert, | ||||
|   error = error, | ||||
|   ipairs = ipairs, | ||||
|   pairs = pairs, | ||||
|   next = next, | ||||
|   tonumber = tonumber, | ||||
|   tostring = tostring, | ||||
|   type = type, | ||||
|   pcall = pcall, | ||||
|   xpcall = xpcall, | ||||
| 
 | ||||
|   math = math, | ||||
|   string = string, | ||||
|   table = table, | ||||
| 
 | ||||
|   dump = dump, | ||||
| } | ||||
| 
 | ||||
| return { | ||||
|   args = '<code>', | ||||
|   desc = 'evaluates code', | ||||
|   run = function(C, msg, owner) | ||||
|     local s = '' | ||||
|     local t = { | ||||
|       msg = msg, | ||||
|       print = function(...) s = s .. prind(...) end, | ||||
| 
 | ||||
|       C = owner and C or nil, | ||||
|       api = owner and C.api or nil, | ||||
|     } | ||||
|     for k,v in pairs(env) do t[k] = v end | ||||
|     local e, err = load(table.concat(msg.args, ' '), 'eval', 't', t) | ||||
|     xpcall(function() | ||||
|       if err then error(err) end | ||||
|       e = tostring(e() or '...') | ||||
|     end, function(err) e = err end) | ||||
|     C.api:send(msg, s .. '\n' .. e) | ||||
|   end | ||||
| } | ||||
							
								
								
									
										6
									
								
								cmds/ping.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								cmds/ping.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| return { | ||||
|   desc = 'ping pong', | ||||
|   run = function(C, msg) | ||||
|     C.api:send(msg, 'Pong! ' .. (os.time() - msg.date) .. 's') | ||||
|   end | ||||
| } | ||||
							
								
								
									
										64
									
								
								cmds/rub.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								cmds/rub.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,64 @@ | |||
| local rub = { | ||||
|   url = 'https://api.factmaven.com/xml-to-json/?xml=' | ||||
|     .. 'https://www.cbr.ru/scripts/XML_daily.asp', | ||||
|   fmt = function(v, fmt) | ||||
|     fmt = type(fmt) == 'string' and fmt or '%d %s = %f' | ||||
|     return fmt:format(v.Nominal, v.Name, v.Value:gsub(',', '.')) | ||||
|   end | ||||
| } | ||||
| 
 | ||||
| function rub:course(wants, fmt) | ||||
|   local resp, succ = (require'tg.tools').requ(self.url) | ||||
|   if not succ then | ||||
|     return {}, '[ошибка]', {} | ||||
|   end | ||||
| 
 | ||||
|   resp = resp.ValCurs | ||||
| 
 | ||||
|   wants = type(wants) == 'table' and wants or {} | ||||
|   local r, founds = {}, {} | ||||
|   for i = 1, #resp.Valute do | ||||
|     local v = resp.Valute[i] | ||||
|     if table.find(wants, v.CharCode) then | ||||
|       table.insert(founds, v.CharCode) | ||||
|       table.insert(r, self.fmt(v, fmt)) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   local i = table.find(wants, 'RUB') | ||||
|   if i then | ||||
|     table.insert(founds, 'RUB') | ||||
|     table.insert(r, i, self.fmt({ | ||||
|       Nominal = 1, | ||||
|       Name = 'Российский рубль', | ||||
|       Value = '1' | ||||
|     }, fmt) .. ' :D') | ||||
|   end | ||||
| 
 | ||||
|   return r, resp.Date, founds | ||||
| end | ||||
| 
 | ||||
| function rub.msg(C, msg) | ||||
|   local wants = {'USD', 'EUR', table.unpack(msg.args)} | ||||
|   for i = 1, #wants do wants[i] = wants[i]:upper() end | ||||
| 
 | ||||
|   local v, d, f = rub:course(wants, '%d %s - %f ₽') | ||||
|   local nf = {} | ||||
| 
 | ||||
|   for i = 1, #wants do | ||||
|     if not table.find(f, wants[i]) then | ||||
|       table.insert(nf, wants[i]) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   local s = 'Курс на ' .. d .. ':\n' .. table.concat(v, '\n') | ||||
|   if #nf > 0 then s = s .. '\n\n' .. 'Не нашлось: ' .. table.concat(nf, ', ') end | ||||
| 
 | ||||
|   C.api:reply(msg, s .. '\nДанные от Центробанка России') | ||||
| end | ||||
| 
 | ||||
| return { | ||||
|   args = '[valute]...', | ||||
|   desc = 'ruble course', | ||||
|   run = rub.msg | ||||
| } | ||||
							
								
								
									
										14
									
								
								config.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								config.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| return { | ||||
|   token = 'atokenyanedam', | ||||
|   owner = 'Er2Official', -- hehe | ||||
|   cmds = { | ||||
|     'eval', | ||||
|     'rub', | ||||
|     'ping', | ||||
|   }, | ||||
|   events = { | ||||
|     'command', | ||||
|     'message', | ||||
|     'ready', | ||||
|   }, | ||||
| } | ||||
							
								
								
									
										22
									
								
								events/command.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								events/command.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| return function(C, api, msg) | ||||
|   local cmd = C.cmds[msg.cmd] | ||||
|   local owner = msg.from.username == C.config.owner | ||||
|   if cmd == nil then | ||||
|     api:send(msg, 'Invaid command provided.') | ||||
| 
 | ||||
|   elseif type(cmd.run) ~= 'function' then | ||||
|     api:send(msg, 'Command cannot be executed.') | ||||
| 
 | ||||
|   elseif cmd.private and not owner then | ||||
|     api:send(msg, 'You can\'t execute private commands!') | ||||
| 
 | ||||
|   else | ||||
|     local succ, err = pcall(cmd.run, C, msg, owner) | ||||
|     if not succ then | ||||
|       api:reply(msg, 'Произошла ошибочка, которая была отправлена создателю') | ||||
|       local cid = api:getChat('@' .. C.config.owner).id | ||||
|       api:forward(cid, msg.chat.id, msg.message_id, false) | ||||
|       api:send(cid, err) | ||||
|     end | ||||
|   end | ||||
| end | ||||
							
								
								
									
										3
									
								
								events/message.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								events/message.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| return function(C, api, msg) | ||||
| --  api:reply(msg, 'хай!') | ||||
| end | ||||
							
								
								
									
										40
									
								
								events/ready.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								events/ready.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | |||
| function table.indexOf(t, w) | ||||
|   local i = {} | ||||
|   for k,v in pairs(t) do i[v] = k end | ||||
|   return i[w] | ||||
| end | ||||
| 
 | ||||
| function table.find(t, w) | ||||
|   local i | ||||
|   for k,v in pairs(t) do | ||||
|     if v == w then | ||||
|       i = k | ||||
|       break | ||||
|     end | ||||
|   end | ||||
|   return i | ||||
| end | ||||
| 
 | ||||
| function dump(t, d) | ||||
|   if not tonumber(d) or d < 0 then d = 0 end | ||||
|   local c = '' | ||||
|   for k,v in pairs(t) do | ||||
|     if type(v) == 'table' then v = '\n' .. dump(v, d + 1) end | ||||
|     c = c .. string.format('%s%s = %s\n', (' '):rep(d), k, v) | ||||
|   end | ||||
|   return c | ||||
| end | ||||
| 
 | ||||
| return function(C, api) | ||||
|   C:load 'cmds' | ||||
|   local a = {} | ||||
|   for k, v in pairs(C.cmds) do | ||||
|     if not v.private then | ||||
|       table.insert(a, { | ||||
|         command = k, | ||||
|         description = (v.args and v.args .. ' - ' or '') .. v.desc or 'no description' | ||||
|       }) | ||||
|     end | ||||
|   end | ||||
|   api:setMyCommands(a) | ||||
| end | ||||
							
								
								
									
										45
									
								
								init.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								init.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| local config = require 'config' | ||||
| 
 | ||||
| local Core = { | ||||
|   tg = require 'tg', | ||||
|   tools = tools, | ||||
|   config = config, | ||||
|   cmds = {}, | ||||
| } | ||||
| local tg = Core.tg | ||||
| 
 | ||||
| function Core:load(what) | ||||
|   local c = config[what] | ||||
|   local s = #c | ||||
|   for i = 1, s do | ||||
|     local v = c[i] | ||||
|     print(('Loading %s (%d / %d) %s...'):format(what:sub(0, -2), i, s, v)) | ||||
|     if not pcall(require, what .. '.' .. v) then print 'fail'; goto f end | ||||
| 
 | ||||
|     local a = require(what .. '.' .. v) | ||||
|     if what == 'events' then | ||||
|       self.api['on' .. v:sub(1, 1):upper() .. v:sub(2)] = function(...) | ||||
|         local succ = pcall(a, self, ...) | ||||
|         if not succ then print('event ' .. v .. ' was failed') end | ||||
|       end | ||||
|     elseif what == 'cmds' then self.cmds[v] = a | ||||
|     end | ||||
|     ::f:: | ||||
|   end | ||||
|   print(('Loaded %d %s'):format(s, what)) | ||||
| end | ||||
| 
 | ||||
| function Core:init() | ||||
|   self.api = tg(config.token) | ||||
|   self.config.token = nil | ||||
|    | ||||
|   print('Logged on as @' .. self.api.info.username) | ||||
|   print 'Client initialization...' | ||||
| 
 | ||||
|   self:load 'events' | ||||
| 
 | ||||
|   print 'Done!' | ||||
|   self.api:run() | ||||
| end | ||||
| 
 | ||||
| Core:init() | ||||
							
								
								
									
										93
									
								
								tg/api.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								tg/api.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,93 @@ | |||
| -- API Library | ||||
| --- (c) Er2 <er2@dismail.de> | ||||
| --- Zlib License | ||||
| 
 | ||||
| local tools = require 'tg.tools' | ||||
| local json = require 'tg.json' | ||||
| local api = { | ||||
|   request = function(self, ...) return tools.request(self.token, ...) end, | ||||
| } | ||||
| api.__index = api -- Make class | ||||
| 
 | ||||
| -- 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) | ||||
|   rmp = type(rmp) == 'table' and json.encode(rmp) or rmp | ||||
|   msg = (type(msg) == 'table' and msg.chat and msg.chat.id) and msg.chat.id or msg | ||||
|   pmod = (type(pmod) == 'boolean' and pmod == true) and 'markdown' or pmod | ||||
|   if dwp == nil then dwp = true 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) | ||||
|   if type(msg) ~= 'table' or not msg.chat or not msg.chat.id or not msg.message_id then return false end | ||||
|   rmp = type(rmp) == 'table' and json.encode(rmp) or rmp | ||||
|   pmod = (type(pmod) == 'boolean' and pmod == true) and 'markdown' or pmod | ||||
| 
 | ||||
|   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:sendPoll(cid, q, opt, anon, ptype, mansw, coptid, expl, pmode, oper, cdate, closed, dnot, rtmid, rmp) | ||||
|   opt = type(opt) == 'string' and opt or json.encode(opt) | ||||
|   rmp = type(rmp) == 'table' and json.encode(rmp) or rmp | ||||
|   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:setMyCommands(cmds) | ||||
|   return self:request('setMyCommands', { commands = json.encode(cmds) }) | ||||
| end | ||||
| 
 | ||||
| return api | ||||
							
								
								
									
										110
									
								
								tg/core.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								tg/core.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,110 @@ | |||
| -- Core file | ||||
| --- (c) Er2 <er2@dismail.de> | ||||
| --- Zlib License | ||||
| 
 | ||||
| local tools = require 'tg.tools' | ||||
| local api = require 'tg.api' | ||||
| api.__index = api -- Make class | ||||
| 
 | ||||
| -- EVENT PROTOTYPES -- | ||||
| function api.onCommand(_) end | ||||
| function api.onChannelPost(_) end | ||||
| function api.onChannelPostEdit(_) end | ||||
| function api.onMessage(_) end | ||||
| function api.onMessageEdit(_) end | ||||
| function api.onInlineResult(_) end | ||||
| function api.onPoll(_) end | ||||
| function api.onPollAnswer(_) end | ||||
| function api.onReady(_) end | ||||
| function api.onQuery(_) end | ||||
| function api.onUpdate(_) end | ||||
| 
 | ||||
| -- UPDATES -- | ||||
| function api:getUpdates(tout, offs, lim, 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 then self:onUpdate(update) end | ||||
| 
 | ||||
|   if update.message then | ||||
|     local txt = update.message.text | ||||
|     local cmd, to = tools.fetchCmd(txt) | ||||
|     if cmd and (not to or to == self.info.username) then | ||||
|       local args = {} | ||||
|       txt = txt:sub(#cmd + #(to or {}) + 3) | ||||
|       for s in txt:gmatch '%S+' do table.insert(args, s) end | ||||
| 
 | ||||
|       update.message.cmd = cmd | ||||
|       update.message.args = args | ||||
|       return self:onCommand(update.message, update.message.chat.type) | ||||
|     elseif cmd then return end | ||||
| 
 | ||||
|     self:onMessage(update.message, update.message.chat.type) | ||||
| 
 | ||||
|   elseif update.edited_message then | ||||
|     self:onMessageEdit(update.edited_message, update.edited_message.chat.type) | ||||
| 
 | ||||
|   elseif update.channel_post then self:onChannelPost(update.channel_post) | ||||
|   elseif update.edited_channel_post then self:onChannelPostEdit(update.edited_channel_post) | ||||
| 
 | ||||
|   elseif update.poll then self:onPoll(update.poll) | ||||
|   elseif update.poll_answer then self:onPollAnswer(update.poll_answer) | ||||
| 
 | ||||
|   elseif update.callback_query then self:onQuery('callback', update.callback_query) | ||||
|   elseif update.inline_query then self:onQuery('inline', update.inline_query) | ||||
|   elseif update.shipping_query then self:onQuery('shipping', update.shipping_query) | ||||
|   elseif update.pre_checkout_query then self:onQuery('preCheckout', update.pre_checkout_query) | ||||
| 
 | ||||
|   elseif update.chosen_inline_result then self:onInlineResult(update.chosen_inline_result) | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| function api:_loop(lim, tout, offs, al) | ||||
|   while true do | ||||
|     local u, ok = self:getUpdates(tout, offs, lim, al) | ||||
|     if not ok or not u or (u and type(u) ~= 'table') or not u.result then goto f end | ||||
|     for _, v in pairs(u.result) do | ||||
|       offs = v.update_id + 1 | ||||
|       receiveUpdate(self, v) | ||||
|     end | ||||
|     ::f:: | ||||
|   end | ||||
|   self:getUpdates(tout, offs, lim, al) | ||||
| end | ||||
| 
 | ||||
| -- RUN -- | ||||
| function api:run(lim, tout, offs, al) | ||||
|   lim = tonumber(lim) or 1 | ||||
|   tout = tonumber(tout) or 0 | ||||
|   offs = tonumber(offs) or 0 | ||||
| 
 | ||||
|   self.runs = true | ||||
|   self:onReady() | ||||
| 
 | ||||
|   self.co = coroutine.create(api._loop) | ||||
|   coroutine.resume(self.co, self, lim, tout, offs, al) | ||||
| end | ||||
| 
 | ||||
| function api:destroy() self.runs = false end | ||||
| 
 | ||||
| return function(token) | ||||
|   if not token or type(token) ~= 'string' then token = nil end | ||||
|   local self = setmetatable({}, api) | ||||
|   self.token = assert(token, 'Provide token!') | ||||
| 
 | ||||
|   repeat | ||||
|     local b,a = self:getMe() | ||||
|     if a then self.info = b end | ||||
|   until (self.info or {}).result | ||||
| 
 | ||||
|   self.info = self.info.result | ||||
|   self.info.name = self.info.first_name | ||||
|   return self | ||||
| end | ||||
							
								
								
									
										1
									
								
								tg/init.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tg/init.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| return require 'tg.core' | ||||
							
								
								
									
										357
									
								
								tg/json.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										357
									
								
								tg/json.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,357 @@ | |||
| -- JSON Library | ||||
| --- (c) 2020 rxi | ||||
| --- MIT License | ||||
| 
 | ||||
| local json = { _version = "0.1.2" } | ||||
| local encode | ||||
| 
 | ||||
| local escape_char_map = { | ||||
|   [ "\\" ] = "\\", | ||||
|   [ "\"" ] = "\"", | ||||
|   [ "\b" ] = "b", | ||||
|   [ "\f" ] = "f", | ||||
|   [ "\n" ] = "n", | ||||
|   [ "\r" ] = "r", | ||||
|   [ "\t" ] = "t", | ||||
| } | ||||
| 
 | ||||
| local escape_char_map_inv = { [ "/" ] = "/" } | ||||
| for k, v in pairs(escape_char_map) do | ||||
|   escape_char_map_inv[v] = k | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function escape_char(c) | ||||
|   return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function encode_nil(val) | ||||
|   return "null" | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function encode_table(val, stack) | ||||
|   local res = {} | ||||
|   stack = stack or {} | ||||
| 
 | ||||
|   -- Circular reference? | ||||
|   if stack[val] then error("circular reference") end | ||||
| 
 | ||||
|   stack[val] = true | ||||
| 
 | ||||
|   if rawget(val, 1) ~= nil or next(val) == nil then | ||||
|     -- Treat as array -- check keys are valid and it is not sparse | ||||
|     local n = 0 | ||||
|     for k in pairs(val) do | ||||
|       if type(k) ~= "number" then | ||||
|         error("invalid table: mixed or invalid key types") | ||||
|       end | ||||
|       n = n + 1 | ||||
|     end | ||||
|     if n ~= #val then | ||||
|       error("invalid table: sparse array") | ||||
|     end | ||||
|     -- Encode | ||||
|     for i, v in ipairs(val) do | ||||
|       table.insert(res, encode(v, stack)) | ||||
|     end | ||||
|     stack[val] = nil | ||||
|     return "[" .. table.concat(res, ",") .. "]" | ||||
| 
 | ||||
|   else | ||||
|     -- Treat as an object | ||||
|     for k, v in pairs(val) do | ||||
|       if type(k) ~= "string" then | ||||
|         error("invalid table: mixed or invalid key types") | ||||
|       end | ||||
|       table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) | ||||
|     end | ||||
|     stack[val] = nil | ||||
|     return "{" .. table.concat(res, ",") .. "}" | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function encode_string(val) | ||||
|   return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function encode_number(val) | ||||
|   -- Check for NaN, -inf and inf | ||||
|   if val ~= val or val <= -math.huge or val >= math.huge then | ||||
|     error("unexpected number value '" .. tostring(val) .. "'") | ||||
|   end | ||||
|   return string.format("%.14g", val) | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local type_func_map = { | ||||
|   [ "nil"     ] = encode_nil, | ||||
|   [ "table"   ] = encode_table, | ||||
|   [ "string"  ] = encode_string, | ||||
|   [ "number"  ] = encode_number, | ||||
|   [ "boolean" ] = tostring, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| encode = function(val, stack) | ||||
|   local t = type(val) | ||||
|   local f = type_func_map[t] | ||||
|   if f then | ||||
|     return f(val, stack) | ||||
|   end | ||||
|   error("unexpected type '" .. t .. "'") | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| function json.encode(val) | ||||
|   return ( encode(val) ) | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local parse | ||||
| 
 | ||||
| local function create_set(...) | ||||
|   local res = {} | ||||
|   for i = 1, select("#", ...) do | ||||
|     res[ select(i, ...) ] = true | ||||
|   end | ||||
|   return res | ||||
| end | ||||
| 
 | ||||
| local space_chars   = create_set(" ", "\t", "\r", "\n") | ||||
| local delim_chars   = create_set(" ", "\t", "\r", "\n", "]", "}", ",") | ||||
| local escape_chars  = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") | ||||
| local literals      = create_set("true", "false", "null") | ||||
| 
 | ||||
| local literal_map = { | ||||
|   [ "true"  ] = true, | ||||
|   [ "false" ] = false, | ||||
|   [ "null"  ] = nil, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| local function next_char(str, idx, set, negate) | ||||
|   for i = idx, #str do | ||||
|     if set[str:sub(i, i)] ~= negate then | ||||
|       return i | ||||
|     end | ||||
|   end | ||||
|   return #str + 1 | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function decode_error(str, idx, msg) | ||||
|   local line_count = 1 | ||||
|   local col_count = 1 | ||||
|   for i = 1, idx - 1 do | ||||
|     col_count = col_count + 1 | ||||
|     if str:sub(i, i) == "\n" then | ||||
|       line_count = line_count + 1 | ||||
|       col_count = 1 | ||||
|     end | ||||
|   end | ||||
|   error( string.format("%s at line %d col %d", msg, line_count, col_count) ) | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function codepoint_to_utf8(n) | ||||
|   -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa | ||||
|   local f = math.floor | ||||
|   if n <= 0x7f then | ||||
|     return string.char(n) | ||||
|   elseif n <= 0x7ff then | ||||
|     return string.char(f(n / 64) + 192, n % 64 + 128) | ||||
|   elseif n <= 0xffff then | ||||
|     return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) | ||||
|   elseif n <= 0x10ffff then | ||||
|     return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, | ||||
|                        f(n % 4096 / 64) + 128, n % 64 + 128) | ||||
|   end | ||||
|   error( string.format("invalid unicode codepoint '%x'", n) ) | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function parse_unicode_escape(s) | ||||
|   local n1 = tonumber( s:sub(1, 4),  16 ) | ||||
|   local n2 = tonumber( s:sub(7, 10), 16 ) | ||||
|    -- Surrogate pair? | ||||
|   if n2 then | ||||
|     return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) | ||||
|   else | ||||
|     return codepoint_to_utf8(n1) | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function parse_string(str, i) | ||||
|   local res = "" | ||||
|   local j = i + 1 | ||||
|   local k = j | ||||
| 
 | ||||
|   while j <= #str do | ||||
|     local x = str:byte(j) | ||||
| 
 | ||||
|     if x < 32 then | ||||
|       decode_error(str, j, "control character in string") | ||||
| 
 | ||||
|     elseif x == 92 then -- `\`: Escape | ||||
|       res = res .. str:sub(k, j - 1) | ||||
|       j = j + 1 | ||||
|       local c = str:sub(j, j) | ||||
|       if c == "u" then | ||||
|         local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) | ||||
|                  or str:match("^%x%x%x%x", j + 1) | ||||
|                  or decode_error(str, j - 1, "invalid unicode escape in string") | ||||
|         res = res .. parse_unicode_escape(hex) | ||||
|         j = j + #hex | ||||
|       else | ||||
|         if not escape_chars[c] then | ||||
|           decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") | ||||
|         end | ||||
|         res = res .. escape_char_map_inv[c] | ||||
|       end | ||||
|       k = j + 1 | ||||
| 
 | ||||
|     elseif x == 34 then -- `"`: End of string | ||||
|       res = res .. str:sub(k, j - 1) | ||||
|       return res, j + 1 | ||||
|     end | ||||
| 
 | ||||
|     j = j + 1 | ||||
|   end | ||||
| 
 | ||||
|   decode_error(str, i, "expected closing quote for string") | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function parse_number(str, i) | ||||
|   local x = next_char(str, i, delim_chars) | ||||
|   local s = str:sub(i, x - 1) | ||||
|   local n = tonumber(s) | ||||
|   if not n then | ||||
|     decode_error(str, i, "invalid number '" .. s .. "'") | ||||
|   end | ||||
|   return n, x | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function parse_literal(str, i) | ||||
|   local x = next_char(str, i, delim_chars) | ||||
|   local word = str:sub(i, x - 1) | ||||
|   if not literals[word] then | ||||
|     decode_error(str, i, "invalid literal '" .. word .. "'") | ||||
|   end | ||||
|   return literal_map[word], x | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function parse_array(str, i) | ||||
|   local res = {} | ||||
|   local n = 1 | ||||
|   i = i + 1 | ||||
|   while 1 do | ||||
|     local x | ||||
|     i = next_char(str, i, space_chars, true) | ||||
|     -- Empty / end of array? | ||||
|     if str:sub(i, i) == "]" then | ||||
|       i = i + 1 | ||||
|       break | ||||
|     end | ||||
|     -- Read token | ||||
|     x, i = parse(str, i) | ||||
|     res[n] = x | ||||
|     n = n + 1 | ||||
|     -- Next token | ||||
|     i = next_char(str, i, space_chars, true) | ||||
|     local chr = str:sub(i, i) | ||||
|     i = i + 1 | ||||
|     if chr == "]" then break end | ||||
|     if chr ~= "," then decode_error(str, i, "expected ']' or ','") end | ||||
|   end | ||||
|   return res, i | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function parse_object(str, i) | ||||
|   local res = {} | ||||
|   i = i + 1 | ||||
|   while 1 do | ||||
|     local key, val | ||||
|     i = next_char(str, i, space_chars, true) | ||||
|     -- Empty / end of object? | ||||
|     if str:sub(i, i) == "}" then | ||||
|       i = i + 1 | ||||
|       break | ||||
|     end | ||||
|     -- Read key | ||||
|     if str:sub(i, i) ~= '"' then | ||||
|       decode_error(str, i, "expected string for key") | ||||
|     end | ||||
|     key, i = parse(str, i) | ||||
|     -- Read ':' delimiter | ||||
|     i = next_char(str, i, space_chars, true) | ||||
|     if str:sub(i, i) ~= ":" then | ||||
|       decode_error(str, i, "expected ':' after key") | ||||
|     end | ||||
|     i = next_char(str, i + 1, space_chars, true) | ||||
|     -- Read value | ||||
|     val, i = parse(str, i) | ||||
|     -- Set | ||||
|     res[key] = val | ||||
|     -- Next token | ||||
|     i = next_char(str, i, space_chars, true) | ||||
|     local chr = str:sub(i, i) | ||||
|     i = i + 1 | ||||
|     if chr == "}" then break end | ||||
|     if chr ~= "," then decode_error(str, i, "expected '}' or ','") end | ||||
|   end | ||||
|   return res, i | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local char_func_map = { | ||||
|   [ '"' ] = parse_string, | ||||
|   [ "0" ] = parse_number, | ||||
|   [ "1" ] = parse_number, | ||||
|   [ "2" ] = parse_number, | ||||
|   [ "3" ] = parse_number, | ||||
|   [ "4" ] = parse_number, | ||||
|   [ "5" ] = parse_number, | ||||
|   [ "6" ] = parse_number, | ||||
|   [ "7" ] = parse_number, | ||||
|   [ "8" ] = parse_number, | ||||
|   [ "9" ] = parse_number, | ||||
|   [ "-" ] = parse_number, | ||||
|   [ "t" ] = parse_literal, | ||||
|   [ "f" ] = parse_literal, | ||||
|   [ "n" ] = parse_literal, | ||||
|   [ "[" ] = parse_array, | ||||
|   [ "{" ] = parse_object, | ||||
| } | ||||
| 
 | ||||
| parse = function(str, idx) | ||||
|   local chr = str:sub(idx, idx) | ||||
|   local f = char_func_map[chr] | ||||
|   if f then | ||||
|     return f(str, idx) | ||||
|   end | ||||
|   decode_error(str, idx, "unexpected character '" .. chr .. "'") | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| function json.decode(str) | ||||
|   if type(str) ~= "string" then | ||||
|     error("expected argument of type string, got " .. type(str)) | ||||
|   end | ||||
|   local res, idx = parse(str, next_char(str, 1, space_chars, true)) | ||||
|   idx = next_char(str, idx, space_chars, true) | ||||
|   if idx <= #str then | ||||
|     decode_error(str, idx, "trailing garbage") | ||||
|   end | ||||
|   return res | ||||
| end | ||||
| 
 | ||||
| return json | ||||
							
								
								
									
										64
									
								
								tg/tools.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								tg/tools.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,64 @@ | |||
| local tools = { | ||||
|   json = require 'tg.json', | ||||
| } | ||||
| 
 | ||||
| local json = tools.json | ||||
| local https = require 'ssl.https' | ||||
| local ltn12 = require 'ltn12' | ||||
| 
 | ||||
| function tools.fetchCmd(text) | ||||
|   local cmd = text:match '/[%w_]+' | ||||
|   local to = text:match '/[%w_]+(@[%w_]+)' | ||||
|   if to then to = to:sub(2) end | ||||
|   if cmd then cmd = cmd:sub(2) end | ||||
|   return cmd, 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) | ||||
|   local resp = {} | ||||
|   local succ, res = https.request { | ||||
|     url = url, | ||||
|     method = 'GET', | ||||
|     sink = ltn12.sink.table(resp), | ||||
|   } | ||||
| 
 | ||||
|   if not succ then | ||||
|     print('Connection error [' .. res .. ']') | ||||
|     return nil, false | ||||
|   end | ||||
|   return resp[1], true | ||||
| end | ||||
| 
 | ||||
| function tools.requ(url) | ||||
|   local res, succ = tools.req(url) | ||||
|   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) | ||||
|   assert(token, 'Provide token!') | ||||
|   assert(endpoint, 'Provide endpoint!') | ||||
| 
 | ||||
|   local params = '' | ||||
|   for k, v in pairs(param or {}) do | ||||
|     params = params .. '&' .. k .. '=' .. tools.urlencode(tostring(v)) | ||||
|   end | ||||
| 
 | ||||
|   local url = 'https://api.telegram.org/bot' .. token .. '/' .. endpoint | ||||
|   if #params > 1 then url = url .. '?' .. params:sub(2) end | ||||
| 
 | ||||
|   local resp = tools.requ(url) | ||||
|   return resp, resp.ok or false | ||||
| end | ||||
| 
 | ||||
| return tools | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue