#by wiebe @ QuakeNet

#removes status (@/%/+) from idle users
#removes idle regular users
#voices active users

#settings:

#general
#+idle			script on/off
# idle-delay		time in minutes between checks (except idle-ban, checked every minute)

#deop idle ops
#+idle-deop		deop idle channel operators (@)
# idle-deoptrigger	being idle for (more than) .. minutes
# idle-deopflags	deop users with these flags
#only active when Q or L is on the channel, as removing ops in that situation is not desirable

#dehop idle halfops
#+idle-dehop		dehalfop idle channel halfoperators (%)
# idle-dehoptrigger	being idle for (more than) .. minutes
# idle-dehopflags	dehop users with these flags

#devoice idle voices
#+idle-devoice		devoice idle channel voices (+)
# idle-devoicetrigger	being idle for (more than) .. minutes
# idle-devoiceflags	devoice users with these flags

#voice active users
#+idle-voice		voice active regular users
# idle-voicetrigger	being active in the last .. minutes
# idle-voiceflags	voice users with these flags
#			format "<withglobal>|<withchan> <withoutglobal>|<withoutchan>" flags


#ban idle regulars
#+idle-ban		ban idle regular users
# idle-bantrigger	being idle for (more than) .. minutes
# idle-bantime	ban duration in minutes
# idle-banmsg		ban reason
#users with any of fvlomnb flags are excluded
#ops/halfops/voices are excluded

#############################################
#code
#############################################

#general
setudef flag idle
setudef int idle-delay

#deop idle ops
setudef flag idle-deop
setudef int idle-deoptrigger
setudef str idle-deopflags

#dehop idle halfops
setudef flag idle-dehop
setudef int idle-dehoptrigger
setudef str idle-dehopflags

#devoice idle voices
setudef flag idle-devoice
setudef int idle-devoicetrigger
setudef str idle-devoiceflags

#voice active users
setudef flag idle-voice
setudef int idle-voicetrigger
setudef str idle-voiceflags

#ban idle regulars
setudef flag idle-ban
setudef int idle-bantrigger
setudef int idle-bantime
setudef str idle-banmsg


#check channels
bind time - "* * * * *" idle:time

proc idle:time { mi ho da mo ye } {
  global idledb

#clean up array
  foreach name [array names idledb] {
    set chan [lindex [split $name ,] 0]
    if { ![validchan $chan] || ![channel get $chan idle] } {
      unset idledb($name)
    } elseif { [string match -nocase *,i,* $name] } {
      set nick [lindex [split $name ,] 2]
      if { ![onchan $nick $chan] } {
        unset idledb($name)
      }
    } elseif { [string match -nocase *,t,* $name] } {
      set ts [lindex [split $idledb($name)] 0]
      set idle [lindex [split $idledb($name)] 1]
      if { [expr [unixtime] - $ts] > "600" } { unset idledb($name) }
    }
  }


  if { ![botonchan] } {
    if { [info exists idledb] } { unset idledb }
    return 0
  }
  if { ![botisop] && ![botishalfop] } { return 0 }

  set now [unixtime]
  foreach chan [channels] {
#off
    if { ![channel get $chan idle] } { continue }
#not (half)opped
    if { ![botisop $chan] && ![botishalfop $chan] } { continue }
#ban
    idle:ban $chan
#delay
    set delay [channel get $chan idle-delay]
    if { $delay <= "0" } { continue }
    if { ![info exists idledb($chan)] } { set idledb($chan) 0 }
    set last $idledb($chan)
    if { [expr ($now - $last) / 60] < $delay } { continue }

    if { ![string equal [expr round(fmod([unixtime] / 60,$delay))] 0] } { continue }
    set idledb($chan) $now
    idle:deop $chan
    idle:dehop $chan
    idle:devoice $chan
    idle:voice $chan
  }
}



#check what users to deop
proc idle:deop { chan } {
  if { ![channel get $chan idle-deop] } { return 0 }
  if { ![botisop $chan] } { return 0 }
  set max [channel get $chan idle-deoptrigger]
  if { $max <= "0" } { return 0 }
  if { $max < "5" } { set max "5" }
  if { [onchansplit L $chan] || [onchansplit Q $chan] } { return 0 }
  if { ![onchan L $chan] && ![onchan Q $chan] } { return 0 }
  set max [expr $max * 60]
  if { [expr [unixtime] - [getchanjoin $::botnick $chan]] < $max } { return 0 }

  set with [channel get $chan idle-deopflags]
  set without [lindex [split $with] 1]
  if { [string equal $without ""] } { set without -|- }
  set with [lindex [split $with] 0]
  if { [string equal $with ""] } { set with -|- }

  if { [string equal $with -|-] } {
    set nicks [chanlist $chan]
  } else { set nicks [chanlist $chan $with] }

  foreach nick $nicks {
    if { [onchansplit $nick $chan] } { continue }
    if { ![isop $nick $chan] } { continue }
    if { [isbotnick $nick] } { continue }
    set handle [nick2hand $nick]
    if { ![matchattr $handle $with $chan] && ![string equal $with -|-] } { continue }
    if { [matchattr $handle $without $chan] && ![string equal $without -|-] } { continue }
    set idle [lindex [split [idle:idle $chan $nick]] 1]
    if { $idle < $max } { continue }
    pushmode $chan -o $nick
  }
}



