~bnrubin/+junk/stalker

« back to all changes in this revision

Viewing changes to stalker.pl

  • Committer: Benjamin Rubin
  • Date: 2012-03-27 17:05:33 UTC
  • Revision ID: bnrubin@ubuntu.com-20120327170533-sm2wztj4ezqdskt8
* Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
use Irssi;
 
2
use vars qw/$VERSION %IRSSI/;
 
3
use File::Spec;
 
4
use DBI;
 
5
use POSIX qw/ strftime /;
 
6
use threads;
 
7
# Requires:
 
8
#   DBI
 
9
#   DBD::SQLite3
 
10
 
 
11
$VERSION = '0.50';
 
12
%IRSSI = (
 
13
    authors     => 'SymKat','Benjamin Rubin',
 
14
    contact     => 'symkat@symkat.com','bnrubin@ubuntu.com',
 
15
    name        => "stalker",
 
16
    decsription => 'Records and correlates nick!user@host information',
 
17
    license     => "BSD",
 
18
    url         => "http://github.com/symkat/stalker",
 
19
    changed     => "2010-10-06",
 
20
    changes     => "See Change Log",
 
21
);
 
22
 
 
23
# Bindings
 
24
Irssi::signal_add_last( 'event 311', \&whois_request );
 
25
Irssi::signal_add_last( 'event 314', \&whois_request );
 
26
Irssi::signal_add( 'message join', \&nick_joined );
 
27
Irssi::signal_add( 'nicklist changed', \&nick_changed_channel );
 
28
Irssi::signal_add( 'channel sync', \&channel_sync );
 
29
 
 
30
Irssi::command_bind( 'host_lookup', \&host_request );
 
31
Irssi::command_bind( 'nick_lookup', \&nick_request );
 
32
 
 
33
Irssi::theme_register([$IRSSI{'name'} => '{whois stalker %|$1}']);
 
34
 
 
35
# Settings
 
36
Irssi::settings_add_str( 'Stalker',  $IRSSI{name} . "_max_recursion", 20 );
 
37
Irssi::settings_add_str( 'Stalker',  $IRSSI{name} . "_guest_nick_regex", "/^Guest.*/i" );
 
38
Irssi::settings_add_str( 'Stalker',  $IRSSI{name} . "_debug_log_file", "stalker.log" );
 
39
 
 
40
Irssi::settings_add_bool( 'Stalker', $IRSSI{name} . "_verbose", 0 );
 
41
Irssi::settings_add_bool( 'Stalker', $IRSSI{name} . "_debug", 0 );
 
42
Irssi::settings_add_bool( 'Stalker', $IRSSI{name} . "_recursive_search", 1 );
 
43
Irssi::settings_add_bool( 'Stalker', $IRSSI{name} . "_search_this_network_only", 0 );
 
44
Irssi::settings_add_bool( 'Stalker', $IRSSI{name} . "_ignore_guest_nicks", 1 );
 
45
Irssi::settings_add_bool( 'Stalker', $IRSSI{name} . "_debug_log", 0 );
 
46
 
 
47
Irssi::settings_add_str( 'Stalker', $IRSSI{name} . "_db_path", "" );
 
48
Irssi::settings_add_str( 'Stalker', $IRSSI{name} . "_db_driver", "SQLite" );
 
49
Irssi::settings_add_str( 'Stalker', $IRSSI{name} . "_db_username", "stalker" );
 
50
Irssi::settings_add_str( 'Stalker', $IRSSI{name} . "_db_password", "" );
 
51
Irssi::settings_add_str( 'Stalker', $IRSSI{name} . "_db_server", "localhost" );
 
52
Irssi::settings_add_str( 'Stalker', $IRSSI{name} . "_db_port", "3306" );
 
53
Irssi::settings_add_str( 'Stalker', $IRSSI{name} . "_db_name", "stalker" );
 
54
 
 
55
my $count;
 
56
my %data;
 
57
my $str;
 
58
 
 
59
# Database
 
60
 
 
61
my $db_driver = Irssi::settings_get_str($IRSSI{name} . '_db_driver');
 
62
my $db_username = Irssi::settings_get_str($IRSSI{name} . '_db_username');
 
63
my $db_password = Irssi::settings_get_str($IRSSI{name} . '_db_password');
 
64
my $db_server = Irssi::settings_get_str($IRSSI{name} . '_db_server');
 
65
my $db_port = Irssi::settings_get_str($IRSSI{name} . '_db_port');
 
66
my $db_name = Irssi::settings_get_str($IRSSI{name} . '_db_name');
 
67
my $db_path = Irssi::settings_get_str($IRSSI{name} . '_db_path');
 
68
my $do = 0;
 
69
 
 
70
if ( $db_path == "" ) {
 
71
    $data_source = 'dbi:'.$db_driver.':database='.$db_name.';host='.$db_server.';port='.$db_port;
 
72
} else {
 
73
    if ( $db_path and ! File::Spec->file_name_is_absolute($db_path) ) {
 
74
        $db_path = File::Spec->catfile( Irssi::get_irssi_dir(), $db_path );
 
75
    }
 
76
    if ( ! -e $db_path ) {
 
77
        open my $fh, '>', $db_path
 
78
        or die "Cannot create database file.  Abort.";
 
79
        close $fh;
 
80
        $do = 1;
 
81
    }
 
82
 
 
83
    $data_source = 'dbi:'.$db_driver.':'.$db_path;
 
84
}
 
85
 
 
86
 
 
87
my $DBH = DBI->connect(
 
88
    $data_source, $db_username, $db_password,
 
89
    {
 
90
        RaiseError => 1,
 
91
        AutoCommit => 1,
 
92
    }
 
93
 
 
94
) or die "Failed to connect to database $data_source: " . $DBI::errstr;
 
95
 
 
96
stat_database( $DBH );
 
97
 
 
98
# Automatic Database Creation And Checking
 
99
sub stat_database {
 
100
    create_table( $DBH ) if $do;
 
101
 
 
102
    my $sth = $DBH->prepare( "SELECT nick from records WHERE serv = ?" );
 
103
    $sth->execute( 'script-test-string' );
 
104
    my $sane = $sth->fetchrow_array;
 
105
 
 
106
    create_table( $DBH ) if $sane == undef;
 
107
}
 
108
 
 
109
sub create_table {
 
110
    my ( $DBH ) = @_;
 
111
 
 
112
    my $query = "CREATE TABLE records (nick VARCHAR(50) NOT NULL," .
 
113
        "user VARCHAR(255) NOT NULL, host VARCHAR(255) NOT NULL, serv
 
114
        VARCHAR(255) NOT NULL)";
 
115
 
 
116
    $DBH->do( "DROP TABLE IF EXISTS records" );
 
117
    $DBH->do( $query );
 
118
    my $sth = $DBH->prepare( "INSERT INTO records (nick, user, host, serv) VALUES( ?, ?, ?, ? )" );
 
119
    $sth->execute( 1, 1, 1, 'script-test-string' );
 
120
}
 
121
 
 
122
 
 
123
# IRSSI Routines
 
124
 
 
125
sub whois_request {
 
126
    my ( $server, $data, $server_name ) = @_;
 
127
    my ( $me, $n, $u, $h ) = split(" ", $data );
 
128
   
 
129
    $server->printformat($n,MSGLEVEL_CRAP,$IRSSI{'name'},$n, 
 
130
        join( ", ", (get_records('host', $h, $server->{address}))));
 
131
}
 
132
 
 
133
sub host_request {
 
134
    windowPrint( join( ", ", (get_records('host', $_[0], $_[1]->{address}))));
 
135
}
 
136
 
 
137
sub nick_request {
 
138
    windowPrint( join( ", ", (get_records('nick', $_[0], $_[1]->{address}))));
 
139
}
 
140
 
 
141
#   Record Adding Functions
 
142
sub nick_joined {
 
143
    add_record($_[2], (split('@', $_[3]), $_[0]->{address}));
 
144
}
 
145
 
 
146
sub nick_changed_channel {
 
147
    add_record( $_[1]->{nick}, (split( '@', $_[1]->{host} )), $_[0]->{server}->{address} );
 
148
}
 
