#!/usr/bin/perl -w

           ###################################################
           #                                                 #
           # Omnibot v2.2.4                                  #
           # (C) 2019 Domero, Email: chaosje@gmail.com       #
           #                                                 #
           # Generic IRC-bot kernel                          #
           #                                                 #
           # Tested networks:                                #
           # - Hybrid                                        #
           # - EFNet                                         #
           # - IRCNet                                        #
           # - UnderNet                                      #
           # - QuakeNet                                      #
           # - FreeNode                                      #
           # - InspIRCd                                      #
           #                                                 #
           # * Services compatible                           #
           # * Event-based                                   #
           # * Timed events                                  #
           # * Supports SSL                                  #
           # * Anti-Flood protection                         #
           # * Auto register/identify with NickServ          #
           # * Auto gain whois information on idle-time      #
           # * Auto join channels                            #
           #                                                 #
           ###################################################

package omnibot;

use strict;
use warnings;
use Exporter;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);

$VERSION     = '2.2.4';
@ISA         = qw(Exporter);
@EXPORT      = ();
@EXPORT_OK   = qw();

use Socket;
use IO::Handle;
use POSIX qw(:sys_wait_h EAGAIN EBUSY);
use Time::HiRes qw ( usleep gettimeofday );
use gclient 7.5.1;

my $SERVER; 

my $rnd=int rand(99999990)+10;

sub init {
  my $self = {

    # $self->{client} is empty at init. You have to set this to the client of the distributing server,
    # which is a hash reference, you can make $client->{omni} point back to omnibot to be uniform in your own scripts.
    # It will be passed in events, creating a man in the middle server for, by example websocket html irc clients or partylines.
    # Leave undefined when running as a client, or fill it with any data you want to pass!

    id => $VERSION,                                # Version identification

    loopmode => 1,                                 # run in infinite loop, when set to 0 you have to call $self->takeloop continously.
                                                   # set to 1 when you run multiple IRC-clients in one server.
    client => {},                                  # Any hash reference to be passed in events (make a multi-client server!!).

    port => 6667,                                  # Port on which to connect to server
    server => 'lichtsnel.nl',                      # Server to connect to
    ssl => 0,                                      # Connect using SSL
    connecttries => 1000,                          # Maximum tries to connect to server (0=infinite)
    timeout => 1300,                               # Ping-Timeout (very dependent on server-config)
    connecttime => 10,                             # Seconds before a connection attempt is timed out
    connectlooptime => 0.1,                        # Seconds to wait between connection attempts in loopmode
    servername =>'LichtSnel IRC Community',        # Server description
    serverpass => '',                              # Server password
    nick => 'omnibot',                              # Name of your bot on the chat
    altnick => 'omnibot_',                          # Alternative nick if nick in use
    channels => [ '#lichtsnel' ],                  # All default channels to join
    ident => 'omnibot',                             # Ident
    desc => 'Omnibot Chat',                         # Description
    nickserv => "NickServ!nickserv\@services.lichtsnel.nl", # Used to identify NickServ (some Networks spoof NickServ to gain passwords)
    autoreg => 0,                                  # automatic register
    password => '',                                # Used for NickServ to register/identify your nick, if present and autoreg==1
    isoper => 0,                                   # Set if IRC-oper flag detected
    email => "ls$rnd\@lichtsnel.nl",               # Email needed to register nick
    quitmessage => "LichtSnel IRC Community :: http://lichtsnel.nl",  # Quit message
    version => "Omnibot v$VERSION (C) 2019 Chaosje, Domero BV Groningen", # Bot kernel version, you can change the version using $self->version for your own bot(s)

    gaininfo => 1,                                 # Gain information on nicks on idle-time; gain will collect the description of users through whois calls!

    stripmakeup => 1,                              # Strip chat-data from italic, bold, underline, strikethrough, colors.

    # It's good policy not to let a bot have operator status, unless you are in a botwar ;)
    preventop => 1,                                # Bot may or may not be @ (disable for botwars)
    preventhalfop => 1,                            # Bot may or may not be %
    preventvoice => 0,                             # Bot may or may not be +

    timerdelay => 0.05,                            # seconds interval to check the timerlist
    inputdelay => 30000,                           # microseconds interval for input-checks from irc-server
    outputdelay => 0.6,                            # seconds interval to output to the irc-server (don't excess flood yourself!)
    gaindelay => 0.5,                              # seconds interval on idle-time to check the whois of the user-infolist

    antiflood => 1,                                # antiflood buffer, set to 0 to disable (disable for botwars)
    floodtimeout => 30,                            # seconds before flood is not seen as flood
    floodlines => 1,                               # number of duplicate lines allowed before it is seen as flood

    verbose => 1,                                  # show/hide Debug-mode, see all raw, except the following parameters.. (if 1 show them)
    verbosepong => 0,                              # show/hide the ping/pong event
    verboseevents => 1,                            # show/hide notifications of events being called
    verbosemotdevents => 0,                        # show/hide the events generated by MOTD
    verbosemotd => 0,                              # show/hide the MOTD
    verboseinput => 0,                             # show/hide all input from the irc-server
    verboseoutput => 0,                            # show/hide all output to the irc-server
    verbosewhois => 0,                             # show/hide all whois calls
    verboseprivmsg => 0,                           # show/hide the raw privmsg calls

    events => {},                                  # fill using $self->addevent('event',\&eventproc)
    timers => {},                                  # fill using $self->addtimer('name',repeat,seconds-interval(float),\&timerproc)

    autoping => 60,                                # ping the server automatically (ensure to stay online)

  };
  bless $self; $SERVER=$self; return $self
}

###############################################################################
1;

sub start {
  my ($self) = @_;

  # Internal variables
  $self->{modes}="";
  $self->{me}=$self->{nick};
  $self->{chan}={};
  $self->{list}=[];
  $self->{serverprop}={};
  $self->{inputbuffer}="";
  $self->{buffer}=();
  $self->{buffertime}=gettimeofday();
  $self->{infotime}=gettimeofday();
  $self->{lastping}=gettimeofday();
  $self->{lasttimercheck}=gettimeofday();
  $self->{floodbuffer}={};

  # nickserv
  $self->{nickservtested}=0;
  $self->{nsfound}=0;
  if ($self->{nickserv} =~ /^(.+)?\!(.+)?\@(.+)$/) {
    $self->{nsnick}=$1;
    $self->{nsident}=$2;
    $self->{nshost}=$3
  }

  $self->{nicklist}={};   # all users found
  $self->{infolist}=[];   # which users still have to be added to the IAL.
  $self->{infolag}=0;     # did the info finish already before calling a new
  $self->{infoevents}={}; # when the bot does a whois

  $self->{initconnect}=1;
  $self->{connecttry}=0;

  my $ircclient=gclient::tcpip($self->{server},$self->{port},0,\&events,$self->{ssl},1);
  $ircclient->{omni}=$self;
  $ircclient->{omni}{ircserver}=$ircclient;
  if ($ircclient->{error}) { $self->{error}=$ircclient->{error}; return $self }
  $self->{connected}=1;

  if ($self->{autoping}) {
    $ircclient->{omni}->addtimer('autoping',30,0,$self->{autoping},\&autoping)
  }

  if ($self->{loopmode}) {
    while (!$ircclient->{quit}) { $ircclient->{omni}->serverloop }
  }

  return $ircclient
}

sub handshake {
  my ($self) = @_;
  $self->{ircserver}->out("NICK $self->{nick}\r\n");
  if ($self->{verboseoutput}) { print "--> NICK $self->{nick}\r\n" }
  $self->{ircserver}->out("USER $self->{ident} $self->{ident} $self->{ident} \:$self->{desc}\r\n\r\n");
  if ($self->{verboseoutput}) { print "--> USER $self->{ident} $self->{ident} $self->{ident} \:$self->{desc}\r\n" }
  $self->{initconnect}=0
}

sub serverloop {
  my ($self) = @_;
  usleep(10000);
  $self->flush;
  $self->{ircserver}->takeloop()
}

sub events {
  my ($client,$cmd,$data) = @_;
  my $self = $client->{omni};
  if ($cmd eq 'input') {
    if ($self->{initconnect}) {
      $self->handshake()
    } else {
      $self->processinput($data)
    }
  } elsif ($cmd eq 'quit') {
    $self->stop('Lost connection to server')
  }
}

