####################################################
# by wiebe @ QuakeNet
#
# script to maintain a limit on the channel (mode +l)
# all settings are done with channel flags
# set with chanset on the partyline
# or easier with the autolimit command (+o can view settings, +m and higher can change)
#
#   in channel:   botnet-nick autolimit [<chan>] set|info|help
#   in private:   /msg bot autolimit [<chan>] set|info|help
#   on partyline: .autolimit [<chan>] set|info|help
#
#  channel flags
#  +autolimit       enable/disable autolimit
#  +autolimit-hid   enable/disable counting of hidden users (chanmode D/d)
#   autolimit-int   check and change limit every X minutes
#   autolimit-buf   change limit to usercount + X
#   autolimit-und   only change limit when current limit is smaller than usercount + X
#   autolimit-ovr   only change limit when current limit is greater than usercount + X
#   autolimit-min   never set limit smaller than X
#   autolimit-max   never set limit greater than X
#   autolimit-bot   timestamp of last change and summary of settings
#                   used internally for sharing settings over botnet, do not touch
# 
# settings are shared over the botnet so that each bot has the same autolimit settings for a channel
#
# netsplits, on ircu (undernet, quakenet, gamesurge), the lowest limit wins
# possibly leaving the channel with a too low limit
# when a server sets a limit, script checks if the limit needs to be changed (and not wait for the next check)
#
# script can use: irc.tcl ircconsole.tcl whichchan.tcl script.tcl
#
####################################################

####################################################
# flags
####################################################
setudef flag autolimit
setudef flag autolimit-hid
setudef int autolimit-int
setudef int autolimit-buf
setudef int autolimit-und
setudef int autolimit-ovr
setudef int autolimit-min
setudef int autolimit-max
setudef str autolimit-bot


####################################################
# autolimit:help:pubm
####################################################
bind pubm omn|omn "% ${::botnet-nick} help autolimit" autolimit:help:pubm
proc autolimit:help:pubm { n u h c t } {
  if {[matchattr $h bkZ]} { return 0 }
  lappend o "autolimit: usage autolimit \[<chan>\] set|info|help"
  autolimit:cnotice $n $no
  putloglev c $c "help: $n $u $h $c autolimit"
  return 1
}


####################################################
# autolimit:help:msg
####################################################
bind msgm omn|omn "help autolimit" autolimit:help:msgm
proc autolimit:help:msgm { n u h t } {
  if {[matchattr $h bkZ]} { return 0 }
  lappend o "autolimit: usage autolimit \[<chan>\] set|info|help"
  autolimit:cnotice $n $o
  putcmdlog "($n!$u) !$h! help autolimit"
  return 1
}


####################################################
# autolimit:pubm
####################################################
bind pubm omn|omn "% ${::botnet-nick} autolimit" autolimit:pubm
bind pubm omn|omn "% ${::botnet-nick} autolimit *" autolimit:pubm
proc autolimit:pubm { n u h c t } {
  if {[matchattr $h bkZ]} { return 0 }
  if {![validchan $c]} { return 0 }
  set t [lrange [split $t] 2 end]; set d [string tolower [lindex $t 0]]
  if {[lsearch -exact "set info help" $d] == -1} {
    set d [string tolower [lindex $t 1]]
    if {$d == ""} { set d [string tolower [lindex $t 0]] }
  }
  set t [join $t]
  if {$d == "set"} { set o [autolimit:set $h $t $c]
  } elseif {$d == "info"} { set o [autolimit:info $h $t $c]
  } elseif {$d == "help"} { set o [autolimit:help $t]
  } else {
    if {$d != ""} { lappend o "autolimit: unknown subcommand $d" }
    lappend o "autolimit: usage autolimit \[<chan>\] set|info|help"
  }
  autolimit:cnotice $n $o
  putloglev c $c "autolimit: $n $u $h $c $t"
  return 1
}


####################################################
# autolimit:msg
####################################################
bind msg omn|omn autolimit autolimit:msg
proc autolimit:msg { n u h t } {
  if {[matchattr $h bkZ]} { return 0 }
  set t [split $t]; set d [string tolower [lindex $t 0]]
  if {[lsearch -exact "set info help" $d] == -1} {
    set d [string tolower [lindex $t 1]]
    if {$d == ""} { set d [string tolower [lindex $t 0]] }
  }
  if {$d == "set"} { set o [autolimit:set $h $t]
  } elseif {$d == "info"} { set o [autolimit:info $h $t]
  } elseif {$d == "help"} { set o [autolimit:help $t]
  } else {
    if {$d != ""} { lappend o "autolimit: unknown subcommand $d" }
    lappend o "autolimit: usage autolimit \[<chan>\] set|info|help"
  }
  autolimit:cnotice $n $o
  return 1
}


