####################################################
# by wiebe @ QuakeNet
#
# deals with pretty much all types of spam
#
####################################################

# spamscan: spamscan keeps a time variable associated with every user which is never lower than the current time. penalty is awarded based on events from the user: join, msg, action, chan ctcp, chan notice, nick change, part.
# spamscan: penalties are also awarded based on the message itself by looking at things like: length, caps, control codes, non alphanum chars, advertise, highlights, repeated consecutive chars, user/chan repeats, repeats of substrings.
# spamscan: once the user's time variable gets too far ahead due to penalties, the user is warned/kicked/banned. when the penalty for the channel as a whole gets too far ahead, the channel is temporary locked. everything is completely configurable with channel settings.


# enable, ignore @ + users
setudef flag spamscan
setudef flag spamscan-op
setudef flag spamscan-voice
# show what user did wrong in warn/kick/ban messages
setudef flag spamscan-detail
# work as normal, but do not actually warn/kick/ban/lock
setudef flag spamscan-test

# warn/kick/ban user at score for N minutes
setudef int spamscan-warn
setudef int spamscan-kick
setudef int spamscan-ban
setudef int spamscan-bantime

# lock chan at score
setudef int spamscan-lock
setudef str spamscan-lockmp
setudef str spamscan-lockmode
setudef int spamscan-locktime

# warn/kick/ban message
setudef str spamscan-warnmsg
setudef str spamscan-kickmsg
setudef str spamscan-banmsg

# score from join msg action ctcp notice part
setudef str spamscan-join
setudef str spamscan-msg
setudef str spamscan-action
setudef str spamscan-ctcp
setudef str spamscan-notice
setudef str spamscan-nick
setudef str spamscan-part

# length / 120 * setting (not if smaller than 20)
setudef str spamscan-length
# text shorter than 20 chars, + setting
setudef str spamscan-short
# number.of.codes * setting
setudef str spamscan-codes
# number.of.caps / 10 * %caps * setting
setudef str spamscan-caps
# for each highlight above 4, * setting (or when just highligh one user, no text, + setting)
setudef str spamscan-highlight
# nonalphanum / 10 * %nonalphanum * setting
setudef str spamscan-nonalphanum
# www # http, + setting
setudef str spamscan-adv
# for each user repeated line in the last 180s * setting
setudef str spamscan-userrepeat
# for each channel repeated line in the last 60s * setting
setudef str spamscan-chanrepeat
# each group of 4 repeated consecutive chars * setting
setudef str spamscan-charrepeat
# repeated.substrings * %.of.repeated.substrings * setting
setudef str spamscan-textrepeat


####################################################
# spamscan:help:msg
####################################################
bind msgm omn|omn "help spamscan" spamscan:help:msgm
proc spamscan:help:msgm { n u h t } {
  if {[matchattr $h bkZ]} { return 0 }
  lappend o "spamscan: usage spamscan info|set|help"
  if {[info procs cnotice] == ""} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "spamscan: " } }
  putcmdlog "($n!$u) !$h! help spamscan"
  return 1
}


####################################################
# spamscan:msg
####################################################
bind msg omn|omn spamscan spamscan:msg
proc spamscan:msg { n u h t } {
  if {[matchattr $h bkZ]} { return 0 }
  set t [split $t]; set c [string tolower [lindex $t 0]]; set t [join [lrange $t 1 end]]
  if {$c == "info"} { set o [spamscan:info $h $t]
  } elseif {$c == "set"} { set o [spamscan:set $h $t]
  } elseif {$c == "help"} { set o [spamscan:help $t]
  } else {
    if {$c != ""} { lappend o "spamscan: unknown subcommand $c" }
    lappend o "spamscan: usage spamscan info|set|help"
  }
  if {[info procs cnotice] == ""} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "spamscan: " } }
  return 1
}


####################################################
# spamscan:dcc
####################################################
bind dcc -|- spamscan spamscan:dcc
proc spamscan:dcc { h i t } {
  set t [split $t]; set c [string tolower [lindex $t 0]]; set t [join [lrange $t 1 end]]
  if {$c == "info"} { set o [spamscan:info $h $t]
  } elseif {$c == "set"} { set o [spamscan:set $h $t]
  } elseif {$c == "help"} { set o [spamscan:help $t]
  } else {
    if {$c != ""} { lappend o "spamscan: unknown subcommand $c" }
    lappend o "spamscan: usage spamscan info|set|help"
  }
  foreach l $o { putidx $i $l }
  return 1
}


####################################################
# spamscan:info
####################################################
proc spamscan:info { h t } {
  set t [split $t]; set c [lindex $t 0]; set p [lindex $t 1];  set q $p
  if {![validchan $c] && [info procs whichchan] != ""} { set c [whichchan $h $c] }
  regsub -all {[][\\]} $q {\\\0} q
  if {$c == ""} {
    lappend o "spamscan: no chan specified"
    lappend o "spamscan: info <chan> <pattern>"
  } elseif {![validchan $c] || ![matchattr $h lomn|lomn $c]} {
    lappend o "spamscan: no access or unknown channel $c"
    lappend o "spamscan: info <chan> <pattern>"
  } elseif {$p == ""} {
    lappend o "spamscan: no pattern specified"
    lappend o "spamscan: info <chan> <pattern>"
  } else {
    lappend o "spamscan: listing spamscan settings matching $p for $c"; set m ""
    if {[channel get $c spamscan]} { lappend m "spamscan=on"
    } else { lappend m "spamscan=off" }
    foreach f "warn kick ban bantime lock lockmp lockmode locktime warnmsg kickmsg banmsg join msg action ctcp notice nick part length short codes caps adv highlight nonalphanum charrepeat userrepeat chanrepeat textrepeat" {
      if {[string match $q $f]} { set v [channel get $c spamscan-$f]; lappend m "$f=$v" }
    }
    foreach f "op voice detail test" {
      if {[string match $q $f]} {
        if {[channel get $c spamscan-$f]} { set v on } else { set v off }; lappend m "$f=$v"
      }
    }
    if {$m != ""} { lappend o "spamscan: [join $m "     "]" }
    lappend o "spamscan: end of list"
  }
  return $o
}


