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…
Reference in a new issue