####################################################
# autolimit:dcc
####################################################
bind dcc -|- autolimit autolimit:dcc
proc autolimit:dcc { h i t } {
  if {![valididx $i]} { return 0 }
  set c [lindex [split [console $i]] 0]
  if {![validchan $c]} { set c "" }
  set t [split $t]; set d [string tolower [lindex $t 0]]
  if {[lsearch -exact "set info help" $d] == -1} {
    set d [string tolower [lindex $t 1]]
    if {$d == ""} { set d [string tolower [lindex $t 0]] }
  }
  if {$d == "set"} { set o [autolimit:set $h $t $c]
  } elseif {$d == "info"} { set o [autolimit:info $h $t $c]
  } elseif {$d == "help"} { set o [autolimit:help $t $c]
  } else {
    if {$d != ""} { lappend o "chan: unknown subcommand $d" }
    lappend o "autolimit: usage autolimit \[<chan>\] set|info|help"
  }
  foreach l $o { putidx $i $l }
  return 1
}


####################################################
# autolimit:info
####################################################
proc autolimit:info { h t {c ""} } {
  set t [split $t]; set d [lindex $t 0]
  if {![string equal -nocase $d info]} { set c $d }
  if {![validchan $c] && [info procs whichchan] != ""} { set c [whichchan $h $c] }
  if {$c == ""} {
    lappend o "autolimit: usage autolimit \[<chan>\] info"
  } elseif {![validchan $c] || ![matchattr $h omn|omn $c]} {
    lappend o "autolimit: no access or unknown channel $c"
  } else {
    if {[channel get $c autolimit]} { set s enabled } else { set s disabled }
    if {[channel get $c autolimit-hid]} { set e enabled } else { set e disabled }
    set i [channel get $c autolimit-int]; set b [channel get $c autolimit-buf]
    set d [channel get $c autolimit-und]; set v [channel get $c autolimit-ovr]
    set m [channel get $c autolimit-min]; set x [channel get $c autolimit-max]
    if {$b < 3} { set b 3; channel set $c autolimit-buf $b }
    if {$i < 1} { set i 3; channel set $c autolimit-int $i }
    if {$v < $b} { set v $b; channel set $c autolimit-ovr $v }
    if {$d > $b || $d < 2} { set d $b; channel set $c autolimit-und $d }
    if {$m > $x} { set x 0; channel set $c autolimit-max $x }
    if {$m < $b} { set m 0; channel set $c autolimit-min $m }
    if {$m == 0} { set m <disabled> }
    if {$x == 0} { set x <disabled> }
    lappend o "autolimit: $s on $c, check limit every $i minutes, change limit to 'usercount + $b' when current limit is smaller than 'usercount + $d' or greater than 'usercount + $v', never set a limit lower than $m and never set a limit greater than $x, count hidden users (chanmode D/d) is $e"
  }
  return $o
}


