From 134c1a519de5ec010b75f8ad8b42e72cc0f732bb Mon Sep 17 00:00:00 2001 From: Ertu Date: Wed, 14 Apr 2021 09:03:25 +0300 Subject: [PATCH] First beta --- LICENSE | 19 +++ cmds/eval.lua | 50 +++++++ cmds/ping.lua | 6 + cmds/rub.lua | 64 ++++++++ config.lua | 14 ++ events/command.lua | 22 +++ events/message.lua | 3 + events/ready.lua | 40 +++++ init.lua | 45 ++++++ tg/api.lua | 93 ++++++++++++ tg/core.lua | 110 ++++++++++++++ tg/init.lua | 1 + tg/json.lua | 357 +++++++++++++++++++++++++++++++++++++++++++++ tg/tools.lua | 64 ++++++++ 14 files changed, 888 insertions(+) create mode 100644 LICENSE create mode 100644 cmds/eval.lua create mode 100644 cmds/ping.lua create mode 100644 cmds/rub.lua create mode 100644 config.lua create mode 100644 events/command.lua create mode 100644 events/message.lua create mode 100644 events/ready.lua create mode 100644 init.lua create mode 100644 tg/api.lua create mode 100644 tg/core.lua create mode 100644 tg/init.lua create mode 100644 tg/json.lua create mode 100644 tg/tools.lua diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..80ff512 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +zlib License + +Copyright (c) 2020 Er2 + +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. diff --git a/cmds/eval.lua b/cmds/eval.lua new file mode 100644 index 0000000..00fced7 --- /dev/null +++ b/cmds/eval.lua @@ -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 = '', + 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 +} \ No newline at end of file diff --git a/cmds/ping.lua b/cmds/ping.lua new file mode 100644 index 0000000..cea65dd --- /dev/null +++ b/cmds/ping.lua @@ -0,0 +1,6 @@ +return { + desc = 'ping pong', + run = function(C, msg) + C.api:send(msg, 'Pong! ' .. (os.time() - msg.date) .. 's') + end +} \ No newline at end of file diff --git a/cmds/rub.lua b/cmds/rub.lua new file mode 100644 index 0000000..cd1c3d8 --- /dev/null +++ b/cmds/rub.lua @@ -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 +} \ No newline at end of file diff --git a/config.lua b/config.lua new file mode 100644 index 0000000..2b06f5f --- /dev/null +++ b/config.lua @@ -0,0 +1,14 @@ +return { + token = 'atokenyanedam', + owner = 'Er2Official', -- hehe + cmds = { + 'eval', + 'rub', + 'ping', + }, + events = { + 'command', + 'message', + 'ready', + }, +} \ No newline at end of file diff --git a/events/command.lua b/events/command.lua new file mode 100644 index 0000000..84bb004 --- /dev/null +++ b/events/command.lua @@ -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 \ No newline at end of file diff --git a/events/message.lua b/events/message.lua new file mode 100644 index 0000000..a864740 --- /dev/null +++ b/events/message.lua @@ -0,0 +1,3 @@ +return function(C, api, msg) +-- api:reply(msg, 'хай!') +end diff --git a/events/ready.lua b/events/ready.lua new file mode 100644 index 0000000..d6fe250 --- /dev/null +++ b/events/ready.lua @@ -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 \ No newline at end of file diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..c54c14c --- /dev/null +++ b/init.lua @@ -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() \ No newline at end of file diff --git a/tg/api.lua b/tg/api.lua new file mode 100644 index 0000000..a541ffc --- /dev/null +++ b/tg/api.lua @@ -0,0 +1,93 @@ +-- API Library +--- (c) Er2 +--- 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 \ No newline at end of file diff --git a/tg/core.lua b/tg/core.lua new file mode 100644 index 0000000..dcf3a92 --- /dev/null +++ b/tg/core.lua @@ -0,0 +1,110 @@ +-- Core file +--- (c) Er2 +--- 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 \ No newline at end of file diff --git a/tg/init.lua b/tg/init.lua new file mode 100644 index 0000000..6cad5d0 --- /dev/null +++ b/tg/init.lua @@ -0,0 +1 @@ +return require 'tg.core' \ No newline at end of file diff --git a/tg/json.lua b/tg/json.lua new file mode 100644 index 0000000..b9826ef --- /dev/null +++ b/tg/json.lua @@ -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 \ No newline at end of file diff --git a/tg/tools.lua b/tg/tools.lua new file mode 100644 index 0000000..b73ad1a --- /dev/null +++ b/tg/tools.lua @@ -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 \ No newline at end of file