####################################################
# spamscan:help
####################################################
proc spamscan:help { t } {
  set t [split $t]; set p [string tolower [lindex $t 0]]; set q $p; regsub -all {[][\\]} $q {\\\0} q
  if {$p == ""} {
    lappend o "spamscan: help set|info|general|<pattern>"
    lappend o "spamscan: shows help for the specified subcommand or shows help for channel settings matching <pattern>"
  } elseif {$p == "general"} {
    lappend o "spamscan: spamscan keeps a time variable associated with every user which is never lower than the current time. penalty is awarded based on events from the user: join, msg, action, chan ctcp, chan notice, nick change, part."
    lappend o "spamscan: penalties are also awarded based on the message itself by looking at things like: length, caps, control codes, non alphanum chars, advertise, highlights, repeated consecutive chars, user/chan repeats, repeats of substrings."
    lappend o "spamscan: once the user's time variable gets too far ahead due to penalties, the user is warned/kicked/banned. when the penalty for the channel as a whole gets too far ahead, the channel is temporary locked. everything is completely configurable with channel settings."
  } elseif {$p == "info"} {
    lappend o "spamscan: info <chan> <pattern>"
    lappend o "spamscan: shows channel settings matching <pattern>"
  } elseif {$p == "set"} {
    lappend o "spamscan: set <chan> <setting> <value> \[<setting> <value> ...\]"
    lappend o "spamscan: changes channel settings"
  } else {
    lappend o "spamscan: listing spamscan settings matching $p"
    lappend o "spamscan: set +spamscan to enable the script"
    foreach f "warn kick ban bantime lock lockmp lockmode locktime warnmsg kickmsg banmsg join msg action ctcp notice nick part length short codes caps adv highlight nonalphanum charrepeat userrepeat chanrepeat textrepeat" {
      if {![string match $q $f]} { continue }
      set s [string repeat " " [expr 12 - [string length $f]]]
      if {$f == "warn"} { lappend o "spamscan: $f $s warn user at penalty X"
      } elseif {$f == "kick"} { lappend o "spamscan: $f $s kick user at penalty X"
      } elseif {$f == "ban"} { lappend o "spamscan: $f $s ban user at penalty X"
      } elseif {$f == "bantime"} { lappend o "spamscan: $f $s ban user for X minutes"
      } elseif {$f == "lock"} { lappend o "spamscan: $f $s lock chan at penalty X"
      } elseif {$f == "lockmp"} { lappend o "spamscan: $f $s chan penalty: user penalty * X"
      } elseif {$f == "lockmode"} { lappend o "spamscan: $f $s lock chan with modes X"
      } elseif {$f == "locktime"} { lappend o "spamscan: $f $s lock chan for X minutes"
      } elseif {$f == "warnmsg"} { lappend o "spamscan: $f $s warn message"
      } elseif {$f == "kickmsg"} { lappend o "spamscan: $f $s kick message"
      } elseif {$f == "banmsg"} { lappend o "spamscan: $f $s ban message"
      } elseif {$f == "join"} { lappend o "spamscan: $f $s penalty for join: X"
      } elseif {$f == "msg"} { lappend o "spamscan: $f $s penalty for msg: X"
      } elseif {$f == "action"} { lappend o "spamscan: $f $s penalty for action: X"
      } elseif {$f == "ctcp"} { lappend o "spamscan: $f $s penalty for chan ctcp: X"
      } elseif {$f == "notice"} { lappend o "spamscan: $f $s penalty for chan notice: X"
      } elseif {$f == "nick"} { lappend o "spamscan: $f $s penalty for nick change: X"
      } elseif {$f == "part"} { lappend o "spamscan: $f $s penalty for part: X"
      } elseif {$f == "length"} {
        lappend o "spamscan: $f $s penalty for text length: length / 120 * X (except when length smaller than 20)"
      } elseif {$f == "short"} { lappend o "spamscan: $f $s penalty for text shorter than 20 chars: X"
      } elseif {$f == "codes"} { lappend o "spamscan: $f $s penalty for control codes: number of codes * X"
      } elseif {$f == "caps"} { lappend o "spamscan: $f $s penalty for caps: number of caps / 10 * %caps * X"
      } elseif {$f == "adv"} { lappend o "spamscan: $f $s penalty for advertise (www http #): X"
      } elseif {$f == "highlight"} {
        lappend o "spamscan: $f $s penalty for highlight: number of highlights * X (for highlights above 4); or when highlighting one user with no text: X"
      } elseif {$f == "nonalphanum"} {
        lappend o "spamscan: $f $s penalty for non alphanum chars: number of non alphanum chars / X * %non alphanum chars"
      } elseif {$f == "charrepeat"} {
        lappend o "spamscan: $f $s penalty for char repeats: each group of 4 repeated consecutive chars * X"
      } elseif {$f == "userrepeat"} {
        lappend o "spamscan: $f $s penalty for user repeats: number of repeats from user (in last 180s) * X"
      } elseif {$f == "chanrepeat"} {
        lappend o "spamscan: $f $s penalty for chan repeats: number of repeats from different users (in last 60s) * X"
      } elseif {$f == "textrepeat"} {
        lappend o "spamscan: $f $s penalty for text repeats: repeated substrings * %repeated substrings * X"
      }
    }
    foreach f "op voice detail test" {
      if {![string match $q $f]} { continue }
      set s [string repeat " " [expr 12 - [string length $f]]]
      if {$f == "op"} { lappend o "spamscan: $f $s ignore opped users"
      } elseif {$f == "voice"} { lappend o "spamscan: $f $s ignore voiced users"
      } elseif {$f == "detail"} { lappend o "spamscan: $f $s show details of penalty in warn/kick/ban messages"
      } elseif {$f == "test"} { lappend o "spamscan: $f $s work as normal, but do not actually warn/kick/ban/lock" }
    }
    lappend o "spamscan: end of list"
  }
  return $o
}



