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