####################################################
# by wiebe @ QuakeNet
#
#  keeps track of idle times of users on the channel,
#  only channel messages change idle time
#  nick changes, actions or other stuff is ignored
#
#  +idle to enable or disable the whole script
#  every idle-delay minutes a check is done
#
#  +idle-deop deops idle channel (half)operators
#  when they have been idle for more than idle-deoptrigger minutes
#  bots and users with O flag for the channel or global are excluded
#
#  +idle-devoice devoices idle channel voices
#  when they have been idle for more than idle-devoicetrigger minutes
#  bots and user with V flag for the channel or global are excluded
#
#  +idle-voice voices active channel regulars (users without voice/halfop/op)
#  when they are idle less than idle-voicetrigger minutes
#
#  +idle-ban to ban idle users who are idle more than idle-bantrigger
#  and ban for idle-bantime minutes
#  this check is done every 2 minutes, not affected by idle-delay
#  users with any of 'b X f v l o m n' flags for the channel or global are excluded
#
#  idle-bot is used for sharing settings over the botnet, do not touch
#
# console mode 4 is used for reporting actions done by this script (-o/-h/-v/+v/+b)
#
# provides procs:
#   idle:get nick chan, returns idle time in seconds
#   idle:set nick chan ?seconds?, sets idle time
#
# script can use: bannick.tcl (for mask conversion for banning idle users)
# script provides info for: script.tcl
####################################################

setudef flag idle
setudef flag idle-deop
setudef flag idle-devoice
setudef flag idle-voice
setudef flag idle-ban
setudef int idle-delay
setudef int idle-deoptrigger
setudef int idle-devoicetrigger
setudef int idle-voicetrigger
setudef int idle-bantime
setudef int idle-bantrigger
setudef str idle-bot

####################################################
# idle:join
####################################################
bind join -|- * idle:join
proc idle:join { n u h c } {
  if {[isbotnick $n]} { return 0 }
  set n [string tolower $n]; set c [string tolower $c]; set m [clock seconds]
  set u [string trimleft [string tolower $u] ~]
  if {[string length $c] > 32} { set c [md5 $c] }
  if {[string length $u] > 32} { set u [md5 $u] }
  global idledb
  if {![info exists idledb(t,$c,$n)] && ![info exists idledb(t,$c,$u)]} { return 0 }
  if {[info exists idledb(t,$c,$n)]} { set i $idledb(t,$c,$n); unset idledb(t,$c,$n) }
  if {[info exists idledb(t,$c,$u)]} { set i $idledb(t,$c,$u); unset idledb(t,$c,$u) }
  set i [split $i]
  if {[expr $m - [lindex $i 0]] > 600} { return 0 }
  set idledb(i,$c,$n) [expr $m - [lindex $i 1]]
}


####################################################
# idle:part  part splt
####################################################
bind part -|- * idle:part; bind splt -|- * idle:part
proc idle:part { n u h c {m ""} } { idle:unset $n $u $c }

####################################################
# idle:kick
####################################################
bind kick -|- * idle:kick
proc idle:kick { n u h c t m } { idle:unset $t [getchanhost $t $c] $c 1 }

####################################################
# idle:sign
####################################################
bind sign -|- * idle:sign
proc idle:sign { n u h c {m ""} } {
  if {[string equal -nocase $m "registered"]} { return 0 }
  if {[string equal -nocase $m "host change"]} { return 0 }
  idle:unset $n $u $c
}


####################################################
# idle:nick
####################################################
bind nick -|- * idle:nick
proc idle:nick { n u h c m } {
  if {![validchan $c]} { return 0 }
  if {[isbotnick $n]} { return 0 }
  set n [string tolower $c]; set m [string tolower $m]; set c [string tolower $c]
  if {[string equal $n $m]} { return 0 }
  global idledb
  if {[string length $c] > 32} { set c [md5 $c] }
  if {![info exists idledb(i,$c,$n)]} { return 0 }
  set idledb(i,$c,$m) $idledb(i,$c,$n); unset idledb($c,$n)
}