####################################################
# spamscan:set
####################################################
proc spamscan:set { h t } {
  set text [split $t]; set c [lindex $t 0]; set r [lrange $t 1 end]
  if {![validchan $c] && [info procs whichchan] != ""} { set c [whichchan $h $c] }
  if {$c == ""} {
    lappend o "spamscan: no chan specified"
    lappend o "spamscan: set <chan> <setting> <value> \[<setting> <value> ...\]"
  } elseif {![validchan $c] || ![matchattr $h mn|mn $c]} {
    lappend o "spamscan: no access or unknown channel $c"
    lappend o "spamscan: set <chan> <setting> <value> \[<setting> <value> ...\]"
  } elseif {$r == ""} {
    lappend o "spamscan: no setting specified"
    lappend o "spamscan: set <chan> <setting> <value> \[<setting> <value> ...\]"
    lappend o "spamscan: settings are: spamscan voice op detail test warn kick ban bantime lock lockmp lockmode locktime warnmsg kickmsg banmsg join msg action ctcp notice nick part length short codes caps adv highlight nonalphanum charrepeat userrepeat chanrepeat textrepeat"
  } else {
    set p 0; set t [llength $r]; set m ""
    while {$p <= $r} {
      set s [string tolower [lindex $r $p]]; set v [lindex $r [expr $p +1]]
      if {[lsearch -exact "warn kick ban bantime lock locktime" $s] > -1} {
        if {![string is digit $v] || $v == ""} {
          lappend o "spamscan: no or invalid value for $s - value must be a digit"
          break
        } else { channel set $c spamscan-$s $v }

      } elseif {[lsearch -exact "lockmp join msg action ctcp notice nick part length short codes caps adv highlight nonalphanum charrepeat userrepeat chanrepeat textrepeat" $s] > -1} {
        if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $v]} {
          lappend o "spamscan: no or invalid value for $s - value must be a number"
          break
        } else { channel set $c spamscan-$s $v }

      } elseif {[lsearch -exact "warnmsg kickmsg banmsg" $s] > -1} {
        set v [join [lrange $r [expr $p +1] end]]
        channel set $c spamscan-$s $v; lappend m "$s=$v"; break

      } elseif {$s == "lockmode"} {
        if {![regexp {^[mir]*$} $v]} {
          lappend o "spamscan: no or invalid value for $s - valid are m i r modes"
          break
        } else { channel set $c spamscan-$s $v }

      } elseif {[lsearch -exact "op voice detail test" $s] > -1} {
        if {[string equal -nocase $v "on"]} { channel set $c +spamscan-$s
        } elseif {[string equal -nocase $v "off"]} { channel set $c -spamscan-$s
        } else {
          lappend o "spamscan: no or invalid value for $s - value must be on or off"
          break
        }

      } elseif {$s == "spamscan"} {
        if {[string equal -nocase $v "on"]} { channel set $c +spamscan
        } elseif {[string equal -nocase $v "off"]} { channel set $c -spamscan
        } else {
          lappend o "spamscan: no or invalid value for $s - value must be on or off"
          break
        }

      } else {
        if {$s != ""} { lappend o "spamscan: invalid setting $s" }
        break
      }
      lappend m "$s=$v"; incr p 2
    }
    if {$m != ""} { lappend o "spamscan: [join $m "     "]" }
  }
  return $o
}



####################################################
# spamscan:evnt
####################################################
bind evnt -|- init-server spamscan:evnt
proc spamscan:evnt { t } { global spamscandb; if {[info exists spamscandb]} { unset spamscandb } }


####################################################
# spamscan:join
####################################################
bind join -|- * spamscan:join
proc spamscan:join { n u h c } {
  global spamscandb
  if {[isbotnick $n]} {
    regsub -all {[][\\]} $c {\\\0} c
    set c [string tolower $c]
# md5
    if {[string length $c] > 32} { set c [md5 $c] }
    foreach m [array names spamscandb] { if {[string match "$c,*" $m]} { unset spamscandb($m) } }
  } else { spamscan:check join $c $n $u $h }
}


####################################################
# spamscan:pubm
####################################################
bind pubm -|- * spamscan:pubm
proc spamscan:pubm { n u h c t } { spamscan:check msg $c $n $u $h $t; return 0 }


####################################################
# spamscan:ctcp
####################################################
bind ctcp -|- * spamscan:ctcp; bind ctcr -|- * spamscan:ctcp
proc spamscan:ctcp { n u h c k t } {
  set c [string trimleft $c +@]; if {![validchan $c]} { return 0 }
  if {[string equal -nocase $k "action"]} { spamscan:check action $c $n $u $h $t
  } else { spamscan:check ctcp $c $n $u $h "$k $t" }
  return 0
}


####################################################
# spamscan:notc
####################################################
bind notc -|- * spamscan:notc
proc spamscan:notc { n u h t {c ""}} {
  set c [string trimleft $c +@]
  if {![validchan $c]} { return 0 }
  spamscan:check notice $c $n $u $h $t
  return 0
}


####################################################
# spamscan:nick
####################################################
bind nick -|- * spamscan:nick
proc spamscan:nick { n u h c m } { spamscan:check nick $c $n $u $h $m }


####################################################
# spamscan:part
####################################################
bind part -|- * spamscan:part
proc spamscan:part { n u h c {t ""} } { spamscan:check part $c $n $u $h $t }


####################################################
# spamscan:kick
####################################################
bind kick -|- * spamscan:kick
proc spamscan:kick { n u h c v t } {
  set u [getchanhost $v $c]; set h [nick2hand $v $c]; spamscan:check kick $c $v $u $h
}


####################################################
# spamscan:sign
####################################################
bind sign -|- * spamscan:sign; bind splt -|- * spamscan:sign
proc spamscan:sign { n u h c {t ""} } {
  if {[string equal -nocase $t "registered"]} { return 0 }
  if {[string equal -nocase $t "host change"]} { return 0 }
  spamscan:check quit $c $n $u $h $t
}


