# Print realname of everyone who join to channels
# for irssi 0.7.99 by Timo Sirainen

use Irssi 20011207;
use strict;
use vars qw($VERSION %IRSSI); 
$VERSION = "0.8.7";
%IRSSI = (
    authors	=> "Timo \'cras\' Sirainen, Bastian Blank",
    contact	=> "tss\@iki.fi, waldi\@debian.org",
    name	=> "auto realname",
    description	=> "Print realname of everyone who join to channels",
    license	=> "GPLv2 or later",
    url		=> "http://irssi.org/",
    changed	=> "2021-01-16"
);

# v0.8.7 changes - bw1
#  - fix Can't call method "nick_find" ... line 282.
# v0.8.6 changes - Juhamatti Niemelä
#  - fix join msg printing when there are multiple common channels
# v0.8.5 changes - Bastian Blank
#  - really use the introduced state variable
# v0.8.4 changes - Bastian Blank
#  - fix queue abort
# v0.8.3 changes - Bastian Blank
#  - on queue abort print any join messages
# v0.8.2 changes - Bastian Blank
#  - use channelname instead of Irssi::Irc::Channel for channelname within message on join
# v0.8.1 changes - Bastian Blank
#  - print any join messages on abort
#  - also remove any timeouts
# v0.8 changes - Bastian Blank
#  - join message includes realname
#  - add timeout
#  - don't print realname for our self
#  - change license because german law doesn't allow to give away the copyright
# v0.71 changes
#  - a bit safer now with using "redirect last"
# v0.7 changes
#  - pretty much a rewrite - shouldn't break anymore
# v0.62 changes
#  - upgraded redirection to work with latest CVS irssi - lot easier
#    to handle it this time :)
# v0.61 changes
#  - forgot to reset %chan_list..
# v0.6 changes
#  - works now properly when a nick joins to multiple channels
# v0.5 changes
#  - Use "message join" so we won't ask realname for ignored people

Irssi::theme_register([
  'join', '{channick_hilight $0} {chanhost_hilight $1} has joined {channel $2}',
  'join_realname', '{channick_hilight $0} ({hilight $1}) {chanhost_hilight $2} has joined {channel $3}',
  'join_realname_only', '{channick_hilight $0} is {hilight $1}',
]);

my $whois_queue_length_before_abort = 10; # max. whois queue length before we should abort the whois queries for next few seconds (people are probably joining behind a netsplit)
my $whois_abort_seconds = 10; # wait for 10 secs when there's been too many joins
my $debug = 0;

my %servers;

my %whois_waiting;
my %whois_queue;
my %aborted;
my %chan_list;

sub sig_connected {
  my $server = shift;
  $servers{$server->{tag}} = {
    abort_time => 0,  # if join event is received before this, abort
    waiting => 0,     # waiting reply for WHOIS request
    queue => [],      # whois queue
    nicks => {}       # nick => [ #chan1, #chan2, .. ]
  };
}

sub sig_disconnected {
  my $server = shift;
  delete $servers{$server->{tag}};
}

