Initial commit
This commit is contained in:
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
_Deparsed_XSubs.pm
|
||||
.idea/**
|
||||
.vscode/**
|
||||
.vstags
|
||||
*.pid
|
||||
*.log
|
||||
data/cache/**
|
||||
data/config.json
|
||||
vendor_perl/**
|
||||
97
.perlcriticrc
Normal file
97
.perlcriticrc
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env perlcritic
|
||||
severity = 5
|
||||
verbose = 8
|
||||
|
||||
# minorify some of warnings that are not really critical
|
||||
[-InputOutput::RequireCheckedClose]
|
||||
# What can we handle in case of error?
|
||||
|
||||
[-Modules::ProhibitExcessMainComplexity]
|
||||
# Noone cares
|
||||
|
||||
[-Subroutines::ProhibitExcessComplexity]
|
||||
# Noone cares
|
||||
|
||||
[-Subroutines::ProhibitExplicitReturnUndef]
|
||||
# But it's very handy sometimes!
|
||||
|
||||
[-ValuesAndExpressions::ProhibitMagicNumbers]
|
||||
# this policy makes too much false positives
|
||||
|
||||
[-RegularExpressions::RequireLineBoundaryMatching]
|
||||
# We use /m as needed
|
||||
|
||||
[-RegularExpressions::RequireExtendedFormatting]
|
||||
# We do not use extended formatting of regex, they still not THAT complex
|
||||
|
||||
[-RegularExpressions::RequireDotMatchAnything]
|
||||
# huh?
|
||||
|
||||
[-RegularExpressions::ProhibitEscapedMetacharacters]
|
||||
# Looks like it pointless
|
||||
|
||||
[-ControlStructures::ProhibitCascadingIfElse]
|
||||
# I like long if..else chains
|
||||
|
||||
[-ControlStructures::ProhibitPostfixControls]
|
||||
# In short lines we can afford ourselves to use postfix controls
|
||||
# but ofcorse we should avoid such constructions in long lines.
|
||||
|
||||
[-Subroutines::ProhibitSubroutinePrototypes]
|
||||
# Contrary to Perl::Critic we consider prototypes as useful, even if
|
||||
# the compile-time checks of Perl are not perfect, Perl may give some
|
||||
# hints.
|
||||
|
||||
[-CodeLayout::RequireTidyCode]
|
||||
# Impossible to complete, because of on higher linting levels "## no critic..."
|
||||
# require be per-case on same line where critique is, but tidy align them
|
||||
# one line down.
|
||||
|
||||
[-CodeLayout::ProhibitParensWithBuiltins]
|
||||
# duh...
|
||||
|
||||
[-ValuesAndExpressions::ProhibitEmptyQuotes]
|
||||
# cause we consider following correct and useful: my $var = '';
|
||||
# the recomended use of q{} looks too complex: my $var = q{};
|
||||
|
||||
[-ValuesAndExpressions::ProhibitNoisyQuotes]
|
||||
# Other people, other opinions: "," is better readable than qw(,)
|
||||
|
||||
[-BuiltinFunctions::ProhibitSleepViaSelect]
|
||||
# We do not wish to use too much external modules.
|
||||
# select undef, undef, undef, 0.25; # actually is okay if you add comment that
|
||||
# it is sleep for 0.25 seconds
|
||||
|
||||
[-NamingConventions::Capitalization]
|
||||
# duh...
|
||||
# Noone care, really
|
||||
|
||||
[-ControlStructures::ProhibitUnlessBlocks]
|
||||
# They should not be too complex - only one condition should be checked,
|
||||
# that's all
|
||||
|
||||
[-Miscellanea::ProhibitTies]
|
||||
# Just allow it. It's very handy stuff and extremely simple and safe enugh to use.
|
||||
|
||||
[-ValuesAndExpressions::RequireNumberSeparators]
|
||||
# Not really
|
||||
|
||||
[-InputOutput::RequireBracedFileHandleWithPrint]
|
||||
# We prefer not to use braced file handles
|
||||
|
||||
[-Documentation::PodSpelling]
|
||||
# It doesn't help, but emits annoying errors
|
||||
|
||||
[Documentation::RequirePodSections]
|
||||
# Considering this as "soft" requirement. For modules that is (or will be)
|
||||
# stand-alone libs well formed documentation is highly desired.
|
||||
lib_sections = NAME | VERSION | DESCRIPTION | AUTHOR | LICENSE AND COPYRIGHT
|
||||
|
||||
[-InputOutput::RequireBriefOpen]
|
||||
# Yes, i do!
|
||||
|
||||
[-ControlStructures::ProhibitDeepNests]
|
||||
# Have to use it, so fuckoff
|
||||
|
||||
[-Variables::ProhibitPackageVars]
|
||||
# fuck off
|
||||
127
LICENSE
Normal file
127
LICENSE
Normal file
@@ -0,0 +1,127 @@
|
||||
The "Artistic License"
|
||||
|
||||
Preamble
|
||||
|
||||
The intent of this document is to state the conditions under which a
|
||||
Package may be copied, such that the Copyright Holder maintains some
|
||||
semblance of artistic control over the development of the package,
|
||||
while giving the users of the package the right to use and distribute
|
||||
the Package in a more-or-less customary fashion, plus the right to make
|
||||
reasonable modifications.
|
||||
|
||||
Definitions:
|
||||
|
||||
"Package" refers to the collection of files distributed by the
|
||||
Copyright Holder, and derivatives of that collection of files
|
||||
created through textual modification.
|
||||
|
||||
"Standard Version" refers to such a Package if it has not been
|
||||
modified, or has been modified in accordance with the wishes
|
||||
of the Copyright Holder as specified below.
|
||||
|
||||
"Copyright Holder" is whoever is named in the copyright or
|
||||
copyrights for the package.
|
||||
|
||||
"You" is you, if you're thinking about copying or distributing
|
||||
this Package.
|
||||
|
||||
"Reasonable copying fee" is whatever you can justify on the
|
||||
basis of media cost, duplication charges, time of people involved,
|
||||
and so on. (You will not be required to justify it to the
|
||||
Copyright Holder, but only to the computing community at large
|
||||
as a market that must bear the fee.)
|
||||
|
||||
"Freely Available" means that no fee is charged for the item
|
||||
itself, though there may be fees involved in handling the item.
|
||||
It also means that recipients of the item may redistribute it
|
||||
under the same conditions they received it.
|
||||
|
||||
1. You may make and give away verbatim copies of the source form of the
|
||||
Standard Version of this Package without restriction, provided that you
|
||||
duplicate all of the original copyright notices and associated disclaimers.
|
||||
|
||||
2. You may apply bug fixes, portability fixes and other modifications
|
||||
derived from the Public Domain or from the Copyright Holder. A Package
|
||||
modified in such a way shall still be considered the Standard Version.
|
||||
|
||||
3. You may otherwise modify your copy of this Package in any way, provided
|
||||
that you insert a prominent notice in each changed file stating how and
|
||||
when you changed that file, and provided that you do at least ONE of the
|
||||
following:
|
||||
|
||||
a) place your modifications in the Public Domain or otherwise make them
|
||||
Freely Available, such as by posting said modifications to Usenet or
|
||||
an equivalent medium, or placing the modifications on a major archive
|
||||
site such as uunet.uu.net, or by allowing the Copyright Holder to include
|
||||
your modifications in the Standard Version of the Package.
|
||||
|
||||
b) use the modified Package only within your corporation or organization.
|
||||
|
||||
c) rename any non-standard executables so the names do not conflict
|
||||
with standard executables, which must also be provided, and provide
|
||||
a separate manual page for each non-standard executable that clearly
|
||||
documents how it differs from the Standard Version.
|
||||
|
||||
d) make other distribution arrangements with the Copyright Holder.
|
||||
|
||||
4. You may distribute the programs of this Package in object code or
|
||||
executable form, provided that you do at least ONE of the following:
|
||||
|
||||
a) distribute a Standard Version of the executables and library files,
|
||||
together with instructions (in the manual page or equivalent) on where
|
||||
to get the Standard Version.
|
||||
|
||||
b) accompany the distribution with the machine-readable source of
|
||||
the Package with your modifications.
|
||||
|
||||
c) give non-standard executables non-standard names, and clearly
|
||||
document the differences in manual pages (or equivalent), together
|
||||
with instructions on where to get the Standard Version.
|
||||
|
||||
d) make other distribution arrangements with the Copyright Holder.
|
||||
|
||||
5. You may charge a reasonable copying fee for any distribution of this
|
||||
Package. You may charge any fee you choose for support of this
|
||||
Package. You may not charge a fee for this Package itself. However,
|
||||
you may distribute this Package in aggregate with other (possibly
|
||||
commercial) programs as part of a larger (possibly commercial) software
|
||||
distribution provided that you do not advertise this Package as a
|
||||
product of your own. You may embed this Package's interpreter within
|
||||
an executable of yours (by linking); this shall be construed as a mere
|
||||
form of aggregation, provided that the complete Standard Version of the
|
||||
interpreter is so embedded.
|
||||
|
||||
6. The scripts and library files supplied as input to or produced as
|
||||
output from the programs of this Package do not automatically fall
|
||||
under the copyright of this Package, but belong to whoever generated
|
||||
them, and may be sold commercially, and may be aggregated with this
|
||||
Package. If such scripts or library files are aggregated with this
|
||||
Package via the so-called "undump" or "unexec" methods of producing a
|
||||
binary executable image, then distribution of such an image shall
|
||||
neither be construed as a distribution of this Package nor shall it
|
||||
fall under the restrictions of Paragraphs 3 and 4, provided that you do
|
||||
not represent such an executable image as a Standard Version of this
|
||||
Package.
|
||||
|
||||
7. C subroutines (or comparably compiled subroutines in other
|
||||
languages) supplied by you and linked into this Package in order to
|
||||
emulate subroutines and variables of the language defined by this
|
||||
Package shall not be considered part of this Package, but are the
|
||||
equivalent of input as in Paragraph 6, provided these subroutines do
|
||||
not change the language in any way that would cause it to fail the
|
||||
regression tests for the language.
|
||||
|
||||
8. Aggregation of this Package with a commercial distribution is always
|
||||
permitted provided that the use of this Package is embedded; that is,
|
||||
when no overt attempt is made to make this Package's interfaces visible
|
||||
to the end user of the commercial distribution. Such use shall not be
|
||||
construed as a distribution of this Package.
|
||||
|
||||
9. The name of the Copyright Holder may not be used to endorse or promote
|
||||
products derived from this software without specific prior written permission.
|
||||
|
||||
10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
The End
|
||||
67
README
Normal file
67
README
Normal file
@@ -0,0 +1,67 @@
|
||||
aleesa-misc
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
Основной роутер команд. Все протокольные плагины шлют команды сюда, а отсюда команды разлетаются в модули-исполнители и
|
||||
ответ от исполнителей идёт обратно заказчику.
|
||||
Работает по протоколу Redis pubsub.
|
||||
|
||||
|
||||
Механика работы
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
Роутер подписан на канал misc.
|
||||
|
||||
Модули, реализующие "клиентские протоколы" - telegram, irc, jabber и проч, подписаны на канал своего протокола. Входящие
|
||||
сообщения они получают только через этот канал.
|
||||
|
||||
Модули, реализующие иную функциональность также подписаны каждый на свой канал. Кроме того, для реализации
|
||||
дополнительных синхронных возможностей, они могут слушать например http запросы на отдельных - ip:портах. Но это уже
|
||||
модуль-специфичная настройка.
|
||||
|
||||
Формат протокола - это json с полями:
|
||||
from - модуль (по сути, redis-канал), в который надо ответить
|
||||
chatid - чат, из которого прилетело сообщение
|
||||
userid - пользователь, на чью фразу мы отвечаем, или пользователь, для которого предназначается ответ
|
||||
message - текст сообщения
|
||||
plugin - первоначальный источник (по сути, redis-канал) сообщения.
|
||||
mode - public или private
|
||||
|
||||
misc - объект с дополнительными полями:
|
||||
|
||||
misc->answer - 0 если ответ не требуется и 1 если ответ требуется (использован в модуле craniac), значение
|
||||
по-умолчанию 1, если поля нет.
|
||||
|
||||
misc->bot_nick - ник бота. Типа как, для разных чятов он может быть разный. (Использовано в модуле craniac.) Если
|
||||
поле отсутствует, проверка на обращение к боту по нику не происходит.
|
||||
|
||||
misc->csign - command sign - значок, которым предваряется команда в чятике.Исторически сложилось так, что для
|
||||
разных ботов этот символ был разный, например, eggdrop использовал точку, а sulci - восклицательный
|
||||
знак. По-умолчанию, aleesa-bot вездепроставляет восклицательный знак, если во входящем сообщении
|
||||
такой настройки нету. Изначально настройка задаётся в конфиге чат-модуля (aleesa-telegram,
|
||||
например).
|
||||
|
||||
misc->fwd_cnt - количество пересылок сообщения. Значение по-умолчанию 5. Предполагается, что при пересылке
|
||||
сообщения,модуль будет инкрементировать это значение и заодно проверять, что оно не превышает
|
||||
настройку forwards_max в конфиге или 5, если в конфиге такой настройки нету.
|
||||
|
||||
misc->msg_format - Использовать форматирование в сообщении. На текущий момент этот флажок выставляется в модуле
|
||||
telegram и благополучно проходит через модуль misc, далее попадает в модуль phrases, и, наконец,
|
||||
возвращается в модуль telegram, чтобы выходное сообщение было отослано как отформатированное.
|
||||
|
||||
Как это запустить?
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
Понадобится perl, cpanm и local::lib, а также несколько разных devel-пакетов для разных библиотек. Как правило, это
|
||||
находится методом тыка, но точно понадобятся zlib-devel, openssl-devel, hiredis-devel. Кроме того, понадобятся
|
||||
development tools - gcc, gcc-g++, perl-devel, make, cmake и так далее.
|
||||
|
||||
Приложение тестировалось на alpine linux x86_64 3.12, со встроенным perl-5.32.1
|
||||
|
||||
В каталоге с приложением под целевым пользователем запускаем bash bootstrap.sh Ждём с надеждой, что оно не обломится в
|
||||
пути.
|
||||
|
||||
Если всё хорошо, то после создания data/config.json из data/config_sample.json и можно запускать приложение. Оно
|
||||
запустится как unix daemon.
|
||||
|
||||
В каталоге contrib находится скрипт для alpine linux, системы инициализации openrc. Этот скрипт достаточно положить В
|
||||
/etc/init.d/aleesa-misc сконфигурить сервис по инструкции выше и после этого сервис запускается командой:
|
||||
service aleesa-misc start
|
||||
в автозагрузку его можно прописать через команду
|
||||
rc-update add aleesa-misc default
|
||||
28
TODO
Normal file
28
TODO
Normal file
@@ -0,0 +1,28 @@
|
||||
Реализовать парсер для команды !admin :
|
||||
* Вероятно, попротокольно, так как эта команда есть только в telegram, но формально должна появиться в jabber и irc.
|
||||
* Понадобится отдельный способ запросить "протокольный" плагин на тему админ ли чел, который хочет использовать
|
||||
команду !admin.
|
||||
* Для телеги у нас есть возможность автоматически определять админов.
|
||||
* Для IRC - есть возможность определить op-ов. (+ к тому нужно следить за сменой статуса - парсить соответствующие
|
||||
ивенты).
|
||||
* Для jabber - пока непонятно.
|
||||
|
||||
Брать каналы из конфига. Те самые redis-каналы, в которые надо отсылать сообщения. Продумать вопрос с оптимальным форматом конфига.
|
||||
Пока что он видится в фомате хэша
|
||||
{
|
||||
channels : {
|
||||
telegram : telegram,
|
||||
craniac : craniac,
|
||||
irc : irc,
|
||||
webapp : webapp,
|
||||
...
|
||||
}
|
||||
}
|
||||
но не на всех этих каналах надо слушать, в какие-то достаточно только писать.
|
||||
|
||||
Реализовать возможность релоада конфига. Но для начала понять, нужно ли оно нам?
|
||||
|
||||
Решить вопрос с Cron-ом. Будет ли это отдельный демонок либо эти функции будут в роутере (чего наверно не хотелось бы).
|
||||
В Cron у нас войдёт как минимум утренняя фортунка и скорее всего rss, когда появится.
|
||||
|
||||
Проверить момент, когда при старте сервиса redis ещё не запущен.
|
||||
184
bin/aleesa-misc
Executable file
184
bin/aleesa-misc
Executable file
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use 5.018;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use open qw (:std :utf8);
|
||||
use English qw ( -no_match_vars );
|
||||
|
||||
use version; our $VERSION = qw (1.0);
|
||||
|
||||
my $workdir;
|
||||
|
||||
# До фактического запуска логики программы, сменим рабочий каталог, чтобы подцепить либы
|
||||
BEGIN {
|
||||
use Cwd qw (chdir abs_path);
|
||||
my @CWD = split /\//xms, abs_path ($PROGRAM_NAME);
|
||||
|
||||
if ($#CWD > 1) {
|
||||
$#CWD = $#CWD - 2;
|
||||
}
|
||||
|
||||
$workdir = join '/', @CWD;
|
||||
chdir $workdir;
|
||||
}
|
||||
|
||||
use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5");
|
||||
no Cwd;
|
||||
use Cwd::utf8 qw (chdir abs_path);
|
||||
use File::Basename qw (basename dirname);
|
||||
use File::Path qw (make_path);
|
||||
use Mojo::Log;
|
||||
use POSIX qw (setgid setuid setsid);
|
||||
use Conf qw (LoadConf);
|
||||
use Misc qw (RunMisc);
|
||||
|
||||
sub daemonize;
|
||||
sub __fork;
|
||||
sub __signal_handler;
|
||||
|
||||
local $OUTPUT_AUTOFLUSH = 1;
|
||||
my $runAsUid = $UID;
|
||||
|
||||
my $piddir = '/var/run/' . basename (abs_path ($PROGRAM_NAME));
|
||||
my $CONF = LoadConf ();
|
||||
my $loglevel = $CONF->{'loglevel'} // 'info';
|
||||
my $logfile;
|
||||
my $log;
|
||||
|
||||
if (defined $CONF->{'log'}) {
|
||||
$logfile = $CONF->{'log'};
|
||||
} elsif (defined $CONF->{'debug_log'}) {
|
||||
$logfile = $CONF->{'debug_log'};
|
||||
}
|
||||
|
||||
# Если мы запускаемся под рутом, то попробуем создать необходимые служебные каталоги и простать на них корректных
|
||||
# владельца и права
|
||||
if ($UID == 0) {
|
||||
my $user = $CONF->{'user'};
|
||||
|
||||
unless (defined $user) {
|
||||
die "Unable to run as root, please define unprivileged user in config.json\n";
|
||||
}
|
||||
|
||||
my ($uid, $gid) = (getpwnam $user)[2,3];
|
||||
|
||||
unless (-d $piddir) {
|
||||
make_path (
|
||||
$piddir, {
|
||||
uid => $uid,
|
||||
group => $gid
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (defined $logfile) {
|
||||
my $logdir = dirname $logfile;
|
||||
|
||||
unless (-d $logdir) {
|
||||
make_path (
|
||||
$logdir, {
|
||||
uid => $uid,
|
||||
group => $gid
|
||||
}
|
||||
) or die "Unable to create $logdir: $OS_ERROR\n";
|
||||
}
|
||||
}
|
||||
|
||||
# Переключаемся на "рабочего" пользователя и сбрасываем привилегии.
|
||||
setgid ($gid) or die "Unable to switch to group of $user($OS_ERROR)\n";
|
||||
setuid ($uid) or die "Unable to switch to account $user($OS_ERROR)\n";
|
||||
} else {
|
||||
$piddir = $workdir;
|
||||
}
|
||||
|
||||
if (defined $logfile) {
|
||||
$log = Mojo::Log->new (path => $logfile, level => $loglevel);
|
||||
} else {
|
||||
$log = Mojo::Log->new (path => '/dev/null', level => 'fatal');
|
||||
}
|
||||
|
||||
# По-умолчанию, демонизируемся, если явно не указано, что этого делать не надо
|
||||
if (defined ($CONF->{'daemonize'}) && $CONF->{'daemonize'}) {
|
||||
daemonize ();
|
||||
} elsif ( ! defined ($CONF->{'daemonize'})) {
|
||||
daemonize ();
|
||||
}
|
||||
|
||||
# Запишем pid-файл
|
||||
my $apath = abs_path $PROGRAM_NAME;
|
||||
|
||||
unless (defined $apath) {
|
||||
$log->fatal ("[FATAL] Unable to get abs_path: $OS_ERROR\n");
|
||||
exit 1;
|
||||
};
|
||||
|
||||
our $pidfile = sprintf '%s/%s.pid', $piddir, basename $apath;
|
||||
$log->info ("[INFO] Creating pidfile at $pidfile");
|
||||
|
||||
open my $MYPID, '>', $pidfile or do {
|
||||
$log->fatal ("[FATAL] Error opening $pidfile: $OS_ERROR");
|
||||
exit 1;
|
||||
};
|
||||
|
||||
print {$MYPID} $PID or do {
|
||||
$log->fatal ("[FATAL] Error writing to $pidfile: $OS_ERROR");
|
||||
exit 1;
|
||||
};
|
||||
|
||||
close $MYPID; ## no critic (InputOutput::RequireCheckedSyscalls)
|
||||
|
||||
# При наличии хака в лице Mojo::IOLoop::Signal в Misc.pm, установка хэндлера в этом месте - это фактически
|
||||
# подстраховка на случай ядерной войны.
|
||||
local $SIG{INT} = \&__signal_handler;
|
||||
local $SIG{TERM} = \&__signal_handler;
|
||||
|
||||
# Поставим правильное имя процесса в proctitle
|
||||
$PROGRAM_NAME = abs_path $PROGRAM_NAME; ## no critic (Variables::RequireLocalizedPunctuationVars)
|
||||
|
||||
# И запустим уже наш сервис
|
||||
RunMisc ();
|
||||
|
||||
if (-f $pidfile) {
|
||||
unlink $pidfile;
|
||||
}
|
||||
|
||||
exit 0;
|
||||
|
||||
sub daemonize {
|
||||
__fork ();
|
||||
POSIX::setsid ();
|
||||
open STDIN, '<', '/dev/null'; ## no critic (InputOutput::RequireCheckedSyscalls, InputOutput::RequireCheckedOpen)
|
||||
__fork ();
|
||||
umask 0;
|
||||
return;
|
||||
}
|
||||
|
||||
sub __fork {
|
||||
my $pid = fork;
|
||||
|
||||
unless (defined $pid) {
|
||||
$log->fatal ("[FATAL] Can't fork: $OS_ERROR");
|
||||
exit 1;
|
||||
}
|
||||
|
||||
if ($pid != 0) {
|
||||
exit;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub __signal_handler {
|
||||
$log->info ("[INFO] Caught a signal $OS_ERROR");
|
||||
|
||||
if (-f $pidfile) {
|
||||
unlink $pidfile;
|
||||
}
|
||||
|
||||
exit 0;
|
||||
}
|
||||
|
||||
__END__
|
||||
# vim: set ft=perl noet ai ts=4 sw=4 sts=4:
|
||||
5
bootstrap.sh
Normal file
5
bootstrap.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
cpanm -l vendor_perl --self-contained --force --installdeps .
|
||||
|
||||
# vim: set ft=sh noet ai ts=4 sw=4 sts=4:
|
||||
11
contrib/aleesa-misc.openrc
Normal file
11
contrib/aleesa-misc.openrc
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/sbin/openrc-run
|
||||
|
||||
name="$SVCNAME"
|
||||
command="/var/lib/$SVCNAME/bin/$SVCNAME"
|
||||
pidfile="/var/run/$SVCNAME/$SVCNAME.pid"
|
||||
|
||||
depend() {
|
||||
need net redis
|
||||
use dns
|
||||
after redis
|
||||
}
|
||||
24
cpanfile
Normal file
24
cpanfile
Normal file
@@ -0,0 +1,24 @@
|
||||
requires 'Carp', '0';
|
||||
requires 'Cwd', '0';
|
||||
requires 'Cwd::utf8', '==0.011';
|
||||
requires 'Data::Dumper', '==2.183';
|
||||
requires 'Encode', '==3.16';
|
||||
requires 'English', '==1.11';
|
||||
requires 'EV', '==4.33';
|
||||
requires 'Exporter', '==5.74';
|
||||
requires 'File::Basename', '0';
|
||||
requires 'File::Path', '==2.18';
|
||||
requires 'IO::Socket::SSL', '==2.072';
|
||||
requires 'JSON', '==4.03';
|
||||
requires 'JSON::XS', '==4.03';
|
||||
requires 'local::lib', '==2.000028';
|
||||
requires 'Math::Random::Secure', '==0.080001';
|
||||
requires 'Mojolicious', '==9.22';
|
||||
requires 'Mojo::Redis', '==3.27';
|
||||
requires 'Mojo::IOLoop::Signal', '==0.003';
|
||||
requires 'Mojo::Log', '0';
|
||||
requires 'open', '0';
|
||||
requires 'POSIX', '0';
|
||||
requires 'Protocol::Redis::XS', '0';
|
||||
requires 'utf8', '0';
|
||||
requires 'version', '0';
|
||||
27
data/config_sample.json
Normal file
27
data/config_sample.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
# redis address
|
||||
"server" : "redis",
|
||||
"port" : "6379",
|
||||
"channels": [
|
||||
"misc"
|
||||
],
|
||||
|
||||
# should we fork() and become unix daemon?
|
||||
"daemonize": 1,
|
||||
|
||||
# unprivileged system user to run bot as
|
||||
"user" : "nobody",
|
||||
|
||||
# messages with commands will begin with this symbol
|
||||
"csign": "!",
|
||||
|
||||
# logs goes there
|
||||
"log" : "/var/log/aleesa-misc/aleesa-misc.log",
|
||||
"loglevel": "info",
|
||||
|
||||
# ditto
|
||||
"pid" : "/var/run/aleesa-misc/aleesa-misc.pid",
|
||||
|
||||
# maximal amount of message forwardings, to avoid endless loops
|
||||
"forwards_max" : 5
|
||||
}
|
||||
37
lib/Conf.pm
Normal file
37
lib/Conf.pm
Normal file
@@ -0,0 +1,37 @@
|
||||
package Conf;
|
||||
# loads config
|
||||
|
||||
use 5.018;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use open qw (:std :utf8);
|
||||
use English qw ( -no_match_vars );
|
||||
use Encode qw (encode);
|
||||
use JSON::XS;
|
||||
|
||||
use version; our $VERSION = qw (1.0);
|
||||
use Exporter qw (import);
|
||||
our @EXPORT_OK = qw (LoadConf);
|
||||
|
||||
sub LoadConf {
|
||||
my $c = 'data/config.json';
|
||||
open my $CH, '<', $c or die "[FATAL] No conf at $c: $OS_ERROR\n";
|
||||
my $json;
|
||||
my $len = (stat $c) [7];
|
||||
my $readlen = read $CH, $json, $len;
|
||||
$json = encode 'UTF-8', $json;
|
||||
|
||||
unless ($readlen) {
|
||||
close $CH; ## no critic (InputOutput::RequireCheckedSyscalls
|
||||
die "[FATAL] Unable to read $c: $OS_ERROR\n";
|
||||
}
|
||||
|
||||
close $CH; ## no critic (InputOutput::RequireCheckedSyscalls
|
||||
my $j = JSON::XS->new->utf8->relaxed;
|
||||
return $j->decode ($json);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
# vim: set ft=perl noet ai ts=4 sw=4 sts=4:
|
||||
205
lib/Misc.pm
Normal file
205
lib/Misc.pm
Normal file
@@ -0,0 +1,205 @@
|
||||
package Misc;
|
||||
|
||||
# Общие модули - синтаксис, кодировки итд
|
||||
use 5.018;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use open qw (:std :utf8);
|
||||
|
||||
# Модули для работы приложения
|
||||
use Math::Random::Secure qw (irand);
|
||||
# Чтобы "уж точно" использовать hiredis-биндинги, загрузим этот модуль перед Mojo::Redis
|
||||
use Protocol::Redis::XS;
|
||||
use Mojo::Redis;
|
||||
use Mojo::IOLoop;
|
||||
use Mojo::IOLoop::Signal;
|
||||
use Mojo::Log;
|
||||
use Data::Dumper;
|
||||
|
||||
use Conf qw (LoadConf);
|
||||
|
||||
use version; our $VERSION = qw (1.0);
|
||||
use Exporter qw (import);
|
||||
our @EXPORT_OK = qw (RunMisc);
|
||||
|
||||
my $c = LoadConf ();
|
||||
|
||||
my $loglevel = $c->{'loglevel'} // 'info';
|
||||
my $fwd_cnt = $c->{'forward_max'} // 5;
|
||||
my $logfile;
|
||||
my $log;
|
||||
|
||||
if (defined $c->{'log'}) {
|
||||
$logfile = $c->{'log'};
|
||||
} elsif (defined $c->{'debug_log'}) {
|
||||
$logfile = $c->{'debug_log'};
|
||||
}
|
||||
|
||||
if (defined $logfile) {
|
||||
$log = Mojo::Log->new (path => $logfile, level => $loglevel);
|
||||
} else {
|
||||
$log = Mojo::Log->new (path => '/dev/null', level => 'fatal');
|
||||
}
|
||||
|
||||
# Основной парсер
|
||||
my $parse_message = sub {
|
||||
my $self = shift;
|
||||
my $m = shift;
|
||||
my $answer = $m;
|
||||
$answer->{from} = 'misc';
|
||||
|
||||
if (defined $answer->{misc}) {
|
||||
unless (defined $answer->{misc}->{fwd_cnt}) {
|
||||
$answer->{misc}->{fwd_cnt} = 1;
|
||||
} else {
|
||||
if ($answer->{misc}->{fwd_cnt} > $fwd_cnt) {
|
||||
$log->error ('Forward loop detected, discarding message.');
|
||||
$log->debug (Dumper $m);
|
||||
return;
|
||||
} else {
|
||||
$answer->{misc}->{fwd_cnt}++;
|
||||
}
|
||||
}
|
||||
|
||||
unless (defined $answer->{misc}->{answer}) {
|
||||
$answer->{misc}->{answer} = 1;
|
||||
}
|
||||
|
||||
unless (defined $answer->{misc}->{csign}) {
|
||||
$answer->{misc}->{csign} = '!';
|
||||
}
|
||||
} else {
|
||||
$answer->{misc}->{answer} = 1;
|
||||
$answer->{misc}->{csign} = '!';
|
||||
$answer->{misc}->{msg_format} = 0;
|
||||
}
|
||||
|
||||
# Если сообщение не попало ни под один шаблон, оно отправляется к craniac-у
|
||||
my $send_to = 'craniac';
|
||||
|
||||
$log->debug ('[DEBUG] Incoming message ' . Dumper($m));
|
||||
|
||||
# Пробуем найти команды. Сообщения, начинающиеся с $answer->{misc}->{csign}
|
||||
if (substr ($m->{message}, 0, 1) eq $answer->{misc}->{csign}) {
|
||||
my $done = 0;
|
||||
my @phrases_cmd = qw (friday пятница proverb пословица fortune фортунка f ф karma карма);
|
||||
|
||||
while (my $cmd = pop @phrases_cmd) {
|
||||
if (substr ($m->{message}, 1) eq $cmd) {
|
||||
$send_to = 'phrases';
|
||||
$done = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
unless ($done) {
|
||||
my @webapp_cmd = qw (buni anek анек анекдот cat кис drink праздник fox лис frog лягушка horse лошадь лошадка
|
||||
monkeyuser rabbit bunny кролик snail улитка owl сова сыч xkcd tits boobs tities boobies
|
||||
сиси сисечки butt booty ass попа попка);
|
||||
|
||||
while (my $cmd = pop @webapp_cmd) {
|
||||
if (substr ($m->{message}, 1) eq $cmd) {
|
||||
$send_to = 'webapp';
|
||||
$done = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unless ($done) {
|
||||
if (substr ($m->{message}, 1, 2) eq 'w ' || substr ($m->{message}, 1, 2) eq 'п ') {
|
||||
$send_to = 'webapp';
|
||||
$done = 1;
|
||||
} elsif (substr ($m->{message}, 1, 8) eq 'weather ' || substr ($m->{message}, 1, 8) eq 'погодка ') {
|
||||
$send_to = 'webapp';
|
||||
$done = 1;
|
||||
} elsif (substr ($m->{message}, 1, 7) eq 'погода ' || substr ($m->{message}, 1, 8) eq 'погадка ') {
|
||||
$send_to = 'webapp';
|
||||
$done = 1;
|
||||
}
|
||||
}
|
||||
# Попробуем найти изменение кармы
|
||||
} elsif (substr ($m->{message}, -2) eq '++' || substr ($m->{message}, -2) eq '--') {
|
||||
my @arr = split /\n/, $m->{message};
|
||||
|
||||
# Предполагается, что фраза - это только одна строка
|
||||
if ($#arr < 1) {
|
||||
$send_to = 'phrases';
|
||||
}
|
||||
}
|
||||
|
||||
$log->debug ("[DEBUG] Sending message to channel $send_to " . Dumper ($answer));
|
||||
|
||||
$self->json ($send_to)->notify (
|
||||
$send_to => $answer
|
||||
);
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
my $__signal_handler = sub {
|
||||
my ($self, $name) = @_;
|
||||
$log->info ("[INFO] Caught a signal $name");
|
||||
|
||||
if (defined $main::pidfile && -f $main::pidfile) {
|
||||
unlink $main::pidfile;
|
||||
}
|
||||
|
||||
exit 0;
|
||||
};
|
||||
|
||||
|
||||
# main loop, он же event loop
|
||||
sub RunMisc {
|
||||
$log->info ("[INFO] Connecting to $c->{server}, $c->{port}");
|
||||
|
||||
my $redis = Mojo::Redis->new (
|
||||
sprintf 'redis://%s:%s/1', $c->{server}, $c->{port}
|
||||
);
|
||||
|
||||
$log->info ('[INFO] Registering connection-event callback');
|
||||
|
||||
$redis->on (
|
||||
connection => sub {
|
||||
my ($r, $connection) = @_;
|
||||
|
||||
$log->info ('[INFO] Triggering callback on new client connection');
|
||||
|
||||
# Залоггируем ошибку, если соединение внезапно порвалось.
|
||||
$connection->on (
|
||||
error => sub {
|
||||
my ($conn, $error) = @_;
|
||||
$log->error ("[ERROR] Redis connection error: $error");
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
my $pubsub = $redis->pubsub;
|
||||
my $sub;
|
||||
$log->info ('[INFO] Subscribing to redis channels');
|
||||
|
||||
foreach my $channel (@{$c->{channels}}) {
|
||||
$log->debug ("[DEBUG] Subscribing to $channel");
|
||||
|
||||
$sub->{$channel} = $pubsub->json ($channel)->listen (
|
||||
$channel => sub { $parse_message->(@_); }
|
||||
);
|
||||
}
|
||||
|
||||
Mojo::IOLoop::Signal->on (
|
||||
TERM => $__signal_handler,
|
||||
INT => $__signal_handler
|
||||
);
|
||||
|
||||
do { Mojo::IOLoop->start } until Mojo::IOLoop->is_running;
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
# vim: set ft=perl noet ai ts=4 sw=4 sts=4:
|
||||
Reference in New Issue
Block a user