#check what users to dehalfop
proc idle:dehop { chan } {
  if { [catch {channel get $chan idle-dehop} error] } { return 0 }
  if { ![channel get $chan idle-dehop] } { return 0 }
  if { ![botisop $chan] } { return 0 }
  set max [channel get $chan idle-dehoptrigger]
  if { $max <= "0" } { return 0 }
  if { $max < "5" } { set max "5" }
  set max [expr $max * 60]
  if { [expr [unixtime] - [getchanjoin $::botnick $chan]] < $max } { return 0 }


  set with [channel get $chan idle-dehopflags]
  set without [lindex [split $with] 1]
  if { [string equal $without ""] } { set without -|- }
  set with [lindex [split $with] 0]
  if { [string equal $with ""] } { set with -|- }

  if { [string equal $with -|-] } {
    set nicks [chanlist $chan]
  } else { set nicks [chanlist $chan $with] }

  foreach nick $nicks {
    if { [onchansplit $nick $chan] } { continue }
    #if { [isop $nick $chan] } { continue }
    if { ![ishalfop $nick $chan] } { continue }
    if { [isbotnick $nick] } { continue }
    set handle [nick2hand $nick]
    if { ![matchattr $handle $with $chan] && ![string equal $with -|-] } { continue }
    if { [matchattr $handle $without $chan] && ![string equal $without -|-] } { continue }
    set idle [lindex [split [idle:idle $chan $nick]] 1]
    if { $idle < $max } { continue }
    pushmode $chan -h $nick
  }
}



#check what users to devoice
proc idle:devoice { chan } {
  if { ![channel get $chan idle-devoice] } { return 0 }
  if { ![botisop $chan] && ![botishalfop $chan] } { return 0 }
  set max [channel get $chan idle-devoicetrigger]
  if { $max <= "0" } { return 0 }
  if { $max < "5" } { set max "5" }
  set max [expr $max * 60]
  if { [expr [unixtime] - [getchanjoin $::botnick $chan]] < $max } { return 0 }

  set with [channel get $chan idle-devoiceflags]
  set without [lindex [split $with] 1]
  if { [string equal $without ""] } { set without -|- }
  set with [lindex [split $with] 0]
  if { [string equal $with ""] } { set with -|- }

  if { [string equal $with -|-] } {
    set nicks [chanlist $chan]
  } else { set nicks [chanlist $chan $with] }

  foreach nick $nicks {
    if { [onchansplit $nick $chan] } { continue }
    if { ![isvoice $nick $chan] } { continue }
    #if { [ishalfop $nick $chan] } { continue }
    #if { [isop $nick $chan] } { continue }
    set handle [nick2hand $nick]
    if { ![matchattr $handle $with $chan] && ![string equal $with -|-] } { continue }
    if { [matchattr $handle $without $chan] && ![string equal $without -|-] } { continue }
    set idle [lindex [split [idle:idle $chan $nick]] 1]
    if { $idle < $max } { continue }
    pushmode $chan -v $nick
  }
}



#check what active users to voice
proc idle:voice { chan } {
  if { ![channel get $chan idle-voice] } { return 0 }
  if { ![botisop $chan] && ![botishalfop $chan] } { return 0 }
  set max [channel get $chan idle-voicetrigger]
  set delay [channel get $chan idle-delay]
  if { $max < [expr $delay +2] } {
    set max [expr $delay +2]
    channel set $chan idle-voicetrigger $max
  }
  if { $max <= "0" } { return 0 }
  if { $max < "5" } { set max "5" }
  set max [expr $max * 60]

  set with [channel get $chan idle-voiceflags]
  set without [lindex [split $with] 1]
  if { [string equal $without ""] } { set without -|- }
  set with [lindex [split $with] 0]
  if { [string equal $with ""] } { set with -|- }


  if { [string equal $with -|-] } {
    set nicks [chanlist $chan]
  } else { set nicks [chanlist $chan $with] }
  foreach nick $nicks {
#check voice op halfop stats, check q flags, check ignores
    if { [onchansplit $nick $chan] } { continue }
    if { [isop $nick $chan] } { continue }
    if { [ishalfop $nick $chan] } { continue }
    if { [isvoice $nick $chan] } { continue }
    if { [isignore $nick![getchanhost $nick]] } { continue }

    set handle [nick2hand $nick]
    if { [matchattr $handle q|q $chan] } { continue }
    if { ![matchattr $handle $with $chan] && ![string equal $with -|-] } { continue }
    if { [matchattr $handle $without $chan] && ![string equal $without -|-] } { continue }

    set idle [idle:idle $chan $nick]
    if { [string match "-1 *" $idle] } { continue }
    set idle [lindex [split $idle] 1]
#user has been idle for too long
    if { $idle > $max } { continue }
    set nickjoin [expr [unixtime] - [getchanjoin $nick $chan]]
#less than 600s on the channel and has no fvlomn flags
    if { $nickjoin < "600" && ![matchattr [nick2hand $nick] fvlomn|fvlomn $chan] } { continue }
#voice user
    pushmode $chan +v $nick
  }
}