####################################################
# spamscan:check
#
# adds score and takes action
####################################################
proc spamscan:check { y c n u h {t ""} } {

  if {[isbotnick $n]} {
    if {[lsearch -exact "part kick"] == -1} { return 0 }
    regsub -all {[][\\]} $c {\\\0} c
    global spamscandb
    foreach m [array names spamscandb] {
      if {[string match -nocase $c,* $m]} { unset spamscan($m) }
    }
    return 0
  }

  if {![validchan $c]} { return 0 }
  if {![channel get $c spamscan]} { return 0 }
  if {[matchattr $h fvlomn|fvlomn $c] && ![matchattr $h kZ]} { return 0 }
  if {[channel get $c spamscan-voice] && ![channel get $c spamscan-op]} {
    channel set $c +spamscan-op
  }
  if {([isop $n $c] || [ishalfop $n $c]) && [channel get $c spamscan-op]} { return 0 }
  if {[isvoice $n $c] && [channel get $c spamscan-voice]} {
    if {![string match *m* [lindex [split [getchanmode $c]] 0]]} { return 0 }
  }

  global spamscandb
  set l [clock seconds]; set y [string tolower $y]; set d $c; set c [string tolower $c]

# md5
  set a $c
  if {[string length $a] > 32} { set a [md5 $a] }

  set m $n; set n [string tolower $n]; set z 0; set p ""

# join msg action ctcp notice nick part
  if {$y == "join"} {
    set s [spamscan:checkjoin $c $n $u]; set z [expr $z + $s]; lappend o "$s join"
  }
  if {$y == "msg"} {
# make list of things todo, proc, message etc., foreach to do all types in one go
    set s [spamscan:checkmsg $c]; set z [expr $z + $s]; lappend o "$s msg"
    set s [spamscan:length $c $t]; set z [expr $z + $s]; lappend o "$s length"; lappend p "$s long line"
    set s [spamscan:short $c $t]; set z [expr $z + $s]; lappend o "$s short"; lappend p "$s short line"
    set s [spamscan:codes $c $t]; set z [expr $z + $s]; lappend o "$s codes"; lappend p "$s control codes"
    set s [spamscan:caps $c $t]; set z [expr $z + $s]; lappend o "$s caps"; lappend p "$s caps"
    set s [spamscan:highlight $c $t]; set z [expr $z + $s]; lappend o "$s highlight"; lappend p "$s highlights"
    set s [spamscan:nonalphanum $c $t]; set z [expr $z + $s]; lappend o "$s nonalphanum"; lappend p "$s non alpha num chars"
    set s [spamscan:adv $c $t]; set z [expr $z + $s]; lappend o "$s adv"; lappend p "$s advertise"
    set s [spamscan:repeat $c $u $t]; set z [expr $z + $s]; lappend o "$s repeat"; lappend p "$s repeats"
    set s [spamscan:charrepeat $c $t]; set z [expr $z + $s]; lappend o "$s charrepeat"; lappend p "$s char repeats"
    set s [spamscan:textrepeat $c $t]; set z [expr $z + $s]; lappend o "$s textrepeat"; lappend p "$s flood"
  }
  if {$y == "action"} {
    set s [spamscan:checkaction $c]; set z [expr $z + $s]; lappend o "$s action"
    set s [spamscan:length $c $t]; set z [expr $z + $s]; lappend o "$s length"; lappend p "$s long line"
    set s [spamscan:short $c $t]; set z [expr $z + $s]; lappend o "$s short"; lappend p "$s short line"
    set s [spamscan:codes $c $t]; set z [expr $z + $s]; lappend o "$s codes"; lappend p "$s control codes"
    set s [spamscan:caps $c $t]; set z [expr $z + $s]; lappend o "$s caps"; lappend p "$s caps"
    set s [spamscan:highlight $c $t]; set z [expr $z + $s]; lappend o "$s highlight"; lappend p "$s highlights"
    set s [spamscan:nonalphanum $c $t]; set z [expr $z + $s]; lappend o "$s nonalphanum"; lappend p "$s non alpha num chars"
    set s [spamscan:adv $c $t]; set z [expr $z + $s]; lappend o "$s adv"; lappend p "$s advertise"
    set s [spamscan:repeat $c $u $t]; set z [expr $z + $s]; lappend o "$s repeat"; lappend p "$s repeats"
    set s [spamscan:charrepeat $c $t]; set z [expr $z + $s]; lappend o "$s charrepeat"; lappend p "$s char repeats"
    set s [spamscan:textrepeat $c $t]; set z [expr $z + $s]; lappend o "$s textrepeat"; lappend p "$s flood"
  }
  if {$y == "ctcp"} {
    set s [spamscan:checkctcp $c]; set z [expr $z + $s]; lappend o "$s ctcp"; lappend p "$s channel CTCP"
    set s [spamscan:length $c $t]; set z [expr $z + $s]; lappend o "$s length"; lappend p "$s long line"
    set s [spamscan:short $c $t]; set z [expr $z + $s]; lappend o "$s short"; lappend p "$s short line"
    set s [spamscan:codes $c $t]; set z [expr $z + $s]; lappend o "$s codes"; lappend p "$s control codes"
    set s [spamscan:caps $c $t]; set z [expr $z + $s]; lappend o "$s caps"; lappend p "$s caps"
    set s [spamscan:nonalphanum $c $t]; set z [expr $z + $s]; lappend o "$s nonalphanum"; lappend p "$s non alpha num chars"
    set s [spamscan:adv $c $t]; set z [expr $z + $s]; lappend o "$s adv"; lappend p "$s advertise"
    set s [spamscan:repeat $c $u $t]; set z [expr $z + $s]; lappend o "$s repeat"; lappend p "$s repeats"
    set s [spamscan:charrepeat $c $t]; set z [expr $z + $s]; lappend o "$s charrepeat"; lappend p "$s char repeats"
    set s [spamscan:textrepeat $c $t]; set z [expr $z + $s]; lappend o "$s textrepeat"; lappend p "$s flood"
  }
  if {$y == "notice"} {
    set s [spamscan:checknotice $c]; set z [expr $z + $s]; lappend o "$s notice"; lappend p "$s channel notice"
    set s [spamscan:length $c $t]; set z [expr $z + $s]; lappend o "$s length"; lappend p "$s long line"
    set s [spamscan:short $c $t]; set z [expr $z + $s]; lappend o "$s short"; lappend p "$s short line"
    set s [spamscan:codes $c $t]; set z [expr $z + $s]; lappend o "$s codes"; lappend p "$s control codes"
    set s [spamscan:caps $c $t]; set z [expr $z + $s]; lappend o "$s caps"; lappend p "$s caps"
    set s [spamscan:nonalphanum $c $t]; set z [expr $z + $s]; lappend o "$s nonalphanum"; lappend p "$s non alpha num chars"
    set s [spamscan:adv $c $t]; set z [expr $z + $s]; lappend o "$s adv"; lappend p "$s advertise"
    set s [spamscan:repeat $c $u $t]; set z [expr $z + $s]; lappend o "$s repeat"; lappend p "$s repeats"
    set s [spamscan:charrepeat $c $t]; set z [expr $z + $s]; lappend o "$s charrepeat"; lappend p "$s char repeats"
    set s [spamscan:textrepeat $c $t]; set z [expr $z + $s]; lappend o "$s textrepeat"; lappend p "$s flood"
  }
  if {$y == "nick"} {
    set s [spamscan:checknick $c $n $t]; set z [expr $s + $s]; lappend o "$s nick"; lappend p "$s nick change"
    set n [string tolower $t]    
  }
  if {$y == "part"} {
    set s [spamscan:checkpart $c]; set z [expr $z + $s]; lappend o "$s part"; lappend p "$s part"
  }
  lappend o "$z score"

  set z [expr round($z)]; set os 0
# md5
  if {[info exists spamscandb($a,$n)]} { set os $spamscandb($a,$n) }
  if {$os < $l} { set ns [expr $l + $z] } else { set ns [expr $os + $z] }
  set spamscandb($a,$n) $ns; set z [expr $ns - $l]

  if {$z} {
    lappend o "$z newscore"; set e ""
    foreach s $o {
      set s [split $s]; set f [string range [lindex $s 0] 0 3]; set s [lindex $s 1]
      if {$f < 1 } { continue }
      lappend e "$s=$f"
    }
    set o "SPAMSCAN: $c $n [join $e "  "]"; putloglev 2 $c $o
  }

  set b [channel get $c spamscan-ban]; set k [channel get $c spamscan-kick]
  set w [channel get $c spamscan-warn]
  if {($w > $b && $b > 0) || ($w > $k && $k > 0) || $w < 2} {
    set w 0; channel set $c spamscan-warn $w
  }
  if {($k > $b && $b > 0) || $k < 2} { set k 0; channel set $c spamscan-kick $k }
  if {$b < 2} { set b 0; channel set $c spamscan-ban $b }
  if {$y == "part"} { set w 0 ; set k 0 }

# show only type with highest score for now.
  set p [lindex [lsort -decreasing -dictionary $p] 0]
  regsub -all {[0-9]+\.{0,1}[0-9]* } $p "" p
  set p [join $p ", "]
  if {$y == "kick"} { set b 0; set k 0; set w 0 }
# ban
  if {$z >= $b && $b > 0} {
    spamscan:doban $d $m $u $z $b $p; unset spamscandb($a,$n)
# kick
  } elseif {$z >= $k && $k > 0} { spamscan:dokick $d $m $u $z $k $p
# warn
  } elseif {$z >= $w && $w > 0} { spamscan:dowarn $d $m $u $z $w $p }
# lock
  spamscan:lock $c $d $z
# cleanup
  if {[lsearch -exact "part kick quit" $y] > -1} {
# md5
    if {$y != "kick"} {
      set spamscandb($a,tr,$n) "$l $z"
      set u [string trimleft [string tolower $u] ~]
      if {[string length $u] > 32} { set u [md5 $u] }
      set spamscandb($a,tr,$u) "$l $z"
    }
    unset spamscandb($a,$n)
  }
}


