Compare commits

...

20 Commits
v06 ... main

Author SHA1 Message Date
Er2 ed6598b145 Update
Rewritten back to Lua.

Using more beautiful classes.

Add Arabic (lol idk why).
2022-08-10 12:51:49 +03:00
Er2 5a54bc1032 add -off in reload 2022-03-01 11:15:33 +03:00
Er2 0b2a7ac8e5 update lib, add some random 2022-02-28 10:17:49 +03:00
Er2 daa848cecd bugfixes, redebalo 2022-02-27 23:29:37 +03:00
Er2 6c0f8b437d more reply reactions and match whole words, replace Unicode library 2022-02-27 13:22:07 +03:00
Er2 14ba3d7a00 update readme, fix .gitignore 2022-02-26 10:08:26 +03:00
Er2 455869fa0c moonscript rewrite 2022-02-25 23:25:21 +03:00
Er2 7e8b53a207 remade message event, add utf8 2022-02-25 19:58:53 +03:00
Er2 17f1727be1 ??? 2022-02-25 12:54:26 +03:00
Er2 b0f616fd32 add хуех on эх, start command 2022-02-25 10:31:32 +03:00
Er2 7e26dfa38d add all valutes in rub 2022-02-16 10:49:40 +03:00
Er2 6add3f5e6b update eval, pattern escape function 2022-02-16 10:35:11 +03:00
Er2 8f538ba65a remove db and strange unicode at start 2022-02-13 18:58:17 +03:00
Er2 3bbccb94ab optional quoted args, update lib 2022-02-13 15:23:16 +03:00
Er2 65b2f6eebc fix reload, new valute 2022-02-13 15:19:47 +03:00
Er2 70da2d717e upgrade lib 2022-02-12 21:48:19 +03:00
Er2 2b72d1c98d update lib, remove depend() in rc file 2022-02-02 18:52:28 +03:00
Er2 dffe9cc9b2 restored localized commands 2022-01-23 21:44:39 +03:00
Er2 f50fe9e35b update tg lib 2022-01-23 18:23:54 +03:00
Er2 c7c2ccc664 luajit support 2022-01-23 18:18:53 +03:00
34 changed files with 2515 additions and 331 deletions

4
.dockerignore Normal file
View File

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

2
.gitignore vendored
View File

@ -2,3 +2,5 @@
*~
\#*#
.env

3
.gitmodules vendored
View File

@ -2,3 +2,6 @@
path = etc/api
url = https://gitdab.com/er2/tg-api-lua
[submodule "etc/utf8"]
path = etc/utf8
url = https://github.com/Stepets/utf8.lua

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"]

15
bot.rc
View File

@ -1,15 +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
depend() {}
pidfile="/run/${RC_SVCNAME}.pid"

View File