sub stop {
  my ($self,$stopmessage,$noquit) = @_;
  if ($self->{alreadyquit}) { 
    if ($noquit) { return }
    exit
  }
  if ($stopmessage) {
    $self->{quitmessage}=$stopmessage;
    $self->{error}=$stopmessage
  }
  $self->event(0,'disconnect',$self->{error});
  $self->serverloop();
  if ($self->{connected}) {
    gclient::out($self->{ircserver},"QUIT \:$self->{quitmessage}\r\n");
    $self->serverloop();
    if ($self->{verboseoutput}) {
      print "--> QUIT \:$self->{quitmessage}\r\n";
    }
    $self->{connected}=0;
    $self->{quit}=1;
    $self->{alreadyquit}=1;
    if ($self->{verbose}) {
      print STDOUT "!!! Lost connection to $self->{server}\n";
    }
  }
}

sub autoping {
  my $rnd=int rand(100000000);
  $SERVER->output("PING :omnibot$rnd\r\n")
}

################# USABLE FUNCTIONS ###########################################

sub flush {
  my ($self) = @_;
  $self->handletimers;
  $self->outputbuffer;
}

sub addevent {
  my ($self,$event,$procptr) = @_;
  $self->{events}{$event}=$procptr
}

sub delevent {
  my ($self,$event) = @_;
  if ($self->{events}{$event}) {
    delete $self->{events}{$event}
  }
}

sub addtimer {
  # seconds maybe float, repeat=0 is infinite
  my ($self,$name,$start,$repeat,$seconds,$procptr,@params) = @_;
  if (!$self->{timers}{$name}) {
    $self->{timers}{$name} = {}
  }
  $self->{timers}{$name}{start}=$start;
  $self->{timers}{$name}{repeat}=$repeat;
  $self->{timers}{$name}{seconds}=$seconds;
  $self->{timers}{$name}{event}=$procptr;
  $self->{timers}{$name}{activate}=gettimeofday()+$start;
  $self->{timers}{$name}{params}=\@params
}

sub deltimer {
  my ($self,$name) = @_;
  if ($self->{timers}{$name}) {
    delete $self->{timers}{$name}
  }
}

sub raw {
  my ($self,$command,$target,$message,$nosemi) = @_;
  if ($nosemi) {
    $self->output(uc($command)." $target $message\r\n")
  } else {
    $self->output(uc($command)." $target \:$message\r\n")
  }
}

sub setnick {
  my ($self,$nick) = @_;
  $self->output("NICK $nick\r\n");
}

sub msg {
  my ($self,$target,$text) = @_;
  if (defined $text && length($text)) {
    $self->output("PRIVMSG $target \:$text\r\n")
  }
}

sub action {
  my ($self,$target,$text) = @_;
  if (defined $text && length($text)) {
    $self->output("PRIVMSG $target \:\001ACTION $text\001\r\n")
  }
}

sub notice {
  my ($self,$target,$text) = @_;
  if (defined $text && length($text)) {
    $self->output("NOTICE $target \:$text\r\n")
  }
}

sub gokick {
  my ($self,$channel,$nick) = @_;
  $self->output("KICK $channel $nick\r\n")
}

sub nickinfo {
  my ($self,$nick) = @_;
  if ($self->{nicklist}{lc($nick)}) {
    return $self->{nicklist}{lc($nick)}
  }
  return {}
}

sub isop {
  my ($self,$nick,$channel) = @_;
  return $self->{chan}{lc($channel)}{user}{lc($nick)}{o}
}
sub ishalfop {
  my ($self,$nick,$channel) = @_;
  return $self->{chan}{lc($channel)}{user}{lc($nick)}{h}
}
sub isvoice {
  my ($self,$nick,$channel) = @_;
  return $self->{chan}{lc($channel)}{user}{lc($nick)}{v}
}
sub ispresent {
  my ($self,$nick,$channel) = @_;
  return $self->{chan}{lc($channel)}{user}{lc($nick)}{present}
}

sub onchannel {
  my ($self,$channel,$nick) = @_;
  if (!$nick) { $nick=$self->{me} }
  if ($self->{chan}{lc($channel)}{user}{lc($nick)}) { return 1 }
  return 0
}
sub joinchannel {
  my ($self,$channel,$key) = @_;
  if (length($key)) {
    $self->output("JOIN $channel $key\r\n")
  } else {
    $self->output("JOIN $channel\r\n")
  }
}
sub partchannel {
  my ($self,$channel) = @_;
  $self->output("PART $channel\r\n")
}
sub numusers {
  my ($self,$channel) = @_;
  if ($self->{chan} && $self->{chan}{lc($channel)} && $self->{chan}{lc($channel)}{user}) {
    my @nicks=keys %{$self->{chan}{lc($channel)}{user}};
    return 0+@nicks
  }
  return 0  
}
sub userlist {
  my ($self,$channel) = @_;
  my $nicks=[];
  if ($self->{chan} && $self->{chan}{lc($channel)} && $self->{chan}{lc($channel)}{user}) {
    foreach my $nick (sort keys %{$self->{chan}{lc($channel)}{user}}) {
      push @$nicks,$nick
    }
  }  
  return $nicks
}
sub userident {
  my ($self,$nick) = @_;
  if ($self->{nicklist}{lc($nick)}) {
    return $self->{nicklist}{lc($nick)}{ident}
  }
  return ""
}
sub userhost {
  my ($self,$nick) = @_;
  if ($self->{nicklist}{lc($nick)}) {
    return $self->{nicklist}{lc($nick)}{host}
  }
  return ""
}

sub hopchannel {
  my ($self,$channel) = @_;
  $self->partchannel($channel);
  $self->joinchannel($channel)
}

sub whois {
  my ($self,$nick,$event) = @_; # $event = \&mysub($ptr to hashinfo)
  if (!$self->{nicklist}{lc($nick)}) {
    $self->{nicklist}{lc($nick)}={ info => 0, present => 1, nick => $nick }
  }

  # we need an event because we can only respond when the whois is finished on raw 318
  unshift @{$self->{infolist}},$self->{nicklist}{lc($nick)}; # make this a priority
  $self->{nicklist}{lc($nick)}{info}=0; # mark as being unread otherwise event will not fire
  if (defined($event)) {
    if (ref($event) eq 'CODE') {
      $self->{infoevents}{lc($nick)}=$event;
    }  
  }
  if (!$self->{gaininfo}) { $self->gaininfo }
#  } elsif (defined($event) && (ref($event) eq 'CODE')) {
#    # nick already quit
#    &$event({})
#  }
}

sub gettopic {
  my ($self,$channel) = @_;
  return $self->{chan}{lc($channel)}{topic}
}

sub topic {
  my ($self,$channel,@topic) = @_;
  my $topic=join(" ",@topic);
  if (length($topic)) {
    $self->output("TOPIC $channel \:$topic\r\n")
  } else {
    $self->output("TOPIC $channel\r\n")
  }  
}

sub cleartopic {
  my ($self,$channel) = @_;
  $self->output("TOPIC $channel :\r\n")
}

sub chanserv_topic {
  my ($self,$channel,$topic,$newstyle) = @_;
  if ($newstyle) {
    $self->msg("ChanServ","TOPIC $channel $topic\r\n")
  }
  else {
    $self->msg("ChanServ","SET $channel TOPIC $topic\r\n")
  }
}

sub setmode {
  my ($self,$nick,$mode) = @_;
  $self->output("MODE $nick +$mode\r\n")
}
sub unsetmode {
  my ($self,$nick,$mode) = @_;
  $self->output("MODE $nick -$mode\r\n")
}

sub setownmode {
  my ($self,$mode) = @_;
  $self->output("MODE $self->{me} +$mode\r\n");
  if ($self->{modes} !~ /$mode/) {
    $self->{modes}.=$mode
  }  
}
sub unsetownmode {
  my ($self,$mode) = @_;
  $self->output("MODE $self->{me} -$mode\r\n");
  if ($self->{modes} =~ /$mode/) {
    $self->{modes} =~ s/$mode//g
  }  
}

###########################################################################

sub ascii {
  my ($self,$curline) = @_;
  for (my $i=0;$i<length($curline);$i++) {
    my $c=substr($curline,$i,1);
    print "[";
    if ((ord($c)!=13) && (ord($c)!=10)) { print $c }
    print "=".ord($c)."]"
  }
  print "\n";
}

