1# Print realname of everyone who join to channels
2# for irssi 0.7.99 by Timo Sirainen
3
4use Irssi 20011207;
5use strict;
6use vars qw($VERSION %IRSSI);
7$VERSION = "0.8.6";
8%IRSSI = (
9    authors	=> "Timo \'cras\' Sirainen, Bastian Blank",
10    contact	=> "tss\@iki.fi, waldi\@debian.org",
11    name	=> "auto realname",
12    description	=> "Print realname of everyone who join to channels",
13    license	=> "GPLv2 or later",
14    url		=> "http://irssi.org/",
15    changed	=> "Fri, 24 Jan 2003 15:40:22 +0100"
16);
17
18# v0.8.6 changes - Juhamatti Niemelä
19#  - fix join msg printing when there are multiple common channels
20# v0.8.5 changes - Bastian Blank
21#  - really use the introduced state variable
22# v0.8.4 changes - Bastian Blank
23#  - fix queue abort
24# v0.8.3 changes - Bastian Blank
25#  - on queue abort print any join messages
26# v0.8.2 changes - Bastian Blank
27#  - use channelname instead of Irssi::Irc::Channel for channelname within message on join
28# v0.8.1 changes - Bastian Blank
29#  - print any join messages on abort
30#  - also remove any timeouts
31# v0.8 changes - Bastian Blank
32#  - join message includes realname
33#  - add timeout
34#  - don't print realname for our self
35#  - change license because german law doesn't allow to give away the copyright
36# v0.71 changes
37#  - a bit safer now with using "redirect last"
38# v0.7 changes
39#  - pretty much a rewrite - shouldn't break anymore
40# v0.62 changes
41#  - upgraded redirection to work with latest CVS irssi - lot easier
42#    to handle it this time :)
43# v0.61 changes
44#  - forgot to reset %chan_list..
45# v0.6 changes
46#  - works now properly when a nick joins to multiple channels
47# v0.5 changes
48#  - Use "message join" so we won't ask realname for ignored people
49
50Irssi::theme_register([
51  'join', '{channick_hilight $0} {chanhost_hilight $1} has joined {channel $2}',
52  'join_realname', '{channick_hilight $0} ({hilight $1}) {chanhost_hilight $2} has joined {channel $3}',
53  'join_realname_only', '{channick_hilight $0} is {hilight $1}',
54]);
55
56my $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)
57my $whois_abort_seconds = 10; # wait for 10 secs when there's been too many joins
58my $debug = 0;
59
60my %servers;
61
62my %whois_waiting;
63my %whois_queue;
64my %aborted;
65my %chan_list;
66
67sub sig_connected {
68  my $server = shift;
69  $servers{$server->{tag}} = {
70    abort_time => 0,  # if join event is received before this, abort
71    waiting => 0,     # waiting reply for WHOIS request
72    queue => [],      # whois queue
73    nicks => {}       # nick => [ #chan1, #chan2, .. ]
74  };
75}
76
77sub sig_disconnected {
78  my $server = shift;
79  delete $servers{$server->{tag}};
80}
81
82sub msg_join {
83  my ($server, $channame, $nick, $host) = @_;
84  $channame =~ s/^://;
85  my $rec = $servers{$server->{tag}};
86
87  # don't display realname for our self
88  return if ($nick eq $server->{nick});
89
90  # don't whois people who netjoin back
91  return if ($server->netsplit_find($nick, $host));
92
93  return if (time < $rec->{abort_time});
94  $rec->{abort_time} = 0;
95
96  Irssi::signal_stop();
97
98  # check if the nick is already found from another channel
99  {
100    my $ret = 0;
101    foreach my $channel ($server->channels()) {
102      my $nickrec = $channel->nick_find($nick);
103      if ($nickrec && $nickrec->{realname}) {
104        # this user already has a known realname - use it.
105        $channel = $server->channel_find($channame);
106        $channel->printformat(MSGLEVEL_JOINS, 'join_realname', $nick, $nickrec->{realname}, $nickrec->{host}, $channel->{name});
107        $channel->print("autorealname: already found: $nick", MSGLEVEL_CLIENTCRAP) if $debug;
108        $ret = 1;
109        last;
110      }
111    }
112
113    return if ($ret);
114  }
115
116  # save channel to nick specific hash so we can later check which channels
117  # it needs to print the realname
118
119  if ($rec->{nicks}->{$nick}) {
120    # don't send the WHOIS again if nick is already in queue
121    push @{$rec->{nicks}->{$nick}->{chans_join}}, $channame;
122    push @{$rec->{nicks}->{$nick}->{chans_realname}}, $channame;
123    $rec->{nicks}->{$nick}->{state} = 0;
124    my $channel = $server->channel_find($channame);
125    $channel->print("autorealname: already in queue: $nick", MSGLEVEL_CLIENTCRAP) if $debug;
126  }
127  else {
128    $rec->{nicks}->{$nick} = {};
129    $rec->{nicks}->{$nick}->{chans_join} = [$channame];
130    $rec->{nicks}->{$nick}->{chans_realname} = [$channame];
131    $rec->{nicks}->{$nick}->{state} = 0;
132
133    # add the nick to queue
134    push @{$rec->{queue}}, $nick;
135
136    # timeout
137    $rec->{nicks}->{$nick}->{timeout} = Irssi::timeout_add(1000, \&timeout_whois, [$server, $nick]);
138    my $channel = $server->channel_find($channame);
139    $channel->print("autorealname: add to queue: $nick", MSGLEVEL_CLIENTCRAP) if $debug;
140  }
141
142  if (scalar @{$rec->{queue}} >= $whois_queue_length_before_abort) {
143    # too many whois requests in queue, abort
144    foreach $nick (@{$rec->{queue}}) {
145      foreach my $channel (@{$rec->{nicks}->{$nick}->{chans_join}}) {
146        my $chanrec = $server->channel_find($channel);
147        my $nickrec = $chanrec->nick_find($nick);
148        if ($chanrec && $nickrec) {
149          $chanrec->printformat(MSGLEVEL_JOINS, 'join', $nick, $nickrec->{host}, $channel);
150          $chanrec->print("autorealname: queue abort: $nick", MSGLEVEL_CLIENTCRAP) if $debug;
151        }
152      }
153      Irssi::timeout_remove($rec->{nicks}->{$nick}->{timeout}) if (!($rec->{nicks}->{$nick}->{state} & 2));
154      delete $rec->{nicks}->{$nick};
155    }
156    $rec->{queue} = [];
157    $rec->{abort_time} = time+$whois_abort_seconds;
158    return;
159  }
160
161  # waiting for WHOIS reply..
162  return if $rec->{waiting};
163
164  request_whois($server, $rec);
165}
166
167sub request_whois {
168  my ($server, $rec) = @_;
169  return if (scalar @{$rec->{queue}} == 0);
170
171  my @whois_nicks = splice(@{$rec->{queue}}, 0, $server->{max_whois_in_cmd});
172  my $whois_query = join(',', @whois_nicks);
173
174  # ignore all whois replies except the first line of the WHOIS reply
175  my $redir_arg = $whois_query.' '.join(' ', @whois_nicks);
176  $server->redirect_event("whois", 1, $redir_arg, 0,
177			  "redir autorealname_whois_last", {
178			    "event 311" => "redir autorealname_whois",
179			    "event 401" => "redir autorealname_whois_unknown",
180			    "redirect last" => "redir autorealname_whois_last",
181			    "" => "event empty" });
182
183  $server->send_raw("WHOIS :$whois_query");
184  $rec->{waiting} = 1;
185}
186
187sub event_whois {
188  my ($server, $data) = @_;
189  my ($num, $nick, $user, $host, $empty, $realname) = split(/ +/, $data, 6);
190  $realname =~ s/^://;
191  my $rec = $servers{$server->{tag}};
192
193  return if not $rec->{nicks}->{$nick};
194
195  $rec->{nicks}->{$nick}->{state} |= 1;
196
197  if (!($rec->{nicks}->{$nick}->{state} & 2)) {
198    Irssi::timeout_remove($rec->{nicks}->{$nick}->{timeout});
199    foreach my $channel (@{$rec->{nicks}->{$nick}->{chans_join}}) {
200      my $chanrec = $server->channel_find($channel);
201      my $nickrec = $chanrec->nick_find($nick);
202      if ($chanrec && $nickrec) {
203        $chanrec->printformat(MSGLEVEL_JOINS, 'join_realname', $nick, $realname, $nickrec->{host}, $channel);
204        $chanrec->print("autorealname: got whois: $nick, state: ".$rec->{nicks}->{$nick}->{state}, MSGLEVEL_CLIENTCRAP) if $debug;
205      }
206    }
207    $rec->{nicks}->{$nick}->{chans_join} = [];
208    $rec->{nicks}->{$nick}->{chans_realname} = [];
209    $rec->{nicks}->{$nick}->{state} |= 2;
210  }
211  else {
212    foreach my $channel (@{$rec->{nicks}->{$nick}->{chans_realname}}) {
213      my $chanrec = $server->channel_find($channel);
214      my $nickrec = $chanrec->nick_find($nick);
215      if ($chanrec && $nickrec) {
216        $chanrec->printformat(MSGLEVEL_JOINS, 'join_realname_only', $nick, $realname);
217        $chanrec->print("autorealname: got whois: $nick, state: ".$rec->{nicks}->{$nick}->{state}, MSGLEVEL_CLIENTCRAP) if $debug;
218      }
219    }
220    $rec->{nicks}->{$nick}->{chans_realname} = [];
221  }
222
223  delete $rec->{nicks}->{$nick} if ($rec->{nicks}->{$nick}->{state} == 3);
224}
225
226sub event_whois_unknown {
227  my ($server, $data) = @_;
228  my ($temp, $nick) = split(" ", $data);
229  my $rec = $servers{$server->{tag}};
230
231  return if not $rec->{nicks}->{$nick};
232
233  $rec->{nicks}->{$nick}->{state} |= 1;
234
235  if (!($rec->{nicks}->{$nick}->{state} & 2)) {
236    Irssi::timeout_remove($rec->{nicks}->{$nick}->{timeout});
237    foreach my $channel (@{$rec->{nicks}->{$nick}->{chans_join}}) {
238      my $chanrec = $server->channel_find($channel);
239      my $nickrec = $chanrec->nick_find($nick);
240      if ($chanrec && $nickrec) {
241        $chanrec->printformat(MSGLEVEL_JOINS, 'join', $nick, $nickrec->{host}, $channel);
242        $chanrec->print("autorealname: got unknown whois: $nick", MSGLEVEL_CLIENTCRAP) if $debug;
243      }
244    }
245    $rec->{nicks}->{$nick}->{chans_join} = [];
246  } else {
247    foreach my $channel (@{$rec->{nicks}->{$nick}->{chans_join}}) {
248      my $chanrec = $server->channel_find($channel);
249      $chanrec->print("autorealname: got unknown whois (already considered): $nick", MSGLEVEL_CLIENTCRAP) if $debug;
250    }
251  }
252
253  delete $rec->{nicks}->{$nick} if ($rec->{nicks}->{$nick}->{state} == 3);
254}
255
256sub event_whois_last {
257  my $server = shift;
258  my $rec = $servers{$server->{tag}};
259
260  $rec->{waiting} = 0;
261  request_whois($server, $rec);
262}
263
264foreach my $server (Irssi::servers()) {
265  sig_connected($server);
266}
267
268sub timeout_whois {
269  my $server = shift @{$_[0]};
270  my $nick = shift @{$_[0]};
271
272  my $rec = $servers{$server->{tag}};
273
274  return if not $rec->{nicks}->{$nick};
275
276  Irssi::timeout_remove($rec->{nicks}->{$nick}->{timeout});
277  $rec->{nicks}->{$nick}->{state} |= 2;
278
279  my @channels = @{$rec->{nicks}->{$nick}->{chans_join}};
280  foreach my $channel (@channels) {
281    my $chanrec = $server->channel_find($channel);
282    my $nickrec = $chanrec->nick_find($nick);
283    if ($nickrec && $chanrec) {
284      $chanrec->printformat(MSGLEVEL_JOINS, 'join', $nick, $nickrec->{host}, $channel);
285      $chanrec->print("autorealname: timeout: $nick, state: ".$rec->{nicks}->{$nick}->{state}, MSGLEVEL_CLIENTCRAP) if $debug;
286    }
287  }
288
289  $rec->{nicks}->{$nick}->{chans_join} = [];
290}
291
292Irssi::signal_add( {
293        'server connected' => \&sig_connected,
294	'server disconnected' => \&sig_disconnected,
295	'message join' => \&msg_join,
296	'redir autorealname_whois' => \&event_whois,
297	'redir autorealname_whois_unknown' => \&event_whois_unknown,
298	'redir autorealname_whois_last' => \&event_whois_last });
299