#ban idle users
proc idle:ban { chan } {
  if { ![validchan $chan] } { return 0 }
  if { ![channel get $chan idle-ban] } { return 0 }
  if { ![botisop $chan] && ![botishalfop $chan] } { return 0 }
  set max [channel get $chan idle-bantrigger]
  if { $max <= "0" } { return 0 }
  if { $max < "10" } { set max "10" }
  set max [expr $max * 60]
  if { [expr [unixtime] - [getchanjoin $::botnick $chan]] < $max } { return 0 }
#duration
  set duration [channel get $chan idle-bantime]
  if { $duration <= "0" } { set duration 20 }
#reason
  set reason [channel get $chan idle-banmsg]
  if { [string equal $reason ""] } { set reason "Please do not idle in $chan" }

#get the most idle users
  set nicks ""
  foreach nick [chanlist $chan] {
    set idle [lindex [split [idle:idle $chan $nick]] 1]
    if { [onchansplit $nick $chan] } { continue }
    if { [isvoice $nick $chan] } { continue }
    if { [ishalfop $nick $chan] } { continue }
    if { [isop $nick $chan] } { continue }
    if { [matchattr [nick2hand $nick] fvlomnb|fvlomn $chan] } { continue }
    if { $idle < $max } { continue }
    lappend nicks [expr $idle + 1000000000].$nick
  }
  set nicks [lsort -decreasing $nicks]
#get the 6 most idle users
  set nicks [lrange $nicks 0 5]
  if { [string equal $nicks ""] } { return 0 }

#ban users
  set bans ""
  foreach nick $nicks {
    set nick [lindex [split $nick .] 1]

#invite only, just kick target
    if { [string match *i* [lindex [split [getchanmode $chan]] 0]] } {
      putkick $chan $nick $reason
      continue
    }

#whox
    set account ""
    if { [catch {set account [whox $nick a]} error] } {
      putlog "ERROR using whox.tcl, perhaps you have not loaded it?"
      error $error
    }
    if { [string equal $account 0] } { set account "" }
#disable until whox is rewritten and enforces account bans as they are set
    set account ""
    set uhost [getchanhost $nick]
    if { ![string equal $account ""] } {
      set ban *!*@$account.users.quakenet.org
    } elseif { [string match -nocase *@*.users.quakenet.org $uhost] } {
      set ban *!*@[lindex [split $uhost @] 1]
    } else {
      set ban *!$uhost
    }
    if { [string equal [lsearch -exact $bans $ban] -1] } { lappend bans $ban }
  }
  if { [string equal $bans ""] } { return 0 }

#get maxbans
  if { ![info exists ::max-bans] || ![string is digit ${::max-bans}] } {
    set maxbans 45
  } else { set maxbans ${::max-bans} }

  if { [expr [llength [chanbans $chan]] +1] > $maxbans } {
    foreach ban $bans { newchanban $chan $ban idle-ban $reason $duration }
  } else {
#get modes-per-line
    if { ![info exists ::modes-per-line] || ![string is digit ${::modes-per-line}] } {
      set maxmodes 3
    } else { set maxmodes ${::modes-per-line} }
    foreach ban $bans {
      pushmode $chan +b $ban
      utimer [expr ((([llength $bans] -1) / $maxmodes) * 2) +2] [list newchanban $chan $ban idle-ban $reason $duration]
    }
  }
}



#text
bind pubm -|- "% *" idle:pubm

proc idle:pubm { nick uhost handle chan text } {
  global idledb
  if { ![validchan $chan] } { return 0 }
  if { ![channel get $chan idle] } { return 0 }
  if { ![onchan $nick $chan] } { return 0 }
  set idledb($chan,i,$nick) [unixtime]
}



#join
bind join -|- * idle:join
bind rejn -|- * idle:join