####################################################
# idle:unset
####################################################
proc idle:unset { n u c {k 0} } {
  if {![validchan $c] && ![isbotnick $n]} { return 0 }
  global idledb
  set n [string tolower $n]; set d [string tolower $c]
  if {[string length $d] > 32} { set d [md5 $d] }
  if {[isbotnick $n]} {
    foreach m [array names idledb] {
      if {![string equal $d [lindex [split $m ,] 1]]} { continue }
      unset idledb($m)
    }
  } else {
    if {[info exists idledb(i,$d,$n)]} { set i $idledb(i,$d,$n); unset idledb(i,$d,$n)
    } else { set i [getchanjoin $n $c] }
    if {$k} { return 0 }
    set u [string trimleft [string tolower $u] ~]; set m [clock seconds]
    if {[string length $u] > 32} { set u [md5 $u] }
    set i "$m [expr $m - $i]"; set idledb(t,$d,$n) $i; set idledb(t,$d,$u) $i
  }
}


####################################################
# idle:pubm
####################################################
bind pubm -|- "% *" idle:pubm
proc idle:pubm { n u h c t } {
  if {![validchan $c]} { return 0 }
  if {![botonchan $c]} { return 0 }
  idle:set $n $c
  return 0
}


####################################################
# idle:time
####################################################
bind time -|- "* * * * *" idle:time
proc idle:time { n h d m y } {
  global idledb
  set m [clock seconds]
  foreach n [array names idledb] {
    if {[string match "t,*,*" $n]} {
      if {[expr $m - [lindex [split $idledb($n)] 0]] > 600} { unset idledb($n) }
    } elseif {[string match "b,*,*" $n]} { if {$m > $idledb($n)} { unset idledb($n) } }
  }
  foreach c [channels] {
    set d [channel get $c idle-delay]
    if {$d < 1} { channel set $c idle-delay 1; set d 1 }
    set a [idle:getf $c]; set b [join [lrange [split [channel get $c idle-bot]] 1 end]]
    if {$b != $a} { channel set $c idle-bot "$m $a"; putallbots "IL S $c $m $a" }
    if {![botisop $c] && ![botishalfop $c]} { continue }
    if {![channel get $c idle]} { continue }
    if {[channel get $c idle-ban] && [string equal [expr round(fmod($m / 60,2))] 0]} { idle:ban $c }
    if {![channel get $c idle-deop] && ![channel get $c idle-devoice]} { continue }
    if {![string equal [expr round(fmod($m / 60,$d))] 0]} { continue }
    if {[channel get $c idle-deop] && [botisop $c]} { idle:deop $c }
    if {[channel get $c idle-devoice]} { idle:devoice $c }
    if {[channel get $c idle-voice]} { idle:voice $c }
  }
}


####################################################
# idle:evnt
####################################################
bind evnt -|- init-server idle:evnt; bind evnt -|- disconnect-server idle:evnt
proc idle:evnt { type } { global idledb; if {[info exists idledb]} { unset idledb } }


####################################################
# idle:get
####################################################
proc idle:get { n c } {
  if {![validchan $c]} { return -2 }
  if {![onchan $n $c] || [onchansplit $n $c]} { return -2 }
  if {![botonchan $c]} { return -2 }
  global idledb
  set n [string tolower $n]; set c [string tolower $c]
  if {[string length $c] > 32} { set c [md5 $c] }
  if {![info exists idledb(i,$c,$n)]} { return -1 }
  set i [expr [clock seconds] - $idledb(i,$c,$n)]
  return $i 
}


####################################################
# idle:set
####################################################
proc idle:set { n c {v 0} } {
  if {![validchan $c]} { return 0 }
  if {![onchan $n $c] || [onchansplit $n $c]} { return 0 }
  if {![botonchan $c]} { return 0 }
  if {![string is digit $v]} { return 0 }
  global idledb
  set n [string tolower $n]; set c [string tolower $c]
  if {[string length $c] > 32} { set c [md5 $c] }
  set v [expr [clock seconds] - $v]; set idledb(i,$c,$n) $v
  return 1
}