####################################################
# spamscan:doban
####################################################
proc spamscan:doban { c n u s b p } {
  set m [channel get $c spamscan-banmsg]; set d spamscan.tcl
  if {$m == ""} { set m "You are violating channel rules." }
  if {[channel get $c spamscan-detail] && $p != ""} { append m " ($p)" }
  set l [channel get $c spamscan-bantime]
  if {$l < 10} { channel set $c spamscan-bantime 10; set l 10 }
  if {[matchban $n!$u $c]} { return 0 }
  set x "*!$u"
  if {[string match "~*@*" $u] || [string match "*.users.quakenet.org" $u]} {
    set x "*!*@[lindex [split $u @] 1]"
  }
  if {![channel get $c spamscan-test]} {
    if {[info procs bannick:ban] != ""} {
      bannick:ban $n $c $d $m $l
    } elseif {[info procs newchanban:ban] != ""} {
      if {![newchanban:ban $c $x $d $m $l]} { return 0 }
    } else { newchanban $c $x $d $m $l }
  }
  if {![spamscan:whichbot $c $n $u]} { return 0 }
  set t "BAN $n ($u) ${l}m :$s / $b"
  if {$p != ""} { append t " ($p)" }
  set h "ban $u"; set h [string tolower $h]; set h [md5 $h]
  if {[info procs ircconsole] != ""} { ircconsole $c s SPAMSCAN $t $h }
  putloglev 2 $c $t
}


####################################################
# spamscan:dokick
####################################################
proc spamscan:dokick { c n u s k p } {
  if {![botisop $c] && ![botishalfop $c]} { return 0 }
  if {![botisop $c] && ([isop $n $c] || [ishalfop $n $c])} { return 0 }
  if {![spamscan:whichbot $c $n $u]} { return 0 }
  set m [channel get $c spamscan-kickmsg]
  if {$m == ""} { set m "You are violating channel rules." }
  if {[channel get $c spamscan-detail] && $p != ""} { append m " ($p)" }
  if {![channel get $c spamscan-test]} { putkick $c $n $m }
  set t "KICK $n ($u) :$s / $k"; set h [md5 [string tolower "kick $u"]]
  if {$p != ""} { append t " ($p)" }
  if {[info procs ircconsole] != ""} { ircconsole $c s SPAMSCAN $t $h }
  putloglev 2 $c $t
}