sub processinput {
  my ($self,$line)=@_;
  if (length($line)) { $self->{connecting}=0 }
  my $process=$line;
  $process =~ s/\r$//;
  if ($self->{stripmakeup}) {
    # strip colors, bold, inverse, underline, italic, strikethrough, etc
    $line =~ s/\x03[0-9]{1,2}(,[0-9]{1,2})?//g;
    $process="";
    for (my $i=0;$i<length($line);$i++) {
      my $c=substr($line,$i,1);
      my $o=ord($c);
      if (($o>=32) || ($o==10) || ($o==1)) { # 1 is used for CTCP, 10 is newline
        $process.=$c
      }
    }  
  }
  if ($self->{verbose}) {
    my @s=split(/\s/,$process);
    if ($s[1] !~ /[^0-9]/) {
      if (($s[1]<=300) || ($s[1]>=320)) {
        if ($self->{verbosemotd} || (($s[1] != 372) && ($s[1] != 375) && ($s[1] != 376))) {
          my $ppr=$process; $ppr =~ s/\r/\\r/g; $ppr =~ s/\n/\\n/g;
          print STDOUT '"'.$ppr."\"\n";
        }
      }
    } else {
      if ($self->{verboseprivmsg} || ($process !~ /privmsg/i)) {
        if ($self->{verbosepong} || ($process !~ /p[i|o]ng/i)) {
          my $ppr=$process; $ppr =~ s/\r/\\r/g; $ppr =~ s/\n/\\n/g;
          print STDOUT '"'.$ppr."\"\n";
        }
      }
    }
  }
  $self->handle($process);
}

sub gaininfo {
  my ($self) = @_;
  my $nick=shift @{$self->{infolist}};
  if ($nick->{present} && !$nick->{info}) {
    $self->{infolag}=1;
    $nick->{info}=1;
    gclient::out($self->{ircserver},"WHOIS $nick->{nick}\r\n");
    if ($self->{verbosewhois} && $self->{verboseoutput}) { print "--> WHOIS $nick->{nick}\r\n" }
  }
}

sub antiflood {
  my ($self,$raw) = @_;
  if (!$raw) { return 1 }
  if (!$self->{antiflood}) { return 1 }
  my $count=1;
  # clean up buffer or report spam, and quickly return, this will not completely clean the buffer but will keep it very fast, and in time it will be cleaned!
  foreach my $flood (keys %{$self->{floodbuffer}}) {
    if (time-$flood>=$self->{floodtimeout}) {
      # Timed out
      delete $self->{floodbuffer}{$flood}
    } elsif ($self->{floodbuffer}{$flood}{raw} eq $raw) {
      # Report flood
      $count++;
      if ($count>=$self->{floodlines}) {
        return 0
      }  
    }
  }
  # Add to buffer
  my $tm=time;
  $self->{floodbuffer}{$tm} = { raw => $raw };
  return 1
}

