rewrite to use classes

This commit is contained in:
Er2 2022-08-10 12:50:56 +03:00
parent da0a266b72
commit faab5a7e6d
7 changed files with 445 additions and 448 deletions

155
api.lua
View file

@ -1,155 +0,0 @@
--[[ 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)
cid = argp(cid)
return self:request('forwardMessage', {
chat_id = cid,
from_chat_id = frcid,
disable_notification = dnot,
message_id = mid,
})
end
function api:sendSticker(cid, stk, dnot, rmp, pcon, rtmid, swr)
cid, rmp = argp(cid, rmp)
return self:request('sendSticker', {
chat_id = cid,
sticker = stk,
disable_notification = dnot,
protect_content = pcon,
reply_to_message_id = rtmid,
allow_sending_without_reply = swr,
reply_markup = rmp,
})
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, lang, scope)
return self:request('setMyCommands', {
commands = json.encode(cmds),
language_code = tostring(lang),
scope = scope,
})
end
return api

141
core.lua
View file

@ -1,141 +0,0 @@
--[[ Core file
-- (c) Er2 2021 <er2@dismail.de>
-- Zlib License
--]]
local tools = require 'api.tools'
local api = require 'api.api'
api.__index = api
api.parseArgs = tools.parseArgs
api.unparseArgs = tools.unparseArgs
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
-- remove needn't text
msg.text = msg.text:sub(#cmd + #(to or '') + 3)
local args = {}
for v in msg.text:gmatch '%S+' do table.insert(args, v) 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 then
-- fallback if very long message
return offs + 1
end
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

287
init.lua
View file

@ -1 +1,286 @@
return require 'api.core'
-- API Library
-- (c) Er2 2022 <er2@dismail.de>
-- Zlib License
require 'class'
require 'api.inline'
local tools = require 'api.tools'
local json = tools.json
-- 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
class 'API' : inherits 'EventsThis' {
function(this, opts)
this:super()
if type(opts) == 'table' then
if opts.token and type(opts.token) == 'string'
then this.token = opts.token end
if opts.norun
then this.nr = true end
if not opts.noinl then
this.inline = new 'APIInline' (this)
end
end
end,
loop = function(this, limit, offset, timeout, allowed)
limit = tonumber(limit) or 1
offset = tonumber(offset) or 0
timeout = tonumber(timeout) or 0
this.runs = true
this:emit 'ready'
while this.runs do
offset = this:recvUpdate(limit, offset, timeout, allowed)
end
end,
parseArgs = tools.parseArgs,
unparseArgs = tools.unparseArgs,
request = function(this, ...)
return tools.request(this.token, ...)
end,
destroy = function(this) this.runs = false end,
login = function(this, token, cb)
this.token = assert(token or this.token, 'Provide token!')
repeat
local result, ok = this:getMe()
if ok and result
then this.info = result
end
until (this.info or {}).result
this.info = this.info.result
this.info.name = this.info.first_name
if type(cb) == 'function'
then cb(this) end
if not this.nr
then this:loop() end
end,
recvUpdate = function(this, limit, offset, timeout, allowed)
local update, ok = this:getUpdates(limit, offset, timeout, allowed)
if not ok then
-- See warning below
return offset + 1
end
if ok
and update
and update.result
then for _, upd in pairs(update.result) do
offset = math.max(upd.update_id + 1, offset)
this:receiveUpdate(upd)
end end
return offset
end,
receiveUpdate = function(this, upd)
this:emit('update', upd)
if upd.message then
local msg = upd.message
local cmd, to = tools.fetchCmd(msg.text or '')
if cmd then
-- need @to bot and /cmd@bot in groups
if to and to ~= this.info.username
or ((msg.chat.type == 'group' or msg.chat.type == 'supergroup')
and not to)
then return end
-- remove command
-- 2 = / + @
msg.text = msg.text:sub(#cmd + #(to or '') + 2 + 1)
local args = {}
for v in msg.text:gmatch '%S+'
do table.insert(args, v) end
msg.cmd = cmd
msg.args = args
this:emit('command', msg)
return
end
this:emit('message', msg)
elseif upd.edited_message
then this:emit('messageEdit', upd.edited_message)
elseif upd.channel_post
then this:emit('channelPost', upd.channel_post)
elseif upd.edited_channel_post
then this:emit('channelPostEdit', upd.edited_channel_post)
elseif upd.poll
then this:emit('poll', upd.poll)
elseif upd.poll_answer
then this:emit('pollAnswer', upd.poll_answer)
elseif upd.callback_query
then this:emit('callbackQuery', upd.callback_query)
elseif upd.inline_query
then this:emit('inlineQuery', upd.inline_query)
elseif upd.shipping_query
then this:emit('shippingQuery', upd.shipping_query)
elseif upd.pre_checkout_query
then this:emit('preCheckoutQuery', upd.pre_checkout_query)
elseif upd.chosen_inline_result
then this:emit('inlineResult', upd.chosen_inline_result)
end
end,
-- Getters without params
getMe = function(this) return this:request 'getMe' end,
getMyCommands = function(this) return this:request 'getMyCommands' end,
-- Getters with params
getChat = function(this, chatId)
return this:request('getChat', {
chat_id = chatId,
})
end,
getUpdates = function(this, limit, offset, timeout, allowed)
-- WARNING: Due to LuaSec body limit, this function can return nil
allowed = type(allowed) == 'table' and json.decode(allowed) or allowed
return this:request('getUpdates', {
timeout = timeout,
offset = offset,
limit = limit,
allowed_updates = allowed,
})
end,
-- Setters
send = function(this, chatId, text, parseMode, disableWeb, notNotify, replyTo, markup)
chatId, markup, parseMode, disableWeb = argp(chatId, markup, parseMode, disableWeb)
return this:request('sendMessage', {
chat_id = chatId,
text = tostring(text),
parse_mode = parseMode,
disable_web_page_preview = disableWeb,
disable_notification = notNotify,
reply_to_message_id = replyTo,
reply_markup = markup,
})
end,
reply = function(this, message, text, parseMode, disableWeb, markup, notNotify)
local _, markup, parseMode, disableWeb = argp(message, markup, parseMode, disableWeb)
return this:request('sendMessage', {
chat_id = message.chat.id,
text = text,
parse_mode = parseMode,
disable_web_page_preview = disableWeb,
disable_notification = notNotify,
reply_to_message_id = message.message_id,
reply_markup = markup,
})
end,
forward = function(this, message, to, notNotify)
to = argp(to)
return this:request('forwardMessage', {
chat_id = to,
from_chat_id = message.chat.id,
disable_notification = notNotify,
message_id = message.message_id,
})
end,
sendSticker = function(this, message, sticker, notNotify, markup, protect, replyTo, sendWreply)
message, markup = argp(message, markup)
return this:request('sendSticker', {
chat_id = message,
sticker = sticker,
disable_notification = notNotify,
protect_content = protect,
reply_to_message_id = replyTo,
allow_sending_without_reply = sendWreply,
reply_markup = markup,
})
end,
sendPhoto = function(this, message, file, caption, parseMode, notNotify, replyTo, markup)
message, markup, parseMode = argp(message, markup, parseMode)
return this:request('sendPhoto', {
chat_id = message,
caption = caption,
parse_mode = parseMode,
disable_notification = notNotify,
reply_to_message_id = replyTo,
reply_markup = markup,
}, {photo = file})
end,
sendDocument = function(this, message, file, caption, parseMode, notNotify, replyTo, markup)
message, markup, parseMode = argp(message, markup, parseMode)
return this:request('sendDocument', {
chat_id = message,
caption = caption,
parse_mode = parseMode,
disable_notification = notNotify,
reply_to_message_id = replyTo,
reply_markup = markup,
}, {document = file})
end,
sendPoll = function(this, message, question, options, anon, type, manyAnswers, correct, explanation, parseMode, period, closes, notNotify, replyTo, markup)
message, markup, parseMode = argp(message, markup, parseMode)
options = type(opt) == 'string' and options or json.decode(options)
anon = type(anon) == 'boolean' and anon or false
manyAnswers = type(manyAnswers) == 'boolean' and manyAnswers or false
return this:request('sendPoll', {
chat_id = message,
question = question,
options = options,
is_anonymous = anon,
type = type,
allows_multiple_answers = manyAnswers,
correct_option_id = correct,
explanation = explanation,
explanation_parse_mode = parseMode,
open_period = period,
close_date = closes,
disable_notification = notNotify,
reply_to_message_id = replyTo,
reply_markup = markup,
})
end,
answerCallback = function(this, id, text, alert, url, caches)
return this:request('answerCallbackQuery', {
callback_query_id = id,
text = text,
show_alert = alert,
url = url,
cache_time = caches,
})
end,
setMyCommands = function(this, commands, language, scope)
return this:request('setMyCommands', {
commands = json.encode(commands),
language_code = tostring(language),
scope = scope,
})
end,
}

View file

@ -1,28 +1,90 @@
--[[ Inline query library
-- (c) Er2 2021 <er2@dismail.de>
-- Inline query library
-- (c) Er2 2022 <er2@dismail.de>
-- Zlib License
--]]
local inline = {}
inline.__index = inline -- Make class
require 'class'
function inline.query(id, from, q, off, ct, loc)
class 'InlineResult' {
function(this, opts)
this.type = opts.type
this.id = tostring(tonumber(opts.id) or 1)
end,
thumb = function(this, url, width, height, mime)
if this.type == 'audio'
or this.type == 'voice'
or this.type == 'game'
then return this end -- cannot do that
this.thumb_url = tostring(url)
if width and height and (
this.type == 'article'
or this.type == 'document'
or this.type == 'contact'
or this.type == 'location'
or this.type == 'venue'
) then
this.thumb_width = tonumber(width)
this.thumb_height = tonumber(height)
end
if mime and (
this.type == 'gif'
or this.type == 'mpeg4_gif'
) then this.thumb_mime_type = mime end
return this
end,
keyboard = function(this, ...)
if not this.type
then return this end
local k = {}
for _, v in pairs {...} do
if type(v) == 'table' then
table.insert(k, v)
end
end
this.reply_markup = k
return this
end,
messageContent = function(this, content)
if this.type == 'game'
or this.type == 'article'
then this.input_message_content = content
end
return this
end,
}
class 'APIInline' {
function(this, api)
this.request = function(self, ...)
return api:request(...)
end
end,
query = function(id, from, query, offset, type, location)
return {
id = tostring(id),
from = from,
query = q,
offset = off,
chat_type = ct,
location = loc,
query = query,
offset = offset,
chat_type = type,
location = location,
}
end
end,
function inline.result(type, id, ...)
result = function(type, id, ...)
type = tostring(type)
local t = setmetatable({
local t = new 'InlineResult' {
type = type,
id = tostring(tonumber(id) or 1),
}, inline)
id = id,
}
local a = {...}
if t.type == 'article' then t.title, t.url, t.hide_url, t.description = table.unpack(a)
@ -82,75 +144,20 @@ function inline.result(type, id, ...)
end
return t
end
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', {
answer = function(this, id, results, caches, personal, nextOffset, pmText, pmParam)
if results.id
then results = {results} end -- single
return this: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,
results = results,
cache_time = caches,
is_personal = personal,
next_offset = nextOffset,
switch_pm_text = pmText,
switch_pm_param = pmParam,
})
end
end,
}
return function(api)
local self = setmetatable({
request = function(_, ...) api:request(...) end
}, inline)
return self
end