####################################################
# idle:ban
####################################################
proc idle:ban { c } {
  global idledb; set t [channel get $c idle-bantrigger]; set l [channel get $c idle-bantime]
  if {$t < 10} { set t 10; channel set $c idle-trigger $t }
  if {$l < 10} { set l 10; channel set $c idle-bantime $l }
  set m [clock seconds]; set e [expr $m + $l * 60]; set bs ""; set ns ""; set x 0
  set k [expr $m - [getchanjoin $::botnick $c]]; set z [lindex [split [getchanmode $c]] 0]]

  set d [string tolower $c]
  if {[string length $d] > 32} { set d [md5 $d] }

  if {[string match "*i*" $z} { set x 1 }
  foreach n [chanlist $c] {
    if {[onchansplit $n $c]} { continue }
    if {[isop $n $c]} { continue }
    if {[ishalfop $n $c]} { continue }
    if {[isvoice $n $c]} { continue }
    if {[matchattr [nick2hand $n $c] Xbfvlomn|Xfvlomn $c]} { continue }
    set i [idle:get $n $c]
    if {[string equal $i "-1"]} {
      set i [getchanjoin $n $c]
      if {[string equal $i "0"]} { set i $k } else { set i [expr $m - $i] }
    }
    set i [expr $i / 60]
    if {$i < [expr $t -2]} { continue }
# ban
    if {$i >= $t} { lappend bs "$i,$n"
putloglev 4 $c "IDLE: ban $c $n"
# warn
    } else { lappend ns $n
putloglev 4 $c "IDLE: warn $c $n"
    }
  }
  if {![string equal $bs ""]} {
    set r "Please do not idle in $c"
    set bs [lsort -decreasing -dictionary $bs]
    if {!$x} { set bs [lrange $bs 0 11] }
    foreach n $bs {
      set n [lindex [split $n ,] 1]
      if {$x} { putkick $c $n $r; continue }
      set h [getchanhost $n $c]
      if {[matchban $n!$h $c]} { continue }
      set u "*!$h"
      if {[string match "*.users.quakenet.org" $u] || [string match "*!~*" $u]} {
        set u "*!*@[lindex [split $u @] 1]"
      }
      if {[info procs bannick:mask] != ""} { set u [bannick:mask $h] }
      if {![string equal [info procs newchanban:ban] ""]} {
        if {![newchanban:ban $c $u idle.tcl $r $l]} { continue }
      } else { newchanban $c $u idle.tcl $r $l }
      set u [string tolower $u]; if {[string length $u] > 32} { set u [md5 $u] }
      set idledb(b,$d,$u) $e
    }
  }
  if {![string equal $ns ""]} {
    set ns [join $ns ,]
    if {[string match *m* $z]} {
      set r "You have been idle for a while. If you did not have a chance to ask your questions, come back later on $c"
    } else { set r "You have been idle for a while. If you have nothing to ask, please part $c" }
    if {![string equal [info procs notice] ""]} { notice $ns $r puthelp
    } else { puthelp "NOTICE $ns :$r" }
  }
}


####################################################
# idle:deop
####################################################
proc idle:deop { c } {
  set s 0
  foreach n [chanlist $c S|S] { if {![onchansplit $n $c] && [isop $n $c]} { set s 1; break } }
  if {!$s} { return 0 }
  set t [channel get $c idle-deoptrigger]
  if {$t < 30} { set t 30; channel set $c idle-deoptrigger $t }
  set m [clock seconds]
  set k [expr $m - [getchanjoin $::botnick $c]]
  foreach n [chanlist $c] {
    if {[onchansplit $n $c]} { continue }
    if {[isbotnick $n]} { continue }
    if {![isop $n $c] && ![ishalfop $n $c]} { continue }
    if {[matchattr [nick2hand $n $c] bO|O $c]} { continue }
    set i [idle:get $n $c]
    if {[string equal $i "-1"]} {
      set i [getchanjoin $n $c]
      if {[string equal $i "0"]} { set i $k } else { set i [expr $m - $i] }
    }
    set i [expr $i / 60]
    if {$i <= $t} { continue }
    if {[isop $n $c]} { pushmode $c -o $n }
    if {[ishalfop $n $c]} { pushmode $c -h $n }
putloglev 4 $c "IDLE: deop $c $n"
  }
}