####################################################
# autolimit:set
####################################################
proc autolimit:set { h t {c ""} } {
  set t [split $t]; set d [lindex $t 0]; set t [lrange $t 1 end]
  if {![string equal -nocase $d set]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c] && [info procs whichchan] != ""} { set c [whichchan $h $c] }
  if {$c == ""} {
    lappend o "autolimit: usage autolimit \[<chan>\] set <setting> <value> \[<setting> <value> ..\]"
  } elseif {![validchan $c] || ![matchattr $h mn|mn $c]} {
    lappend o "autolimit: no access or unknown channel $c"
  } elseif {[llength $t] < 1} {
    lappend o "autolimit: usage autolimit \[<chan>\] set <setting> <value> \[<setting> <value> ..\]"
    lappend o "autolimit: valid settings are: autolimit hid int buf und ovr min max"
  } else {
    foreach [list e v] $t {
      set e [string tolower $e]; set v [string tolower $v]
      if {$e == "autolimit"} {
        if {$v == "on"} { channel set $c +autolimit
        } elseif {$v == "off"} { channel set $c -autolimit
        } elseif {$v == ""} {
          lappend o "autolimit: no value for setting $e (must be on or off)"; break
        } else {
          lappend o "autolimit: invalid value $v for seting $e (must be on or off)"; break
        }
      } elseif {$e == "hid"} {
        if {$v == "on"} { channel set $c +autolimit-hid
        } elseif {$v == "off"} { channel set $c -autolimit-hid
        } elseif {$v == ""} {
          lappend o "autolimit: no value for setting $e (must be on or off)"; break
        } else {
          lappend o "autolimit: invalid value $v for seting $e (must be on or off)"; break
        }
      } elseif {[lsearch -exact "int buf und ovr min max" $e] > -1} {
        if {$v == ""} {
          lappend o "autolimit: no value for setting $e (must be 0,1,2,etc)"; break
        } elseif {[string is digit $v]} { channel set $c autolimit-$e $v
        } else {
          lappend o "autolimit: invalid value $v for setting $e (must be 0,1,2,etc)"; break
        }
      } else { lappend o "autolimit: unknown setting $v" }
    }
    set s [join [autolimit:info $h info $c]]; lappend o $s; set n [clock seconds]
    set a [autolimit:get $c]; set b [join [lrange [split [channel get $c autolimit-bot]] 1 end]]
    if {$b != $a} {
      putallbots "AL S $c $n $a"
      if {[info procs ircconsole] != ""} { ircconsole $c u AUTOLIMIT "$h SET $s" }
    }
    channel set $c autolimit-bot "$n $a"
  }
  return $o
}


####################################################
# autolimit:help
####################################################
proc autolimit:help { t } {
  set t [split $t]; set c [string tolower [lindex $t 0]]
  if {$c == "info"} {
    lappend o "autolimit: autolimit \[<chan>\] info"
    lappend o "autolimit: shows the current autolimit settings"
  } elseif {$c == "set"} {
    lappend o "autolimit: autolimit \[<chan>\] set <setting> <value> \[<setting> <value> ..\]"
    lappend o "autolimit: changes autolimit settings, available settings are autolimit hid int buf und ovr min max"
  } else {
    lappend o "autolimit: usage help info|set"
  }
  return $o
}


####################################################
# autolimit:time
####################################################
bind time -|- * autolimit:time
proc autolimit:time { n h d m y } {
  set l ""; set t [clock seconds]
  foreach c [channels] {
    set d [channel get $c autolimit-int]
    if {$d < 1} { set d 3; channel set $c autolimit-int $d }
    set a [autolimit:get $c]; set b [join [lrange [split [channel get $c autolimit-bot]] 1 end]]
    if {$b != $a} { channel set $c autolimit-bot "$t $a"; putallbots "AL S $c $t $a" }
    if {[channel get $c inactive]} { continue }
    if {![channel get $c autolimit]} { continue }
    if {![botisop $c] && ![botishalfop $c]} { continue }
    if {[expr round(fmod($t / 60,$d))] > 0} { continue }
    if {[channel get $c autolimit-hid]} {
      if {[string match *d* [lindex [split [getchanmode $c]] 0]]} {
        if {[string length "[join $l ,],$c"] > 500} { puthelp "LIST [join $l ,]"; set l "" }
        lappend l $c
        continue
      }
    }
    autolimit:newlimit $c
  }
  if {$l != ""} { puthelp "LIST [join $l ,]" }
}


####################################################
# autolimit:list
####################################################
bind raw -|- 322 autolimit:list
proc autolimit:list { s n t } {
  set t [split $t]; set c [lindex $t 1]; set u [lindex $t 2]
  if {![validchan $c]} { return 0 }
  if {![string is digit $u]} { return 0 }
  autolimit:newlimit $c $u
  return 0
}


####################################################
# autolimit:mode
####################################################
bind mode -|- "% +l" autolimit:mode
proc autolimit:mode { n u h c m l } {
  if {![validchan $c]} { return 0 }
  if {$m != "+l"} { return 0 }
  if {![string is digit $l]} { return 0 }
  if {![channel get $c autolimit]} { return 0 }
  if {![botisop $c] && ![botishalfop $c]} { return 0 }
  if {$n != ""} { return 0 }
  if {[channel get $c autolimit-hid] && [string match *d* [lindex [split [getchanmode $c]] 0]]} {
    puthelp "LIST $c"
  } else { autolimit:newlimit $c }
}