sub output {
  my ($self,$raw) = @_;
  if (!$raw) { return }
  if ($self->antiflood($raw)) {
    my $pnt=0;
    if (length($raw)>450) {
      my ($pre,@actxt) = split(/\:/,$raw);
      my $ctxt=join(":",@actxt); my $len=length($ctxt);
      my $line=""; my $p=0; my %flags=('b'=>0,'u'=>0,'s'=>0,'i'=>0,'c'=>-1,'g'=>-1); my $cm=0; my $htm=0; my $htmp=0; my $onlyspace=1;
      while ($p<$len) {
        my $c=substr($ctxt,$p,1); my $cc=ord($c);
        if ($cm==1) {
          if (($c lt '0') || ($c gt '9')) {
            if ($c eq ',') {
              if ($flags{'c'}<0) { $cm=0; $flags{'g'}=-1 }
              else { $cm=2 }
            } else {
              $cm=0; if ($flags{'c'}<0) { $flags{'g'}=-1 }
            }  
          } else {
            if (($flags{'c'}>=0) && (length($flags{'c'})==2)) {
              $cm=0
            } else {
              if ($flags{'c'}<0) { $flags{'c'}=$c } else { $flags{'c'}.=$c }
            }
          }
        } elsif ($cm==2) {
          if (($c ge '0') && ($c le '9')) {
            if (($flags{'g'}>=0) && (length($flags{'g'})==2)) { $cm=0 }
            else {
              if ($flags{'g'}<0) { $flags{'g'}=$c } else { $flags{'g'}.=$c }
            }  
          } else {
            $cm=0
          }  
        }
        if ($cc==2) { $flags{'b'}=1-$flags{'b'} }
        elsif ($cc==3) { $cm=1; $flags{'c'}=-1  }
        elsif ($cc==15) { $flags{'b'}=0; $flags{'u'}=0; $flags{'s'}=0; $flags{'i'}=0; $flags{'c'}=-1; $flags{'g'}=-1; $cm=0 }
        elsif ($cc==19) { $flags{'s'}=1-$flags{'s'} }
        elsif (($cc==21) || ($cc==31)) { $flags{'u'}=1-$flags{'u'} }
        elsif ($cc==22) { my $cur=$flags{'c'}; $flags{'c'}=$flags{'b'}; $flags{'b'}=$cur }
        elsif ($cc==29) { $flags{'i'}=1-$flags{'i'} }
        if ($htm) {
          if ($c !~ /[A-Za-z0-9\.\~\!\@\#\$\%\^\&\*\(\)\_\-\+\=\{\}\[\]\:\;\'\,\?\/]/) {
            $htm=0
          }
        }
        if (lc(substr($ctxt,$p,7)) eq 'http://') {
          $htm=1; $htmp=length($line); $line.=substr($ctxt,$p,7); $p+=7
        } elsif (lc(substr($ctxt,$p,8)) eq 'https://') {
          $htm=1; $htmp=length($line); $line.=substr($ctxt,$p,8); $p+=8
        } else {
          $line.=$c; $p++;
        }
        if (($cc != 32) && ($cc != 160)) { $onlyspace=0 }
        if ((length($line)>=445) || ($p>=$len)) {
          if (!$cm || ($p>=$len)) {
            my $newline="";
            if ($p<$len) {
              if ($htm) {
                if ($htmp>50) {
                  $newline=substr($line,$htmp);
                  $line=substr($line,0,$htmp)
                } else {
                  $htm=0
                }
              }
              $line.=chr(15)." ...\r\n"
            }
            if (!$onlyspace) {
              push @{$self->{buffer}},$pre.":".$line;
            }
            $line="... "; $onlyspace=1;
            if ($flags{'b'}) { $line.=chr(2) }
            if ($flags{'s'}) { $line.=chr(19) }
            if ($flags{'i'}) { $line.=chr(29) }
            if ($flags{'u'}) { $line.=chr(31) }
            if (($flags{'c'}>=0) || ($flags{'g'}>=0)) { 
              $line.=chr(3);
              if ($flags{'c'}>=0) { $line.=$flags{'c'} } else { $line.='16' }
              if ($flags{'g'}>=0) { $line.=','.$flags{'g'} }
              $line.=chr(2).chr(2)
            }
            $line.=$newline
          }
        }
      } 
    } else {
      push @{$self->{buffer}},$raw
    }
  } else {
    $self->event(0,'flood',$raw)
  }
}

sub ownmessage {
  my ($self,$raw) = @_;
  if (substr($raw,0,7) eq 'PRIVMSG') {
    my ($pre,@rest) = split(/\:/,$raw);
    my $txt=join(':',@rest);
    my $chan=substr($pre,8,length($pre)-9);
    $self->event(0,'ownmsg',$txt,$chan,$self->{me},$self->{ident},$self->{host})
  }
}

sub outputbuffer {
  my ($self) = @_;
  if ($#{$self->{buffer}}>=0) {
    $self->{client}{beenactive}=1;
    my $curtime=gettimeofday();
    if ($#{$self->{buffer}}>=3) {
      my $delta=$curtime-$self->{buffertime};
      if ($delta>$self->{outputdelay}) {
        my $raw=shift @{$self->{buffer}};
        gclient::out($self->{ircserver},$raw);
        $self->ownmessage($raw);
        if ($self->{verboseoutput}) {
          if ($self->{verbosepong} || ($raw !~ /\sP[I|O]NG\s/)) {
            print "--> $raw"
          }
        }
        $self->{buffertime}=$curtime;
      }
    } else {
      my $raw=shift @{$self->{buffer}};
      gclient::out($self->{ircserver},$raw);
      $self->ownmessage($raw);
      if ($self->{verboseoutput}) {
        if ($self->{verbosepong} || ($raw !~ /\sP[I|O]NG\s/)) {
          print "--> $raw"
        }
      }
      if ($self->{gaininfo}) {
        # not much to do, so gain some information :)
        my $gaindelta=$curtime-$self->{infotime};
        if ($gaindelta>$self->{gaindelay}) {
          $self->gaininfo
        }
      }  
    }
    $self->{infotime}=$curtime;
  } elsif ($self->{gaininfo}) {
    my $curtime=gettimeofday();
    my $gaindelta=$curtime-$self->{infotime};
    if (($gaindelta>$self->{gaindelay}) && !$self->{infolag}) {
      $self->gaininfo;
      $self->{infotime}=$curtime;
    }
  }
}

sub handle {
  # Handle IRCD input and redirect it or react to it.
  my ($self,$msg)=@_;
  if ($self->{verboseinput}) {
    print " <-- $msg\n"
  }
  my @s=split(/\s/,$msg);
  if (substr($msg,0,5) eq 'ERROR') {
    $self->{quit}=1;
    if ($msg =~ /killed/i) {
      $self->{error}="You were killed from the server!";
      my @ks=split(/killed\s/i,$msg);
      if ($self->{verbose}) {
        print STDOUT "*** YOU WERE KILLED! $ks[1]\n"
      }
      $self->event(0,'killed',$ks[1])
    } else {
      $self->{error}=substr($msg,7);
    }
    $self->stop;
    return
  }
  if ($msg =~ /Found your hostname/i) {
    $self->{server}=shift @s;    
    if (substr($self->{server},0,1) eq ':') {
      $self->{server}=substr($self->{server},1)
    }
    if ($self->{server} eq 'NOTICE') {
      $self->{server}='EFNET'
    }
    return
  }  
  if ($msg =~ /ping \:(.+)$/i) {
    my $pong=$1;
    $self->dopong($pong);
    $self->{lastping}=gettimeofday();
    $self->{pongmsg}=$pong;
    return
  }
  if ($msg =~ /pong .*\:(.+)$/i) {
    if ($self->{verbosepong}) {
      print "<- PONG :".$1."\n"
    }
    if ($self->{randomping}) {
      if ($1 eq 'omnibot'.$self->{randomping}) {
        $self->{lastping}=gettimeofday();
        $self->{randomping}=0
      }
    }
    return
  }
  my $code=0;
  if ($s[1] eq '001') {
    $self->{server}=substr($s[0],1);
    $self->{connected}=1;
  }
  if (($s[0] eq $self->{server}) || ($s[0] eq "\:$self->{server}")) {
    # Server messages
    if ($s[1] =~ /[0-9]{3}/) {
      my $rawfunc="raw_".$s[1];
      if (defined(&$rawfunc)) {
        $self->$rawfunc(@s[2..$#s])
      }
    } elsif ($s[1] eq 'NOTICE') {
      $self->snotice(@s[2..$#s])
    } elsif ($s[1] eq 'MODE') {
      $self->smode(@s[2..$#s])
    }
    return
  }
  if ($s[0] =~ /^:(.+)?\!\~?(.+)?\@(.+)$/) {
    # Client messages
    if ($1 eq $self->{me}) {
      if (!$self->{host}) {
        $self->{host}=$3
      }
      $self->ownmsg(@s[1..$#s]);
    } else {
      $self->message(@s)
    }
    return
  }
  if ($self->{quit} && $self->{connected}) {
    $self->stop
  }
}

sub dopong {
  my ($self,$pong) = @_;
  if ($self->{ircnetid}) {
    gclient::out($self->{ircserver},"PONG $self->{server} \:$pong\r\n");
    if ($self->{verbosepong}) {
      print STDOUT "--> PONG $self->{server} \:$pong\n";
    }
  } else {
    gclient::out($self->{ircserver},"PONG \:$pong\r\n");
    if ($self->{verbosepong}) {
      print STDOUT "--> PONG \:$pong\n";
    }
  }
}

sub handletimers {
  my ($self)=@_;
  my $curtime=gettimeofday();
  if ($curtime-$self->{lasttimercheck}>$self->{timerdelay}) {
    $self->{lasttimercheck}=$curtime;
    my @list = sort { $self->{timers}{$a}{activate} <=> $self->{timers}{$b}{activate} } keys %{$self->{timers}};
    foreach my $timer (@list) {
      if ($self->{timers}{$timer}{activate}>$curtime) { return } # we're done, rest of timers is later then now
      # fire event
      my $proc=$self->{timers}{$timer}{event};
      &$proc($timer,$curtime,$self->{timers}{$timer}{repeat},$self->{timers}{$timer}{params});
      if ($self->{timers}{$timer}{repeat}>0) {
        $self->{timers}{$timer}{repeat}--;
        if ($self->{timers}{$timer}{repeat}==0) {
          $self->deltimer($timer)
        } else {
          $self->{timers}{$timer}{activate}+=$self->{timers}{$timer}{seconds}
        }
      } else {
        $self->{timers}{$timer}{activate}+=$self->{timers}{$timer}{seconds}
      }
    }
  }
}


sub event {
  my ($self,$level,$event,$message,$channel,$nick,$ident,$host) = @_;
  # level 0 = me
  # level 1 = server
  # level 2 = nick/channel
  if (!$channel) { $channel="" }
  if ($self->{events}{$event}) {
    $event=lc($event);
    if (ref($channel) ne 'ARRAY') {
      if (substr($channel,0,1) eq ':') {
        $channel=substr($channel,1)
      }
      ($channel) = split(/ /,$channel); # obsolete, but to be sure.
    }  
    my $func=$self->{events}{$event};
    if ($self->{verboseevents}) {
      if ($self->{verbosewhois} || ($event ne 'whois')) {
        if ($self->{verboseprivmsg} || ($event ne 'privmsg')) {
          if (($event ne 'motd') || $self->{verbosemotdevents}) {
            print STDOUT "*** Calling $event -> $level,".($message || "").",".($channel || "").",".($nick || "").",".($ident || "") .",".($host || "")."\n";
          }  
        }
      }
    }
    &$func($self->{client},$level,$message,$channel,$nick,$ident,$host)
  }
}

sub stripinfo {
  my ($self,@params) = @_;
  shift @params;
  $params[0]=substr($params[0],1,length($params[0])-1);
  return join(" ",@params)
}

sub masksplit {
  my ($self,$mask) = @_;
  $mask =~ s/\~//g;
  if ($mask =~ /^:(.+)?\!(.+)?\@(.+)$/) {
    my $ident=$2;
    $ident =~ s/\~//g;
    return ($1,$ident,$3);
  }
}

sub parsemodes {
  my ($self,$modes,$chan) = @_;
  my $i=0; my $a=1; my $l=length($modes); my $val='+';
  while ($i<$l) {
    my $c=substr($modes,$i,1);
    if ($c ne ':') {
      if ($c eq '+') { $a=1; $val='+' }
      elsif ($c eq '-') { $a=0; $val='-' }
      else {
        if ($a) {
          if ($self->{chan}{$chan}{modes} !~ /$c/) {
            $self->{chan}{$chan}{modes}.=$c
          }
        } else {
          $self->{chan}{$chan}{modes} =~ s/$c//g          
        }
        $self->event(1,'mode',"$val$c",$chan)
      }
    }
    $i++
  }
}

sub gettime {
  my @t=localtime($_[0]);
  my $sec=$t[0]; if ($sec<10) { $sec="0$sec" }
  my $min=$t[1]; if ($min<10) { $min="0$min" }
  my $hr=$t[2]; if ($hr<10) { $hr="0$hr" }
  return "[$hr:$min:$sec]"
}

sub getdate {
  my @t=localtime($_[0]);
  my $time=gettime($_[0]);
  my $day=$t[3]; my $mon=$t[4]+1; my $year=$t[5]+1900;
  return "$day-$mon-$year $time"
}

sub joinchannels {
  my ($self) = @_;
  if (!$self->{alreadyjoined}) {
    if ($self->{verbose}) {
      print STDOUT "*** Joining channels.. \n";
    }  
    foreach my $chan (@{$self->{channels}}) {
      gclient::out($self->{ircserver},"JOIN $chan\r\n");
      if ($self->{verboseoutput}) { print "--> JOIN $chan\r\n" }
    }
  }
}

sub channelmodes {
  my ($self,$channel) = @_;
  gclient::out($self->{ircserver},"MODE $channel\r\n")
}

sub setchannelmode {
  my ($self,$channel,$mode,$param) = @_;
  if ($param) {
    gclient::out($self->{ircserver},"MODE $channel +$mode $param\r\n")
  } else {
    gclient::out($self->{ircserver},"MODE $channel +$mode\r\n")
  }
}

sub unsetchannelmode {
  my ($self,$channel,$mode,$param) = @_;
  if ($param) {
    gclient::out($self->{ircserver},"MODE $channel -$mode $param\r\n")
  } else {
    gclient::out($self->{ircserver},"MODE $channel -$mode\r\n")
  }
}

sub channelinfo {
  my ($self,$channel) = @_;
  my $topic=$self->{chan}{lc($channel)}{topic};
  my $modes=$self->{chan}{lc($channel)}{modes};
  my $key=$self->{chan}{lc($channel)}{key};
  my $limit=$self->{chan}{lc($channel)}{limit};
  my $created=$self->{chan}{lc($channel)}{created};
  return ($modes,$key,$limit,$created,$topic)
}

sub nickservtest {
  my ($self) = @_;
  if ($self->{nsnick} && !$self->{nickservtested}) {
    # fire whois for verification
    gclient::out($self->{ircserver},"WHOIS $self->{nsnick}\r\n");
    if ($self->{verbose}) {
      print "*** Checking NickServ '$self->{nsnick}'\n"
    }  
  } else {
    $self->{nickservtested}=1
  }
}

sub nickservidentify {
  my ($self) = @_;
  # fire identify
  gclient::out($self->{ircserver},"PRIVMSG $self->{nsnick} :IDENTIFY $self->{password}\r\n");
}

sub nickservmsg {
  my ($self,$message) = @_;
  if (($message =~ /nick.+?not.+?registered/i) || ($message =~ /nick.+?isn.t.+?registered/i)) {
    if ($self->{autoreg}) {
      # register our nick
      if ($self->{verbose}) {
        print STDOUT "*** Registering our nick to NickServ\n";
      }  
      gclient::out($self->{ircserver},"PRIVMSG $self->{nsnick} :REGISTER $self->{password} $self->{email}\r\n");
    } else {
      $self->joinchannels
    }  
  }
  elsif (($message =~ /password.+?remember/i) || ($message =~ /nick.+?registered/i)) {
    # registered succesfully
    if ($self->{verbose}) {
      print STDOUT "*** Succesfully Registered our nick to NickServ\n";
    }  
    $self->joinchannels
  }
  elsif ($message =~ /password.+?accepted/i) {
    if ($self->{verbose}) {
      print "*** Identified to NickServ\n";
    }  
    $self->joinchannels
  }
  elsif ($message =~ /already.+?identified/i) {
    if ($self->{verbose}) {
      print "*** Already identified to NickServ\n";
    }  
    $self->joinchannels
  }
  $self->event(2,'notice',$message,undef,"NickServ")
}

sub initconnect {
  my ($self) = @_;
  if (!$self->{nickservtested}) {
    $self->nickservtest
  }
  if ($self->{nickservtested}) {
    $self->joinchannels
  }
}

##########################################################################

################  Server events

sub snotice {
  my ($self,@params) = @_;
  if ($self->{verbose}) {
    print STDOUT "*** SNOTICE ".$self->stripinfo(@params)."\n";
  }  
  $self->event(1,'notice',join(" ",substr($params[1],1),@params[2..$#params]),undef,$self->{server})
}

sub smode {
  my ($self,@params) = @_;
  my $chan=$params[0];
  $self->parsemodes($params[1],lc($chan));
}

#################  Own events

sub ownmode {
  my ($self,$target,$mode,$pars) = @_;
  if ($target =~ /\#/) {
    $self->mode($target,join(" ",$mode,$pars),$self->{nick},$self->{ident},$self->{host});
    return
  }
  my $val='+'; $mode =~ s/\://; my $param;
  ($mode,$param) = split(/ /,$mode);
  if (!$param) { $param="" }
  for (my $c=0;$c<length($mode);$c++) {
    my $a=substr($mode,$c,1);
    if ($a eq '+') { $val='+' }
    elsif ($a eq '-') { $val='-' }
    else {
      $self->event(0,'ownmode',"$val$a $param",$self->{nick},$self->{ident},$self->{host});
      if ($val eq '+') {
        if ($self->{modes} !~ /$a/) {
          $self->{modes}.=$a
        }
      } else {
        if ($self->{modes} =~ /$a/) {
          $self->{modes} =~ s/$a//g
        }
      }
    }     
  }  
  if ($self->{verbose}) {
    print STDOUT "*** OWNMODE $mode $param\n";
  }  
}

sub ownjoin {
  my ($self,$channel) = @_;
  $self->{chan}{lc($channel)}={ modes => "", user => {} };
  if ($self->{verbose}) {
    print STDOUT "*** JOINED $channel\n";
  }
  $self->channelmodes($channel);
  $self->event(0,"join","",$channel,$self->{nick},$self->{ident},$self->{host});
}

sub ownpart {
  my ($self,$channel) = @_;
  delete $self->{chan}{lc($channel)};
  if ($self->{verbose}) {
    print STDOUT "*** PARTED $channel\n";
  }  
  $self->event(0,"part","",$channel,$self->{nick},$self->{ident},$self->{host})
}

sub owntopic {
  my ($self,$channel,$topic) = @_;
  if ($self->{verbose}) {
    print STDOUT "*** TOPIC for $channel changed to '$topic'\n";
  }
  $self->event(0,"topic",$topic,$channel,$self->{nick},$self->{ident},$self->{host})
}

sub ownnick {
  my ($self,$tonick) = @_;
  if (!$tonick) { return }
  if ($self->{verbose}) {
    print STDOUT "*** NICK $tonick\n";
  }
  my $nick=$self->{me}; $self->{me}=$tonick; $self->{nick}=$tonick;
  $self->{nicklist}{lc($tonick)}={ %{$self->{nicklist}{lc($nick)}} };
  delete $self->{nicklist}{lc($nick)}; my $chans=[];
  foreach my $chan (keys %{$self->{chan}}) {
    push @{$chans},$chan;
    if ($self->{chan}{lc($chan)}{user}{lc($nick)}) {
      $self->{chan}{lc($chan)}{user}{lc($tonick)} = { %{$self->{chan}{lc($chan)}{user}{lc($nick)}} };
      $self->{chan}{lc($chan)}{user}{lc($tonick)}{nick}=$tonick;
      if (!$self->{chan}{lc($chan)}{user}{lc($tonick)}{prevnick}) {
        $self->{chan}{lc($chan)}{user}{lc($tonick)}{prevnick}=[$nick]
      } else {
        push @{$self->{chan}{lc($chan)}{user}{lc($tonick)}{prevnick}},$nick
      }
      delete $self->{chan}{lc($chan)}{user}{lc($nick)};
    }
  }
  $self->event(0,'nick',$tonick,$chans,$nick,$self->{ident},$self->{host})
}

sub ownkick {
  my ($self,$channel,$nick,$msg) = @_;
  print "[OWNKICK ".join(" - ",@_[1..$#_])."]\n";
  if (lc($nick) eq lc($self->{me})) {
    delete $self->{chan}{lc($channel)};
    if ($self->{verbose}) {
      print STDOUT "*** YOU KICKED YOURSELF FROM $channel ($msg) !!\n";
    }
    $self->event(0,"kick",$nick,$msg,$channel,$self->{nick},$self->{ident},$self->{host})
  } else {
    if ($self->{verbose}) {
      print STDOUT "*** YOU KICKED $nick FROM $channel ($msg) !!\n";
    }
    $self->event(2,"kick",$nick,$msg,$channel,$self->{nick},$self->{ident},$self->{host})
  }
}

#################  User events

sub privmsg {
  my ($self,$channel,$message,$nick,$ident,$host) = @_;
  if ($message =~ /^\001VERSION\001$/) {
    if ($self->{verbose}) {
      print STDOUT "* ".$self->gettime." <$nick> VERSION -> $self->{version}\n";
    }  
    $self->event(2,'version',undef,undef,$nick,$ident,$host);
    $self->notice($nick,"\001VERSION $self->{version}\001")
  } else {
    if ($self->{verbose}) {
      print STDOUT $self->gettime." $channel <$nick> $message\n";
    }  
    if ($channel =~ /\#/) {
      $self->event(2,"privmsg",$message,$channel,$nick,$ident,$host)
    } else {
      $self->event(2,"privmsg",$message,$nick,$nick,$ident,$host)
    }
  }
}

sub joined {
  my ($self,$channel,$message,$nick,$ident,$host) = @_;
  if ($self->{verbose}) {
    print STDOUT $self->gettime." * $nick joined $channel\n";
  }  
  if (!$self->{chan}{lc($channel)}{user}{$nick}) {
    $self->{chan}{lc($channel)}{user}{$nick}={}
  }
  $self->{chan}{lc($channel)}{user}{lc($nick)}{nick}=$nick;
  $self->{chan}{lc($channel)}{user}{lc($nick)}{present}=1;
  $self->{chan}{lc($channel)}{user}{lc($nick)}{o}=0;
  $self->{chan}{lc($channel)}{user}{lc($nick)}{h}=0;
  $self->{chan}{lc($channel)}{user}{lc($nick)}{v}=0;
  if (!$self->{nicklist}{lc($nick)}) {
    $self->{nicklist}{lc($nick)} = { nick => $nick, present => 1, info => 0 }
  } else {
    $self->{nicklist}{lc($nick)}{present}=1
  }
  if ($self->{gaininfo}) {
    if (!$self->{nicklist}{lc($nick)}{info}) {
      push @{$self->{infolist}},$self->{nicklist}{lc($nick)};
    }
  }  
  $self->event(2,"join",undef,$channel,$nick,$ident,$host);
}

sub parted {
  my ($self,$channel,$message,$nick,$ident,$host) = @_;
  if ($self->{verbose}) {
    print STDOUT $self->gettime." * $nick parted $channel\n";
  }  
  delete $self->{chan}{lc($channel)}{user}{lc($nick)};
  $self->event(2,"part",undef,$channel,$nick,$ident,$host)
}

sub quit {
  my ($self,$channel,$message,$nick,$ident,$host) = @_;
  if (!$nick) { return }
  if ($self->{verbose}) {
    print STDOUT $self->gettime." * $nick\!$ident\@$host quit ($message)\n";
  }  
  my $chans=[];
  delete $self->{nicklist}{lc($nick)};
  foreach my $chan (keys %{$self->{chan}}) {
    if ($self->{chan}{lc($chan)}{user}{lc($nick)}) {
      delete $self->{chan}{lc($chan)}{user}{lc($nick)};
      push @$chans,$chan
    }
  }
  $self->event(2,"quit",$message,$chans,$nick,$ident,$host)
}

sub mode {
  my ($self,$channel,$message,$nick,$ident,$host) = @_;
  my $level=2; if ($nick eq $self->{nick}) { $level=1 }
  if ($self->{verbose}) {
    print STDOUT $self->gettime." * $channel: $nick set modes $message\n";
  }  
  # process channel mode-list
  my ($modes,@pars) = split(/ /,$message); my $pnr=0; my $a='+';
  my $ml=$self->{chan}{lc($channel)}{modes};
  for (my $i=0;$i<length($modes);$i++) {
    my $c=lc(substr($modes,$i,1));
    if ($c eq '+') { $a='+' }
    elsif ($c eq '-') { $a='-' }
    else {
      if ($c =~ /[ntmiklspr]/) {
        if ($c =~ /[kl]/) {
          my $p='key'; if ($c eq 'l') { $p='limit' }
          $self->{chan}{lc($channel)}{$p}=$pars[$pnr];
          $self->event($level,'mode',"$a$c $pars[$pnr]",$channel,$nick,$ident,$host);
          $pnr++
        } else {
          if ($a eq '+') {
            if ($ml !~ /$c/) {
              $ml.=$c
            }
          } else {
            $ml =~ s/$c//g;
          }
          $self->event($level,'mode',"$a$c",$channel,$nick,$ident,$host)
        }
      } elsif ($c =~ /[ohv]/) {
        my $val=0; if ($a eq '+') { $val=1 }
        $self->{chan}{lc($channel)}{user}{lc($pars[$pnr])}{$c}=$val;
        if (lc($pars[$pnr]) eq lc($self->{me})) {
          if ($val) {
            $self->preventmode(lc($channel),$c)
          }
        }  
        $self->event($level,'mode',"$a$c $pars[$pnr]",$channel,$nick,$ident,$host);
        $pnr++
      }
    }
  }
  $self->{chan}{lc($channel)}{modes}=$ml
}

sub preventmode {
  my ($self,$channel,$mode) = @_;
  # check prevent modes, ethics!
  if (($mode eq 'o') && $self->{preventop}) {
    $self->raw("MODE",$channel,"-o $self->{me}",1)
  }
  if (($mode eq 'h') && $self->{preventhalfop}) {
    $self->raw("MODE",$channel,"-h $self->{me}",1)
  }
  if (($mode eq 'v') && $self->{preventvoice}) {
    $self->raw("MODE",$channel,"-v $self->{me}",1)
  }
}

sub nick {
  my ($self,$tonick,$nick,$ident,$host) = @_;
  if ($self->{verbose}) {
    print STDOUT "*** $nick has changed nick to $tonick\n";
  }
  $self->{nicklist}{lc($tonick)} = {}; # new pointer so we can delete old 
  if ($nick && $self->{nicklist}{lc($nick)} && (ref($self->{nicklist}{lc($nick)}) eq 'HASH')) {
    foreach my $k (keys %{$self->{nicklist}{lc($nick)}}) {
      $self->{nicklist}{lc($tonick)}{$k} = $self->{nicklist}{lc($nick)}{$k}
    }
  }
  $self->{nicklist}{lc($tonick)}{nick}=$tonick;
  delete $self->{nicklist}{lc($nick)}; my $chans=[];
  foreach my $chan (keys %{$self->{chan}}) {
    push @$chans,$chan;
    if ($self->{chan}{lc($chan)}{user}{lc($nick)}) {
      $self->{chan}{lc($chan)}{user}{lc($tonick)} = { %{$self->{chan}{lc($chan)}{user}{lc($nick)}} };
      $self->{chan}{lc($chan)}{user}{lc($tonick)}{nick}=$tonick;
      if (!$self->{chan}{lc($chan)}{user}{lc($tonick)}{prevnick}) {
        $self->{chan}{lc($chan)}{user}{lc($tonick)}{prevnick}=[$nick]
      } else {
        push @{$self->{chan}{lc($chan)}{user}{lc($tonick)}{prevnick}},$nick
      }
      delete $self->{chan}{lc($chan)}{user}{lc($nick)};
    }
  }
  $self->event(2,'nick',$tonick,$chans,$nick,$ident,$host)
}

sub kick {
  my ($self,$channel,$kicknick,$message,$nick,$ident,$host) = @_;
  if ($self->{verbose}) {
    print STDOUT "*** $kicknick was kicked from $channel by $nick ($message)\n";
  }  
  delete $self->{chan}{lc($channel)};
  $self->event(2,'kick',$kicknick,$message,$channel,$nick,$ident,$host)
}

sub noticed {
  my ($self,$message,$nick,$ident,$host) = @_;
  if ($self->{nsfound} && ($self->{nsnick} eq $nick) && ($self->{nsident} eq $ident) && ($self->{nshost} eq $host)) {
    $self->nickservmsg($message)
  } else {
    if ($self->{verbose}) {
      print STDOUT "*** NOTICE <$nick> $message\n";
    }  
    $self->event(2,"notice",$message,$nick,$nick,$ident,$host)
  }
}

sub topicset {
  my ($self,$channel,$topic,$nick,$ident,$host) = @_;
  $self->{chan}{lc($channel)}{topic}=$topic;
  if ($self->{verbose}) {
    print "* $nick changed topic of $channel to '$topic'\n"
  }
  $self->event(2,"topic",$topic,$channel,$nick,$ident,$host)
}

#################  Event control

sub ownmsg {
  my ($self,@params) = @_;
  if ($params[0] eq 'MODE') {
    $self->ownmode($params[1],join(" ",@params[2..$#params]))
  } elsif ($params[0] eq 'JOIN') {
    my $chan=substr($params[1],1);
    $self->ownjoin($chan);
  } elsif ($params[0] eq 'PART') {
    $self->ownpart($params[1]);
  } elsif ($params[0] eq 'NICK') {
    my $nick=substr($params[1],1);
    $self->ownnick($nick);
  } elsif ($params[0] eq 'KICK') {
    $self->ownkick($params[1],$params[2],join(" ",substr($params[3],1),@params[4..$#params]));
  } elsif ($params[0] eq 'TOPIC') {
    $self->owntopic($params[1],join(" ",substr($params[2],1),@params[3..$#params]));
  }
}

sub message {
  my ($self,@params) = @_;
  if ($params[1] eq 'PRIVMSG') {
    $self->privmsg($params[2],join(" ",substr($params[3],1),@params[4..$#params]),$self->masksplit($params[0]))
  } elsif ($params[1] eq 'JOIN') {
    $self->joined(substr($params[2],1),undef,$self->masksplit($params[0]))
  } elsif ($params[1] eq 'PART') {
    $self->parted($params[2],undef,$self->masksplit($params[0]))
  } elsif ($params[1] eq 'QUIT') {
    $self->quit(undef,join(" ",substr($params[2],1),@params[3..$#params]),$self->masksplit($params[0]))
  } elsif ($params[1] eq 'MODE') {
    $self->mode($params[2],join(" ",@params[3..$#params]),$self->masksplit($params[0]))
  } elsif ($params[1] eq 'KICK') {
    $self->kick($params[2],$params[3],join(" ",substr($params[4],1),@params[5..$#params]),$self->masksplit($params[0]))
  } elsif ($params[1] eq 'NICK') {
    $self->nick(substr($params[2],1),$self->masksplit($params[0]))
  } elsif ($params[1] eq 'NOTICE') {
    $self->noticed(join(" ",substr($params[3],1),@params[4..$#params]),$self->masksplit($params[0]))
  } elsif ($params[1] eq 'TOPIC') {
    $self->topicset($params[2],join(" ",substr($params[3],1),@params[4..$#params]),$self->masksplit($params[0]))
  }
}


#######################  R  A  W  ############################################

sub raw_001 {
  # server welcome message
  my ($self,@params) = @_;
  if ($self->{server} eq 'EFNET') {
    $self->{server}=substr($params[0],1)
  }
  $self->{nick}=$params[0];
  $self->{me}=$params[0];
  $self->{serverwelcome}=$self->stripinfo(@params);
  $self->event(1,'serverwelcome',$self->{serverwelcome});
  if ($self->{verbose}) {
    print STDOUT "*** Server = $self->{server} nick=$self->{nick}\n";
  }  
}

sub raw_002 {
  # server info message
  my ($self,@params) = @_;
  $self->{serverinfo}=$self->stripinfo(@params);
  $self->event(1,'serverinfo',$self->{serverinfo})
}

sub raw_003 {
  # server created message
  my ($self,@params) = @_;
  $self->{servercreated}=$self->stripinfo(@params);
  $self->event(1,'servercreated',$self->{servercreated})
}

sub raw_004 {
  # server identification message
  my ($self,@params) = @_;
  $self->{serverversion}=$params[2];
  $self->{usermodes}=$params[3];
  $self->{chanmodes}=$params[4];
  $self->event(1,'serverident',join(" ",@params))
}

sub raw_005 {
  # server properties message
  my ($self,@params) = @_;
  shift @params;
  foreach my $prop (@params) {
    if (substr($prop,0,1) eq ':') { return }
    my ($key,$val)=split(/=/,$prop);
    $key=lc($key);
    $self->{serverprop}{$key}=$val;
  }
  $self->event(1,'serverprop',join(" ",@params))
}

sub raw_042 {
  # IRCNet ID
  my ($self,@params) = @_;
  $self->{ircnetid}=$params[1]
}

sub raw_250 {
  # total connections
  my ($self,@params) = @_;
  $self->{totalconnections}=substr($params[7],1);
  $self->event(1,'serverconn',join(" ",@params))
}

sub raw_251 {
  # number of connections
  my ($self,@params) = @_;
  $self->{numusers}=$params[3];
  $self->{invisibleusers}=$params[6];
  $self->{totalusers}=$self->{numusers}+$self->{invisibleusers};
  $self->{numservers}=$params[9];
  $self->event(1,'servernumusers',join(" ",@params))
}

sub raw_252 {
  # number of operators
  my ($self,@params) = @_;
  $self->{numopers}=$params[1];
  $self->event(1,'serveropers',join(" ",@params))
}

sub raw_254 {
  # number of channels
  my ($self,@params) = @_;
  $self->{numchannels}=$params[1];
  $self->event(1,'servernumchans',join(" ",@params))
}

sub raw_266 {
  # global connection stats
  my ($self,@params) = @_;
  $self->{maxconnections}=$params[7];
  $self->event(1,'serverglobconn',join(" ",@params))
}

sub raw_301 {
  # Whois - Away
  my ($self,@params) = @_;
  my $nick = $params[1];
  my $msg=substr($params[2],1)." ".join(" ",@params[3..$#params]);
  $self->{nicklist}{lc($nick)}{away}=$msg;
}

sub raw_311 {
  # Whois
  my ($self,@params) = @_;
  my $nick = $params[1];
  my $ident = $params[2];
  my $host = $params[3];
  my $desc = join(" ",substr($params[5],1),@params[6..$#params]);
  $self->{nicklist}{lc($nick)}{away}=""; # reset before 301 event.
  $self->{nicklist}{lc($nick)}{desc}=$desc;
  $self->{nicklist}{lc($nick)}{ident}=$ident;
  $self->{nicklist}{lc($nick)}{ident} =~ s/\~//g;
  $self->{nicklist}{lc($nick)}{host}=$host;
  if ($host =~ /[^0-9\.]/) {
    # host is no IP, so find IP
    $self->{nicklist}{lc($nick)}{ip}=join('.',unpack('C4',inet_aton($host)))
  } else {
    $self->{nicklist}{lc($nick)}{ip}=$host
  }
}
sub raw_312 {
  # Whois - Server
  my ($self,@params) = @_;
  my $nick = $params[1];
  $self->{nicklist}{lc($nick)}{server}=$params[2];
}

sub raw_313 {
  # Whois - IRC-admin
  my ($self,@params) = @_;
  my $nick = $params[1];
  $self->{nicklist}{lc($nick)}{admin}=1;
}

sub raw_317 {
  # Whois - Idle time & Signon time
  my ($self,@params) = @_;
  my $nick = $params[1];
  my $tm=localtime($params[3]);
  $self->{nicklist}{lc($nick)}{idle}=$params[2];
  $self->{nicklist}{lc($nick)}{signon}=$params[3];
}

sub raw_318 {
  # End of Whois
  my ($self,@params) = @_;
  $self->{infolag}=0; # sign we're ready for next info-call
  my $nick = $params[1];
  if (!$self->{nickservtested} && ($nick eq $self->{nsnick})) {
    $self->{nickservtested}=1;
    if (($self->{nicklist}{lc($nick)}{ident} eq $self->{nsident}) && ($self->{nicklist}{lc($nick)}{host} eq $self->{nshost})) {
      $self->{nsfound}=1;
      if ($self->{autoreg}) {
        if ($self->{verbose}) {
          print STDOUT "*** NickServ found as '$nick'... Identifying\n";
        }  
        $self->nickservidentify
      } else {
        $self->joinchannels
      }
    } else {
      if ($self->{verbose}) {
        print STDOUT "*** NickServ '$nick' has a different host than expected\n";
      }  
      $self->joinchannels
    }
  } else {
    if ($self->{infoevents}{$nick}) {
      my $proc=$self->{infoevents}{$nick};
      if (ref($proc) eq 'CODE') {
        print "Calling whois procedure\n";
        &$proc($self->{nicklist}{lc($nick)});
      }  
      # remove in case of quit/join of user (event would fire)
      delete $self->{infoevents}{$nick}
    }
    $self->event(1,'whois',$nick,$self->{nicklist}{lc($nick)}{ident},$self->{nicklist}{lc($nick)}{host})
  }
}

sub raw_319 {
  # Whois channel list
  my ($self,@params) = @_;
  my $nick = $params[1];
  my @chans = (substr($params[2],1),@params[3..$#params]);
  $self->{nicklist}{lc($nick)}{channels}=join(" ",@chans);
  foreach my $wchan (@chans) {
    if ($wchan =~ /([\+\@\%]*)?\#(.*)/) {
      my $modes=$1; my $fchan="#".$2;
      foreach my $chan (keys %{$self->{chan}}) {
        if ($chan eq $fchan) {
          if ($modes =~ /\@/) {
            $self->{chan}{lc($chan)}{user}{lc($nick)}{o}=1
          }
          if ($modes =~ /\%/) {
            $self->{chan}{lc($chan)}{user}{lc($nick)}{h}=1
          }
          if ($modes =~ /\+/) {
            $self->{chan}{lc($chan)}{user}{lc($nick)}{v}=1
          }
        }
      }
    }
  }
}

sub raw_321 {
  # start of LIST
  my ($self,@params) = @_;
  $self->{list}=[];
  $self->event(1,'startlist',$params[0])
}

sub raw_322 {
  # LIST entry
  my ($self,@params) = @_;
  my $chan=$params[1]; my $users=$params[2]; my $name=substr($params[3],1);
  my $modes; 
  if ($name =~ /^\[\+([^\]])+\] (.+)$/) {
    $modes=$1; $name=$2
  }
  push @{$self->{list}},{ chan => $chan, users => $users, name => $name, modes => $modes };
  $self->event(1,'list',$chan,$users,$name,$params[0])
}

sub raw_323 {
  # end of LIST
  my ($self,@params) = @_;
  $self->event(1,'listend',$params[0])
}

sub raw_324 {
  # Channel modes
  my ($self,@params) = @_;
  my $chan=$params[1];
  my $modes=substr($params[2],1);
  my $pnr=3; $self->{chan}{lc($chan)}{modes}="";
  for (my $i=0;$i<length($modes);$i++) {
    my $mode=substr($modes,$i,1);
    if ($mode eq 'k') {
      $self->{chan}{lc($chan)}{key}=$params[$pnr]; $pnr++
    } elsif ($mode eq 'l') {
      $self->{chan}{lc($chan)}{limit}=$params[$pnr]; $pnr++
    } else {
      $self->{chan}{lc($chan)}{modes}.=$mode
    }
  }
  $self->event(1,'chanmodes',$chan,$params[0],$self->{chan}{lc($chan)}{modes},$self->{chan}{lc($chan)}{key},$self->{chan}{lc($chan)}{limit})
}

sub raw_329 {
  # channel creation time
  my ($self,@params) = @_;
  my $chan=$params[1];  
  $self->{chan}{lc($chan)}{created}=$params[2];
  $self->event(1,'chancreated',$chan,$params[0],$params[2])
}

sub raw_331 {
  # No topic set
  my ($self,@params) = @_;
  my $chan=$params[1];
  $self->{chan}{lc($chan)}{topic}="";
  $self->event(1,'notopic',$chan)
}

sub raw_332 {
  # Topic
  my ($self,@params) = @_;
  my $chan=$params[1]; my $topic=join(" ",substr($params[2],1),@params[3..$#params]);
  $self->{chan}{lc($chan)}{topic}=$topic;
  $self->event(1,'topic',$topic,$chan,$params[0])
}

sub raw_333 {
  # Topic set by, and time
  my ($self,@params) = @_;
  my $chan=$params[1];
  $self->{chan}{lc($chan)}{topicowner}=$params[2];
  $self->{chan}{lc($chan)}{topictime}=$params[3];
  $self->event(1,'topicident',@params[1..3])
}

sub raw_353 {
  # Channel Names list
  my ($self,@params) = @_;
  my $chan=$params[2];
  $params[3]=substr($params[3],1);
  for (my $i=3;$i<=$#params;$i++) {
    my $nick=$params[$i];
    $nick =~ s/[+%@]//g;
    $self->{chan}{lc($chan)}{user}{lc($nick)}={};
    my $voice=0; my $op=0; my $halfop=0;
    my $pre=substr($params[$i],0,1);
    if ($pre eq '+') { $voice=1 }
    if ($pre eq '@') { $op=1 }
    if ($pre eq '%') { $halfop=1 }
    $self->{chan}{lc($chan)}{user}{lc($nick)}{nick}=$nick;
    $self->{chan}{lc($chan)}{user}{lc($nick)}{o}=$op;
    $self->{chan}{lc($chan)}{user}{lc($nick)}{h}=$halfop;
    $self->{chan}{lc($chan)}{user}{lc($nick)}{v}=$voice;
    $self->{chan}{lc($chan)}{user}{lc($nick)}{present}=1;
    if (!$self->{nicklist}{lc($nick)}) {
      $self->{nicklist}{lc($nick)} = { nick =>$nick, present => 1, info => 0 }
    }
    if ($self->{gaininfo}) {
      if (!$self->{nicklist}{lc($nick)}{info}) {
        push @{$self->{infolist}},$self->{nicklist}{lc($nick)}
      }  
    }
  }
  $self->event(1,'names',join(" ",@params))
}

sub raw_366 {
  # end of names list
  my ($self,@params) = @_;
  my $nick=$params[0]; my $chan=$params[1];
  $self->event(1,"joined",$nick,$chan)
}

sub raw_372 {
  # MOTD
  my ($self,@params) = @_;
  $self->{motd}.=$self->stripinfo(@params)."\n";  
  $self->event(1,'motd',join(" ",@params))
}

sub raw_375 {
  # start of MOTD
  my ($self,@params) = @_;
  if ($self->{motd}) { $self->{alreadyjoined}=1 }
  $self->{motd}="";
  $self->event(1,'motdstart',join(" ",@params))
}

sub raw_376 {
  # end of MOTD
  my ($self,@params) = @_;
  if ($self->{verbosemotd}) {    
    print STDOUT "*** END OF MOTD\n";
  }
  $self->initconnect;
  $self->event(1,'motdend',join(" ",@params))
}

sub raw_381 {
  # became IRC-oper
  my ($self,@params) = @_;
  $self->{isoper}=1;
  $self->event(1,'oper',join(" ",@params))
}

sub raw_401 {
  # no such nick/channel
  my ($self,@params) = @_;
  my $nick=$params[1];
  if (!$self->{nickservtested} && ($nick eq $self->{nsnick})) {
    # nickserv check failed
    $self->{nickservtested}=1;
    if ($self->{verbose}) {
      print STDOUT "*** NickServ not found!\n";
    }  
    $self->joinchannels;
  } else {
    if ($self->{verbose}) {
      print STDOUT "*** ERROR Nick/Channel '$nick' does not exist!\n";
    }
    if ($self->{nicklist}{lc($nick)}) {
      $self->{nicklist}{lc($nick)}{present}=0
    }
    $self->event(1,"nosuch",$nick)
  }
}

sub raw_404 {
  # channel is moderated
  my ($self,@params) = @_;
  $self->event(1,"cannotsend",$params[0],$params[1],join(" ",substr($params[2],1),@params[3..$#params]))
}

sub raw_421 {
  # unknown command
  my ($self,@params) = @_;
  if ($self->{verbose}) {
    print STDOUT "\n*!*!*!*! WARNING !*!*!*!*\n";
    print STDOUT "Unknown command: ".join(" ",@params)."\n\n";
  }  
}

sub raw_422 {
  # MOTD file is missing
  my ($self,@params) = @_;
  if ($self->{verbosemotd}) {
    print "*** MOTD FILE MISSING\n";
  }
  $self->initconnect;
  $self->event(1,'nomotd',join(" ",@params))
}

sub raw_432 {
  # erroneus nickname
  my ($self,@params) = @_;
  $self->event(1,"nickerror",join(" ",@params));
  $self->{quit}=1;
  if ($self->{verbose}) {
    print STDOUT "*** ERROR: Erroneus Nickname\n"
  }  
}

sub raw_433 {
  # nickname already in use
  my ($self,@params) = @_;
  if ($self->{nick} eq $self->{altnick}) {
    $self->event(1,'nicksused',join(" ",@params)); return
  }
  if ($self->{altnick}) {
    $self->{nick}=$self->{altnick};
    gclient::out($self->{ircserver},"NICK $self->{altnick}\r\n");
    if ($self->{verboseoutput}) { print "--> NICK $self->{altnick}\r\n" }
    if ($self->{verbose}) {
      print STDOUT "*** Changed nick to '$self->{altnick}'\n";
    }
  }  
  $self->event(1,'nickinuse',join(" ",@params))
}

sub raw_436 {
  # nickname collision
  my ($self,@params) = @_;
  if ($self->{me} eq $self->{altnick}) {
    $self->{quit}=1;
    print "*** ERROR: Nick Collision\n"
  } else {
    if ($self->{altnick}) {
      gclient::out($self->{ircserver},"NICK $self->{altnick}\r\n");
      if ($self->{verboseoutput}) { print "--> NICK $self->{altnick}\r\n" }
      if ($self->{verbose}) {
        print STDOUT "*** Changed nick to '$self->{altnick}'\n";
      }  
    } else {
      $self->{quit}=1;
      print "*** ERROR: Nick Collision\n"
    }
  }
  $self->event(1,"nickcollision",join(" ",@params));
}

sub raw_471 {
  # unable to join channel (channel is full)
  my ($self,@params) = @_;
  $self->event(1,"cannotjoin",$params[0],$params[1],$params[$#params])
}
sub raw_472 {
  # illegal mode
}
sub raw_473 {
  # unable to join channel (invite only)
  my ($self,@params) = @_;
  $self->event(1,"cannotjoin",$params[0],$params[1],$params[$#params])
}
sub raw_474 {
  # unable to join channel (banned)
  my ($self,@params) = @_;
  $self->event(1,"cannotjoin",$params[0],$params[1],$params[$#params])
}
sub raw_475 {
  # unable to join channel (need key)
  my ($self,@params) = @_;
  $self->event(1,"cannotjoin",$params[0],$params[1],$params[$#params])
}
sub raw_479 {
  # illegal channel
  my ($self,@params) = @_;
  $self->event(1,'cannotjoin',$params[0],$params[1],'x')
}

sub raw_482 {
  # not a channel operator
  my ($self,@params) = @_;
  $self->event(1,"notchanop",$params[1],join(" ",@params))
}

########### EOF Omnibot.pm (C) 2019 Chaosje, Domero ###################