####################################################
# idle:devoice
####################################################
proc idle:devoice { c } {
  set t [channel get $c idle-devoicetrigger]
  if {$t < 30} { set t 30; channel set $c idle-devoicetrigger $t }
  set m [clock seconds]
  set k [expr $m - [getchanjoin $::botnick $c]]
  foreach n [chanlist $c] {
    if {[onchansplit $n $c]} { continue }
    if {[isop $n $c]} { continue }
    if {![isvoice $n $c]} { continue }
    set h [nick2hand $n $c]
    if {[matchattr $h bV|V $c]} { continue }
    if {[channel get $c idle-ban] && [matchattr $h fvlomn|fvlomn $c]} { continue }
# forward to queue.tcl - auto queue
    set i [idle:get $n $c]
    if {[string equal $i "-1"]} {
      set i [getchanjoin $n $c]
      if {[string equal $i "0"]} { set i $k } else { set i [expr $m - $i] }
    }
    set i [expr $i / 60]
    if {$i <= $t} { continue }
    pushmode $c -v $n
putloglev 4 $c "IDLE: devoice $c $n"
  }
}


####################################################
# idle:voice
####################################################
proc idle:voice { c } {
  set t [channel get $c idle-voicetrigger]
  if {$t < 1} { set t 1; channel set $c idle-voicetrigger $t }
  set m [clock seconds]
  set k [expr ($m - [getchanjoin $::botnick $c]) / 60]
  foreach n [chanlist $c] {
    if {[onchansplit $n $c]} { continue }
    if {[isop $n $c]} { continue }
    if {[isvoice $n $c]} { continue }
    if {![string equal [info procs matchignore] ""] && [matchignore $n![getchanhost $n $c]]} { continue }
    set h [nick2hand $n $c]
    if {[matchattr $h bq|q $c]} { continue }
    set i [idle:get $n $c]
    if {[string equal $i "-1"]} { continue }
    set i [expr $i / 60]
    if {$i > $t} { continue }
    set j [getchanjoin $n $c]
    if {[string equal $j "0"]} { set j $k } else { set j [expr ($m - $j) / 60] }
    if {$j < [expr $t +1] && ![matchattr $h fvlomn|fvlomn $c]} { continue }
    pushmode $c +v $n
putloglev 4 $c "IDLE: voice $c $n"
  }
}


####################################################
# idle:mode
# reset idle time for voiced/op user
# else user gets voiced/opped (by whatever means),
# and devoiced/deopped after for being idle
####################################################
bind mode -|- "% +v" idle:mode; bind mode -|- "% +h" idle:mode; bind mode -|- "% +o" idle:mode
proc idle:mode { n u h c m t } {
  if {![validchan $c]} { return 0 }
  if {![string match {+[vho]} $m]} { return 0 }
  if {![onchan $t $c]} { return 0 }
  idle:set $t $c 31
}


####################################################
# idle:unban
####################################################
bind mode -|- "% -b" idle:unban
proc idle:unban { n u h c m b } {
  global idledb
  if {![validchan $c]} { return 0 }
  if {![string equal $m "-b"]} { return 0 }
  set e [string tolower $b]; set d [string tolower $c]
  if {[string length $d] > 32} { set d [md5 $d] }
  if {[string length $e] > 32} { set e [md5 $e] }
  if {![info exists idledb(b,$d,$e)]} { return 0 }
  if {![isban $b $c]} { unset idledb(b,$d,$e); return 0 }
  killchanban $c $b; unset idledb(b,$d,$e)
}


####################################################
# idle:getf
####################################################
proc idle:getf { c } {
  if {![validchan $c]} { return "" }
  lappend l "" -deop -devoice -voice -ban -delay -deoptrigger -devoicetrigger -voicetrigger -bantime -bantrigger
  foreach e $l { lappend o [channel get $c idle$e] }
  return [join $o]
}