sub msg_join {
  my ($server, $channame, $nick, $host) = @_;
  $channame =~ s/^://;
  my $rec = $servers{$server->{tag}};

  # don't display realname for our self
  return if ($nick eq $server->{nick});

  # don't whois people who netjoin back
  return if ($server->netsplit_find($nick, $host));

  return if (time < $rec->{abort_time});
  $rec->{abort_time} = 0;

  Irssi::signal_stop();

  # check if the nick is already found from another channel
  {
    my $ret = 0;
    foreach my $channel ($server->channels()) {
      my $nickrec = $channel->nick_find($nick);
      if ($nickrec && $nickrec->{realname}) {
        # this user already has a known realname - use it.
        $channel = $server->channel_find($channame);
        $channel->printformat(MSGLEVEL_JOINS, 'join_realname', $nick, $nickrec->{realname}, $nickrec->{host}, $channel->{name});
        $channel->print("autorealname: already found: $nick", MSGLEVEL_CLIENTCRAP) if $debug;
        $ret = 1;
        last;
      }
    }

    return if ($ret);
  }

  # save channel to nick specific hash so we can later check which channels
  # it needs to print the realname

  if ($rec->{nicks}->{$nick}) {
    # don't send the WHOIS again if nick is already in queue
    push @{$rec->{nicks}->{$nick}->{chans_join}}, $channame;
    push @{$rec->{nicks}->{$nick}->{chans_realname}}, $channame;
    $rec->{nicks}->{$nick}->{state} = 0;
    my $channel = $server->channel_find($channame);
    $channel->print("autorealname: already in queue: $nick", MSGLEVEL_CLIENTCRAP) if $debug;
  }
  else {
    $rec->{nicks}->{$nick} = {};
    $rec->{nicks}->{$nick}->{chans_join} = [$channame];
    $rec->{nicks}->{$nick}->{chans_realname} = [$channame];
    $rec->{nicks}->{$nick}->{state} = 0;

    # add the nick to queue
    push @{$rec->{queue}}, $nick;

    # timeout
    $rec->{nicks}->{$nick}->{timeout} = Irssi::timeout_add(1000, \&timeout_whois, [$server, $nick]);
    my $channel = $server->channel_find($channame);
    $channel->print("autorealname: add to queue: $nick", MSGLEVEL_CLIENTCRAP) if $debug;
  }

  if (scalar @{$rec->{queue}} >= $whois_queue_length_before_abort) {
    # too many whois requests in queue, abort
    foreach $nick (@{$rec->{queue}}) {
      foreach my $channel (@{$rec->{nicks}->{$nick}->{chans_join}}) {
        my $chanrec = $server->channel_find($channel);
        my $nickrec = $chanrec->nick_find($nick);
        if ($chanrec && $nickrec) {
          $chanrec->printformat(MSGLEVEL_JOINS, 'join', $nick, $nickrec->{host}, $channel);
          $chanrec->print("autorealname: queue abort: $nick", MSGLEVEL_CLIENTCRAP) if $debug;
        }
      }
      Irssi::timeout_remove($rec->{nicks}->{$nick}->{timeout}) if (!($rec->{nicks}->{$nick}->{state} & 2));
      delete $rec->{nicks}->{$nick};
    }
    $rec->{queue} = [];
    $rec->{abort_time} = time+$whois_abort_seconds;
    return;
  }

  # waiting for WHOIS reply..
  return if $rec->{waiting};

  request_whois($server, $rec);
}

sub request_whois {
  my ($server, $rec) = @_;
  return if (scalar @{$rec->{queue}} == 0);

  my @whois_nicks = splice(@{$rec->{queue}}, 0, $server->{max_whois_in_cmd});
  my $whois_query = join(',', @whois_nicks);

  # ignore all whois replies except the first line of the WHOIS reply
  my $redir_arg = $whois_query.' '.join(' ', @whois_nicks);
  $server->redirect_event("whois", 1, $redir_arg, 0, 
			  "redir autorealname_whois_last", {
			    "event 311" => "redir autorealname_whois",
			    "event 401" => "redir autorealname_whois_unknown",
			    "redirect last" => "redir autorealname_whois_last",
			    "" => "event empty" });

  $server->send_raw("WHOIS :$whois_query");
  $rec->{waiting} = 1;
}

