Rewritten back to Lua.

Using more beautiful classes.

Add Arabic (lol idk why).
This commit is contained in:
Er2 2022-08-10 12:51:49 +03:00
parent 5a54bc1032
commit ed6598b145
41 changed files with 762 additions and 505 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
.env
.gitignore
.git

3
.gitignore vendored
View File

@ -2,4 +2,5 @@
*~ *~
\#*# \#*#
src/*/*.lua .env

21
Dockerfile Normal file
View File

@ -0,0 +1,21 @@
FROM alpine AS build
RUN apk add luarocks5.1 libressl-dev gcc musl-dev lua5.1-dev \
&& luarocks-5.1 install luasec \
&& mkdir -p /dist/usr/local \
&& mkdir -p /dist/usr/local/lib/lua && cp -rpv /usr/local/lib/lua/* /dist/usr/local/lib/lua \
&& mkdir -p /dist/usr/local/share/lua && cp -rpv /usr/local/share/lua/* /dist/usr/local/share/lua
COPY run.sh /dist
COPY . /dist/app
FROM alpine
VOLUME ["/app"]
RUN apk add libressl luajit \
&& ln -sf $(which luajit) /usr/bin/lua \
&& adduser -D -h /app user
COPY --from=build /dist /
ENTRYPOINT ["/run.sh"]

13
bot.rc
View File

@ -1,13 +0,0 @@
#!/sbin/openrc-run
# My bot service
# CHANGE VALUES TO MATCH YOURS
name="mybot"
description="My bot"
command="/home/er2/comp-tg/start.sh"
command_args=""
command_user="er2:er2"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid"

View File

@ -1,12 +1,13 @@
return { return {
token = 'atokenyanedam', token = os.getenv 'TOKEN',
owner = 935626920 , -- hehe owner = tonumber(os.getenv 'OWNER'),
cmds = { cmds = {
'eval', 'eval',
'reload', 'reload',
'ping', 'ping',
'rub', 'rub',
'start', 'start',
'shell',
}, },
events = { events = {
'command', 'command',

@ -1 +1 @@
Subproject commit da0a266b72f0919c5ed1bfa3fbc75e7d2f398229 Subproject commit faab5a7e6d0dc684d04cf5121b9735b786716aca

116
etc/class.lua Normal file
View File

@ -0,0 +1,116 @@
-- Class library
-- (c) Er2 2022 <er2@dismail.de>
-- Zlib License
-- (Not) Virtual classes table
local vtable = {}
-- Metamethods
local cls = {}
cls.__index = cls
-- :__tostring() - Calls when using Lua tostring() or error occurs.
function cls:__tostring()
local str = 'Class <'.. self.__name ..'>'
while self.__super do
str = str.. ' inherits '.. self.__super.__name
self = self.__super
end
return str
end
-- :__call(table) - Calls when defining class body
function cls:__call(t)
for k, v in pairs(t) do
self[k] = v
end
if self.__init
then self:__init()
end
end
-- :new(...) - Creates new instance of class. Please use new(...) function instead.
-- Can throw error if no constructor defined in class or subclasses.
function cls:new(...)
local cl = self
local cons
repeat cons = rawget(cl, 1) or rawget(cl, 'new')
cl = cl.__super
until cons or not cl
assert(cons, 'No constructor found in class')
--self.__index = self.__index or cls.__index
self = setmetatable({}, self)
cons(self, ...)
return self
end
-- :super(...) - Call parent constructor.
function cls:super(...)
return cls.superM(self, 1, ...)
or cls.superM(self, 'new', ...)
end
-- :superM(method, ...) - Call method from parent class.
-- Can throw error if class is not inherited.
function cls:superM(meth, ...)
local cl = self.__super
local fn
while not fn and cl do
fn = cl[meth]
if cl.__sup then fn = nil end
cl = cl.__super
end
if not fn then return nil end
if cl then cl.__sup = true end
local v = {fn(self, ...)}
if cl then cl.__sup = nil end
return table.unpack(v)
end
-- :clone() - Make clone of class.
function cls:clone()
local cl = {}
for k, v in pairs(cls)
do cl[k] = v end
for k, v in pairs(self)
do cl[k] = v end
cl.__index = cl
cl.__super = self
return setmetatable(cl, self)
end
-- :inherits(className) - Make this class inherits from another.
-- Can throw error if class is already inherited.
function cls:inherits(name)
-- assert(self.__super, 'no')
local cl = vtable[name]
assert(cl, 'Class is not exists')
return setmetatable(self, cl:clone())
end
-- :extends(className) - Same as :inherits(className).
cls.extends = cls.inherits
-- class(className) - Make new class.
-- Can throw error if class already exists with this name.
function class(name)
assert(not vtable[name], 'Cannot override defined class')
local cl = setmetatable({__name = name, __tostring = cls.__tostring}, cls)
cl.__index = cl
vtable[name] = cl
return cl
end
-- new(className) - Make new instance of class. Use this instead of :new(class).
-- Can throw error if class by name was not found.
function new(name)
local cl = vtable[name]
assert(cl, 'No class found')
return function(...)
return cls.new(cl, ...)
end
end

View File

@ -1,51 +1,74 @@
--[[ Events library -- Events library
-- (c) Er2 2021 <er2@dismail.de> -- (c) Er2 2022 <er2@dismail.de>
-- Zlib License -- Zlib License
--]]
local events = {} require 'class'
events.__index = events
function events:_add(t, n, f) class 'Events' {
table.insert(self._ev_, { function(this)
type = t, this._ev_ = {}
name = n, end,
fn = f,
})
end
function events:on(n,f) self:_add('on', n,f) end _add = function(this, type, name, func)
function events:once(n,f) self:_add('once', n,f) end table.insert(this._ev_, {
type = tostring(type),
name = tostring(name),
func = func,
})
end,
function events:off(f) on = function(this, name, func)
for k, v in pairs(self._ev_) do this:_add('on', name, func)
if v.fn == f then end,
table.remove(self._ev_, k)
once = function(this, name, func)
this:_add('once', name, func)
end,
off = function(this, func)
for k, v in pairs(this._ev_) do
if v.func == func
then table.remove(this._ev_, k)
end
end end
end end,
end
function events:_ev(t, i, name, ...) _ev = function(this, t, i, name, ...)
local v = t[i] local v = t[i]
if v.name == name then if v.name == name then
v.fn(...) v.func(...)
if v.type == 'once' then table.remove(t, i) end if v.type == 'once'
end then table.remove(t, i)
end end
end
end,
function events:emit(name, ...) emit = function(this, name, ...)
local t = self._ev_ local t = this._ev_
for i = 1, #t do for i = 1, #t do
local v = t[i] or {} local v = t[i]
if type(v) == 'table' if type(v) == 'table'
and type(v.name) == 'string' and type(v.name) == 'string'
and type(v.type) == 'string' and type(v.type) == 'string'
and type(v.fn) == 'function' and type(v.func) == 'function'
then self:_ev(t, i, name, ...) end then this:_ev(t, i, name, ...)
end else print 'Invalid event'
end if v then print(v, v.name, v.type, v.func)
else print 'nil' end
end
end
end,
}
class 'EventsThis' : inherits 'Events' {
_ev = function(this, t, i, name, ...)
local v = t[i]
if v.name == name then
v.func(this, ...)
if v.type == 'once'
then table.remove(t, i)
end
end
end,
}
return function(t)
t._ev_ = {}
return setmetatable(t, events)
end

3
example.env Normal file
View File

@ -0,0 +1,3 @@
TOKEN= # insert bot token here
OWNER= # insert owner ID here

View File

@ -1,6 +1,6 @@
Zlib License 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 This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,6 @@
* Computer bot * Computer bot
Original is *[[https://gitdab.com/er2/comp-tg][here]]* and on my private server. Original is *[[https://gitdab.com/er2/comp-tg][here]]* and [[https://git.er2.tech/er2/comp-tg][here]].
Mirrors can update irregularly. Mirrors can update irregularly.
----- -----
@ -15,36 +15,19 @@ and use it or fallback to English.
Bot uses an OOP-style of Lua Bot uses an OOP-style of Lua
as [[https://is.gd/f0Vadk][described on Wikipedia]]. as [[https://is.gd/f0Vadk][described on Wikipedia]].
For more readability bot's userland written in MoonScript.
Maybe I will rewrite bot's core to C but here already so many Lua code. Maybe I will rewrite bot's core to C but here already so many Lua code.
* Installation * Installation
[[https://alpinelinux.org][Alpine linux]], root: Officially supported only [[https://docker.com][Docker]], but you can use it without Docker.
+ Enable community repo (described in wiki) Docker installation:
+ Install lua and tools: ~apk add doas git lua5.3-dev luarocks musl-dev gcc openssl-dev~ + Clone repository with submodules: ~git clone --recursive https://gitdab.com/er2/comp-tg~
+ Install LuaSec for https requests: ~luarocks-5.3 install luasec~ and enter ~cd comp-tg~
+ Install MoonScript: ~luarocks-5.1 install moonscript~ + Build image: ~docker build -t comp-tg .~
+ Create user: ~adduser user~ + Start it: ~docker run -d -e .env --name comp-tg --restart always comp-tg~
setup it (add to doas) and login to this user
+ Clone repo: ~git clone --recursive https://gitdab.com/er2/comp-tg~
and enter ~cd comp-tg~
+ Change token and owner in *config.lua*
+ Compile bot: ~moonc src/~
+ Add service ~doas cp bot.rc /etc/init.d/mybot && doas chmod +x /etc/init.d/mybot~
+ Configure it ~doas vi /etc/init.d/mybot~ (change user)
+ Start it ~doas service mybot start~

7
run.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/sh
chown user:user -R /app 2> /dev/null
cd /app
su user -c "lua init.lua"

60
src/cmds/eval.lua Normal file
View File

@ -0,0 +1,60 @@
local function prind(...)
local s = ''
for _, v in pairs {...} do
if #s > 0
then s = s .. '\t'
end
s = s .. tostring(v) 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 {
private = true,
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(C.api.unparseArgs(msg.args), 'eval', 'bt', t)
xpcall((function()
if err
then error(err)
end
e = tostring(e() or '...')
end), function(err)
e = err
end)
s = s ..'\n'.. e
s = s:gsub(C.api.token:escp(), '<TOKEN>')
C.api:reply(msg, s)
end
}

View File

@ -1,54 +0,0 @@
prind = (...) ->
t = {...}
s = ''
for i = 1, #t
if i > 1
s ..= '\t'
s ..= tostring t[i] or 'nil'
s .. '\n'
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
{
private: true
run: (msg, owner) =>
s = ''
t =
msg: msg
print: (...) -> s ..= prind ...
C: owner and @ or nil
api: owner and @api or nil
for k, v in pairs env
t[k] = v
e, err = load @api.unparseArgs(msg.args), 'eval', 'bt', t
xpcall (->
error err if err
e = tostring e! or '...'
), (err) -> e = err
s ..= '\n'.. e
s = s\gsub @api.token\escp!, '<TOKEN>'
@api\reply msg, s
return
}

11
src/cmds/ping.lua Normal file
View File

@ -0,0 +1,11 @@
return {
run = function(C, msg)
local t = os.time()
local ps, ls = t - msg.date, t - C.loaded
local lm = ls / 60
local lh = lm / 60
local ld = lh / 24
C.api:send(msg, msg.loc.pat:format(ps, ld, lh, lm, ls))
end
}

View File

@ -1,10 +0,0 @@
{
run: (msg) =>
t = os.time!
ps, ls = t - msg.date, t - @loaded
lm = ls / 60
lh = lm / 60
ld = lh / 24
@api\send msg, msg.loc.pat\format ps, ld, lh, lm, ls
return
}

32
src/cmds/reload.lua Normal file
View File

@ -0,0 +1,32 @@
return {
private = true,
run = function(C, msg)
local cat, sub, arg = table.unpack(msg.args)
if not (cat and sub)
then return C.api:reply(msg, '/reload cmds ping')
end
local path = 'src.'.. cat ..'.'.. sub
C.api:off(package.loaded[path])
package.loaded[path] = nil
if arg == '-off'
then C.api:reply(msg, 'Turned off')
else
local suc, m = pcall(require, path)
if not suc
then return C.api:reply(msg, 'Reload failed. '.. m)
end
if cat == 'events'
then C.api:on(sub, m)
elseif cat == 'cmds'
then C.cmds[sub] = m
else m(C)
end
C.api:reply(msg, 'Reloaded. '.. tostring(m))
end
end
}

View File

@ -1,26 +0,0 @@
{
private: true
run: (msg) =>
cat, sub, arg = table.unpack msg.args
if not (cat and sub)
return @api\reply msg, '/reload cmds ping'
path = "src.#{cat}.#{sub}"
@api\off package.loaded[path]
package.loaded[path] = nil
if arg == '-off'
@api\reply msg, 'Turned off'
else
suc, m = pcall require, path
if not suc then return @api\reply msg, "Reload failed. #{m}"
switch cat
when 'events' then @api\on sub, m
when 'cmds' then @cmds[sub] = m
else m @
@api\reply msg, "Reloaded. #{m}"
return
}

91
src/cmds/rub.lua Normal file
View File

@ -0,0 +1,91 @@
class 'Rub' {
url = 'https://api.factmaven.com/xml-to-json/?xml=https://www.cbr.ru/scripts/XML_daily.asp',
pat = '%d %s (%s) - %f ₽',
function(this)
this.tools = require 'api.tools'
end,
getPat = function(this, val)
return this.pat:format(val.Nominal, val.Name, val.CharCode, val.Value:gsub(',', '.'))
end,
course = function(this, wants)
local res, ok = this.tools._req(this.url, 'GET')
if not ok
then return 'err'
end
res = this.tools.json.decode(res or '{}')
res = res.ValCurs
if not res
then return 'err'
end
-- Pseudo-valutes
table.insert(res.Valute, {
ID = 'R01000',
NumCode = '001',
CharCode = 'RUB',
Nominal = 1,
Name = 'Российский рубль',
Value = '1',
})
local uah = table.findV(res.Valute, {CharCode = 'UAH'})
assert(uah, 'No UAH found')
table.insert(res.Valute, {
ID = 'R02000',
NumCode = '200',
CharCode = 'SHT',
Nominal = 1,
Name = 'Штаны',
Value = ('%f'):format(tonumber(uah.Value:gsub(',', '.'), nil) / uah.Nominal * 40), -- 40 UAH
})
local r, founds = {}, {}
if table.find(wants, 'ALL') then
for _, v in pairs(res.Valute)
do table.insert(r, this:getPat(v))
end
return r, res.Date, wants -- string, date, found
end
for _, v in pairs(res.Valute) do
if table.find(wants, v.CharCode) then
table.insert(founds, v.CharCode)
table.insert(r, this:getPat(v))
end
end
return r, res.Date, founds --
end,
}
local rub = new 'Rub' ()
return {
run = function(self, 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)
if v == 'err' then
return self.api:reply(msg, self.locale:get('error', 'req_err', msg.l))
end
local nf = { }
for _, i in pairs(wants) do
if not table.find(f, i) then
table.insert(nf, i)
end
end
local s = msg.loc.cur:format(d, table.concat(v, '\n'))
if #nf > 0 then
s = s .. msg.loc.notf .. table.concat(nf, ',')
end
self.api:reply(msg, s .. msg.loc.prov)
end
}

View File

@ -1,75 +0,0 @@
-- It uses data from central bank of Russia
--- and external service to get json from xml
-- Privacy and security is unknown
rub =
url: 'https://api.factmaven.com/xml-to-json/?xml=https://www.cbr.ru/scripts/XML_daily.asp'
tools: require 'etc.api.tools'
pat: '%d %s (%s) - %f ₽'
getPat: (val) =>
@pat\format val.Nominal, val.Name, val.CharCode, val.Value\gsub ',', '.'
course: (wants) =>
res, suc = @tools._req @url, 'GET'
return 'err' if not suc
res = @tools.json.decode res or '{}'
res = res.ValCurs
return 'err' if not res
table.insert res.Valute, {
ID: 'R01000'
NumCode: '001'
CharCode: 'RUB'
Nominal: 1
Name: 'Российский рубль',
Value: '1'
}
uah = table.findV res.Valute, CharCode: 'UAH'
table.insert res.Valute, {
ID: 'R02000'
NumCode: '200'
CharCode: 'SHT'
Nominal: 1
Name: 'Штаны'
Value: ('%f')\format tonumber(uah.Value\gsub(',', '.'), nil) / uah.Nominal * 40
}
wants = type(wants) == 'table' and wants or {}
r, founds = {}, {}
if table.find wants, 'ALL'
for _, v in pairs res.Valute
table.insert r, @getPat v
return r, res.Date, wants
for _, v in pairs res.Valute
if table.find wants, v.CharCode
table.insert founds, v.CharCode
table.insert r, @getPat v
return r, res.Date, founds
{
run: (msg) =>
wants = {'USD', 'EUR', table.unpack msg.args}
for i = 1, #wants
wants[i] = wants[i]\upper!
v, d, f = rub\course wants
if v == 'err'
return @api\reply msg, @locale\get 'error', 'req_err', msg.l
nf = {}
for _, i in pairs wants
table.insert nf, i if not table.find f, i
s = msg.loc.cur\format d, table.concat v, '\n'
if #nf > 0
s = s .. msg.loc.notf.. table.concat nf, ','
@api\reply msg, s.. msg.loc.prov
return
}

16
src/cmds/shell.lua Normal file
View File

@ -0,0 +1,16 @@
return {
private = true,
run = function(C, msg)
local file = io.popen(C.api.unparseArgs(msg.args))
if file then
local r = file:read '*a'
file:close()
if #r == 0
then r = '...'
end
C.api:reply(msg, r)
else return C.api:reply(msg, 'error')
end
end
}

7
src/cmds/start.lua Normal file
View File

@ -0,0 +1,7 @@
return {
hide = true,
run = function(C, msg)
C.api:reply(msg, msg.loc.msg)
end,
}

View File

@ -1,6 +0,0 @@
{
hide: true
run: (msg) =>
@api\reply msg, msg.loc.msg
return
}

29
src/events/command.lua Normal file
View File

@ -0,0 +1,29 @@
return function(C, api, msg)
local l = msg.from.language_code
local owner = msg.from.id == C.config.owner
local cmd = C.cmds[msg.cmd]
msg.l = l
if not cmd
then api:send(msg, C.locale:get('error', 'inv_cmd', l))
elseif type(cmd.run) ~= 'function'
then api:send(msg, C.locale:get('error', 'cmd_run', l))
elseif cmd.private and not owner
then api:send(msg, C.locale:get('error', 'adm_cmd', l))
else
if cmd.useQArgs
then msg.args = api.parseArgs(api.unparseArgs(msg.args))
end
msg.loc = C.locale:get('cmds', msg.cmd, l)
local suc, err = pcall(cmd.run, C, msg, owner)
if not suc then
print(err)
api:forward(C.config.owner, msg, msg.message_id, false)
api:send(C.config.owner, err)
api:reply(msg, C.locale:get('error', 'not_suc', l))
end
end
end

View File

@ -1,28 +0,0 @@
(api, msg) =>
l = msg.from.language_code
owner = msg.from.id == @config.owner
cmd = @cmds[msg.cmd]
msg.l = l
if not cmd
api\send msg, @locale\get 'error', 'inv_cmd', l
elseif type(cmd.run) ~= 'function'
api\send msg, @locale\get 'error', 'cmd_run', l
elseif cmd.private and not owner
api\send msg, @locale\get 'error', 'adm_cmd', l
else
msg.args = api.parseArgs api.unparseArgs msg.args if cmd.useQArgs
msg.loc = @locale\get 'cmds', msg.cmd, l
suc, err = pcall cmd.run, @, msg, owner
if not suc
-- whoops
print err
api\forward @config.owner, msg.chat.id, msg.message_id, false
api\send @config.owner, err
api\reply msg, @locale\get 'error', 'not_suc', l
return

View File

@ -0,0 +1,10 @@
return function(C, api, q)
api.inline:answer(q.id, {
api.inline.result('photo', '1',
'https://cdn.discordapp.com/attachments/871474214270554124/876506659311202395/unknown.png'
):thumb 'https://cdn.discordapp.com/attachments/871474214270554124/876506659311202395/unknown.png',
api.inline.result('photo', '2',
'https://cdn.discordapp.com/attachments/710425649449402408/1002915343116795914/unknown.png'
):thumb 'https://cdn.discordapp.com/attachments/710425649449402408/1002915343116795914/unknown.png',
})
end

View File

@ -1,6 +0,0 @@
(api, q) =>
api.inline\answer q.id, api.inline.result(
'photo', '1',
'https://cdn.discordapp.com/attachments/871474214270554124/876506659311202395/unknown.png'
)\thumb 'https://cdn.discordapp.com/attachments/871474214270554124/876506659311202395/unknown.png'
return

22
src/events/message.lua Normal file
View File

@ -0,0 +1,22 @@
local stick = {
{
'AgADwAADcpO1DQ',
'редебало',
'CAACAgIAAx0CUY2umQACFItiHHUg6w_MPu6Vs8k76cwn4OIHNQACwAADcpO1DVbNTDlmHOWMIwQ'
}
}
return function(C, api, msg)
if msg.from.is_premium then
api:reply(msg, 'Премак юзер, надо бы забанить. TODO БЛЯТЬ')
end
if msg.sticker then
for k, v in pairs(stick) do
if msg.sticker.file_unique_id == v[1] then
if math.random() <= 0.5
then api:reply(msg, v[2])
else api:sendSticker(msg, v[3])
end
end
end
end
end

View File

@ -1,42 +0,0 @@
reg = {
{'эх+%.*', 'хуех'} -- надоели эхать
{'мета', 'хуета'}
{'meta', 'xueta'}
{'цукерберг', 'цукерхуй'}
{'zuckerberg', 'zuckerhui'}
{'wh?atsapp?', 'вадзад'}
{'в[ао][тсц]+апп?', 'вадзад'}
{'tiktok', 'деградация'}
{'ч[ую]ма', 'капитализм'}
{'минет', 'еблет'}
{'еблет', 'пакет'}
{'да', 'пизда'}
{'нет', 'минет'}
{'че%?*', 'пиши ё, грамотей'}
{'чё%?*', 'ничё'}
}
stick = {
{
'AgADwAADcpO1DQ'
'редебало'
'CAACAgIAAx0CUY2umQACFItiHHUg6w_MPu6Vs8k76cwn4OIHNQACwAADcpO1DVbNTDlmHOWMIwQ'
}
}
(api, msg) =>
if msg.text
msg.text = utf8.lower ' '.. msg.text ..' '
t = msg.text
for _, v in pairs reg
t = utf8.gsub t, '%s+'.. v[1] ..'%s+', ' '.. v[2] ..' '
api\reply msg, t if t ~= msg.text
elseif msg.sticker
for k, v in pairs stick
if msg.sticker.file_unique_id == v[1]
if math.random! <= 0.5
api\reply msg, v[2]
else api\sendSticker msg, v[3] --, _, _, _, msg.message_id
return

78
src/events/ready.lua Normal file
View File

@ -0,0 +1,78 @@
-- Standard Library in bot
-- table.indexOf(table, value) - Get value of inverter value-key table.
function table.indexOf(t, val)
local i = {}
for k, v in pairs(t) do
i[v] = k
end
return i[val]
end
-- table.find(table, value) - Return index if value matches.
function table.find(t, val)
for k, v in pairs(t) do
if v == val then
return k
end
end
end
-- table.findV(table, value) - return value in table matching value table
function table.findV(t, val)
local b = nil
for _, v in pairs(t) do
for k, x in pairs(val) do
if x ~= v[k] then
b = 1
break
end
end
if b then
b = nil
else
return v
end
end
end
-- string.escp(string) - Escape reserved regex values
function string.escp(s)
return s:gsub('[%^%$%%%(%)%.%[%]%*%+%-%?]', '%%%0')
end
-- dump(table, depth?) - Return string that shows table contents.
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)
elseif type(v) == 'userdata' then
v = '<USERDATA>'
end
c = c .. ('%s%s = %s\n'):format((' '):rep(d), k, v)
end
return c
end
return function(C, api)
C:load 'cmds'
for _, lang in pairs(C.locale.list) do
local a = {}
for k, v in pairs(C.cmds) do
if not (v.private or v.hide) then
local cmd = C.locale:get('cmds', k, lang or {})
table.insert(a, {
command = k,
description = (cmd.args and cmd.args .. ' - ' or '') .. (cmd.desc or C.locale:get('cmds', 'not_des'))
})
end
end
api:setMyCommands(a, lang)
end
end

View File

@ -1,50 +0,0 @@
table.indexOf = (t, w) ->
i = {}
for k, v in pairs t
i[v] = k
i[w]
table.find = (t, w) ->
for _, v in pairs t
return v if v == w
table.findV = (t, w) ->
b = nil
for _, v in pairs t
for k, x in pairs w
if x ~= v[k]
b = 1
break
if b then b = nil -- continue
else return v
string.escp = (s) ->
s\gsub '[%^%$%%%(%)%.%[%]%*%+%-%?]', '%%%0'
export dump = (t, d) ->
d = 0 if not tonumber(d) or d < 0
c = ''
for k, v in pairs t
if type(v) == 'table'
v = '\n'.. dump v, d + 1
elseif type(v) == 'userdata'
v = '<USERDATA>'
c ..= ('%s%s = %s\n')\format (' ')\rep(d), k, v
c
(api) =>
@\load 'cmds'
for _, lang in pairs @locale.list
a = {}
for k, v in pairs @cmds
if not (v.private or v.hide)
cmd = @locale\get 'cmds', k, lang or {}
table.insert a, {command: k,
description: (cmd.args and cmd.args .. ' - ' or '') .. (cmd.desc or @locale\get 'cmds', 'not_des')
}
api\setMyCommands a, lang
return

40
src/locales/ar.json Normal file
View File

@ -0,0 +1,40 @@
{
"error": {
"inv_cmd": "تم تحديد أمر غير معروف.",
"adm_cmd": "لا يمكنك تنفيذ أوامر المسؤول!",
"cmd_run": "لا يمكن تنفيذ هذا الأمر الآن.",
"not_suc": "حدث خطأ وتم إرساله إلى المنشئ.",
"unk_err": "خطأ غير معروف.",
"req_err": "فشل الطلب."
},
"cmds": {
"not_des": "بدون وصف",
"eval": {
"args": "<الرمز>",
"desc": "ينفذ الكود"
},
"shell": {
"args": "<الرمز>",
"desc": "ينفذ التعليمات البرمجية في غلاف يونكس"
},
"ping": {
"desc": "بينج بونج",
"pat": "بونغ! %d ثانية. الجهوزية: %.1f يوم (%.1f ساعة ، %.1f دقيقة)"
},
"rub": {
"args": "[عملة]...",
"desc": "سعر صرف الروبل",
"cur": "متوجها %s:\n%s",
"notf": "\n لا يوجد لا تجد: ",
"prov": "\n البيانات مقدمة من البنك المركزي لروسيا."
},
"start": {
"desc": "بداية العمل",
"msg": "يا! أنا Computer - بوت مفتوح المصدر.\n إذا كنت تبحث عن مصادر: https://gitdab.com/er2/comp-tg"
}
}
}

View File

@ -14,6 +14,10 @@
"args": "<code>", "args": "<code>",
"desc": "executes code" "desc": "executes code"
}, },
"shell": {
"args": "<code>",
"desc": "executes code in Unix shell"
},
"ping": { "ping": {
"desc": "ping pong", "desc": "ping pong",

View File

@ -14,6 +14,10 @@
"args": "<код>", "args": "<код>",
"desc": "исполняет код" "desc": "исполняет код"
}, },
"shell": {
"args": "<код>",
"desc": "исполняет код в Unix shell"
},
"ping": { "ping": {
"desc": "пинг-понг", "desc": "пинг-понг",

29
src/parts/client.lua Normal file
View File

@ -0,0 +1,29 @@
require 'etc.api'
return function(C)
C.api = new 'API' {
norun = true,
}
print 'Client initialization...'
C.api._ev = function(_, t, i, name, ...)
return C:_ev(t, i, name, C.api, ...)
end
C:load 'events'
C.api:login(C.config.token, function()
print('Logged on as @'.. C.api.info.username)
C.config.token = nil
C.api:emit 'ready'
end)
local offset = 0
C.api.runs = true
C:on('ready', function()
while C.api.runs do
C:emit 'tick'
offset = C.api:recvUpdate(1, offset, 0)
end
end)
end

View File

@ -1,32 +0,0 @@
tg = require 'etc.api'
=>
@api = tg { norun: true }
@cmds = {}
print 'Client initialization...'
@_ev = (ev, ...) -> @\ev ...
@api._ev = (_, t, i, n, ...) ->
@._ev _, t, i, n, @api, ...
@\load 'events'
@api\login @config.token, ->
print "Logged on as @#{@api.info.username}"
@config.token = nil
@api\emit 'ready'
return
offs, o = 0
@api.runs = true
@\on 'ready', ->
while @api.runs
@\emit 'tick'
o = @api\_getUpd 1, offs, 0
offs = o and o or offs
@api\getUpdates 1, offs, 0
return
return

62
src/parts/core.lua Normal file
View File

@ -0,0 +1,62 @@
local config = require 'config'
require 'etc.events'
require 'class'
class 'Core' : inherits 'EventsThis' {
config = config,
loaded = 0,
cmds = {},
function(this)
this:super()
this:load 'parts'
utf8 = require 'etc.utf8'
require 'etc.utf8data'
utf8.config = {
conversion = {
uc_lc = utf8_uc_lc,
lc_uc = utf8_lc_uc
}
}
utf8:init()
print 'Done!'
this:emit 'ready'
end,
stop = function(this)
this.api:destroy()
print 'Stopped'
print('Uptime: ' .. (os.time() - self.loaded) .. ' seconds')
end,
load = function(this, what)
local c = config[what]
for i = 1, #c do
local v = c[i]
print('Loading ' .. what:sub(0, -2) ..' (' .. i ..' / '.. #c ..') ' .. v ..'...')
-- Lint?
local e, a = pcall(require, 'src.' .. what ..'.'.. v)
print(e, a)
if e then
if what == 'events'
then this.api:on(v, a)
elseif what == 'cmds'
then this.cmds[v] = a
elseif what == 'parts'
then a(this)
end
else print 'fail'
end
end
print('Loaded '.. #c ..' '.. what)
this.loaded = os.time()
end,
}
local core = new 'Core' ()

View File

@ -1,58 +0,0 @@
config = require 'config'
Core =
config: config
loaded: 0
load: (what) =>
c = config[what]
for i = 1, #c
v = c[i]
print "Loading #{what\sub 0, -2} (#{i} / #{#c}) #{v}..."
-- Lint
e, a = pcall require, "src.#{what}.#{v}"
print e, a
if e
switch what
when 'events' then @api\on v, a
when 'cmds' then @cmds[v] = a
when 'parts' then a @
else print 'fail'
print "Loaded #{#c} #{what}"
@loaded = os.time!
ev: (t, i, name, ...) =>
v = t[i]
if v.name == name
suc, err = pcall v.fn, @, ...
if not suc
print "event \"#{name}\" was failed"
print err
table.remove t, i if v.type == 'once'
init: =>
@\load 'parts'
export utf8 = require 'etc.utf8'
require 'etc.utf8data'
utf8.config =
conversion:
uc_lc: utf8_uc_lc,
lc_uc: utf8_lc_uc
utf8\init!
print 'Done!'
@\emit 'ready'
return
stop: =>
@api\destroy!
print 'Stopped'
print "Uptime: #{os.time! - @loaded} seconds"
return
require('etc.events')(Core) -- add events
Core\init!
return

36
src/parts/locale.lua Normal file
View File

@ -0,0 +1,36 @@
class 'Locale' {
__newindex = function() end,
list = {
'en',
'ru',
'ar',
},
main = 'en',
__init = function(this)
local json = require 'etc.json'
for i = 1, #this.list do
local n = this.list[i]
local f = io.open('src/locales/'.. n ..'.json')
this[n] = json.decode(f:read 'a')
end
end,
function(this, C)
C.locale = this
end,
get = function(this, category, key, lang)
assert(category, 'Category not provided')
assert(key, 'Key not provided')
lang = lang or this.main
local v = (this[lang] or {})[category]
if not v
then return this[this.main][category][key] or ''
else return v[key] or ''
end
end,
}
return new 'Locale'

View File

@ -1,30 +0,0 @@
Locale =
__newindex: -> -- ro
list: {
'en'
'ru'
}
main: 'en'
get: (cat, k, lang) =>
assert cat, 'Give category'
assert k, 'Give key'
lang or= @main
v = (@[lang] or {})[cat]
if not v
@[@main][cat][k] or {}
else v[k] or {}
Locale.__index = Locale
(C) ->
json = require 'etc.json'
for i = 1, #Locale.list
n = Locale.list[i]
f = io.open "src/locales/#{n}.json"
Locale[n] = json.decode f\read 'a'
C.locale = setmetatable {}, Locale

View File

@ -1,3 +0,0 @@
#!/bin/sh
cd $(dirname $0)
luajit init.lua || lua5.3 init.lua