####################################################
# idle:link
####################################################
bind link -|- * idle:link
proc idle:link { b v } { if {[islinked $b] && [string equal -nocase ${::botnet-nick} $v]} { putbot $b "IL ?" } }


####################################################
# idle:bot
####################################################
# bot IL "?|Y|B|S <chan> <ts> <status> <deop> <devoice> <voice> <ban> <deoptrigger> <devoicetrigger> <voicetrigger> <bantime> <bantrigger>"
# ?=idle?, Y=yes, B=burst when linking, S=setting changed, or informing botnet of change after B
bind bot -|- IL idle:bot
proc idle:bot { b c t } {
  if {![islinked $b]} { return 0 }
  if {![string equal -nocase $c IL]} { return 0 }
  set t [split $t]; set s [string tolower [lindex $t 0]]; set c [lindex $t 1]; set n [lindex $t 2]
  if {$s == "?"} {
    putbot $b "IL Y"
  } elseif {$s == "y"} {
    set n [clock seconds]
    foreach c [channels] {
      set x [split [channel get $c idle-bot]]; set d [join [lrange $x 1 end]]; set x [lindex $x 0]
      set a [idle:getf $c]
      if {![string is digit $x] || $d != $a} { set x $n; channel set $c idle-bot "$x $a" }
      putbot $b "IL B $c $x $a"
    }
  } elseif {[string match {[bs]} $s]} {
    if {[llength $t] != 13} { return 0 }
    if {![validchan $c] && (![string match *g* [botattr $b]] || [catch {channel add $c +inactive}])} { return 0 }
    set x [split [channel get $c idle-bot]]; set d [join [lrange $x 1 end]]; set x [lindex $x 0]
    if {![string is digit $x]} { set x 0 }
    if {$s == "b"} {
      # equal timestamps, the greater bot wins (ab smaller than xy)
      if {$x == $n} { if {$b > ${::botnet-nick}} { return 0 } } elseif {$x > $n} { return 0 }
    }
    set p 3; set l [list "" -deop -devoice -voice -ban -delay]
    foreach e $l { if {[lindex $t $p]} { set v + } else { set v - }; channel set $c ${v}idle$e; incr p }
    set l [list deoptrigger devoicetrigger voicetrigger bantime bantrigger]
    foreach e $l { channel set $c idle-$e [lindex $t $p]; incr p }
    set t [join [lrange $t 3 end]]; channel set $c idle-bot "$n $t"
    if {$s != "b" || $d == $t} { return 0 }
    putallbots "IL S $c $n $t"    
  }
}


set ::userflagdb(idle) {
  "idle.tcl: X=exclude from idle kick    V=exclude from idle devoice    O=exclude from idle deop    S=service (idle deop only when an S user is on the channel)"
}


set ::scriptdb(idle) {
  "keeps track of idle times of users on the channel, only channel messages change the idle time, nick changes, actions or other stuff is ignored. +idle to enable or disable the whole script. every idle-delay minutes a check is done."
  "+idle-deop deops idle channel (half)operators when they have been idle for more than idle-deoptrigger minutes (bots and users with O flag for the channel or global are excluded). idle deop is only done when at least one user with S|S flags is on the channel."
  "+idle-devoice devoices idle channel voices when they have been idle for more than idle-devoicetrigger minutes (bots and users with V flag for the channel or global are excluded)."
  "+idle-voice voices active channel regulars (users without voice/halfop/op) when they are idle less than idle-voicetrigger minutes."
  "+idle-ban to ban idle users who are idle more than idle-bantrigger and ban for idle-bantime minutes. this check is done every 2 minutes, not affected by idle-delay. users with any of 'b X f v l o m n' flags for the channel or global are excluded. idle-bot is used for sharing settings over the botnet, do not touch. console mode 4 is used for reporting actions done by this script (-o/-h/-v/+v/+b)"
}

