Files
Autobot/bin/auto
T
2011-01-19 22:42:25 -07:00

226 lines
6.0 KiB
Perl
Executable File

#!/usr/bin/env perl
# Auto IRC Bot. An advanced, lightweight and powerful IRC bot.
# Copyright (C) 2010-2011 Xelhua Development Team (doc/CREDITS)
# This program is free software; rights to this code are stated in doc/LICENSE.
package Auto;
require 5.10.0;
use strict;
use warnings;
use POSIX;
use locale;
use Mouse;
use IO::Socket;
use Async;
use FindBin qw($Bin);
our $Bin = $Bin;
BEGIN { unshift(@INC, "$Bin/../src"); }
use API::Std qw(conf_get err);
use API::Log qw(println alog dbug);
#use DB::Flatfile;
use Parser::Config;
use Parser::Lang;
use Parser::IRC;
#use Sys;
# Set version information.
use constant {
NAME => 'Auto IRC Bot',
VER => 3,
SVER => 0,
REV => 0,
RSTAGE => 'd'
};
# Check for build files.
unless (-e "$Bin/../build/os" and -e "$Bin/../build/perl" and -e "$Bin/../build/time" and -e "$Bin/../build/ver") {
println "Missing build file(s). Please build Auto before running it." and exit;
}
# Check build OS.
open BFOS, "<$Bin/../build/os" or println "Cannot start: Broken build." and exit;
my @BFOS = <BFOS>;
close BFOS;
if ($BFOS[0] ne $^O."\n") {
println "Cannot start: Broken build." and exit;
}
undef @BFOS;
# Check build Perl version.
open BFPERL, "<$Bin/../build/perl" or println "Cannot start: Broken build." and exit;
my @BFPERL = <BFPERL>;
close BFPERL;
if ($BFPERL[0] ne $]."\n") {
println "Cannot start: Broken build." and exit;
}
undef @BFPERL;
# Check build Auto version.
open BFVER, "<$Bin/../build/ver" or println "Cannot start: Broken build." and exit;
my @BFVER = <BFVER>;
close BFVER;
if ($BFVER[0] ne VER.".".SVER.".".REV.RSTAGE."\n") {
println "Cannot start: Broken build." and exit;
}
undef @BFVER;
# Print startup message.
println "* ".NAME." (version ".VER.".".SVER.".".REV.RSTAGE.") is starting up...";
our ($PID);
# Check for debug mode.
our $DEBUG = 0;
if (defined $ARGV[0]) {
if ($ARGV[0] eq '-d') {
$DEBUG = 1;
}
}
# Parse configuration file.
println "* Parsing configuration file auto.conf...";
our $CONF = Parser::Config->new("auto.conf") or err(1, "Failed to parse configuration file!", 1);
our %SETTINGS = $CONF->parse or err(1, "Failed to parse configuration file!", 1);
println " Success";
# Check for required configuration values.
my @REQCVALS = qw(locale expire_logs server);
foreach my $REQCVAL (@REQCVALS) {
if (!conf_get($REQCVAL)) {
my $err = 2;
if ($REQCVAL eq "expire_logs") {
$err = 1;
}
err($err, "Missing required configuration value: $REQCVAL", 1);
}
}
undef @REQCVALS;
# Parse translations.
println "* Parsing translation files...";
our $LOCALE = (conf_get("locale"))[0][0];
my @lang = split('_', $LOCALE);
Parser::Lang::parse($lang[0]) or err(2, "Failed to parse translation files!", 1);
undef @lang;
println " Success";
# Expire old logs.
API::Log::expire_logs();
# Successful startup.
our $STARTTIME = time;
println "* Auto successfully started at ".POSIX::strftime("%Y-%m-%d %I:%M:%S %p", localtime).".";
alog "Auto successfully started.";
# Fork into the background if not in debug mode.
unless ($DEBUG) {
println "*** Becoming a daemon...";
open STDIN, '/dev/null' or err(2, "Can't read /dev/null: $!", 1);
open STDOUT, '>>/dev/null' or err(2, "Can't write to /dev/null: $!", 1);
open STDERR, '>>/dev/null' or err(2, "Can't write to /dev/null: $!", 1);
$PID = fork();
unless ($PID == 0) {
alog "* Successfully forked into the background. Process ID: ".$PID;
unless (-e "$Bin/auto.pid") {
`touch $Bin/auto.pid`;
}
open FPID, ">$Bin/auto.pid" or exit;
print FPID "$PID\n" or exit;
close FPID or exit;
exit;
}
POSIX::setsid() or err(2, "Can't start a new session: $!", 1);
}
else {
$PID = $$;
}
## Create sockets.
alog "* Connecting to servers...";
dbug "* Connecting to servers...";
# Get servers from config.
my %cservers = conf_get("server");
# Set the hashes and processes hashes.
my (%SOCKET, %SOCKPROC);
my $it = 0;
# Iterate through each configured server.
foreach my $cskey (keys %cservers) {
# Create the socket.
$SOCKET{$cskey} = IO::Socket::INET->new(
Proto => "tcp",
LocalAddr => $cservers{$cskey}{'bind'}[0],
PeerAddr => $cservers{$cskey}{'host'}[0],
PeerPort => $cservers{$cskey}{'port'}[0]
) or err(2, "Failed to connect to server: ".$cskey." [".$cservers{$cskey}{'host'}[0].":".$cservers{$cskey}{'port'}[0]."]", 0) and next; # Or error.
# Send PASS if we have one.
if (defined $cservers{$cskey}{'pass'}[0]) {
socksnd($cskey, "PASS :".$cservers{$cskey}{'pass'}[0]) or
err(2, "Failed to connect to server: ".$cskey." [".$cservers{$cskey}{'host'}[0].":".$cservers{$cskey}{'port'}[0]."]", 0)
and next;
}
# Send USER and NICK.
socksnd($cskey, "USER ".$cservers{$cskey}{'ident'}[0]." * * :".$cservers{$cskey}{'realname'}[0]) or
err(2, "Failed to connect to server: ".$cskey." [".$cservers{$cskey}{'host'}[0].":".$cservers{$cskey}{'port'}[0]."]", 0)
and next;
socksnd($cskey, "NICK ".$cservers{$cskey}{'nick'}[0]) or
err(2, "Failed to connect to server: ".$cskey." [".$cservers{$cskey}{'host'}[0].":".$cservers{$cskey}{'port'}[0]."]", 0)
and next;
# Create the process.
$SOCKPROC{$cskey} = Async->new(sub {
# Infinite loop.
while (1) {
# Read data from socket.
my $data = readline($SOCKET{$cskey});
# Lost connection: kill process.
unless (defined $data) {
die;
}
# Get rid of the newlines.
chomp $data;
# Debug.
dbug "$cskey -> You: $data";
# Parse data.
Parser::IRC::_parse($cskey, $data);
}
}) or err(2, "Failed to create process for ".$cskey, 0) and next;
# Success!
alog "** Successfully connected to server: ".$cskey;
dbug "** Successfully connected to server: ".$cskey;
$it = 1;
}
# Success!
if ($it) {
alog "** Success: Connected to server(s).";
dbug "** Success: Connected to server(s).";
}
else {
err(2, "No server connections.", 1);
}
undef $it;
# Keep Auto alive.
while (1) { sleep 1; }
###############
# Subroutines #
###############
# Send data to socket.
sub socksnd
{
my ($svr, $data) = @_;
if (defined $SOCKET{$svr}) {
send($SOCKET{$svr}, $data."\n", 0);
dbug "You -> $svr: $data";
return 1;
}
else {
return 0;
}
}