####################################################
# spamscan:dowarn
####################################################
proc spamscan:dowarn { c n u s w p } {
  if {![botisop $c] && ![botishalfop $c]} { return 0 }
  if {![botisop $c] && ([isop $n $c] || [ishalfop $n $c])} { return 0 }
  if {![spamscan:whichbot $c $n $u]} { return 0 }
  set m [channel get $c spamscan-warnmsg]
  if {$m == ""} { set m "You are violating channel rules." }
  if {[channel get $c spamscan-detail] && $p != ""} { append m " ($p)" }
  set m "\[$c\]: $m"
  if {![channel get $c spamscan-test]} {
    if {[info procs cnotice] == ""} { puthelp "NOTICE $n :$m"
    } else { cnotice $n $m puthelp }
  }
  set t "WARN $n ($u) :$s / $w"; set h [md5 [string tolower "warn $u"]]
  if {$p != ""} { append t " ($p)" }
  if {[info procs ircconsole] != ""} { ircconsole $c s SPAMSCAN $t $h }
  putloglev 2 $c $t
}


####################################################
# spamscan:whichbot
# returns 1 if this bot should respond, 0 otherwise
####################################################
proc spamscan:whichbot { c n t } {
  if {[info procs script:check] == ""} { return 1 }
  global botnet-nick; set a [chanlist $c b]; lappend r ${botnet-nick}
  foreach b $a {
    if {[onchansplit $b $c]} { continue }
    if {![isop $b $c] && ![ishalfop $b $c]} { continue }
    if {![isop $b $c] && ([isop $n $c] || [ishalfop $n $c])} { continue }
    if {![script:check $b spamscan]} { 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 }
}


####################################################
# spamscan:lock
####################################################
proc spamscan:lock { c d s } {
  global spamscandb; set t [clock seconds]; set f [channel get $d spamscan-lockmp]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { channel set $d spamscan-lockmp 0; return 0 }
  set s [expr round($s * $f)];  set o 0
# md5
  if {[string length $c] > 32} { set c [md5 $c] }

  if {[info exists spamscandb($c,$c)]} { set o $spamscandb($c,$c) }
  if {$o < $t} { set n [expr $t + $s] } else { set n [expr $o + $s] }
  set spamscandb($c,$c) $n
  set n [expr $n - $t]
  if {$n > 0} { putloglev 2 $d "chan score $n" }
  if {![botisop $d] && ![ishalfop $d]} { return 0 }
  set l [channel get $d spamscan-lock]
  if {$l < 2} { return 0 }
  if {$n < $l} { return 0 }
  set a [channel get $d spamscan-locktime]
# unset chan score ?
  set spamscandb($c,$c) 0
  if {$a < 1} { channel set $c spamscan-locktime 1; set a 1 }
  set b [channel get $c spamscan-lockmode]
  if {$b == ""} { set b "mr" }
  if {![info exists spamscandb($c,$c,mode)]} { set spamscandb($c,$c,mode) "" }
  set e 0; set x [lindex [split [getchanmode $d]] 0]
  if {![channel get $c spamscan-test]} {
    foreach m [split $b ""] {
      if {![string match {[mir]} $m]} { continue }
      if {[string match *$m* $x]} { continue }
      set e 1;  pushmode $c +$m
      if {![string match *$m* $spamscandb($c,$c,mode)]} { append spamscandb($c,$c,mode) $m }
      set spamscandb($c,$c,lock) [expr $t + $a * 60]
    }
    flushmode $c
  }
  if {!$e} { return 0 }
  if {![spamscan:whichbot $d "" $c]} { return 0 }
  set t "locking channel :$n / $l"; set h [md5 "lock"]
  putloglev 2 $d $t
  if {[info procs ircconsole] != ""} { ircconsole $d s SPAMSCAN $t $h }
}


####################################################
# spamscan:unlock
####################################################
proc spamscan:unlock { c } {
# md5
  if {![channel get $c spamscan]} { return 0 }
  if {![botisop $c] && ![botishalfop $c]} { return 0 }
  set d $c; set d [string tolower $d]; set t [clock seconds]; global spamscandb
  if {[string length $d] > 32} { set d [md5 $d] }
  if {![info exists spamscandb($d,$d,lock)]} { return 0 }
  if {![info exists spamscandb($d,$d,mode)]} { unset spamscandb($d,$d,lock); return 0 }
  if {$spamscandb($d,$d,lock) > $t} { return 0 }
  set z [lindex [split [getchanmode $c]] 0]
  foreach m [split $spamscandb($d,$d,mode) ""] {
    if {[string match *$m* $z]} { pushmode $c -$m }
  }
  unset spamscandb($d,$d,mode); unset spamscandb($d,$d,lock)
}


####################################################
# spamscan:checkjoin
#
# returns score for join
####################################################
proc spamscan:checkjoin { c n u } {
  global spamscandb; set f [channel get $c spamscan-join]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-join $f }
  set s 0; set m [clock seconds]; set u [string trimleft [string tolower $u] ~]
  if {[string length $u] > 32} { set u [md5 $u] }
# md5
  if {[string length $c] > 32} { set c [md5 $c] }
  if {[info exists spamscandb($c,tr,$u)]} {
    set t $spamscandb($c,tr,$u); set o [lindex [split $t] 1]; set t [lindex [split $t] 0]
    if {[expr $m - $t] <= 600} { set spamscandb($c,$n) [expr $m + $o]; set s [expr $s + $o] }

    unset spamscandb($c,tr,$u)
  } elseif {[info exists spamscandb($c,tr,$n)]} {
    set t $spamscandb($c,tr,$n); set o [lindex [split $t] 1]; set t [lindex [split $t] 0]
    if {[expr $m - $t] <= 600} { set spamscandb($c,$n) [expr $m + $o]; set s [expr $s + $o] }
    unset spamscandb($c,tr,$n)
  }
  if {[info exists spamscandb($c,tr,$n)]} { unset spamscandb($c,tr,$n) }
  set s [expr $s + $f]
  return $s
}


####################################################
# spamscan:checkmsg
#
# returns score for msg
####################################################
proc spamscan:checkmsg { c } {
  set s 0; set f [channel get $c spamscan-msg]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-msg $f }
  if {!$f} { return 0 }
  set s [expr $s + $f]; return $s
}


####################################################
# spamscan:checkaction
#
# returns score for action
####################################################
proc spamscan:checkaction { c } {
  set s 0; set f [channel get $c spamscan-action]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-action $f }
  if {!$f} { return 0 }
  set s [expr $s + $f]; return $s
}


####################################################
# spamscan:checkctcp
#
# returns score for ctcp
####################################################
proc spamscan:checkctcp { c } {
  set s 0; set f [channel get $c spamscan-ctcp]; set f [string tolower $f]
  if {[lsearch -exact "warn kick ban" $f] > -1} { set f [channel get $c spamscan-$f] }
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-ctcp $f }
  if {!$f} { return 0 }
  set s [expr $s + $f]
  return $s 
}


####################################################
# spamscan:checknotice
#
# returns score for notice
####################################################
proc spamscan:checknotice { c } {
  set s 0; set f [channel get $c spamscan-notice]; set f [string tolower $f]
  if {[lsearch -exact "warn kick ban" $f] > -1} { set f [channel get $c spamscan-$f] }
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-notice $f }
  if {!$f} { return 0 }
  set s [expr $s + $f]; return $s
}


####################################################
# spamscan:checknick
#
# returns score for nick
####################################################
proc spamscan:checknick { c n m } {
# md5
  global spamscandb; set s 0; set f [channel get $c spamscan-nick]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-nick $f }
  if {!$f} { return 0 }
  set s [expr $s + $f]
  if {[string length $c] > 32} { set c [md5 $c] }
  if {[string equal -nocase $n $m]} { return $s }
  if {![info exists spamscandb($c,$n)]} { return $s }
  set m [string tolower $m]; set spamscandb($c,$m) $spamscandb($c,$n); return $s 
}


####################################################
# spamscan:checkpart
#
# returns score for part
####################################################
proc spamscan:checkpart { c } {
  set s 0; set f [channel get $c spamscan-part]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-part $f }
  if {!$f} { return 0 }
  set s [expr $s + $f]; return $s 
}


####################################################
# spamscan:time
####################################################
bind time -|- "* * * * *" spamscan:time
proc spamscan:time { n h d m y } {
  global spamscandb; set t [clock seconds]
  foreach e [array names spamscandb] {
    set s [lindex [split $spamscandb($e)] 0]
    if {$s == ""} { unset spamscandb($e); continue }
    if {![string is digit $s]} { continue }
    if {[expr $t - $s] < "600"} { continue }
    unset spamscandb($e)
  }
  foreach c [channels] { spamscan:unlock $c }
}


####################################################
# spamscan:length
####################################################
proc spamscan:length { c t } {
  set f [channel get $c spamscan-length]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-length $f }
  if {!$f} { return 0 }
  set l [string length $t]; set s 0.0; if {$l < 20} { return $s } 
  set s [expr $s + ($l.0 / 120) * $f]; return $s
}


####################################################
# spamscan:short
####################################################
proc spamscan:short { c t } {
  set f [channel get $c spamscan-short]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-short $f }
  if {!$f} { return 0 }
  set l [string length $t]; set s 0.0; if {$l > 20} { return $s } 
  set s [expr $s + $f]; return $s
}


####################################################
# spamscan:codes
# bold \002, color \003, end \017, reverse \026, underline \037
####################################################
proc spamscan:codes { c t } {
  set f [channel get $c spamscan-codes]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-codes $f }
  if {!$f} { return 0 }
  set s 0.0; set p "(?:\\002|\\003|\\017|\\026|\\037)"; set z [regexp -all -- $p $t]
  if {$z == 0} { return $s }
# number.of.codes * setting
  set s [expr $s + $z.0 * $f]; return $s
}


####################################################
# spamscan:caps
####################################################
proc spamscan:caps { c t } {
  set f [channel get $c spamscan-caps]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-caps $f }
  if {!$f} { return 0 }
  set s 0.0; set l [string length $t]; set n [lindex [split $t ",;:"] 0]
#  if {$l < 10} { return $s }
  if {[onchan $n $c]} { set t [string range $t [string length $n] end] }
  set z [regexp -all -- {[A-Z]} $t]
  if {$z == 0} { return $s }
# caps / 10 * %caps * setting
  set s [expr $s + ($z.0 / 10.0) * ($z.0 / $l.0) * $f]
  if {$s < 0} { return 0 }
  return $s
}


