[WIP] Initial commit
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
_Deparsed_XSubs.pm
|
||||
.idea/**
|
||||
.vscode/**
|
||||
.vstags
|
||||
*.pid
|
||||
*.log
|
||||
data/config.json
|
||||
vendor_perl/**
|
||||
95
.perlcriticrc
Normal file
95
.perlcriticrc
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/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
|
||||
|
||||
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
|
||||
4
TODO
Normal file
4
TODO
Normal file
@@ -0,0 +1,4 @@
|
||||
Исправить:
|
||||
[Sun Aug 22 12:37:23 2021] (in cleanup) Can't call method "unsubscribe" on an undefined value at /root/aleesa-bot/aleesa-irc/vendor_perl/lib/perl5/Mojo/EventEmitter.pm line 34 during global destruction.
|
||||
|
||||
Это происходит при завершении работы. Предположительно исправляется с помощью хэндлера SIGINT и SIGKILL. Выйти с каналов и завершить соединение.
|
||||
197
bin/aleesa-irc
Executable file
197
bin/aleesa-irc
Executable file
@@ -0,0 +1,197 @@
|
||||
#!/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;
|
||||
|
||||
# before we run, change working dir
|
||||
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 Log::Any qw ($log);
|
||||
use Log::Any::Adapter;
|
||||
use Log::Any::For::Std;
|
||||
use POSIX qw (setgid setuid setsid);
|
||||
use Conf qw (LoadConf);
|
||||
use IRCApp qw (RunIRCApp);
|
||||
|
||||
sub daemonize;
|
||||
sub __fork;
|
||||
sub __signal_handler;
|
||||
|
||||
local $OUTPUT_AUTOFLUSH = 1;
|
||||
my $runAsUid = $UID;
|
||||
|
||||
my $piddir = '/var/run/' . basename (abs_path ($PROGRAM_NAME));
|
||||
my $C = LoadConf ();
|
||||
my $loglevel = $C->{'loglevel'} // 'notice';
|
||||
my $logfile;
|
||||
|
||||
if (defined $C->{'log'}) {
|
||||
$logfile = $C->{'log'};
|
||||
} elsif (defined $C->{'debug_log'}) {
|
||||
$logfile = $C->{'debug_log'};
|
||||
}
|
||||
|
||||
# if we run under root, create pid dir and set correct owner for it, same for debug log if it is defined
|
||||
if ($UID == 0) {
|
||||
my $user = $C->{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";
|
||||
}
|
||||
}
|
||||
|
||||
# drop privs
|
||||
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::Any::Adapter->set ('File', $logfile, log_level => $loglevel);
|
||||
|
||||
# Some annoying CHI logging events come with category set to CHI::Driver and log_level set to debug.
|
||||
# Until we use start using Log::Any native features and set whole loglevel accordingly, lets lower
|
||||
# verbosity paticulary for CHI.
|
||||
Log::Any::Adapter->set (
|
||||
{
|
||||
category => 'CHI::Driver'
|
||||
},
|
||||
'File',
|
||||
$logfile,
|
||||
log_level => 'warn'
|
||||
);
|
||||
} else {
|
||||
Log::Any::Adapter->set ('File', '/dev/null');
|
||||
}
|
||||
|
||||
# по-умолчанию, демонизируемся, если явно не указано, что этого делать не надо
|
||||
if (defined ($C->{daemonize}) && $C->{daemonize}) {
|
||||
daemonize ();
|
||||
} elsif ( ! defined ($C->{daemonize})) {
|
||||
daemonize ();
|
||||
}
|
||||
|
||||
# write pidfile
|
||||
my $apath = abs_path $PROGRAM_NAME;
|
||||
|
||||
unless (defined $apath) {
|
||||
$log->fatal ("[FATAL] Unable to get abs_path: $OS_ERROR\n");
|
||||
exit 1;
|
||||
};
|
||||
|
||||
my $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
|
||||
|
||||
local $SIG{INT} = \&__signal_handler;
|
||||
local $SIG{TERM} = \&__signal_handler;
|
||||
|
||||
# set correct proctitle
|
||||
$PROGRAM_NAME = abs_path $PROGRAM_NAME; ## no critic (Variables::RequireLocalizedPunctuationVars)
|
||||
|
||||
# run bot, at last
|
||||
RunIRCApp ();
|
||||
|
||||
if (-f $pidfile) {
|
||||
unlink $pidfile;
|
||||
}
|
||||
|
||||
exit 0;
|
||||
|
||||
sub daemonize {
|
||||
__fork ();
|
||||
POSIX::setsid ();
|
||||
open STDIN, '<', '/dev/null'; ## no critic (InputOutput::RequireCheckedSyscalls, InputOutput::RequireCheckedOpen)
|
||||
# we handle STDOUT && STDERR via Log::Any::For::Std, so do not close them explicitly
|
||||
#open STDOUT, '>', '/dev/null'; ## no critic (InputOutput::RequireCheckedSyscalls, InputOutput::RequireCheckedOpen)
|
||||
#open STDERR, '>', '/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:
|
||||
17
cpanfile
Normal file
17
cpanfile
Normal file
@@ -0,0 +1,17 @@
|
||||
requires 'Carp';
|
||||
requires 'Cwd::utf8';
|
||||
requires 'Date::Format::ISO8601';
|
||||
requires 'Encode';
|
||||
requires 'EV';
|
||||
requires 'File::Basename';
|
||||
requires 'File::Path';
|
||||
requires 'IO::Socket::SSL';
|
||||
requires 'JSON';
|
||||
requires 'JSON::XS';
|
||||
requires 'local::lib';
|
||||
requires 'Log::Any';
|
||||
requires 'Log::Any::Adapter';
|
||||
requires 'Log::Any::For::Std';
|
||||
requires 'Mojo::IRC';
|
||||
requires 'Mojo::Redis';
|
||||
requires 'POSIX';
|
||||
37
data/config_sample.json
Normal file
37
data/config_sample.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
# таймаут подключения задаётся переменной MOJO_IRC_CONNECT_TIMEOUT, в секундах, что есть форменное пидорство
|
||||
"server" : "localhost",
|
||||
|
||||
"port" : 6667,
|
||||
|
||||
# 0 если ssl не нужен и 1 - если нужен. Проверка ssl сертификата выключена, так как нам надо работать
|
||||
"ssl" : 0,
|
||||
|
||||
# к нему же мы приводим и user и real name
|
||||
"nick" : "aleesa",
|
||||
|
||||
# каналы, на которые заходит бот. предполагается, что бот заходит на каналы в обязательном порядке
|
||||
"channels": [
|
||||
"#a",
|
||||
"#b"
|
||||
],
|
||||
|
||||
# редиска, через которую бот общается с другини компонентами
|
||||
"redisaddr" : "redis",
|
||||
"redisport" : 6379,
|
||||
|
||||
# 1 если запускаемся как unix daemon и 0 - если нет
|
||||
"daemonize" : 0,
|
||||
|
||||
# обычный пользователь, от имени которого запускается приложение
|
||||
"user" : "nobody",
|
||||
|
||||
# команды бота начинаются с этого символа, крайне желательно, чтобы он был одинаковым на всех компонентах
|
||||
"csign": "!",
|
||||
|
||||
# в этот файл пишем логи
|
||||
"log" : "/var/log/aleesa-irc/aleesa-irc.log",
|
||||
|
||||
# ditto
|
||||
"pid" : "/var/run/aleesa-irc/aleesa-irc.pid"
|
||||
}
|
||||
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 decode);
|
||||
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:
|
||||
462
lib/IRCApp.pm
Normal file
462
lib/IRCApp.pm
Normal file
@@ -0,0 +1,462 @@
|
||||
package IRCApp;
|
||||
# TODO: настроить в логгировании loglevel, всякие [ERRO] [WARN] [INFO] итд
|
||||
# общие модули - синтаксис, кодировки итд
|
||||
use 5.018;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use open qw (:std :utf8);
|
||||
|
||||
# модули для работы приложения
|
||||
use Date::Format::ISO8601 qw (gmtime_to_iso8601_datetime);
|
||||
use Log::Any qw ($log);
|
||||
use Mojo::Redis;
|
||||
use Mojo::IOLoop;
|
||||
use Mojo::IRC;
|
||||
use Data::Dumper;
|
||||
|
||||
use Conf qw (LoadConf);
|
||||
|
||||
use version; our $VERSION = qw (1.0);
|
||||
use Exporter qw (import);
|
||||
our @EXPORT_OK = qw (RunIRCApp);
|
||||
|
||||
my $c = LoadConf ();
|
||||
my $identified = 0;
|
||||
|
||||
# основной парсер
|
||||
my $parse_redis_message = sub {
|
||||
my $self = shift;
|
||||
my $m = shift;
|
||||
my $answer = $m;
|
||||
my $send_to = $answer->{from};
|
||||
|
||||
$self->json ($send_to)->notify (
|
||||
$send_to => {
|
||||
from => $answer->{from},
|
||||
userid => $answer->{userid},
|
||||
chatid => $answer->{chatid},
|
||||
plugin => $answer->{plugin},
|
||||
message => $answer->{message}
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
my $on_irc_privmsg = sub {
|
||||
my $self = shift;
|
||||
my $m = shift;
|
||||
|
||||
my $reply;
|
||||
|
||||
if (substr ($text, 1, 3) eq 'rum' || substr ($text, 1, 3) eq 'ром') {
|
||||
my $target;
|
||||
|
||||
if ((length ($text) > 5)) {
|
||||
$target = trim (substr $text, 5);
|
||||
}
|
||||
|
||||
if (! defined $target || ($target eq '')) {
|
||||
$target = $chattername;
|
||||
}
|
||||
|
||||
$reply = 'притаскивает на подносе стопку рома для ' . $target . ', края стопки искрятся кристаллами соли.';
|
||||
$bot->send_long_message ('utf8', 0, "PRIVMSG\001ACTION", $chatid, $reply);
|
||||
return;
|
||||
} elsif (substr ($text, 1, 5) eq 'vodka' || substr ($text, 1, 5) eq 'водка') {
|
||||
my $target;
|
||||
|
||||
if ((length ($text) > 7)) {
|
||||
$target = trim (substr $text, 7);
|
||||
}
|
||||
|
||||
if (! defined $target || ($target eq '')) {
|
||||
$target = $chattername;
|
||||
}
|
||||
|
||||
$reply = 'подаёт шот водки с небольшим маринованным огурчиком на блюдце для ' . $target . '. Из огурчика торчит небольшая вилочка.';
|
||||
$bot->send_long_message ('utf8', 0, "PRIVMSG\001ACTION", $chatid, $reply);
|
||||
return;
|
||||
} elsif (substr ($text, 1, 4) eq 'beer' || substr ($text, 1, 4) eq 'пиво') {
|
||||
my $target;
|
||||
|
||||
if ((length ($text) > 6)) {
|
||||
$target = trim (substr $text, 6);
|
||||
}
|
||||
|
||||
if (! defined $target || ($target eq '')) {
|
||||
$target = $chattername;
|
||||
}
|
||||
|
||||
$reply = 'бахает об стол перед ' . $target . ' кружкой холодного пива, часть пенной шапки сползает по запотевшей стенке кружки.';
|
||||
$bot->send_long_message ('utf8', 0, "PRIVMSG\001ACTION", $chatid, $reply);
|
||||
return;
|
||||
} elsif (substr ($text, 1, 7) eq 'tequila') {
|
||||
my $target;
|
||||
|
||||
if ((length ($text) > 9)) {
|
||||
$target = trim (substr $text, 9);
|
||||
}
|
||||
|
||||
if (! defined $target || ($target eq '')) {
|
||||
$target = $chattername;
|
||||
}
|
||||
|
||||
$reply = 'ставит рядом с ' . $target . ' шот текилы, аккуратно на ребро стопки насаживает дольку лайма и ставит кофейное блюдце с горочкой соли.';
|
||||
$bot->send_long_message ('utf8', 0, "PRIVMSG\001ACTION", $chatid, $reply);
|
||||
return;
|
||||
} elsif (substr ($text, 1, 6) eq 'текила') {
|
||||
my $target;
|
||||
|
||||
if ((length ($text) > 8)) {
|
||||
$target = trim (substr $text, 8);
|
||||
}
|
||||
|
||||
if (! defined $target || ($target eq '')) {
|
||||
$target = $chattername;
|
||||
}
|
||||
|
||||
$reply = 'ставит рядом с ' . $target . ' шот текилы, аккуратно на ребро стопки насаживает дольку лайма и ставит кофейное блюдце с горочкой соли.';
|
||||
$bot->send_long_message ('utf8', 0, "PRIVMSG\001ACTION", $chatid, $reply);
|
||||
return;
|
||||
} elsif (substr ($text, 1, 6) eq 'whisky') {
|
||||
my $target;
|
||||
|
||||
if ((length ($text) > 8)) {
|
||||
$target = trim (substr $text, 8);
|
||||
}
|
||||
|
||||
if (! defined $target || ($target eq '')) {
|
||||
$target = $chattername;
|
||||
}
|
||||
|
||||
$reply = 'демонстративно достаёт из морозилки пару кубических камушков, бросает их в толстодонный стакан и аккуратно наливает Jack Daniels. Запускает стакан вдоль барной стойки, он останавливается около ' . $target . '.';
|
||||
$bot->send_long_message ('utf8', 0, "PRIVMSG\001ACTION", $chatid, $reply);
|
||||
return;
|
||||
} elsif (substr ($text, 1, 5) eq 'виски') {
|
||||
my $target;
|
||||
|
||||
if ((length ($text) > 7)) {
|
||||
$target = trim (substr $text, 7);
|
||||
}
|
||||
|
||||
if (! defined $target || ($target eq '')) {
|
||||
$target = $chattername;
|
||||
}
|
||||
|
||||
$reply = 'демонстративно достаёт из морозилки пару кубических камушков, бросает их в толстодонный стакан и аккуратно наливает Jack Daniels. Запускает стакан вдоль барной стойки, он останавливается около ' . $target . '.';
|
||||
$bot->send_long_message ('utf8', 0, "PRIVMSG\001ACTION", $chatid, $reply);
|
||||
return;
|
||||
} elsif (substr ($text, 1, 8) eq 'absinthe') {
|
||||
my $target;
|
||||
|
||||
if ((length ($text) > 10)) {
|
||||
$target = trim (substr $text, 10);
|
||||
}
|
||||
|
||||
if (! defined $target || ($target eq '')) {
|
||||
$target = $chattername;
|
||||
}
|
||||
|
||||
$reply = 'наливает абсент в стопку. Смочив кубик сахара в абсенте кладёт его на дырявую ложечку и пожигает. Как только пламя потухнет, ' . $c->{nick} . ' размешивает оплавившийся кубик в абсенте и подносит стопку ' . $target . '.';
|
||||
$bot->send_long_message ('utf8', 0, "PRIVMSG\001ACTION", $chatid, $reply);
|
||||
return;
|
||||
} elsif (substr ($text, 1, 6) eq 'абсент') {
|
||||
my $target;
|
||||
|
||||
if ((length ($text) > 8)) {
|
||||
$target = trim (substr $text, 8);
|
||||
}
|
||||
|
||||
if (! defined $target || ($target eq '')) {
|
||||
$target = $chattername;
|
||||
}
|
||||
|
||||
$reply = 'наливает абсент в стопку. Смочив кубик сахара в абсенте кладёт его на дырявую ложечку и пожигает. Как только пламя потухнет, ' . $c->{nick} . ' размешивает оплавившийся кубик в абсенте и подносит стопку ' . $target . '.';
|
||||
$bot->send_long_message ('utf8', 0, "PRIVMSG\001ACTION", $chatid, $reply);
|
||||
return;
|
||||
} elsif (substr ($text, 1, 2) eq 'w ' || substr ($text, 1, 2) eq 'п ') {
|
||||
my $city = substr $text, 2;
|
||||
$reply = Weather ($city) =~ tr/\n/ /r;
|
||||
} elsif (substr ($text, 1) eq 'anek' || substr ($text, 1) eq 'анек' || substr ($text, 1) eq 'анекдот' ) {
|
||||
$reply = Anek ();
|
||||
} elsif (substr ($text, 1) eq 'coin' || substr ($text, 1) eq 'монетка') {
|
||||
if (rand (101) < 0.016) {
|
||||
$reply = 'ребро';
|
||||
} else {
|
||||
if (irand (2) == 0) {
|
||||
if (irand (2) == 0) {
|
||||
$reply = 'орёл';
|
||||
} else {
|
||||
$reply = 'аверс';
|
||||
}
|
||||
} else {
|
||||
if (irand (2) == 0) {
|
||||
$reply = 'решка';
|
||||
} else {
|
||||
$reply = 'реверс';
|
||||
}
|
||||
}
|
||||
}
|
||||
} elsif (substr ($text, 1) eq 'roll' || substr ($text, 1) eq 'dice' || substr ($text, 1) eq 'кости') {
|
||||
$reply = sprintf 'На первой кости выпало %d, а на второй — %d.', irand (6) + 1, irand (6) + 1;
|
||||
} elsif (substr ($text, 1) eq 'version' || substr ($text, 1) eq 'ver') {
|
||||
$reply = 'Версия нуль.чего-то_там.чего-то_там';
|
||||
} elsif (substr ($text, 1) eq 'lat' || substr ($text, 1) eq 'лат') {
|
||||
$reply = Lat ();
|
||||
} elsif (substr ($text, 1) eq 'cat' || substr ($text, 1) eq 'кис') {
|
||||
$reply = Kitty ();
|
||||
} elsif (substr ($text, 1) eq 'fox' || substr ($text, 1) eq 'лис') {
|
||||
$reply = Fox ();
|
||||
} elsif (substr ($text, 1) eq 'frog' || substr ($text, 1) eq 'лягушка') {
|
||||
$reply = Frog ();
|
||||
} elsif (substr ($text, 1) eq 'horse' || substr ($text, 1) eq 'лошадь' || substr ($text, 1) eq 'лошадка') {
|
||||
$reply = Horse ();
|
||||
} elsif (substr ($text, 1) eq 'snail' || substr ($text, 1) eq 'улитка') {
|
||||
$reply = Snail ();
|
||||
} elsif (substr ($text, 1) eq 'dig' || substr ($text, 1) eq 'копать') {
|
||||
$reply = Dig ($chattername);
|
||||
} elsif (substr ($text, 1) eq 'fish' || substr ($text, 1) eq 'fisher' || substr ($text, 1) eq 'рыба' || substr ($text, 1) eq 'рыбка' || substr ($text, 1) eq 'рыбалка') {
|
||||
$reply = Fish ($chattername);
|
||||
} elsif (substr ($text, 1) eq 'buni') {
|
||||
$reply = Buni ();
|
||||
} elsif (substr ($text, 1) eq 'xkcd') {
|
||||
$reply = Xkcd ();
|
||||
} elsif (substr ($text, 1) eq 'monkeyuser') {
|
||||
$reply = Monkeyuser ();
|
||||
} elsif (substr ($text, 1) eq 'drink' || substr ($text, 1) eq 'праздник') {
|
||||
$reply = Drink ();
|
||||
} elsif (substr ($text, 1) eq 'bunny' || substr ($text, 1) eq 'rabbit' || substr ($text, 1) eq 'кролик') {
|
||||
$reply = Rabbit ();
|
||||
} elsif (substr ($text, 1) eq 'owl' || substr ($text, 1) eq 'сова') {
|
||||
$reply = Owl ();
|
||||
} elsif ((length ($text) >= 6 && (substr ($text, 1, 6) eq 'karma ' || substr ($text, 1, 6) eq 'карма ')) || substr ($text, 1) eq 'karma' || substr ($text, 1) eq 'карма') {
|
||||
my $mytext = '';
|
||||
|
||||
if (length ($text) > 6) {
|
||||
$mytext = substr $text, 7;
|
||||
chomp $mytext;
|
||||
$mytext = trim $mytext;
|
||||
} else {
|
||||
$mytext = '';
|
||||
}
|
||||
|
||||
$reply = KarmaGet ($chatid, $mytext);
|
||||
} elsif (substr ($text, 1) eq 'friday' || substr ($text, 1) eq 'пятница') {
|
||||
$reply = Friday ();
|
||||
} elsif (substr ($text, 1) eq 'proverb' || substr ($text, 1) eq 'пословица') {
|
||||
$reply = Proverb ();
|
||||
} elsif (substr ($text, 1) eq 'fortune' || substr ($text, 1) eq 'фортунка' || substr ($text, 1) eq 'f' || substr ($text, 1) eq 'ф') {
|
||||
my $phrase = Fortune ();
|
||||
$reply = $phrase;
|
||||
} elsif (substr ($text, 1) eq 'ping') {
|
||||
$reply = 'Pong.';
|
||||
} elsif (substr ($text, 1) eq 'пинг') {
|
||||
$reply = 'Понг.';
|
||||
} elsif (substr ($text, 1) eq 'kde' || substr ($text, 1) eq 'кде') {
|
||||
my @phrases = (
|
||||
'Нет, я не буду поднимать вам плазму.',
|
||||
'Повторяйте эту мантру по утрам не менее 5 раз: "Плазма не падает." И, возможно, она перестанет у вас падать.'
|
||||
);
|
||||
|
||||
$reply = $phrases[irand ($#phrases + 1)];
|
||||
} elsif (substr ($text, 1) eq '=(' || substr ($text, 1) eq ':(' || substr ($text, 1) eq '):') {
|
||||
$reply = ':)';
|
||||
} elsif (substr ($text, 1) eq '=)' || substr ($text, 1) eq ':)' || substr ($text, 1) eq '(:') {
|
||||
$reply = ':D';
|
||||
}
|
||||
|
||||
$log->warn (Dumper ($m));
|
||||
return;
|
||||
};
|
||||
|
||||
my $on_ctcp_source = sub {
|
||||
my $self = shift;
|
||||
my $msg = shift;
|
||||
my $response_to = (split /!/, $msg->{prefix}, 2)[0];
|
||||
$self->write (NOTICE => $response_to => $self->ctcp (SOURCE => 'https://github.com/aleesa-bot/aleesa-irc'));
|
||||
return;
|
||||
};
|
||||
|
||||
my $on_ctcp_finger = sub {
|
||||
my $self = shift;
|
||||
my $msg = shift;
|
||||
my $response_to = (split /!/, $msg->{prefix}, 2)[0];
|
||||
$self->write (NOTICE => $response_to => $self->ctcp (FINGER => $c->{nick}));
|
||||
return;
|
||||
};
|
||||
|
||||
my $on_ctcp_ping = sub {
|
||||
my $self = shift;
|
||||
my $msg = shift;
|
||||
my $response_to = (split /!/, $msg->{prefix}, 2)[0];
|
||||
$self->write (NOTICE => $response_to => $self->ctcp (PING => $msg->{params}->[1]));
|
||||
return;
|
||||
};
|
||||
|
||||
my $on_ctcp_time = sub {
|
||||
my $self = shift;
|
||||
my $msg = shift;
|
||||
my $response_to = (split /!/, $msg->{prefix}, 2)[0];
|
||||
$self->write (NOTICE => $response_to => $self->ctcp (TIME => gmtime_to_iso8601_datetime (time ())));
|
||||
return;
|
||||
};
|
||||
|
||||
my $on_ctcp_clientinfo = sub {
|
||||
my $self = shift;
|
||||
my $msg = shift;
|
||||
my $response_to = (split /!/, $msg->{prefix}, 2)[0];
|
||||
$self->write (NOTICE => $response_to => $self->ctcp (CLIENTINFO => 'ACTION CLIENTINFO FINGER PING SOURCE TIME USERINFO VERSION'));
|
||||
return;
|
||||
};
|
||||
|
||||
my $on_irc_end_of_motd = sub {
|
||||
my $self = shift;
|
||||
|
||||
# Идентифицируемся в NickServ, если надо
|
||||
if (defined ($c->{identify}) && $c->{identify} ne '') {
|
||||
# TODO: invent heuristics to detect different login|identify commands depending on particular irc net or
|
||||
# known ircd detected during login procedure
|
||||
# Эта команда используется на фриноде (мир праху её) и libera.chat
|
||||
$self->write ( qw (PRIVMSG NickServ IDENTIFY $c->{nick} $c->{identify}), \&_on_nickserv_identify);
|
||||
|
||||
until ($identified) { ## no critic (ControlStructures::ProhibitUntilBlocks)
|
||||
sleep 1;
|
||||
}
|
||||
}
|
||||
|
||||
# А теперь заходим на все интересующие нас каналы
|
||||
foreach my $chan (@{$c->{channels}}) {
|
||||
$self->write ( ('JOIN', $chan), \&_on_join_channel);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
sub _on_nickserv_identify {
|
||||
my $self = shift;
|
||||
my $error = shift;
|
||||
|
||||
if ($error) {
|
||||
$log->error ("NickServ identify failed: $error");
|
||||
exit 1;
|
||||
} else {
|
||||
$log->info ('Identified with Nickserv');
|
||||
$identified = 1;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub _on_join_channel {
|
||||
my $self = shift;
|
||||
my $error = shift;
|
||||
|
||||
if ($error) {
|
||||
$log->error ("Unable to join: $error");
|
||||
} else {
|
||||
$log->info ('Joined');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
# зовётся в момент "после коннекта"
|
||||
sub _on_connect {
|
||||
my $self = shift;
|
||||
my $error = shift;
|
||||
|
||||
if ($error) {
|
||||
$log->error ("Unable to connect: $error");
|
||||
exit 1;
|
||||
} else {
|
||||
$log->info ('Connected.');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub _on_disconnect {
|
||||
my $self = shift;
|
||||
my $error = shift;
|
||||
|
||||
if ($error) {
|
||||
$log->error ("Unable to Disconnect: $error");
|
||||
} else {
|
||||
$log->info ('Disconnected.');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
# main loop, он же event loop
|
||||
sub RunIRCApp {
|
||||
my $redis = Mojo::Redis->new (
|
||||
sprintf 'redis://%s:%s/1', $c->{server}, $c->{port}
|
||||
);
|
||||
|
||||
my $pubsub = $redis->pubsub;
|
||||
my $sub;
|
||||
|
||||
$pubsub->listen (
|
||||
# Вот такая ебическая конструкция для авто-подписывания на все новые каналы.
|
||||
# Странное ограничение, при котором на шаблон каналов можно подписаться только, подписавшись на каждый из
|
||||
# каналов. То есть подписка создаётся по запросу. В AnyEvent::Redis подписаться можно сразу на * :(
|
||||
# Но конкретно в моём случае этот момент неважен, т.к. подразумевается, что каналы будут добавляться, но не
|
||||
# будут убавляться.
|
||||
'webapp:*' => sub {
|
||||
my ($ps, $channel) = @_ ;
|
||||
|
||||
unless (defined $sub->{$channel}) {
|
||||
$log->info ("Subscribing to $channel");
|
||||
|
||||
$sub->{$channel} = $ps->json ($channel)->listen (
|
||||
$channel => sub { return $parse_redis_message->(@_); }
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
my $irc = Mojo::IRC->new (
|
||||
nick => $c->{nick},
|
||||
user => $c->{nick},
|
||||
server => sprintf '%s:%s', $c->{server}, $c->{port},
|
||||
);
|
||||
|
||||
# Нахуй эту вашу проверку подлинности, нам работать надо, а не шашечки.
|
||||
if ($c->{ssl}) {
|
||||
$irc->tls({ 'insecure' => 1 });
|
||||
}
|
||||
|
||||
# включим обработку ctcp-событий
|
||||
$irc->parser(Parse::IRC->new(ctcp => 1));
|
||||
|
||||
# добавим коллбэк на момент "после подключения"
|
||||
$irc->connect(\&_on_connect);
|
||||
|
||||
# когда приходит это событие, сервер нас поприветствовал и мы можем уже пробовать идентифицироваться у nickserv
|
||||
# (если надо) и джойниться к каналам.
|
||||
$irc->on ( irc_rpl_endofmotd => sub { return $on_irc_end_of_motd->(@_); } );
|
||||
|
||||
# этот коллбэк будет зваться по мере бесед, сюда попадают все фразы и ctcp
|
||||
$irc->on ( irc_privmsg => sub { return $on_irc_privmsg->(@_); } );
|
||||
|
||||
# эти коллбэки будет зваться когда приходят ctcp запросы.
|
||||
$irc->on ( ctcp_source => sub { return $on_ctcp_source->(@_); } );
|
||||
$irc->on ( ctcp_finger => sub { return $on_ctcp_finger->(@_); } );
|
||||
$irc->on ( ctcp_userinfo => sub { return $on_ctcp_finger->(@_); } );
|
||||
$irc->on ( ctcp_ping => sub { return $on_ctcp_ping->(@_); } );
|
||||
$irc->on ( ctcp_time => sub { return $on_ctcp_time->(@_); } );
|
||||
$irc->on ( ctcp_clientinfo => sub { return $on_ctcp_clientinfo->(@_); } );
|
||||
|
||||
# зарегистрируем дефолтные коллбэки на всё, что не закастомизировано
|
||||
$irc->register_default_event_handlers;
|
||||
|
||||
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