@ -1,17 +1,19 @@
return {
token = 'atokenyanedam',
owner = 935626920 , -- hehe
token = os.getenv 'TOKEN',
owner = tonumber(os.getenv 'OWNER'),
cmds = {
'eval',
'reload',
'ping',
'rub',
'start',
'shell',
},
events = {
'command',
'ready',
'inlineQuery',
'message',
},
parts = {
'locale',

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

1
etc/utf8 Submodule

@ -0,0 +1 @@
Subproject commit 17f4e009a22fb2f2e6ad316a05b2cca8e071fc3b

1860
etc/utf8data.lua Normal file

File diff suppressed because it is too large Load Diff

3
example.env Normal file
View File

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

View File

@ -1,3 +1,3 @@
package.path = 'etc/?.lua;etc/?/init.lua;' .. package.path
package.path = 'etc/?.lua;etc/?/init.lua;?.lua;?/init.lua;' .. package.path
require 'src.parts.core'

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,6 +1,6 @@
* 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.
-----
@ -19,26 +19,15 @@ Maybe I will rewrite bot's core to C but here already so many Lua code.
* 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~
+ Create user: ~adduser user~
+ Build image: ~docker build -t comp-tg .~
setup it (add to doas) and login to this user
+ Start it: ~docker run -d -e .env --name comp-tg --restart always comp-tg~
+ Clone repo: ~git clone --recursive https://gitdab.com/er2/comp-tg~
and enter ~cd comp-tg~
+ Change token and owner in *config.lua*
+ 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"

View File

@ -1,11 +1,12 @@
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')
for _, v in pairs {...} do
if #s > 0
then s = s .. '\t'
end
s = s .. tostring(v) or 'nil'
end
return s .. '\n'
return s ..'\n'
end
local env = {
@ -19,11 +20,9 @@ local env = {
type = type,
pcall = pcall,
xpcall = xpcall,
math = math,
string = string,
table = table,
dump = dump,
}
@ -33,17 +32,29 @@ return {
local s = ''
local t = {
msg = msg,
print = function(...) s = s .. prind(...) end,
print = function(...)
s = s .. prind(...)
end,
C = owner and C or nil,
api = owner and C.api 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
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)
C.api:send(msg, s .. '\n' .. e)
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,11 +1,11 @@
return {
run = function(C, msg)
local t = os.time()
local ps, ls, lm, lh, ld
ps, ls = t - msg.date, t - C.loaded
lm = ls / 60
lh = lm / 60
ld = lh / 24
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,21 +1,32 @@
return {
private = true,
run = function(C, msg)
local cat, sub = table.unpack(msg.args)
if not (cat and sub) then
return C.api:reply(msg, '/reload cmds ping')
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
local path = 'src.'.. cat ..'.'.. sub
C.api:off(package.loaded[path])
package.loaded[path] = nil
local err, m = pcall(require, path)
if not err then return C.api:reply(msg, 'Reload failed. ' .. m)
elseif cat == 'events' then C.api:off(m); C.api:on(sub, m)
elseif cat == 'cmds' then C.cmds[sub] = m
elseif cat == 'parts' then m(C)
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
C.api:reply(msg, 'Reloaded. ' .. tostring(m))
end
}

View File

@ -1,61 +1,91 @@
-- It uses data from central bank of Russia
--- and external service to get json from xml
-- Privacy and security is unknown
class 'Rub' {
url = 'https://api.factmaven.com/xml-to-json/?xml=https://www.cbr.ru/scripts/XML_daily.asp',
pat = '%d %s (%s) - %f ₽',
local rub = {
url = 'https://api.factmaven.com/xml-to-json/?xml='
.. 'https://www.cbr.ru/scripts/XML_daily.asp',
function(this)
this.tools = require 'api.tools'
end,
tools = require 'etc.api.tools',
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,
}
function rub:course(wants)
local resp, succ = self.tools._req(self.url, 'GET')
if not succ then return 'err' end
resp = self.tools.json.decode(resp or '{}')
resp = resp.ValCurs
table.insert(resp.Valute, {
ID = 'R01000',
NumCode = '001',
CharCode = 'RUB',
Nominal = 1,
Name = 'Российский рубль',
Value = '1'
})
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, ('%d %s (%s) - %f ₽'):format(v.Nominal, v.Name, v.CharCode, v.Value:gsub(',', '.')))
end
end
return r, resp.Date, founds
end
local rub = new 'Rub' ()
return {
run = function(C, msg)
local wants = {'USD', 'EUR', table.unpack(msg.args)}
for i = 1, #wants do wants[i] = wants[i]:upper() end -- uppercase
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 == 'error' then
return C.api:reply(msg, C.locale:get('error', 'req_err', msg.l))
if v == 'err' then
return self.api:reply(msg, self.locale:get('error', 'req_err', msg.l))
end
local nf = {}
local nf = { }
for _, i in pairs(wants) do
if not table.find(f, i) then table.insert(nf, i) end
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
C.api:reply(msg, s .. msg.loc.prov)
if #nf > 0 then
s = s .. msg.loc.notf .. table.concat(nf, ',')
end
self.api:reply(msg, s .. msg.loc.prov)
end
}

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
}

View File

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

View File

@ -1,28 +1,29 @@
return function(C, api, msg)
local cmd = C.cmds[msg.cmd]
local owner = msg.from.id == C.config.owner
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 cmd == nil then
api:send(msg, C.locale:get('error', 'inv_cmd', 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))
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 succ, err = pcall(cmd.run, C, msg, owner)
if not succ then
local suc, err = pcall(cmd.run, C, msg, owner)
if not suc then
print(err)
local cid = C.config.owner
api:forward(cid, msg.chat.id, msg.message_id, false)
api:send(cid, 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,8 +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: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

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,48 +1,78 @@
function table.indexOf(t, w)
-- 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[w]
for k, v in pairs(t) do
i[v] = k
end
return i[val]
end
function table.find(t, w)
local i
for k,v in pairs(t) do
if v == w then
i = k
break
-- 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
return i
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
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)
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'
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) 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)
--[[
a = {'levels', }
for i = 1, #a do
if not C.db[a[i] ] then C.db[a[i] ] = {} end
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

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>",
"desc": "executes code"
},
"shell": {
"args": "<code>",
"desc": "executes code in Unix shell"
},
"ping": {
"desc": "ping pong",
@ -26,6 +30,11 @@
"cur": "Currency at %s:\n%s",
"notf": "\nNot found: ",
"prov": "\nData provided from central bank of Russia."
},
"start": {
"desc": "getting started",
"msg": "Hi! I'm Computer - an open-source bot.\nIf you looking for source code: https://gitdab.com/er2/comp-tg"
}
}
}

View File

@ -14,6 +14,10 @@
"args": "<код>",
"desc": "исполняет код"
},
"shell": {
"args": "<код>",
"desc": "исполняет код в Unix shell"
},
"ping": {
"desc": "пинг-понг",
@ -26,6 +30,11 @@
"cur": "Курс на %s:\n%s",
"notf": "\nНе нашлось: ",
"prov": "\nДанные предоставлены центральным банком России."
},
"start": {
"desc": "начало работы",
"msg": "Привет! Я Computer - бот с открытым исходным кодом.\nЕсли ты ищешь исходники: https://gitdab.com/er2/comp-tg"
}
}
}

View File

@ -1,35 +1,29 @@
local tg = require 'etc.api'
return function(Core)
local self = Core
self.api = tg { norun = true }
self.cmds = {}
require 'etc.api'
return function(C)
C.api = new 'API' {
norun = true,
}
print 'Client initialization...'
function Core._ev(ev, ...) self:ev(...) end
function self.api._ev(_, t, i, n, ...)
self._ev(_, t, i, n, self.api, ...)
C.api._ev = function(_, t, i, name, ...)
return C:_ev(t, i, name, C.api, ...)
end
self:load 'events'
C:load 'events'
self.api:login(self.config.token, function()
print('Logged on as @' .. self.api.info.username)
self.config.token = nil
self.api:emit 'ready'
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 offs, o = 0
self.api.runs = true
self:on('ready', function()
while self.api.runs do
self:emit 'tick'
o = self.api:_getUpd(1, offs, 0)
offs = o and o or offs
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
self.api:getUpdates(1, offs, 0)
end)
end

View File

@ -1,54 +1,62 @@
local config = require 'config'
local config = require 'config'
require 'etc.events'
require 'class'
local Core = {
class 'Core' : inherits 'EventsThis' {
config = config,
loaded = 0,
}
(require 'etc.events')(Core) -- add events
cmds = {},
function Core:load(what)
local c = config[what]
local s = #c
for i = 1, s do
local v = c[i]
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,
print(('Loading %s (%d / %d) %s...'):format(what:sub(0, -2), i, s, v))
-- Lint
if pcall(require, 'src.'.. what ..'.'.. v) then
local a=require('src.'.. what ..'.'.. v)
if what == 'events' then self.api:on(v, a)
elseif what == 'cmds' then self.cmds[v] = a
elseif what == 'parts' then a(self)
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
else print 'fail' end
end
print(('Loaded %d %s'):format(s, what))
self.loaded = os.time()
end
function Core:ev(t, i, name, ...)
local v = t[i]
if v.name == name then
local succ, err = pcall(v.fn, self, ...)
if not succ then
print('event "' .. name .. '" was failed')
print(err)
end
if v.type == 'once' then table.remove(t, i) end
end
end
print('Loaded '.. #c ..' '.. what)
this.loaded = os.time()
end,
}
function Core:init()
self:load 'parts'
local core = new 'Core' ()
print 'Done!'
self:emit 'ready'
end
function Core:stop()
self.api:destroy()
print 'Stopped'
print('Uptime: '.. os.time() - self.loaded.. ' seconds')
end
Core:init()

View File

@ -1,16 +0,0 @@
return function(Core)
local path = 'etc/db' -- from root
local time = 60 * 5 -- 5min
Core.db = require 'etc.db' (path)
local t = os.time()
Core:on('tick', function()
if os.time() - t >= time then
t = os.time()
print 'saving...'
Core.db:save()
end
end)
end

View File

@ -1,33 +1,36 @@
local Locale = {
class 'Locale' {
__newindex = function() end,
list = {
'en',
'ru'
'ru',
'ar',
},
main = 'en',
__newindex = function()end -- ro
__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,
}
Locale.__index = Locale
function Locale:get(cat, k, lang)
assert(cat, 'Give category')
assert(k, 'Give key')
lang = lang or self.main
return new 'Locale'
local v = (self[lang] or {})[cat]
if not v then
return self[self.main][cat][k]
else return v[k] end
end
return function(C)
local json = require 'etc.json'
for i = 1, #Locale.list do
local n = Locale.list[i]
local f = io.open(('src/locales/%s.json'):format(n))
Locale[n] = json.decode(f:read 'a')
end
C.locale = setmetatable({}, Locale)
end

View File

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