Initial commit

This commit is contained in:
eleksir
2021-08-25 17:48:44 +03:00
commit 6956655fb3
12 changed files with 821 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
_Deparsed_XSubs.pm
.idea/**
.vscode/**
.vstags
*.pid
*.log
data/cache/**
data/config.json
vendor_perl/**

97
.perlcriticrc Normal file
View 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
View 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
View 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
View 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
View 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
View 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:

View 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
View 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
View 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
View 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
View 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: