#!/usr/bin/perl -w # XChat hooks and hacks # my $name = "Waider's XChat hacks"; my $version = "0.3.1"; # constants, which should be in the fricking IRC:: module. sub IRC::xchat_version { 0 } # xchat version sub IRC::my_nickname { 1 } # your nickname sub IRC::channel { 2 } # channel sub IRC::server { 3 } # server sub IRC::xchatdir { 4 } # xchatdir sub IRC::away_status { 5 } # away status sub IRC::network_name { 6 } # network name sub IRC::server_host { 7 } # server hostname sub IRC::channel_topic { 8 } # channel topic # Register only if we're not already registered. Durr. Can't do that, can we? IRC::register( $name, $version, sub { print "shutting down\n" }, "" ); # name, version, shutdown routine, unused IRC::print( "Loading $name $version\n" ); IRC::print( "Registering handlers..." ); # IRC::add_command_handler( "cmd", "handler" ) cmd is a /cmd # IRC::add_message_handler( "msg", "handler" ) msg is IRC message/numeric code # IRC::add_print_handler( "msg", "handler" ) msg is XChat message IRC::add_message_handler( "PRIVMSG", "privmsg_handler" ); IRC::add_message_handler( "NOTICE", "notice_handler" ); # BAD WAIDER IRC::add_timeout_handler( 5000, "check_screensaver" ); IRC::add_print_handler( "Notify Online", "notify_handler" ); IRC::print( "Registering handlers...done.\n" ); # state stuff my %notified; my $lastsplash = 0; my $tried_nickserv = 0; # config my %conf; my $xchatdir = IRC::get_info( IRC::xchatdir ); $xchatdir ||= $ENV{'HOME'} . "/.xchat"; # config file: # [section] # option = value if ( open( FILE, "<" . $xchatdir . "/waider.conf" )) { my $section = "general"; while (my $line = ) { next if $line =~ /^([;#].*|\s*)$/; # skip blanks + comments if ( $line =~ /^\[(.*)\]\s*$/ ) { $section = $1; next; } my ( $option, $value ) = split( /\s*=\s*/, $line, 2 ); if ( defined( $option ) and defined( $value )) { $conf{$section} ||= {}; $conf{$section}->{$option} = $value; } } } # small amount of sanity $conf{general} ||= {}; $conf{general}->{timeout} = 20 if !defined( $conf{general}->{timeout}) or $conf{general}->{timeout} !~ /^\d+$/; $conf{general}->{xsplash} = "xsplash -geometry -0+0 -font lucidasanstypewriter-24 -text '\%s' -timeout 5"; $conf{general}->{debug} ||= 0; use Data::Dumper; if ( $conf{general}->{debug}) { print STDERR Dumper( \%conf ) . "\n"; } # Docs: # http://www.irchelp.org/irchelp/rfc1459.html # http://www.irchelp.org/irchelp/ircd/numerics.html # http://www.irchelp.org/irchelp/ircd/hybrid6.html sub notify_handler { # args: 'user host ', 'user', 'host' my ( $comp, $user, $host ) = @_; my $cmd = sprintf( $conf{general}->{xsplash}, "$user is online" ); system( "sh -c \"$cmd &\"" ); } sub notice_handler { my ( $sender, $type, $channel, $message ) = split( ' ', $_[0], 4 ); my $user; # Now clean things up $message =~ s/^://; ( $sender, $user ) = parse_sender( $sender ); # RFC says NEVER EVER send an automated reply to a NOTICE. So # we'll just try once, and give up. # nickserv auto-identify if ( $sender eq "NickServ" ) { print STDERR scalar( localtime( time )) . " IRC: NickServ chatter: $message\n" if $conf{general}->{debug}; if ( !$tried_nickserv and $message =~ /If this is your nickname, type / ) { my $nickservpass = $conf{nickserv}->{password}; IRC::command( "/msg nickserv identify $nickservpass" ) if $nickservpass; $tried_nickserv = 1; } } return; } sub privmsg_handler { my ( $sender, $type, $channel, $message ) = split( ' ', $_[0], 4 ); my $user; # Now clean things up $message =~ s/^://; ( $sender, $user ) = parse_sender( $sender ); # Ignore "foo is back" messages. return if $message =~ /^.ACTION/; # bitlbee autologin if ( $sender eq "root" and $channel =~ /bitlbee/ ) { if ( $message =~ /Welcome to the BitlBee gateway/ ) { my $pass = $conf{bitlbee}->{password} if $conf{bitlbee}; IRC::command( "identify $pass" ) if $pass; print STDERR "IRC: bitlbee detected but no passwd configured\n" unless $pass; } return; } # don't bother with the rest of this if I'm marked away if ( IRC::get_info( IRC::away_status )) { print STDERR scalar( localtime( time )) . " IRC: you're already away\n" if $conf{general}->{debug}; return; } # focus file doesn't update when you swap around tabs within the # same window, so we need to check what the current channel in # XChat is. my $current_channel = IRC::get_info( IRC::channel ); # Check focus # I used use a .focus file, generated by window-switching events in fvwm. # new mechanism uses xprop to query the WM directly my $id = `xprop -root ' \$0\n' _NET_ACTIVE_WINDOW 2>&1`; my $class = "filler"; my $title; if ( $id !~ /0x/ ) { $id ||= "unset"; print STDERR scalar( localtime( time )) . " IRC: can't get active window ($id)\n"; return; } else { ( undef, $id ) = split( /\s/, $id, 2 ); chomp( $id ); $title = `xprop -id $id ' \$0\n' WM_NAME 2>&1`; $title ||= "wtf unset"; chomp( $title ); ( undef, $title ) = split( /\s/, $title, 2 ); } if (( $title =~ m@/ $channel@i ) or ( $title =~ m@Dialog with $sender@i ) or ( $title =~ m@X-Chat@i and ( lc( $current_channel ) eq lc( $channel )))) { return; # no need, I'm already paying attention } else { print STDERR scalar( localtime( time )) . " IRC: Apparently you're looking at $title/$class/$id, which doesn't match $sender or $channel, or maybe $current_channel is wrong.\n" if $conf{general}->{debug}; } # focus is wrong, or I'm not looking at the channel I'm being addressed. # find out what my nick is (is this per channel?) my $nick = IRC::get_info( IRC::my_nickname ); my $bitlbee_regexp = $conf{bitlbee}->{regexp} if $conf{bitlbee}; # middle one's a bit bogus, I think, and the third one's definitely bogus. if ( $message =~ /^$nick:/i or $channel =~ /$nick/ or ( defined( $bitlbee_regexp ) and $channel =~ /$bitlbee_regexp/ )) { # check if I'm there my $status = screensaving(); if ( $status eq "blanked" ) { system( "xscreensaver-command", "--deactivate" ); } elsif ( $status eq "locked" ) { if ( defined( $notified{$user})) { IRC::print( $user . " already knows I'm away" ); } else { IRC::command( "/msg " . $sender . " sorry, I'm apparently away (screen locked)" ) unless $sender =~ /twitter/; IRC::command( "/away (screen locked)" ); $notified{$user} = time; } return; } # not more than every 20 seconds if ( time - $lastsplash > $conf{general}->{timeout} ) { my $cmd = sprintf( $conf{general}->{xsplash}, "$sender/$channel" ); system( "sh -c \"$cmd &\"" ); $lastsplash = time; } else { print STDERR scalar( localtime( time )) . " IRC: No xsplash because of frequency\n" if $conf{general}->{debug}; } } else { # talking to someone else } # got to here? make sure the notified hash is empty! %notified = (); return; } # split sender into nick / user@host sub parse_sender { my $sender = shift; $sender =~ m/^:(.*?)!(.*)$/; } sub which_screensaver { # XXX return "KDE"; } sub unblank_screen { if ( which_screensaver() eq "xscreensaver" ) { system( "xscreensaver-command", "--deactivate" ); } else { #system( "dcop kdesktop KScreensaverIface quit, apparently" ); } } sub screensaving { return "unknown"; # until I figure out why it's so damned flaky if ( which_screensaver() eq "xscreensaver" ) { if ( open( FILE, "xscreensaver-command --time 2>&1 |" )) { local $/ = undef; my $line = ; close( FILE ); if ( $line =~ /non-blanked/ or $line =~ /no saver status/si or $line =~ /no screensaver is running/ ) { # screen's active or hasn't yet slept or you didn't start # xscreensaver, disregard return "unknown"; } elsif ( $line =~ /blanked/ ) { return "blank"; } else { return "locked"; } } } else { # KDE doesn't appear to know the difference between blanked and locked. if ( open( FILE, "dcop kdesktop KScreensaverIface isBlanked|" )) { local $/ = undef; my $line = ; close( FILE ); if ( $line =~ /true/ ) { return "locked"; } } } return "unknown"; } # this is called on a 5-second timer to see if the screen's locked. It # would be nicer to run xscreensaver-command -watch on a blocking # read, but I don't know how well xchat would cope. sub check_screensaver { my $status = screensaving(); if ( $status eq "locked" ) { if ( !IRC::get_info( IRC::away_status )) { IRC::print( "Detected locked screen, marking you Away" ); # ngh. server_list rather daftly returns a list of servers # you're not connected to as well as ones you ARE # connected to, and there's no way of saying "Am I # connected to $srv?" # And more stupidly, if you're on multiple channels you # get the same server several times. my %done; for my $srv ( IRC::server_list()) { if ( !defined( $done{$srv})) { IRC::command_with_server( "/away (screen locked)", $srv ); $done{$srv} = $srv; } } } } # reschedule myself IRC::add_timeout_handler( 5000, "check_screensaver" ); }