sub event_whois {
  my ($server, $data) = @_;
  my ($num, $nick, $user, $host, $empty, $realname) = split(/ +/, $data, 6);
  $realname =~ s/^://;
  my $rec = $servers{$server->{tag}};

  return if not $rec->{nicks}->{$nick};

  $rec->{nicks}->{$nick}->{state} |= 1;

  if (!($rec->{nicks}->{$nick}->{state} & 2)) {
    Irssi::timeout_remove($rec->{nicks}->{$nick}->{timeout});
    foreach my $channel (@{$rec->{nicks}->{$nick}->{chans_join}}) {
      my $chanrec = $server->channel_find($channel);
      my $nickrec = $chanrec->nick_find($nick);
      if ($chanrec && $nickrec) {
        $chanrec->printformat(MSGLEVEL_JOINS, 'join_realname', $nick, $realname, $nickrec->{host}, $channel);
        $chanrec->print("autorealname: got whois: $nick, state: ".$rec->{nicks}->{$nick}->{state}, MSGLEVEL_CLIENTCRAP) if $debug;
      }
    }
    $rec->{nicks}->{$nick}->{chans_join} = [];
    $rec->{nicks}->{$nick}->{chans_realname} = [];
    $rec->{nicks}->{$nick}->{state} |= 2;
  }
  else {
    foreach my $channel (@{$rec->{nicks}->{$nick}->{chans_realname}}) {
      my $chanrec = $server->channel_find($channel);
      next unless (defined $chanrec);
      my $nickrec = $chanrec->nick_find($nick);
      if ($chanrec && $nickrec) {
        $chanrec->printformat(MSGLEVEL_JOINS, 'join_realname_only', $nick, $realname);
        $chanrec->print("autorealname: got whois: $nick, state: ".$rec->{nicks}->{$nick}->{state}, MSGLEVEL_CLIENTCRAP) if $debug;
      }
    }
    $rec->{nicks}->{$nick}->{chans_realname} = [];
  }

  delete $rec->{nicks}->{$nick} if ($rec->{nicks}->{$nick}->{state} == 3);
}

sub event_whois_unknown {
  my ($server, $data) = @_;
  my ($temp, $nick) = split(" ", $data);
  my $rec = $servers{$server->{tag}};

  return if not $rec->{nicks}->{$nick};

  $rec->{nicks}->{$nick}->{state} |= 1;

  if (!($rec->{nicks}->{$nick}->{state} & 2)) {
    Irssi::timeout_remove($rec->{nicks}->{$nick}->{timeout});
    foreach my $channel (@{$rec->{nicks}->{$nick}->{chans_join}}) {
      my $chanrec = $server->channel_find($channel);
      my $nickrec = $chanrec->nick_find($nick);
      if ($chanrec && $nickrec) {
        $chanrec->printformat(MSGLEVEL_JOINS, 'join', $nick, $nickrec->{host}, $channel);
        $chanrec->print("autorealname: got unknown whois: $nick", MSGLEVEL_CLIENTCRAP) if $debug;
      }
    }
    $rec->{nicks}->{$nick}->{chans_join} = [];
  } else {
    foreach my $channel (@{$rec->{nicks}->{$nick}->{chans_join}}) {
      my $chanrec = $server->channel_find($channel);
      $chanrec->print("autorealname: got unknown whois (already considered): $nick", MSGLEVEL_CLIENTCRAP) if $debug;
    }
  }

  delete $rec->{nicks}->{$nick} if ($rec->{nicks}->{$nick}->{state} == 3);
}

sub event_whois_last {
  my $server = shift;
  my $rec = $servers{$server->{tag}};

  $rec->{waiting} = 0;
  request_whois($server, $rec);
}

foreach my $server (Irssi::servers()) {
  sig_connected($server);
}

sub timeout_whois {
  my $server = shift @{$_[0]};
  my $nick = shift @{$_[0]};

  my $rec = $servers{$server->{tag}};

  return if not $rec->{nicks}->{$nick};

  Irssi::timeout_remove($rec->{nicks}->{$nick}->{timeout});
  $rec->{nicks}->{$nick}->{state} |= 2;

  my @channels = @{$rec->{nicks}->{$nick}->{chans_join}};
  foreach my $channel (@channels) {
    my $chanrec = $server->channel_find($channel);
    next unless (defined $chanrec);
    my $nickrec = $chanrec->nick_find($nick);
    if ($nickrec && $chanrec) {
      $chanrec->printformat(MSGLEVEL_JOINS, 'join', $nick, $nickrec->{host}, $channel);
      $chanrec->print("autorealname: timeout: $nick, state: ".$rec->{nicks}->{$nick}->{state}, MSGLEVEL_CLIENTCRAP) if $debug;
    }
  }

  $rec->{nicks}->{$nick}->{chans_join} = [];
}

Irssi::signal_add( {
        'server connected' => \&sig_connected,
	'server disconnected' => \&sig_disconnected,
	'message join' => \&msg_join,
	'redir autorealname_whois' => \&event_whois,
	'redir autorealname_whois_unknown' => \&event_whois_unknown,
	'redir autorealname_whois_last' => \&event_whois_last });

# vim:set sw=2 ts=8 et:
