####################################################
# by wiebe @ QuakeNet
#
# script deals with flood detection (the flood-* settings)
# default action is to kick the user
#
# +flood           enable/disable (including default flood settings and actions!)
# +flood-msgban    ban user for private flood (msg, notice & ctcp, set flood-msg & set flood-ctcp)
# +flood-pubban    ban user for pub flood (flood-chan & flood-ctcp)
# +flood-joinban   ban user for join flood (flood-join)
# +flood-nickban   ban user for nick flood (flood-nick)
# +flood-deopban   ban user for deop flood (flood-deop)
# +flood-kickban   ban user for kick flood (flood-kick)
# +flood-shareban  ban user on ALL other channels where +flood, +flood-shareban and +flood-<type>ban is set
# +flood-op        ignore opped users
# +flood-voice     ignore voiced users
#  flood_bantime   ban for X minutes (default 60)
#  flood_bot       used for sharing settings (including standard flood-*) over botnet, do not touch
#
#
#  as reminder (from .help chaninfo):
#  flood-chan      Set here how many channel messages in how many seconds
#                  from one host constitutes a flood. Setting this to 0 or 0:0
#                  disables text flood protection for the channel.
#  flood-ctcp      Set here how many channel ctcps in how many seconds from
#                  one host constitutes a flood. Setting this to 0 or 0:0
#                  disables ctcp flood protection for the channel.
#  flood-join      Set here how many joins in how many seconds from one
#                  host constitutes a flood. Setting this to 0 or 0:0 disables
#                  join flood protection for the channel.
#  flood-kick      Set here how many kicks in how many seconds from one
#                  host constitutes a flood. Setting this to 0 or 0:0 disables
#                  kick flood protection for the channel.
#  flood-deop      Set here how many deops in how many seconds from one
#                  host constitutes a flood. Setting this to 0 or 0:0 disables
#                  deop flood protection for the channel.
#  flood-nick      Set here how many nick changes in how many seconds from
#                  one host constitutes a flood. Setting this to 0 or 0:0
#                  disables nick flood protection for the channel.
#
# some relevant settings from the eggdrop conf file:
# Set this to 1 if you want the bot to kick for control character/ctcp
# avalanches to a channel. Remember that if it does, it won't ban them.
# This can start kick floods.
set ::kick-fun 0
# Set this to 1 if you want the bot to ban for control character/ctcp
# avalanches to a channel. This can prevent kick floods, but it also can
# fill the banlist.
set ::ban-fun 0
# Set here how many msgs in how many seconds from one host constitutes
# a flood. If you set this to 0:0, msg flood protection will be disabled.
set ::flood-msg 5:60
# Set here how many ctcps in how many seconds from one host constitutes
# a flood. If you set this to 0:0, ctcp flood protection will be disabled.
set ::flood-ctcp 3:60
#
#
# script can use: bannick.tcl ircconsole.tcl newchanban.tcl script.tcl
# script provides info for: script.tcl
#
####################################################

####################################################
# flags
####################################################
setudef flag flood
setudef flag flood-pubban
setudef flag flood-msgban
setudef flag flood-joinban
setudef flag flood-nickban
setudef flag flood-deopban
setudef flag flood-kickban
setudef flag flood-shareban
setudef flag flood-op
setudef flag flood-voice
setudef int flood_bantime
setudef str flood_bot

####################################################
# flood:flud
####################################################
bind flud -|- * flood:flud
proc flood:flud { n u h t c } {
  set t [string tolower $t]; set m "*!$u"; set d "flood.tcl"; set b 0; set k 0
  if {$t == "join" || [string match "~*@*" $u] || [string match "*.users.quakenet.org" $u]} {
    set m "*!*@[lindex [split $u @] 1]"
  }
# private flood, msg/notice/ctcp
  if {$t == "msg" || $t == "ctcp"} {
    if {[matchattr $h fvlomn]} { return 1 }
    set r "msg flood"
    foreach c [channels] {
      if {![channel get $c flood]} { continue }
      if {![channel get $c flood-msgban]} { continue }
      if {[matchattr $h fvlomn|fvlomn $c]} { continue }
      if {[isop $n $c] || [ishalfop $n $c]} {
        if {[channel get $c flood-voice] || [channel get $c flood-op]} { continue }
      }
      if {[isvoice $n $c] && [channel get $c flood-voice]} { continue }
      if {[matchban $n!$u $c]} { continue }
      set l [channel get $c flood_bantime]
      if {$l < 1} { set l 60; channel set $c flood_bantime $l }
      if {[info procs bannick:ban] != ""} { bannick:ban $n $c $d $r $l
      } elseif {[info procs newchanban:ban] != ""} { newchanban:ban $c $m $d $r $l
      } else { newchanban $c $m $d $r $l }
    }
    return 0
  }
  if {[validchan $c]} {
    if {![channel get $c flood]} { return 1 }
    if {[matchattr $h fvlomn|fvlomn $c] && ![matchattr $h Z]} { return 1 }
    if {[matchattr $h k|k $c]} { return 1 }
    if {[isop $n $c] || [ishalfop $n $c]} {
      if {[channel get $c flood-voice] || [channel get $c flood-op]} { return 1 }
    }
    if {[isvoice $n $c] && [channel get $c flood-voice]} { return 1 }
  }

# pub (actions, ctcp, messages)
  if {$t == "pub"} {
    if {[channel get $c flood-pubban]} { set b 1 } else { set k 1 }
    set r "flood";  set s [join [split [channel get $c flood-chan]] " lines in "]s
    append s " / [join [split [channel get $c flood-ctcp]] " CTCPs in "]s"
# join
  } elseif {$t == "join"} {
    if {[channel get $c flood-joinban]} { set b 1 } else { set k 1 }
    set r "join flood"
    set s [join [split [channel get $c flood-join]] " joins in "]s
# nick
  } elseif {$t == "nick"} {
    if {[channel get $c flood-nickban]} { set b 1 } else { set k 1 }
    set r "nick flood"; set s [join [split [channel get $c flood-nick]] " nickchanges in "]s
# deop
  } elseif {$t == "deop"} {
    if {[channel get $c flood-deopban]} { set b 1 } else { set k 1 }
    set r "mass deop"; set s [join [split [channel get $c flood-deop]] " deops in "]s
# kick
  } elseif {$t == "kick"} {
    if {[channel get $c flood-kickban]} { set b 1 } else { set k 1 }
    set r "mass kick"; set s [join [split [channel get $c flood-kick]] " kicks in "]s
  }
# kick user
  if {$k} {
    if {![botisop $c] && ![botishalfop $c]} { return 1 }
    if {![botisop $c] && ([isop $n $c] || [ishalfop $n $c])} { return 1 }
    if {![flood:whichbot $c $n $u]} { return 1 }
    putkick $c $n $r
    if {[info procs ircconsole] != ""} { ircconsole $c f FLOOD "kick $n ($u) :$s" }
# ban user
  } elseif {$b} {
    if {[channel get $c flood-shareban]} {
      foreach z [channels] {
        if {[string equal -nocase $c $z]} { continue }
        if {![channel get $z flood]} { continue }
        if {![channel get $z flood-shareban]} { continue }
        if {[matchban $n!$u $z]} { continue }
        if {$t == "pub" && ![channel get $z flood-pubban]} { continue }
        if {$t == "join" && ![channel get $z flood-joinban]} { continue }
        if {$t == "nick" && ![channel get $z flood-nickban]} { continue }
        if {$t == "deop" && ![channel get $z flood-deopban]} { continue }
        if {$t == "kick" && ![channel get $z flood-kickban]} { continue }
        set l [channel get $z flood_bantime]
        if {$l < 1} { set l 60; channel set $z flood_bantime $l }
        if {[info procs bannick:ban] != "" && $t != "join"} { bannick:ban $n $z $d $r $l
        } elseif {[info procs newchanban:ban] != ""} { newchanban:ban $z $m $d $r $l
        } else { newchanban $z $m $d $r $l }        
      }
    }
    if {[matchban $n!$u $c]} { return 1 }
    set l [channel get $c flood_bantime]
    if {$l < 1} { set l 60; channel set $c flood_bantime $l }
    if {[info procs bannick:ban] != "" && $t != "join"} { bannick:ban $n $c $d $r $l
    } elseif {[info procs newchanban:ban] != ""} { if {[newchanban:ban $c $m $d $r $l]} { return 1 }
    } else { newchanban $c $m $d $r $l }
    if {![flood:whichbot $c $n $u]} { return 1 }
    if {[info procs ircconsole] != ""} { ircconsole $c f FLOOD "ban $m ${l}m :$s" }
  }
  return 1
}


####################################################
# flood:whichbot
# returns 1 if this bot should respond, 0 otherwise
####################################################
proc flood:whichbot { c v t } {
  if {[info procs script:check] == ""} { return 1 }
  global botnet-nick; set a [chanlist $c b]; lappend r ${botnet-nick}
  foreach b $a {
    if {$b == $v} { continue }
    if {[onchansplit $b $c]} { continue }
    if {![isop $b $c] && ![ishalfop $b $c]} { continue }
    if {![script:check $b flood]} { continue }
    set h [nick2hand $b $c]
    if {[lsearch -exact $r $h] > -1} { continue }
    lappend r $h
  }
  set r [lrange [lsort $r] 0 15]; set x [expr [llength $r] -1]
  if {$x == 0} { return 1 }
  set d [lsearch -exact [split a01b23c45d67e89f ""] [string range [md5 $t] 0 0]]
  set b [lindex $r [expr round(${x}. / 16. * $d)]]
  if {$b == ${botnet-nick}} { return 1 } else { return 0 }
}


####################################################
# flood:get
####################################################
proc flood:get { c } {
  if {![validchan $c]} { return "" }
  lappend l "" -op -voice -pubban -joinban -kickban -deopban -nickban -msgban -shareban
  lappend l -chan -ctcp -join -kick -deop -nick _bantime
  foreach e $l { lappend o [join [channel get $c flood$e] :] }
  return [join $o]
}


####################################################
# flood:time
####################################################
bind time -|- "* * * * *" flood:time
proc flood:time { n h d m y } {
  set t [clock seconds]
  foreach c [channels] {
    set a [flood:get $c]; set b [join [lrange [split [channel get $c flood_bot]] 1 end]]
    if {$b != $a} { channel set $c flood_bot "$t $a"; putallbots "FL S $c $t $a" }
  }
}


####################################################
# flood:link
####################################################
bind link -|- * flood:link
proc flood:link { b v } { if {[islinked $b] && [string equal -nocase ${::botnet-nick} $v]} { putbot $b "FL ?" } }


####################################################
# flood:bot
####################################################
# bot FL "?|Y|B|S <chan> <ts> <status> <op> <voice> <pubban> <kickban> <deopban> <nickban> <msgban> <shareban> <chan> <ctcp> <join> <kick> <deop> <nick> <bantime>"
# ?=flood?, Y=yes, B=burst when linking, S=setting changed, or informing botnet of change after B
bind bot -|- FL flood:bot
proc flood:bot { b c t } {
  if {![islinked $b]} { return 0 }
  if {![string equal -nocase $c FL]} { 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 "FL Y"
  } elseif {$s == "y"} {
    set n [clock seconds]
    foreach c [channels] {
      set x [split [channel get $c flood_bot]]; set d [join [lrange $x 1 end]]; set x [lindex $x 0]
      set a [flood:get $c]
      if {![string is digit $x] || $d != $a} { set x $n; channel set $c flood_bot "$x $a" }
      putbot $b "FL B $c $x $a"
    }
  } elseif {[string match {[bs]} $s]} {
    if {[llength $t] != 20} { return 0 }
    if {![validchan $c] && (![string match *g* [botattr $b]] || [catch {channel add $c +inactive}])} { return 0 }
    set x [split [channel get $c flood_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 "" -op -voice -pubban -joinban -kickban -deopban -nickban -msgban -shareban]
    foreach e $l { if {[lindex $t $p]} { set v + } else { set v - }; channel set $c ${v}flood$e; incr p }
    set l [list -chan -ctcp -join -kick -deop -nick _bantime]
    foreach e $l { channel set $c flood$e [lindex $t $p]; incr p }
    set t [join [lrange $t 3 end]]; channel set $c flood_bot "$n $t"
    if {$s != "b" || $d == $t} { return 0 }
    putallbots "FL S $c $n $t"    
  }
}


####################################################
# set info for script.tcl
####################################################
set ::scriptdb(flood) {
  "deals with flood detection as defined by the flood-* settings, default action is to kick the user. +flood (enable/disable, including default flood settings and actions), +flood-pubban (ban for chan/ctcp flood), +flood-msgban (ban for private flood (msg, notice & ctcp)), +flood-joinban (ban for join flood), +flood-nickban (ban for nick flood), +flood-deopban (ban for deop flood), +flood-kickban (ban for kick flood), when not set to ban, user is only kicked."
  "+flood-op (ignore ops), +flood-voice (ignore voices). +flood-shareban (when a user is banned for any channel flood type, and +flood-shareban is set there, the user will also be banned on ALL other +flood-shareban channels, when the same +flood-<type>ban is set as well. flood_bantime (ban user for X minutes, default 60), flood_bot (used for sharing settings over botnet, do not touch)"
}