####################################################
# autolimit:newlimit
####################################################
proc autolimit:newlimit { c {u ""} } {
  if {![validchan $c]} { return 0 }
  if {$u == ""} { set u [autolimit:usercount $c] }
  if {![botisop $c] && ![botishalfop $c]} { return 0 }
  if {![channel get $c autolimit]} { return 0 }
  set b [channel get $c autolimit-buf]; set d [channel get $c autolimit-und]
  set o [channel get $c autolimit-ovr]; set m [channel get $c autolimit-min]
  set x [channel get $c autolimit-max]; set l [autolimit:getlimit $c]
  if {$b < 3} { set b 3; channel set $c autolimit-buf $b }
  if {$o < $b} { set o $b; channel set $c autolimit-ovr $o }
  if {$d > $b || $d < 2} { set d $b; channel set $c autolimit-und $d }
  if {$m > $x} { set x 0; channel set $c autolimit-max $x }
  if {$m < $b} { set m 0; channel set $c autolimit-min $m }
  set b [expr $b + $u]; set d [expr $d + $u]; set o [expr $o + $u]
  if {$b < $m} { set n $m } elseif {$b > $x && $x > 0} { set n $x } else { set n $b }
  if {$l == $n} { return 0 }
  if {$l > $d && $l < $o && $l > $m && ($l < $x || $x == 0)} { return 0 }
  pushmode $c +l $n
}


####################################################
# autolimit:usercount
####################################################
proc autolimit:usercount { c } { set x 0; foreach n [chanlist $c] { if {![onchansplit $n $c]} { incr x } }; return $x }


####################################################
# autolimit:getlimit
####################################################
proc autolimit:getlimit { c } {
  set m [split [getchanmode $c]]; set p [lrange $m 1 end]; set m [lindex $m 0]
  if {![string match *l* $m]} { return "" }
  if {[llength $p] < 1} { return "" }
  if {[llength $p] == 1} { return [lindex $p 0] }
  set x 0; set m [split $m ""]
  foreach n $m { if {$n == "l"} { break } elseif {$n == "k"} { incr x } }
  set l [lindex $p $x]
  return $l
}


####################################################
# autolimit:get
####################################################
proc autolimit:get { c } {
  if {![validchan $c]} { return "" }
  foreach e [list "" -hid -int -buf -und -ovr -min -max] { lappend o [channel get $c autolimit$e] }
  return [join $o]
}


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


####################################################
# autolimit:bot
####################################################
# bot AL "?|Y|B|S <chan> <ts> <status> <hid> <int> <buf> <und> <ovr> <min> <max>"
# ?=autolimit?, Y=yes, B=burst when linking, S=setting changed, or informing botnet of change after B
bind bot -|- AL autolimit:bot
proc autolimit:bot { b c t } {
  if {![islinked $b]} { return 0 }
  if {![string equal -nocase $c AL]} { 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 "AL Y"
  } elseif {$s == "y"} {
    set n [clock seconds]
    foreach c [channels] {
      set x [split [channel get $c autolimit-bot]]; set d [join [lrange $x 1 end]]; set x [lindex $x 0]
      set a [autolimit:get $c];if {![string is digit $x] || $d != $a} { set x $n; channel set $c autolimit-bot "$x $a" }
      putbot $b "AL B $c $x $a"
    }
  } elseif {[string match {[bs]} $s]} {
    if {[llength $t] != 11} { return 0 }
    if {![validchan $c] && (![string match *g* [botattr $b]] || [catch {channel add $c +inactive}])} { return 0 }
    set x [split [channel get $c autolimit-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
    foreach e [list "" -hid] {
      if {[lindex $t $p]} { set v + } else { set v - }
      channel set $c ${v}autolimit$e; incr p
    }
    foreach e [list int buf und ovr min max] { channel set $c autolimit-$e [lindex $t $p]; incr p }
    set t [join [lrange $t 3 end]]; channel set $c autolimit-bot "$n $t"
    if {$s != "b" || $d == $t} { return 0 }
    putallbots "AL S $c $n $t"
  }
}


####################################################
# autolimit:cnotice
####################################################
proc autolimit:cnotice { n o } {
  if {[info procs cnotice] == ""} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "autolimit: " } }
}


####################################################
# set info for script.tcl
####################################################
set ::scriptdb(autolimit) {
  "changes limit (chanmode +l) on channels. channel settings +autolimit (enable), +autolimit-hid (enable counting of hidden users, chanmode D/d), autolimit-int (interval in minutes to perform limit check), autolimit-buf (buffer), autolimit-und (under), autolimit-ovr (over), autolimit-min (minimum limit), autolimit-max (maximum limit). provides autolimit command. autolimit-bot (used for sharing settings over the botnet, do not touch)"
}

