#!/usr/bin/perl ######################################################################## # CTCP Extensions install instructions: # place ctcp.pl in your ~/.xchat directory. # # CTCP Extensions provides a CTCP PAGE and SEEN responses along with # providing CTCP flood protection # # Programming notes: All 'global' variables are in hash form for # easier tracking. If xchat saves prefs on exit, so does this # script. Use /ctcpsave to manually save CTCP Extentions prefs. # # Copyright (C) 2000-2001 Gatewood Green # http://woody.linif.org/ ######################################################################## ##### use declarations ################################################# package CTCP; use warnings; ##### Global variables ################################################# my %scriptinfo = ( 'name' => 'CTCP Extensions', 'version' => '0.1.2', 'author' => 'Woody Green', 'email' => 'woody@linif.org', 'url' => 'http://woody.linif.org/xchat/' ); # Script configuration my %config = ( 'floodcontrol' => 1, 'timespan' => 60, 'totallimit' => 10, 'masklimit' => 3, 'page' => 0, 'pagesound' => '', 'pageplayer' => 'play', 'seen' => 0, 'maxseendays' => 30 ); # CTCP limit scratch pad my %ctcp = ( 'totalcount' => [], 'maskcount' => {} ); # SEEN scratch pad my %seen = ( ); # Config and data files my %temp = ( 'seenDB' => IRC::get_info( 4 ) . "/CTCP.seenDB", 'config' => IRC::get_info( 4 ) . "/CTCP.config" ); # Error scratch pad my %errors = ( 'config' => 0, 'seen' => 0 ); # Load the seen DB &CTCP::readseen( ); &CTCP::readconfig( ); ##### Script setup ##################################################### # Register script with xchat IRC::register( $scriptinfo{'name'}, $scriptinfo{'version'}, 'CTCP::cleanup', '' ); # Message handlers IRC::add_message_handler( 'PRIVMSG' , 'CTCP::handler_message_privmsg' ); # Catches channel messages, personal messages and CTCPs. IRC::add_message_handler( 'JOIN' , 'CTCP::handler_message_join' ); # Track users for seen function (as they join channels) IRC::add_message_handler( '352' , 'CTCP::handler_message_352' ); # Track users for seen function (users in a channel when you join) IRC::add_message_handler( 'PART' , 'CTCP::handler_message_part' ); # Track users for seen function (as they part channels) IRC::add_message_handler( 'QUIT' , 'CTCP::handler_message_quit' ); # Track users for seen function (as they quit) IRC::add_message_handler( "NICK" , 'CTCP::handler_message_nick' ); # Track users for seen function (nick change) # Command handlers IRC::add_command_handler( 'CTCPSTATS' , 'CTCP::handler_command_ctcpstats' ); IRC::add_command_handler( 'PAGE' , 'CTCP::handler_command_page' ); IRC::add_command_handler( 'SEEN' , 'CTCP::handler_command_seen' ); IRC::add_command_handler( 'FLOODCONTROL', 'CTCP::handler_command_floodcontrol' ); IRC::add_command_handler( 'CTCPSAVE' , 'CTCP::handler_command_ctcpsave' ); IRC::add_command_handler( 'CTCPHELP' , 'CTCP::handler_command_ctcphelp' ); IRC::add_command_handler( 'CTCPSET' , 'CTCP::handler_command_ctcpset' ); IRC::add_command_handler( 'SET' , 'CTCP::handler_command_ctcpset' ); IRC::add_command_handler( 'SAVE' , 'CTCP::handler_command_save' ); # Timeout handlers IRC::add_timeout_handler( 1800000 , 'CTCP::handler_timeout_maintenance' ); # 30 minutes # Confirm load &CTCP::printLine( ); IRC::print ( "Loading \002" . $scriptinfo{'name'} . "\002 version: \002" . $scriptinfo{'version'} . "\002\n" . " By: " . $scriptinfo{'author'} . " \002<\002" . $scriptinfo{'email'} . "\002>\002\n" . " " . $scriptinfo{'url'} . "\n" . " For help: \002/ctcphelp\002\n" ); &CTCP::printLine( ); #### Message handler functions ######################################### sub handler_message_privmsg ( $ ) { my $privmsg = shift; # CTCP flood control if ( $config{'floodcontrol'} ) { if ( $privmsg =~ /\001.+\001/ && $privmsg !~ /\001ACTION\s/i ) { my $time = time( ); # Store it in a variable to use the exact same time in all comparisions. my $mask = substr( $privmsg, 1, index( $privmsg, " " ) - 1 ); $mask = lc( $mask ); my @ignore = IRC::ignore_list( ); for ( my $i = 0; $i <= $#ignore; $i += 8 ) { if ( length( $ignore[$i] ) > 1 ) { $ignore[$i] =~ s/\*/.*?/g; if ( $mask =~ /$ignore[$i]/i && $ignore[$i + 3] ) { return 0; } # We're already ignoring } } # Add ctcp time to overall total and mask total arrays push @{$ctcp{'maskcount'}{$mask}}, $time; push @{$ctcp{'totalcount'}}, $time; #IRC::print( $mask, @{$ctcp{'maskcount'}{$mask}} ); # Cull data over the time threshold preenLimits( $time ); my $error = ""; if ( $#{$ctcp{'totalcount'}} >= $config{'totallimit'} ) { $error .= "Total CTCP limit " . $config{'totallimit'} . " exceeded " . $#{$ctcp{'totalcount'}} . " for the past " . $config{'timespan'} . ". Ignoring all CTCPs.\n"; } if ( $#{$ctcp{'maskcount'}{$mask}} >= $config{'masklimit'} ) { $mask =~ s/!~/!*/; IRC::command( "/ignore $mask CTCP QUIET" ); $error .= "$mask added to the ignore list for CTCP flooding.\n"; delete $ctcp{'maskcount'}{$mask}; } if ( $error ) { IRC::print( $error, "Ignoring CTCP request from $mask. (\002" . $scriptinfo{'name'} . "\002)\n" ); return 1; } } } # Custom CTCP responses if ( $privmsg =~ /\001PAGE\001/i ) { # Deal with CTCP PAGE requests my ( $sender ) = ( $privmsg =~ /:(.+)!/ ); if ( $config{'page'} ) { # Acknowledge the page IRC::command( "/NOTICE $sender PAGE received. (" . $scriptinfo{'name'} . " " . $scriptinfo{'version'} . ")" ); # Spawns instead of waits IRC::add_timeout_handler( 0, "CTCP::handler_timeout_page" ); } else { my $nick = IRC::get_info( 1 ); IRC::command( "/NOTICE $sender $nick has CTCP PAGE disabled. (" . $scriptinfo{'name'} . " " . $scriptinfo{'version'} . ")" ); } } elsif ( $privmsg =~ /\001SEEN/i && $config{'seen'} ) { # Deal with CTCP SEEN requests my ( $sender ) = ( $privmsg =~ /:(.+)!/ ); my ( $nick ) = ( $privmsg =~ /SEEN (.+)\001$/ ); $nick =~ s/\s//g; if ( uc( IRC::get_info( 1 ) ) ne uc( $nick ) ) { IRC::command( "/NOTICE $sender " . CTCP::seen( $nick ) ); } else { IRC::command( "/NOTICE $sender Why are you asking if I've seen myself?" ); } } elsif ( $privmsg =~ /PRIVMSG #/ && $config{'seen'} ) { # Capture data for seen function my $server = IRC::get_info( 3 ); my ( $nick, $mask, $channel ) = ( $privmsg =~ /^:(.+)!(.+) PRIVMSG (\S+) :.*$/ ); $seen{uc( $nick )}[0] = $mask; $seen{uc( $nick )}[1] = $channel; $seen{uc( $nick )}[2] = $server; $seen{uc( $nick )}[3] = time( ); $seen{uc( $nick )}[4] = "talking"; } return 0; } sub handler_message_nick ( $ ) { # This deals with your nick change notices # Note: you get nick change notices when anyone in any # channel you are in or in msg/query with changes their nick my $arg = shift; if ( $config{'seen'} ) { # Get data for Seen function my ( $oldnick, $mask, $newnick ) = ( $arg =~ /^:(.+)!(.+) NICK :(.+)$/ ); my $server = IRC::get_info( 3 ); # Get server context my $channel = IRC::get_info( 2 ); # Old nick data $seen{uc( $oldnick )}[0] = $mask; $seen{uc( $oldnick )}[1] = $channel; $seen{uc( $oldnick )}[2] = $server; $seen{uc( $oldnick )}[3] = time( ); $seen{uc( $oldnick )}[4] = "newnick:$newnick"; # New nick data $seen{uc( $newnick )}[0] = $mask; $seen{uc( $newnick )}[1] = $channel; $seen{uc( $newnick )}[2] = $server; $seen{uc( $newnick )}[3] = time( ); $seen{uc( $newnick )}[4] = "oldnick:$oldnick"; } return 0; # Pass info on to xchat for further processing } sub handler_message_join ( $ ) { # Capture user data for seen function my $arg = shift; if ( $config{'seen'} ) { my $server = IRC::get_info( 3 ); my ( $nick, $mask, $channel ) = ( $arg =~ /:(.+)!(.+) JOIN :(.+)/ ); $seen{uc( $nick )}[0] = $mask; $seen{uc( $nick )}[1] = $channel; $seen{uc( $nick )}[2] = $server; $seen{uc( $nick )}[3] = time( ); $seen{uc( $nick )}[4] = "joining"; } return 0; } sub handler_message_352 ( $ ) { # Capture user data for seen function my $arg = shift; if ( $config{'seen'} ) { my $mynick = IRC::get_info( 1 ); my ( $channel, $user, $host, $server, $nick ) = ( $arg =~ /.+$mynick (\S+) (\S+) (\S+) (\S+) (\S+) .+/ ); $seen{uc( $nick )}[0] = "$user\@$host"; $seen{uc( $nick )}[1] = $channel; $seen{uc( $nick )}[2] = $server; $seen{uc( $nick )}[3] = time( ); $seen{uc( $nick )}[4] = "in"; } return 0; } sub handler_message_part ( $ ) { # Capture user data for seen function my $arg = shift; if ( $config{'seen'} ) { my $server = IRC::get_info( 3 ); my ( $nick, $mask, $channel ) = ( $arg =~ /:(.+)!(.+) PART :(.+)/ ); $seen{uc( $nick )}[0] = $mask; $seen{uc( $nick )}[1] = $channel; $seen{uc( $nick )}[2] = $server; $seen{uc( $nick )}[3] = time( ); $seen{uc( $nick )}[4] = "leaving"; } return 0; } sub handler_message_quit ( $ ) { # Capture user data for seen function my $arg = shift; if ( $config{'seen'} ) { my $server = IRC::get_info( 3 ); my $channel = IRC::get_info( 2 ); my ( $nick, $mask ) = ( $arg =~ /:(.+)!(.+) QUIT :/ ); $seen{uc( $nick )}[0] = $mask; $seen{uc( $nick )}[1] = $channel; $seen{uc( $nick )}[2] = $server; $seen{uc( $nick )}[3] = time( ); $seen{uc( $nick )}[4] = "quitting"; } return 0; } #### Command handler functions ######################################### sub handler_command_ctcphelp ( ) { # CTCP provides a CTCP PAGE and SEEN responses along with providing CTCP flood protection CTCP::printLine( ); IRC::print ( "CTCP Extensions provides a CTCP PAGE and SEEN responses \n" . " along with providing CTCP flood protection\n\n" . "CTCP Extensions has the following (documented) commands:\n" . " page seen floodcontrol\n" . " ctcpstats\n" . " ctcpsave\n\n" . "For detailed help: \002/ help\002\n" ); CTCP::printLine( ); return 1; } sub handler_command_ctcpset ( ;$ ) { my ( $var, $value ) = ( $_[0] =~ /(\S*)\s(.*)/ ); if ( exists( $config{lc( $var )} ) ) { $config{lc( $var )} = $value; if ( lc( $var ) == 'seen' && $value == 1 ) { CTCP::readseen( ); } IRC::print( "$var set to $value." ); return 1; } else { IRC::print( "Sorry, I have no setting called '$var'." ); return 0; } } sub handler_command_ctcpstats ( ) { preenLimits( time( ) ); CTCP::printLine( ); IRC::print( "Max CTCPs in $config{'timespan'} seconds: $config{'totallimit'}\n" ); IRC::print( "Max CTCPs from one hostmask in $config{'timespan'} seconds: $config{'masklimit'}\n" ); my $message = "Number of CTCP requests honored in the last $config{'timespan'} seconds: "; if ( $#{$ctcp{'totalcount'}} <= $config{'totallimit'} - 1 ) { $message .= ( $#{$ctcp{'totalcount'}} + 1 ) . "\n"; } else { $message .= ( $config{'totallimit'} ) . "\n"; } IRC::print( $message ); foreach my $mask ( keys %{$ctcp{'maskcount'}} ) { my $message = "Number of CTCPs from $mask in the last $config{'timespan'} seconds: " . ( $#{$ctcp{'maskcount'}{$mask}} + 1 ) . "\n"; IRC::print( $message ); } CTCP::printLine( ); return 1; } sub handler_command_page ( $ ) { # Accepts 1 of many possible arguments: ON|OFF|SHOW|HELP|NONE|PLAY||PLAYER my $arg = shift; $arg =~ s/^\s+?//; # Remove leading spaces if ( $arg =~ /^ON/i ) { $config{'page'} = 1; IRC::print( "Turned CTCP PAGE on.\n" ); } elsif ( $arg =~ /^OFF/i ) { $config{'page'} = 0; IRC::print( "Turned CTCP PAGE off.\n" ); } elsif ( $arg =~ /^HELP/i ) { IRC::print( "/page ON|OFF|SHOW|HELP|NONE|PLAY||PLAYER \n" . " Turns the CTCP PAGE feature on or off or sets the PAGE sound to .\n" . " /page show <-- View current CTCP PAGE settings.\n" ); } elsif ( $arg =~ /^SHOW/i ) { my $status; ( $config{'page'} ) ? ( $status = "ON" ) : ( $status = "OFF" ); CTCP::printLine( ); IRC::print( "Current CTCP PAGE status is $status.\n" . "Current CTCP PAGE sound is " . $config{'pagesound'} . "\n" . "Current CTCP PAGE player is " . $config{'pageplayer'} . "\n" ); CTCP::printLine( ); } elsif ( $arg =~ /^NONE/i ) { $config{'pagesound'} = ""; IRC::print( "Set CTCP PAGE sound to nothing.\n" ); } elsif ( $arg =~ /^PLAYER/i ) { $arg =~ s/player/PLAYER/i; my ( $player ) = ( $arg =~ /PLAYER (.+)/ ); $config{'pageplayer'} = $player; IRC::print( "Set CTCP PAGE player to $player.\n" ); } elsif ( $arg =~ /^PLAY/i ) { IRC::print( "Playing CTCP PAGE sound...\n" ); IRC::add_timeout_handler( 0, "CTCP::handler_timeout_page" ); } elsif ( length( $arg ) > 0 ) { $config{'pagesound'} = $arg; IRC::print( "Set CTCP PAGE sound to $arg.\n" ); } else { IRC::print( "/page ON|OFF|SHOW|HELP|NONE|PLAY||PLAYER \n" . " Turns the CTCP PAGE feature on or off or sets the PAGE sound to .\n" . " /page show <-- View current CTCP PAGE settings.\n" ); } return 1; } sub handler_command_seen ( $ ) { my $arg = shift; $arg =~ s/^\s+//; $arg =~ s/\s+$//; if ( $arg =~ /^ON$/i ) { $config{'seen'} = 1; CTCP::readseen( ); IRC::print( "Turned CTCP SEEN on.\n" ); } elsif ( $arg =~ /^OFF$/i ) { $config{'seen'} = 0; CTCP::writeseen( ); IRC::print( "Turned CTCP SEEN off.\n" ); } elsif ( $arg =~ /^CLEAR$/i ) { %seen = ( ); CTCP::writeseen( ); IRC::print( "Turned CTCP SEEN database cleared.\n" ); } elsif ( $arg =~ /^SHOW$/i ) { my $status; ( $config{'seen'} ) ? ( $status = "ON" ) : ( $status = "OFF" ); CTCP::printLine( ); IRC::print( "CTCP SEEN is $status.\n" ); IRC::print( "There are " . keys( %seen ) . " entries in the SEEN database.\n" ); CTCP::printLine( ); } elsif ( $arg =~ /^HELP$/i ) { IRC::print( "/seen ON|OFF|CLEAR|HELP|SHOW| \n" . " Shows the last time a given nick was seen by xchat.\n" . " /seen show <-- View current SEEN settings.\n" . " /seen clear <-- Clear SEEN database.\n" ); } elsif ( length( $arg ) > 0 ) { IRC::print( CTCP::seen( $arg ) ); } else { IRC::print( "Who is it that you are curious about?" ); } return 1; } sub handler_command_floodcontrol ( $;$ ) { my ( $arg1, $arg2 ) = shift; if ( $arg1 =~ /^ON$/i ) { $config{'floodcontrol'} = 1; IRC::print( "Turned CTCP flood control on.\n" ); } elsif ( $arg1 =~ /^OFF$/i ) { $config{'floodcontrol'} = 0; IRC::print( "Turned CTCP flood control off.\n" ); } elsif ( $arg1 =~ /^TIMESPAN$/i && $arg2 > 0 ) { $config{$arg1} = $arg2; IRC::print( "Set flood control TIMESPAN to $arg2 seconds.\n" ); } elsif ( $arg1 =~ /^TOTALLIMIT$/i && $arg2 > 0 ) { $config{$arg1} = $arg2; IRC::print( "Set max CTCP requests to $arg2 in " . $config{'timespan'} . " seconds.\n" ); } elsif ( $arg1 =~ /^MASKLIMIT$/i && $arg2 > 0 ) { $config{$arg1} = $arg2; IRC::print( "Set max CTCP requests from one user to $arg2 in " . $config{'timespan'} . " seconds.\n" ); } elsif ( $arg1 =~ /^HELP$/i ) { IRC::print( "/FLOODCONTROL ON|OFF|TIMESPAN |TOTALLIMIT |MASKLIMIT |HELP\n" . " Set various CTCP flood contol values.\n" . " ON and OFF enable and disable flood control functions.\n" . " TIMESPAN is the length of running time over which to track\n" . " CTCP request.\n" . " TOTALLIMIT is the max number of CTCP requests to honor over\n" . " the set time span.\n" . " MASKLIMIT is the max number of CTCP requests from one user to\n" . " honor over the set time span.\n" ); } return 1; #'timespan' => 60, #'totallimit' => 10, #'masklimit' => 3 } sub handler_command_ctcpsave ( ) { CTCP::writeconfig( ); if ( $config{'seen'} == 1 ) { CTCP::writeseen( ); IRC::print( "'CTCP Extensions' settings and seen database saved." ); } else { IRC::print( "'CTCP Extensions' settings saved." ); } return 1; } sub handler_command_save ( ) { # Pass through save handler_command_ctcpsave ( ); return 0; } #### Timeout handler functions ######################################### sub handler_timeout_maintenance ( ) { # Backup seen database IRC::add_timeout_handler( 1800000, "CTCP::handler_timeout_maintenance" ); CTCP::writeseen( ); return 0; } sub handler_timeout_page ( ) { # If a page sound exists, play it. if ( $config{'pagesound'} ) { if ( -e $config{'pagesound'} ) { system( $config{'pageplayer'} . " " . $config{'pagesound'} ); } else { IRC::print( "Configured CTCP PAGE sound file not found.\n" . " For CTCP PAGE sound, try /page HELP\n" ); } } else { IRC::print( "CTCP PAGE received, but no sound configured.\n" . " For CTCP PAGE sound, try /page HELP\n" ); } return 0; } #### Helper functions ################################################## sub printLine ( ;$$ ) { my $server = shift; my $channel = shift; if ( $server ) { IRC::print_with_channel ( "\0034--------------------------------------------\003\n", $channel, $server ); } else { IRC::print ( "\0034--------------------------------------------\003\n" ); } return 0; } sub getDate ( ;$ ) { if ( $_[0] ) { $time = $_[0]; } else { $time = time( ); } my ( $sec,$min,$hour,$mday,$mon,$year ) = localtime( $time ); my $date = sprintf "%04u-%02u-%02u %02u:%02u:%02u", ( $year += 1900 ), ( $mon += 1 ), $mday, $hour, $min, $sec; return $date; } sub convertsecs ( $ ) { my ( $sec, $min, $hour, $time ); $sec = shift; $min = $sec / 60; $sec = $sec % 60; $hour = $min / 60; $min = $min % 60; $hour =~ s/\..*$//; $time = "$hour hours, $min minutes."; return $time; } sub preenLimits ( $ ) { # Cull data over the time threshold @{$ctcp{'totalcount'}} = grep( ( $_ >= $_[0] - $config{'timespan'} ), @{$ctcp{'totalcount'}} ); my @keys = keys( %{$ctcp{'maskcount'}} ); for( my $i =0; $i <= $#keys; $i++ ) { @{$ctcp{'maskcount'}{$keys[$i]}} = grep( ( $_ >= $_[0] - $config{'timespan'} ), @{$ctcp{'maskcount'}{$keys[$i]}} ); if ( $#{$ctcp{'maskcount'}{$keys[$i]}} == -1 ) { delete( $ctcp{'maskcount'}{$keys[$i]} ); } } } sub seen ( $ ) { my $nick = shift; my $result = ""; if ( exists( $seen{uc( $nick )} ) ) { if ( $seen{uc( $nick )}[4] =~ /^newnick:/ ) { my ( $newnick ) = ( $seen{uc( $nick )}[4] =~ /^newnick:(.+)$/ ); $result = sprintf "\002$nick\002 (\002" . $seen{uc( $nick )}[0] . "\002) was last seen \0034changing to $newnick\003" . " on server: \002" . $seen{uc( $nick )}[2] . "\002 at \002" . CTCP::getDate( $seen{uc( $nick )}[3] ) . "\002."; } elsif ( $seen{uc( $nick )}[4] =~ /^oldnick:/ ) { my ( $oldnick ) = ( $seen{uc( $nick )}[4] =~ /^oldnick:(.+)$/ ); $result = sprintf "\002$nick\002 (\002" . $seen{uc( $nick )}[0] . "\002) was last seen \0034changing from $oldnick\003" . " on server: \002" . $seen{uc( $nick )}[2] . "\002 at \002" . CTCP::getDate( $seen{uc( $nick )}[3] ) . "\002."; } elsif ( $seen{uc( $nick )}[4] =~ /^quitting/ ) { my ( $oldnick ) = ( $seen{uc( $nick )}[4] =~ /^oldnick:(.+)$/ ); $result = sprintf "\002$nick\002 (\002" . $seen{uc( $nick )}[0] . "\002) was last seen \0034quitting\003" . " on server: \002" . $seen{uc( $nick )}[2] . "\002 at \002" . CTCP::getDate( $seen{uc( $nick )}[3] ) . "\002."; } else { $result = sprintf "\002$nick\002 (\002" . $seen{uc( $nick )}[0] . "\002) was last seen \0034" . $seen{uc( $nick )}[4] . "\003 \002" . $seen{uc( $nick )}[1] . "\002" . " on server: \002" . $seen{uc( $nick )}[2] . "\002 at \002" . CTCP::getDate( $seen{uc( $nick )}[3] ) . "\002."; } } else { $result = sprintf "I have not seen \002$nick\002."; } return $result; } sub readseen ( ) { if ( -e $temp{'seenDB'} ) { open SEEN, "<" . $temp{'seenDB'} or CTCP::seenerror( "READ" ); if ( !$errors{'seen'} ) { while ( ) { $_ =~ s/^#.*$//; if ( length( $_ ) > 1 ) { my ( $nick, $mask, $channel, $server, $date, $action ) = ( /^(\S+) (\S+) (\S+) (\S+) (\S+) (\S+)$/ ); $seen{uc( $nick )}[0] = $mask; $seen{uc( $nick )}[1] = $channel; $seen{uc( $nick )}[2] = $server; $seen{uc( $nick )}[3] = $date; $seen{uc( $nick )}[4] = $action; } } close (SEEN); } } return 0; } sub writeseen ( ) { if ( !$errors{'seen'} ) { open SEEN, ">" . $temp{'seenDB'} or CTCP::seenerror( "WRITE" ); if ( !$errors{'seen'} ) { print SEEN "# CTCP seen file.\n"; print SEEN "# Do not edit this file.\n"; print SEEN "# Format: nick mask channel server time action\n"; print SEEN "# mask format: user\@host\n"; print SEEN "# channel can also be a server for certain actions\n"; print SEEN "# time is in seconds since Jan 1 1970 (UNIX)\n"; print SEEN "# action has several formats depending on the situation\n\n"; foreach my $key ( keys %seen ) { if ( ( ( time( ) - 86400 * $config{'maxseendays'} ) < $seen{$key}[3] ) && length( $key ) > 0 ) { # Only keep XX days worth of records print SEEN "$key ". join( " ", $seen{$key}[0], $seen{$key}[1], $seen{$key}[2], $seen{$key}[3], $seen{$key}[4] ) . "\n"; } } close (SEEN); } } return 0; } sub seenerror ( $ ) { if ( $_[0] =~ /READ/i ) { IRC::print( "Seen file found but inaccesable.\n" ); $errors{'seen'} = 1; } elsif ( $_[0] =~ /WRITE/i ) { IRC::print( "Could not open seen file for writing.\n" ); $errors{'seen'} = 2; } return 0; } sub readconfig ( ) { if ( -e $temp{'config'} ) { open CONFIG, "<" . $temp{'config'} or CTCP::configerror( "READ" ); if ( !$errors{'config'} ) { while ( ) { $_ =~ s/=/ /g; $_ =~ s/\s+/ /g; $_ =~ s/^\s//; $_ =~ s/#.*$//; if ( length( $_ ) > 1 ) { my ( $var, $val ) = split " ", $_; $config{$var} = $val; } } close (CONFIG); } } return 0; } sub writeconfig ( ) { if ( !$errors{'config'} ) { open CONFIG, ">" . $temp{'config'} or CTCP::configerror( "WRITE" ); if ( !$errors{'config'} ) { print CONFIG "# CTCP config file.\n"; print CONFIG "# Line style: variable = value\n"; print CONFIG "# #'s indicate coments.\n\n"; foreach my $key ( keys %config ) { print CONFIG "$key = " . $config{$key} . "\n"; } close (CONFIG); } } return 0; } sub configerror ( $ ) { if ( $_[0] =~ /READ/i ) { IRC::print( "Config file found but inaccesable.\n" ); $errors{'config'} = 1; } elsif ( $_[0] =~ /WRITE/i ) { IRC::print( "Could not open config file for writing.\n" ); $errors{'config'} = 2; } return 0; } sub cleanup ( ) { # If xchat autosaves, so do we if ( IRC::get_prefs( "autosave" ) ) { CTCP::writeconfig( ); } if ( $config{'seen'} ) { CTCP::writeseen( ); } }