####################################################
# spamscan:highlight
####################################################
proc spamscan:highlight { c t } {
  set s 0.0; set f [channel get $c spamscan-highlight]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-highlight $f }
  if {!$f} { return 0 }
  set h -4; set t [stripcodes bcru $t]
  regsub -all {[^a-zA-Z0-9\-\^\[\]\{\}\|`_\\]} $t " " t; set t [split $t]
  foreach n $t { if {[onchan $n $c] && ![onchansplit $n $c]} { incr h 1 } }
  if {[llength $t] == 1 && $h == -3} { return [expr $s + $f] }
  if {$h <= 0} { return $s }
  set s [expr $s + $h.0 * $f]; return $s
}


####################################################
# spamscan:nonalphanum
####################################################
proc spamscan:nonalphanum { c t } {
  set s 0.0; set f [channel get $c spamscan-nonalphanum]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-nonalphanum $f }
  if {!$f} { return 0 }
  set t [stripcodes bcru $t]; set n [lindex [split $t ",;:"] 0]
  if {[onchan $n $c]} { set t [string range $t [string length $n] end] }
  set l [string length $t]; set z [regexp -all -- {[^a-zA-Z0-9]} $t]
  if {$l < 1} { return $s }
# nonalphanum / 10 * %nonalphanum * setting
  set s [expr $s + ($z.0 / 10.0) * ($z.0 / $l.0) * $f]
  if {$s < 0} { return 0 }
  return $s
}


####################################################
# spamscan:adv
####################################################
proc spamscan:adv { c t } {
  set s 0.0; set f [channel get $c spamscan-adv]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-adv $f }
  if {!$f} { return 0 }
  set t [stripcodes bcru $t]
# www. http: #chan
  if {![regexp {(?:www\.|https?\:|\#)} $t]} { return $s }
  set s [expr $s + $f]; return $s
}


####################################################
# spamscan:repeat
####################################################
proc spamscan:repeat { c u t } {
  set f [channel get $c spamscan-userrepeat]; set g [channel get $c spamscan-chanrepeat]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-userrepeat $f }
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $g]} { set g 0; channel set $c spamscan-chanrepeat $g }
  if {$f == 0 && $g == 0} { return 0 }
  global spamscandb; set s 0.0; set n [clock seconds]; set l [string length $t]
  set u [string trimleft [string tolower $u] ~]
  if {[string length $u] > 32} { set u [md5 $u] }
# md5
  if {[string length $c] > 32} { set c [md5 $c] }
  set t [stripcodes bcru $t]; set t [string tolower $t]; regsub -all {[^a-z0-9]} $t "" t
  set t [md5 $t]; set a 0; set b 0
  for {set p 30} {$p > 0} {incr p -1} {
    if {[info exists spamscandb($c,repeat,$p)]} {
      set m [split $spamscandb($c,repeat,$p)]
      set x [lindex $m 1] ; set y [lindex $m 2] ; set m [lindex $m 0]
      if {$t == $y} {
        if {$u == $x} {
          if {[expr $n - $m] < "180"} { incr a }
        } else { if {[expr $n - $m] < "60"} { incr b } }
      }
      if {$p < 30} { set spamscandb($c,repeat,[expr $p +1]) "$m $x $y" }
    }
  }
  set spamscandb($c,repeat,1) "$n $u $t"
  if {$l < 10} { incr a -2; incr b -2 } ; if {$a < 0} { set a 0 } ; if {$b < 0} { set b 0 }
  set s [expr $s + $f * $a.0]; set s [expr $s + $g * $b.0]
  return $s
}


####################################################
# spamscan:charrepeat
####################################################
proc spamscan:charrepeat { c t } {
  set s 0.0; set f [channel get $c spamscan-charrepeat]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-charrepeat $f }
  if {!$f} { return 0 }
  set t [stripcodes bcru $t]; set n [lindex [split $t ",;:"] 0]
  if {[onchan $n $c]} { set t [string range $t [string length $n] end] }
  set l [string length $t]
# groups of 4 or more of the same chars
  regsub -all {([\w])\1{3,}} $t "" t
  regsub -all {([^\w])\1{3,}} $t "" t
  set l [expr $l - [string length $t]]
# each group of 4 repeated consecutive chars * setting
  set s [expr $s + ($l / 4.0) * $f]
  return $s
}


#move this into spamscan:repeat
#adjust text for repeat detection?
#take into account charrepeat?
####################################################
# spamscan:textrepeat
####################################################
# what happens here?
# we loop the string
# start at char 1, and take the first 3 chars (so char 1 2 3)
#  then check how many times this substring occurs within the string, only 1 time then move up 1 char (so chars 2 3 4)
#   if substring (char 1 2 3) occurs more than 1 time in the string, we expand our substring to 4 chars (char 1 2 3 4)
#   we keep doing that, until we have reached a peak (taking another char makes the substring occurs less than without)
#   we remove all occurences of this substring, and turn the string into a list to avoid 'false positives'
# why? well, imagine this string "moo abmoo c abc"
# we remove "moo " twice, leaving us with string "abcd abcd"
# then we have fabricated another match, substring "abcd" occurs twice now in the resulting string
# but not in the original string
# so this happens "moo abcmoo d abcd" -> "{}{abc}{}{ d abcd}" -> "{}{}{}{ d }{}{d}"
# at the end we compare the length of the original string
# with the length of the resulting string and calculate a penalty
# which is in this example 17 vs 4 ({} indicates a list element)
# we create a list by replacing the substrings with \n
proc spamscan:textrepeat { c x } {
  set s 0.0; set f [channel get $c spamscan-textrepeat]
  if {![regexp {^[0-9]+\.{0,1}[0-9]*$} $f]} { set f 0; channel set $c spamscan-textrepeat $f }
  if {!$f} { return 0 }
  set x [stripcodes bcru $x]
  if {$x == ""} { return $s }
# regsub -all {[^a-zA-Z0-9]} $x " " x
  set g 0; set p 0; set y "$x "; set z ""; set l [string length $y]
  while {[expr $p +2] < $l} {
    set q 2
    set t [string range $y $p [expr $p + $q]]
    regsub -all {[][\\^$.|?*+(){}]} $t {\\\0} t

    set h 0
    foreach line [split $y \n] { incr h [regexp -all -- $t $line] }
    if {$h > 1} {
      set nt [string range $y $p [expr $p + $q +1]]
      regsub -all {[][\\^$.|?*+(){}]} $nt {\\\0} nt
      set nh 0
      foreach line [split $y \n] { incr nh [regexp -all -- $nt $line] }
      while {$h <= $nh} {
        if {[string match "*\n*" $nt]} { break }
        set t $nt; set h $nh; set nt [string range $y $p [expr $p + $q +1]]
        regsub -all {[][\\^$.|?*+(){}]} $nt {\\\0} nt
        set nh 0
        foreach line [split $y \n] { incr nh [regexp -all -- $nt $line] }
        incr q 1
      }
      regsub -all -- $t $y \n y
      set l [string length $y]
      append z $t
      incr g [expr $h -1]
    }
    incr p 1
    while {[string match "*\n*" [string range $y $p [expr $p + 2]]]} { incr p 1 }
  }
  set l2 [string length $x].0
  set l [expr $l2 - ($l.0 + [string length $z].0)]
  set g [expr $g.0 * $l / $l2]
  set s [expr $s + $g * $f]
  if {$s < 0} { return 0 }
  return $s
}


#17:3412 <+Msmo> err :: if {[regexp {([^ .!?]{4,})(?=(?:.{0,8}\1){5})} $line]} { ..... <--- at least 4 chars (except space . ! ?), repeated 6 times, with at most 8 chars between each other


set scriptdb(spamscan) {
  "deals with spam. spamscan keeps a time variable associated with every user which is never lower than the current time. penalty is awarded based on events from the user: join, msg, action, chan ctcp, chan notice, nick change, part."
  "penalties are also awarded based on the message itself by looking at things like: length, caps, control codes, non alphanum chars, advertise, highlights, repeated consecutive chars, user/chan repeats, repeats of substrings."
  "once the user's time variable gets too far ahead due to penalties, the user is warned/kicked/banned. when the penalty for the channel as a whole gets too far ahead, the channel is temporary locked. everything is completely configurable with channel settings."
}