149
 
 
150
sub channel_sync {
 
151
    my ( $channel ) = @_;
 
152
    
 
153
    my $serv = $channel->{server}->{address};
 
154
    
 
155
    for my $nick ( $channel->nicks() ) {
 
156
        last if $nick->{host} eq ''; # Sometimes channel sync doesn't give us this...
 
157
        add_record( $nick->{nick}, ( split( '@', $nick->{host} ) ), $serv );
 
158
        #my $thread = threads->create("add_record",( $nick->{nick}, ( split( '@', $nick->{host} ) ), $serv ));
 
159
    } 
 
160
}
 
161
 
 
162
 
 
163
# Other Routines
 
164
 
 
165
sub add_record {
 
166
    my ( $nick, $user, $host, $serv ) = @_;
 
167
    
 
168
    # Check if we already have this record.
 
169
    my $q = "SELECT nick FROM records WHERE nick = ? AND user = ? AND host = ? AND serv = ? ORDER BY nick";
 
170
    my $sth = $DBH->prepare( $q );
 
171
    $sth->execute( $nick, $user, $host, $serv );
 
172
    my $result = $sth->fetchrow_hashref;
 
173
 
 
174
    if ( $result->{nick} eq $nick ) {
 
175
        debugPrint( "info", "Record for $nick skipped - already exists." );
 
176
        return 1;
 
177
    }
 
178
   
 
179
    debugPrint( "info", "Adding to DB: nick = $nick, user = $user, host = $host, serv = $serv" );
 
180
 
 
181
    # We don't have the record, add it.
 
182
    $sth = $DBH->prepare
 
183
        ("INSERT INTO records (nick,user,host,serv) VALUES( ?, ?, ?, ? )" );
 
184
    eval { $sth->execute( $nick, $user, $host, $serv ) };
 
185
    if ($@) {
 
186
        debugPrint( "crit", "Failed to process record, database said: $@" );
 
187
    }
 
188
 
 
189
    debugPrint( "info", "Added record for $nick!$user\@$host to $serv" );
 
190
}
 
191
 
 
192
sub get_records {
 
193
    my ( $type, $query, $serv, @return ) = @_;
 
194
    
 
195
    $count = 0; %data = (  );
 
196
    my %data = _r_search( $serv, $type, $query );
 
197
    for my $k ( keys %data ) {
 
198
        #_msg("$type query for records on $query from server $serv returned: $k" );
 
199
        push @return, $k if $data{$k} eq 'nick';
 
200
    }
 
201
    return @return;
 
202
}
 
203
 
 
204
sub _r_search {
 
205
    my ( $serv, $type, @input ) = @_;
 
206
    return %data if $count > 1000;
 
207
    return %data if $count > Irssi::settings_get_str($IRSSI{name} . "_max_recursion");
 
208
    return %data if $count == 2 and ! Irssi::settings_get_bool( $IRSSI{name} . "_recursive_search" );
 
209
 
 
210
    debugPrint( "info", "Recursion Level: $count" );
 
211
    
 
212
    if ( $type eq 'nick' ) {
 
213
        $count++;
 
214
        for my $nick ( @input ) {
 
215
            next if exists $data{$nick};
 
216
            $data{$nick} = 'nick';
 
217
            my @hosts = _get_hosts_from_nick( $nick, $serv );
 
218
            _r_search( $serv, 'host', @hosts );
 
219
        }
 
220
    } elsif ( $type eq 'host' ) {
 
221
        $count++;
 
222
        for my $host ( @input ) {
 
223
            next if exists $data{$host};
 
224
            $data{$host} = 'host';
 
225
            my @nicks = _get_nicks_from_host( $host, $serv );
 
226
            verbosePrint( "Got nicks: " . join( ", ", @nicks ) . " from host $host" );
 
227
            _r_search( $serv, 'nick', @nicks );
 
228
        }
 
229
    }
 
230
 
 
231
    return %data;
 
232
}
 
