use strict; use warnings; use Xchat qw(:all); # kick anyone idling longer than this many seconds my $max_idle_time = 15 * 60; # number of seconds between scans for lurkers my $scan_interval = 30 * 60; # Users with these modes won't be kicked # use a space to specify people without a mode my $protected_modes = ''; # anyone with this mode or higher won't get kicked # setting this to ' ' will have the same effect as turning the script off my $min_protection_level = '+'; my $kick_message = 'No lurking'; # number of seconds for tempban my $ban_time = 3; # only work in these channels # if it is empty then it will be active on all channels # empty is: # my @active_channels = (); my @active_channels = ('#foo', '#bar'); ### END CONFIGURATION ### register( "Idle Kicker", "0.0200", "Automatically kick idlers on a channel" ); my $START_TIME = time(); sub prefix_level { my $all_prefixes = shift; $all_prefixes = $all_prefixes->{nickprefixes} if ref $all_prefixes; my $prefix = shift; return ~1 unless $prefix; my $level = index( $all_prefixes, $prefix ); return $level > -1 ? $level : ~1; } sub evict { my @users = @_; temp_ban( @users ); kick( @users ); } sub apply_mode { my $mode = shift; while ( @_ ) { my @targets = map { "$_->{nick}!*@*" } splice @_, 0, 3; commandf( "MODE %s%s %s", $mode, ( "b" x @targets ), @targets ); } } sub temp_ban { my @users = @_; my $channel = get_info "channel"; apply_mode( '+', @users ); hook_timer( $ban_time * 1_000, sub { apply_mode( '-', @users ); return REMOVE; }); } sub kick { my @users = @_; commandf( 'KICK %s "%s"', $_->{nick}, $kick_message ) for @users; } sub protected { my ($user, $channel) = @_; my $required_level; if( $min_protection_level ) { $required_level = prefix_level( $channel, letter_to_symbol( $channel, $min_protection_level ) ); } else { $required_level = -1; } my $user_mode = $user->{prefix} || " "; return 1 if $min_protection_level eq ' ' && $user_mode eq ' '; my $user_level = prefix_level( $channel, $user_mode ); #prnt "user[$user_level] required[$required_level]"; return 1 if $user_level <= $required_level; my %protected_modes; my $modes = letter_to_symbol( $channel, $protected_modes ); @protected_modes{ split //, $modes } = (); #prntf 'protected[%d]', exists $protected_modes{ $user_mode }; return 1 if exists $protected_modes{ $user_mode }; return 0; } sub is_idle { my $user = shift; my $now = time; return ($user->{lasttalk} && ($now - $user->{lasttalk}) > $max_idle_time) || (!$user->{lasttalk} && ($now - base_time()) > $max_idle_time); } sub letter_to_symbol { my $channel = shift; my $mode_string = shift; return $mode_string unless $mode_string; my %mode_map; @mode_map{ split //, $channel->{nickmodes} } = split //, $channel->{nickprefixes}; $mode_string =~ s/([$channel->{nickmodes}])/$mode_map{$1}/g; return $mode_string; } my %join_time; sub base_time { return $join_time{ get_context() } || $START_TIME; } hook_print( "You Join", sub { $join_time{ get_context() } = time(); }); my %active_channels; @active_channels{ @active_channels } = (); hook_timer( $scan_interval * 1_000, sub { #set_context(find_context); #prnt "Scanning ..."; for my $channel ( grep { $_->{type} == 2 } get_list "channels" ) { next if @active_channels and not exists $active_channels{ $channel->{channel} }; unless( set_context( $channel->{context} ) ) { prntf 'set context failed for [%s]', $channel->{channel}; next; } #prntf "checking users in %s", get_info "channel"; my $nick_prefixes = $channel->{nickprefixes}; my $my_nick = get_info "nick"; my @users = get_list "users"; my ($my_info) = grep { $_->{nick} eq $my_nick } @users; my $required_level; if( $nick_prefixes =~ /%/ ) { $required_level = prefix_level( $nick_prefixes, '%' ); } else { $required_level = prefix_level( $nick_prefixes, '@' ); } my $my_level = prefix_level( $nick_prefixes, $my_info->{prefix} ); # skip channels where we don't have half-op or higher # strange looking comparison but lower number means higher level if( $my_level <= $required_level ) { my @idle_users; for my $user ( @users ) { next if $user->{nick} eq $my_nick; if( not protected( $user, $channel ) and is_idle( $user ) ) { push @idle_users, $user; } } evict( @idle_users ); } } return KEEP; });