proc idle:join { nick uhost handle chan } {
  global idledb
  if { ![validchan $chan] } { return 0 }
#bot joins
  if { [isbotnick $nick] } {
    foreach name [array names idledb] {
      set c [lindex [split $name ,] 0]
      if { [string equal -nocase $chan $c] } {
        if { [string match -nocase *,i,* $name] } {
          unset idledb($name)
        } elseif { [string match -nocase *,t,* $name] } {
          set ts [lindex [split $idledb($name)] 0]
          set idle [lindex [split $idledb($name)] 1]
          if { [expr [unixtime] - $ts] > "600" } { unset idledb($chan,t,$host) }
        }
      }
    }
#user joins
  } else {
    lappend masks $nick
    set user [getchanhost $nick]
    set host @[string tolower [lindex [split $user @] 1]]
    lappend masks $host
    set user [string tolower [lindex [split $user @] 0]]
    if { [string match -nocase *.users.quakenet.org $host] } {
    } elseif { [string match *?.*?.?* $host] } {
      set host $user@*.[join [lrange [split $host .] end-1 end] .]
      lappend masks $host
    }
    set ts ""
    foreach mask $masks {
      if { [info exists idledb($chan,t,$mask)] } {
        if { [string equal $ts ""] || [lindex [split $idledb($chan,t,$mask)] 0] > $ts } {
          set ts [lindex [split $idledb($chan,t,$mask)] 0]
          set idle [lindex [split $idledb($chan,t,$mask)] 1]
        }
        unset idledb($chan,t,$mask)
      }
    }
    if { [string equal $ts ""] } { return 0 }

    set idle2 [unixtime]
    if { [info exists idledb($chan,i,$nick)] } {
      set idle2 [expr [unixtime] - $idledb($chan,i,$nick)]
    }

#saved idle time less than 600s old
#current idle time for nick is smaller than saved idle time
    if { [expr [unixtime] - $ts] < "600" && $idle2 > $idle } {
      set idledb($chan,i,$nick) [expr [unixtime] - $idle]
    }
  }
}



#nick change
bind nick -|- * idle:nick

proc idle:nick { nick uhost handle chan newnick } {
  global idledb
  if { ![validchan $chan] } { return 0 }
  if { ![channel get $chan idle] } { return 0 }
  if { [info exists idledb($chan,i,$nick)] } {
    set idledb($chan,i,$newnick) $idledb($chan,i,$nick)
    idle:unset $chan $nick 0
  }
}


#part
bind part -|- * idle:part
proc idle:part { nick uhost handle chan {msg ""}} {
  if { ![validchan $chan] } { return 0 }
  if { ![channel get $chan idle] } { return 0 }
  idle:unset $chan $nick 1
}



#kick
bind kick -|- * idle:kick
proc idle:kick { nick uhost handle chan target reason } {
  if { ![validchan $chan] } { return 0 }
  if { ![channel get $chan idle] } { return 0 }
  idle:unset $chan $target 1
}



#split
bind splt -|- * idle:splt
proc idle:splt { nick uhost handle chan } {
  if { ![validchan $chan] } { return 0 }
  if { ![channel get $chan idle] } { return 0 }
  idle:unset $chan $nick 1
}



#quit
bind sign -|- * idle:sign
proc idle:sign { nick uhost handle chan message } {
  if { ![validchan $chan] } { return 0 }
  if { ![channel get $chan idle] } { return 0 }
  if { [string equal -nocase $message "registered"] } { return 0 }
  if { [string equal -nocase $message "host change"] } { return 0 }
  idle:unset $chan $nick 1
}



#unsets the variable for that user and channel
#sets variable for reseting idletime
proc idle:unset { chan nick tr } {
  global idledb
  if { [info exists idledb($chan,i,$nick)] } {
    if { $tr } {
      set idle [idle:idle $chan $nick]
      if { [string match "1 *" $idle] } {
        set idle "[unixtime] [lindex [split $idle] 1]"
#nick
        set idledb($chan,t,$nick) $idle
        set user [getchanhost $nick]
#host
        set host @[string tolower [lindex [split $user @] 1]]
        set idledb($chan,t,$host) $idle
        set user [string tolower [lindex [split $user @] 0]]
#account host
        if { [string match -nocase *.users.quakenet.org $host] } {
#user@*.domain.tld        
        } elseif { [string match *?.*?.?* $host] } {
          set host $user@*.[join [lrange [split $host .] end-1 end] .]
          set idledb($chan,t,$host) $idle
        }
      }
    }
    unset idledb($chan,i,$nick)
  }
}



#returns "1 idletime" in seconds for nick on chan
#returns "-1 idletime" if no idle time is known (user hasnt been active)
proc idle:idle { chan nick } {
  global idledb
  if { ![info exists idledb($chan,i,$nick)] } {
    set ts [getchanjoin $nick $chan]
    if { [string equal $ts 0] } { set ts [getchanjoin $::botnick $chan] }
    return "-1 [expr [unixtime] - $ts]"
  }
  return "1 [expr [unixtime] - $idledb($chan,i,$nick)]"
}