View file

@ -1,6 +1,6 @@
Zlib License
Copyright (c) 2021 Er2 <er2@dismail.de>
Copyright (c) 2022 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

View file

@ -1,17 +1,18 @@
* Telegram API
This is bindings of Telegram API on Lua, part of [[https://gitdab.com/er2/comp-tg][my bot]].
This is bindings of Telegram API written in Lua, part of [[https://gitdab.com/er2/comp-tg][my bot]].
* Installation
We recommended to use *.gitmodules* file for that.
Yourpath is any directory inside your project.
Recommended to use *.gitmodules* file for that.
To include, add to gitmodules this:
#+begin_src
[submodule "tg-api"]
path = # enter your path
path = yourpath/api
url = https://gitdab.com/er2/tg-api-lua
#+end_src
Also you need to:
@ -23,7 +24,7 @@ Also you need to:
package.path = 'yourpath/?.lua;yourpath/?/init.lua;' .. package.path
#+end_src
+ Copy to yourpath +/api+ files *event.lua* and *json.lua*
+ Copy to yourpath files *class.lua*, *event.lua*, *json.lua*
They can be taken from [[https://gitdab.com/er2/comp-tg/src/branch/main/etc][here]].

View file

@ -23,7 +23,7 @@ function tools.parseArgs(text)
local iq, buf, args = false, '', {}
local qt, esc = nil, false -- quote type and escape, addition
-- helper functin, add args and clear buffer
-- helper function, add args and clear buffer
local function psh()
if #buf > 0 then table.insert(args, buf) end
buf, qt = '', nil
@ -146,7 +146,7 @@ function tools.request(token, endpoint, param, f)
local url = 'https://api.telegram.org/bot' ..token.. '/' ..endpoint
dbg = true
--dbg = true
local resp = tools.req(url, param, f, dbg)
return resp, resp.ok or false
end