233
 
 
234
sub _get_hosts_from_nick {
 
235
    my ( $nick, $serv, @return ) = @_;
 
236
 
 
237
    my $sth;
 
238
    if ( Irssi::settings_get_bool( $IRSSI{name} .  "_search_this_network_only" ) ){
 
239
        $sth = $DBH->prepare( "select host from records where nick = ? and serv = ?" );
 
240
        $sth->execute( $nick, $serv );
 
241
    } else {
 
242
        $sth = $DBH->prepare( "select host from records where nick = ?" );
 
243
        $sth->execute( $nick );
 
244
    }
 
245
 
 
246
    while ( my $row = $sth->fetchrow_hashref ) {
 
247
        push @return, $row->{host};
 
248
    }
 
249
    return @return;
 
250
}
 
251
 
 
252
sub _get_nicks_from_host {
 
253
    my ( $host, $serv, @return ) = @_;
 
254
 
 
255
    my $sth;
 
256
    if ( Irssi::settings_get_bool( $IRSSI{name} .  "_search_this_network_only" ) ){
 
257
        $sth = $DBH->prepare( "select nick from records where host = ? and serv = ? order by nick" );
 
258
        $sth->execute( $host, $serv );
 
259
    } else {
 
260
        $sth = $DBH->prepare( "select nick from records where host = ? order by nick" );
 
261
        $sth->execute( $host );
 
262
    }
 
263
    
 
264
    while ( my $row = $sth->fetchrow_hashref ) {
 
265
        if ( Irssi::settings_get_bool($IRSSI{name} . "_ignore_guest_nicks") ) {
 
266
            my $regex = Irssi::settings_get_str( $IRSSI{name} . "_guest_nick_regex" );
 
267
            next if $row->{nick} =~ m/$regex/i;
 
268
        }
 
269
        push @return, $row->{nick};
 
270
    }
 
271
    return @return;
 
272
}
 
273
 
 
274
# Handle printing.
 
275
sub debugPrint {
 
276
    # Short cut - instead of two debug statements thoughout the code,
 
277
    # we'll send all debugPrint's to the debugLog function as well
 
278
 
 
279
    windowPrint( $IRSSI{name} . " Debug: " . $_[1] )
 
280
        if Irssi::settings_get_bool($IRSSI{name} . "_debug");
 
281
    debugLog( $_[0], $_[1] );
 
282
}
 
283
 
 
284
sub verbosePrint {
 
285
    windowPrint( $IRSSI{name} . " Verbose: " . $_[0] )
 
286
        if Irssi::settings_get_bool($IRSSI{name} . "_verbose");
 
287
}
 
288
 
 
289
sub debugLog {
 
290
    my ( $lvl, $msg ) = @_;
 
291
    return unless Irssi::settings_get_bool($IRSSI{name} . "_debug_log" );
 
292
    my $now = strftime( "[%D %H:%M:%S]", localtime );
 
293
 
 
294
    my $logpath = Irssi::settings_get_str( $IRSSI{name} . "_debug_log_file" );
 
295
    if ( ! File::Spec->file_name_is_absolute($logpath) ) {
 
296
        $logpath = File::Spec->catfile( Irssi::get_irssi_dir(), $logpath );
 
297
    }
 
298
 
 
299
    open my $fh, ">>", $logpath
 
300
        or die "Fatal error: Cannot open my logfile at " . $IRSSI{name} . "_debug_log_file for writing: $!";
 
301
    print $fh "[$lvl] $now $msg\n";
 
302
    close $fh;
 
303
}
 
304
 
 
305
sub windowPrint {
 
306
    Irssi::active_win()->print( $_[0] );
 
307
}
 
308
 
 
309
sub _error {
 
310
    my ($msg) = @_;
 
311
    my $win = Irssi::active_win();
 
312
    $win->print($msg, Irssi::MSGLEVEL_CLIENTERROR);
 
313
}
 
314
 
 
315
sub _msg {
 
316
    my ($msg) = @_;
 
317
    my $win = Irssi::active_win();
 
318
    $win->print($msg, Irssi::MSGLEVEL_CLIENTCRAP);
 
319
}
 
320
 
 
321
windowPrint( "Loaded $IRSSI{'name'}" );