1 package Xchat::VPIT::Clones;
6 use Scalar::Util qw<dualvar>;
10 use lib get_info 'xchatdir';
12 use Xchat::XPI::Events qw<delay>;
20 our $VERSION = '0.04';
22 my ($ph, %users, %fetched);
23 my $extractor = PRE . '(.+?)' . POST;
24 $extractor = qr/$extractor/;
27 my ($host, $nick, $clones) = @_;
29 my @clones = $clones =~ /$extractor/go;
31 return "\002$nick\002 ($host) is a clone of : \002@clones\002\n";
35 my ($host, $nick, $clones) = @_;
37 my @clones = $clones =~ /$extractor/go;
39 return "\002$nick\002 ($host) has "
40 . int($clones) . ' clone' . ($clones > 1 ? 's' : '')
41 . " left : \002@clones\002\n";
45 my ($host, $clones) = @_;
47 my @clones = $clones =~ /$extractor/go;
49 return "Clones ($host) : \002@clones\002\n";
53 my $userhost = lc $_[0];
55 return $userhost =~ /@([^@]+)$/ ? $1 : $userhost;
59 my ($nick, $host, $serv, $chan) = @_;
61 my $hosts = $users{$serv}{$chan};
62 $users{$serv}{$chan} = $hosts = { } unless defined $hosts;
64 my $clones = $hosts->{$host};
66 $nick = PRE . $nick . POST;
67 if (defined $clones) {
68 if (index($clones, $nick) < $[) {
69 $hosts->{$host} = dualvar int($clones) + 1, $clones . $nick;
72 $clones = dualvar 0, '';
73 $hosts->{$host} = dualvar 1, $nick;
80 my ($nick, $host, $serv, $chan) = @_;
82 my $hosts = $users{$serv}{$chan};
83 return 0 unless $hosts;
85 my $clones = $hosts->{$host};
86 return 0 unless $clones;
88 $nick = PRE . $nick . POST;
89 if ((my $off = index $clones, $nick) >= $[) {
90 my $count = int $clones;
92 substr $clones, $off, length $nick, '';
93 return $hosts->{$host} = dualvar $count - 1, $clones;
95 delete $hosts->{$host};
102 my ($old, $new, $host, $serv, $chan) = @_;
104 my $hosts = $users{$serv}{$chan};
105 $users{$serv}{$chan} = $hosts = { } unless defined $hosts;
107 my $clones = $hosts->{$host};
109 $_ = PRE . $_ . POST for $old, $new;
110 if (defined $clones and (my $off = index $clones, $old) >= $[) {
111 my $count = int $clones;
112 substr $clones, $off, length $old, '';
113 $hosts->{$host} = dualvar $count, $clones . $new;
115 $hosts->{$host} = dualvar 1, $new;
120 my ($serv, $chan) = @_;
122 return unless $fetched{$serv}{$chan};
123 my $hosts = $users{$serv}{$chan};
126 while (my ($host, $clones) = each %$hosts) {
129 print $ph clone_list_str $host, $clones;
137 my ($serv, $chan) = @_;
139 return 0 unless $serv and $chan and $fetched{$serv}{$chan};
141 delete $users{$serv}{$chan};
142 delete $users{$serv} unless keys %{$users{$serv}};
144 delete $fetched{$serv}{$chan};
145 delete $fetched{$serv} unless keys %{$fetched{$serv}};
151 my ($serv, $chan) = @_;
154 for (get_list 'users') {
155 my $host = $_->{host};
156 next unless defined $host and length $host;
157 add $_->{nick}, host($host), $serv, $chan;
160 $fetched{$serv}{$chan} = 1 if $users;
165 hook_server '315', sub { # WHO end
166 my $serv = get_info 'server';
167 my $chan = lc $_[0][3];
169 return EAT_NONE if $fetched{$serv}{$chan};
171 my $oldctxt = get_context;
172 set_context $chan, $serv or return EAT_NONE;
175 set_context $oldctxt;
180 # On join, the who finishes sometimes before the tab opens, so the scan result
181 # isn't always displayed in the proper context. Hence the delay()
183 hook_server 'JOIN', sub {
184 my ($nick, $userhost) = ($_[0][0] =~ /^:([^!]+)!(.*)/);
185 my $serv = get_info 'server';
186 my $chan = lc substr $_[0][2], 1; # starts with colon
188 my $clones = add $nick, host($userhost), $serv, $chan;
190 my $oldctxt = get_context;
191 if (set_context $chan, $serv) {
192 print $ph clone_enter_str $userhost, $nick, $clones;
193 set_context $oldctxt;
195 delay JOIN_DELAY, sub {
196 my $oldctxt = get_context;
197 return unless set_context $chan, $serv;
198 print $ph clone_enter_str $userhost, $nick, $clones;
199 set_context $oldctxt;
207 hook_server 'KICK', sub {
209 my $serv = get_info 'server';
210 my $chan = lc $_[0][2];
212 if (nickcmp get_info('nick'), $nick) {
213 my $userinfo = user_info $nick;
214 my $userhost = $userinfo->{host};
215 if (defined $userhost and length $userhost) {
216 # If this isn't true, the kick happened before the first WHO response was
217 # received and the nick isn't in the database yet.
218 my $clones = remove $nick, host($userhost), $serv, $chan;
219 print $ph clone_leave_str $userhost, $nick, $clones if $clones > 0;
228 hook_server 'PART', sub {
229 my ($nick, $userhost) = ($_[0][0] =~ /^:([^!]+)!(.*)/);
230 my $serv = get_info 'server';
231 my $chan = lc $_[0][2];
233 if (nickcmp get_info('nick'), $nick) {
234 my $clones = remove $nick, host($userhost), $serv, $chan;
235 print $ph clone_leave_str $userhost, $nick, $clones if $clones > 0;
243 hook_server 'QUIT', sub {
244 my ($nick, $userhost) = ($_[0][0] =~ /^:([^!]+)!(.*)/);
245 my $serv = get_info 'server';
246 my @chans = map [ lc $_->{channel}, $_->{context} ],
247 grep { $_->{type} == 2 and $_->{server} eq $serv }
250 if (nickcmp get_info('nick'), $nick) {
251 my $oldctxt = get_context;
252 my $host = host $userhost;
254 my $clones = remove $nick, $host, $serv, $_->[0];
256 set_context $_->[1] or next;
257 print $ph clone_leave_str $userhost, $nick, $clones;
260 set_context $oldctxt;
262 flush $serv, $_->[0] for @chans;
268 hook_print 'Disconnected', sub {
269 my %servers = map { $_->{server} => 1 }
270 grep { $_->{flags} & 9 and not($_->{flags} & 2) }
273 delete @users{ grep !$servers{$_}, keys %users};
274 delete @fetched{grep !$servers{$_}, keys %fetched};
280 my ($old, $new) = @{$_[0]};
282 my $userinfo = user_info $new;
283 $userinfo = user_info $old unless defined $userinfo;
285 my $userhost = $userinfo->{host};
286 if (defined $userhost and length $userhost) {
287 # If the host isn't defined, the first WHO response hasn't been received yet,
288 # so the old nick isn't even in our database.
289 # Otherwise, the new nick would be added right now, and the old one would be
290 # when the WHO responses arrives (which may still refer to the old nick).
291 replace $old => $new, host($userhost),
292 get_info('server'), lc get_info('channel');
298 hook_print $_, \&nick_cb for 'Change Nick', 'Your Nick Changing';
300 hook_command 'CLSCAN', sub {
301 my $serv = get_info 'server';
302 my $chan = lc get_info 'channel';
304 if (!$fetched{$serv}{$chan} and !fetch($serv, $chan)) {
305 print $ph "Data still not available\n";
306 } elsif (!scan($serv, $chan)) {
307 print $ph "No clones found\n";
312 help_text => 'Scan for clones in the current channel'
315 $ph = Xchat::XPI->new(
316 name => 'Clones scanner',
318 desc => 'Automatic & on-demand clones scanner',
319 author => 'Vincent Pit (VPIT)',
320 email => 'perl@profvince.com',
321 url => 'http://www.profvince.com',
322 unload => sub { undef %users },