####################################################
# by wiebe @ QuakeNet
#
# script can use: bannick.tcl ircconsole.tcl newban.tcl newchanban.tcl
#
####################################################

#all - all

#pub - all chan
#msg - chan msg
#action - chan action
#ctcp - chan ctcp
#notice - chan notice
#quit - quit msg
#part - part msg

#private - all private
#pmsg - private msg
#paction - private action
#pctcp - private ctcp
#pnotice - private notice

# ai, reply to pm, not opped, relay over botnet


setudef flag censor
setudef flag censor-pm
setudef flag censor-op
setudef flag censor-voice
#setudef flag censor-ai


####################################################
# censor:help:msgm
####################################################
bind msgm fvlomn|fvlomn "help censor" censor:help:msgm
proc censor:help:msgm { n u h t } {
  if {[matchattr $h bkZ]} { return 0 }
  lappend o "censor: usage censor add|del|list|help"
  if {[string equal [info procs cnotice] ""]} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "censor: " } }
  putcmdlog "($n!$u) !$h! help censor"
  return 1
}


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

####################################################
# censor:notc
####################################################
bind notc -|- * censor:notc
proc censor:notc { n u h t d } {
  set d [string trimleft $d @+]
  if {[validchan $d]} { censor:pub $n $u $h $d $t notice
  } elseif {[isbotnick $d]} { censor:prv $n $u $h $t pnotice }
  return 0
}

####################################################
# censor:ctcp
####################################################
bind ctcp -|- * censor:ctcp; bind ctcr -|- * censor:ctcp
proc censor:ctcp { n u h d k t } {
  set d [string trimleft $d @+]
  if {[string equal -nocase $k "action"]} { set x action
  } else { set t "$k $t"; set x ctcp }
  if {[validchan $d]} { censor:pub $n $u $h $d $t $x
  } elseif {[isbotnick $d]} { censor:prv $n $u $h $t p$x }
  return 0
}

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

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

####################################################
# censor:msgm
####################################################
bind msgm -|- * censor:msgm
proc censor:msgm { n u h t } { censor:prv $n $u $h $t pmsg; return 0 }


####################################################
# censor:pub
####################################################
proc censor:pub { n u h c t x } {
  if {![validchan $c]} { return 0 }
  if {[matchban $n!$u]} { return 0 }
  if {![channel get $c censor]} { return 0 }
  if {[matchattr $h fvlomn|fvlomn $c] && ![matchattr $h Z]} { return 0 }
  if {[matchattr $h k|k $c]} { return 0 }
  if {[channel get $c censor-voice]} {
    if {![channel get $c censor-op]} { channel set $c +censor-op }
    if {[isvoice $n $c]} { return 0 }
  }
  if {[isop $n $c] && [channel get $c censor-op]} { return 0 }
  set t [stripcodes bcru $t]; set d "\004"; regsub -all {[ ]+} $t "$d" t
  foreach a [split "ban chanban kick warn"] {
    foreach y [split "all pub $x"] {
      set m [join [split "\$CENSOR $y $a $t *!*@*"] $d]
      if {![matchexempt $m $c]} { continue }
      set r [censor:findexempt $m $c]
      if {[string equal $a "ban"]} { censor:ban $n $u $c $r }
      if {[string equal $a "chanban"]} { censor:chanban $n $u $c $r }
      if {[string equal $a "kick"]} { censor:kick $n $u $c $r }
      if {[string equal $a "warn"]} { censor:warn $n $u $c $r }
      return 0
    }
  }
}


####################################################
# censor:prv
####################################################
proc censor:prv { n u h t x } {
  if {[matchban $n!$u]} { return 0 }
  if {[matchattr $h k]} { return 0 }
  if {![onchan $n]} { return 0 }
  if {[matchattr $h fvlomn]} { return 0 }
  set o 0
  foreach c [channels] { if {[matchattr $h -|fvlomn $c]} { set o 1; break } }
  if {[matchattr $h Z]} { set o 0 }
  if {$o} { return 0 }
  set t [stripcodes bcru $t]; set d "\004"; regsub -all {[ ]+} $t "$d" t
  foreach chan [channels] {
    if {![onchan $n $c]} { continue }
    if {![channel get $c censor]} { continue }
    if {![channel get $c censor-pm]} { continue }

    if {[channel get $c censor-voice]} {
      if {![channel get $c censor-op]} { channel set $c +censor-op }
      if {[isvoice $n $c]} { continue }
    }
    if {[isop $n $c] && [channel get $c censor-op]} { continue }

    foreach a [split "ban chanban kick warn"] {
      foreach y [split "all private $x"] {
        set m [join [split "\$CENSOR $y $a $t *!*@*"] $char]
        if {![matchexempt $m $c]} { continue }
        set r [censor:findexempt $m $c]
        if {[string equal $a "ban"]} { censor:ban $n $u $c $r ; return 0 }
        if {[string equal $a "chanban"]} { censor:chanban $n $u $c $r }
        if {[string equal $a "kick"]} { censor:kick $n $u $c $r }
        if {[string equal $a "warn"]} { censor:warn $n $u $c $r }
        return 0
      }
    }
  }
}


####################################################
# censor:findexempt
####################################################
proc censor:findexempt { m c } {
  if {[matchexempt $m]} { set f [exemptlist]
  } elseif {[matchexempt $m $c]} { set f [exemptlist $c] }
  foreach e $f {
    set b [lindex $e 0]
    regsub -all {[][\\]} $b {\\\0} b
    if {![string match -nocase $b $m]} { continue }
    set r [lindex $e 1]
    return $r
  }
}


####################################################
# censor:ban
####################################################
proc censor:ban { n u c r } {
  if {[matchban $n!$u]} { return 0 }
  set m "*!$u"; set d censor.tcl
  if {[string match "~*@*" $u] || [string match "*.users.quakenet.org" $u]} {
    set m "*!*@[lindex [split $u @] 1]"
  }
  set r [split $r]; set l [lindex $r 0]; set r [join [lrange $r 1 end]]
  if {[string equal $r ""]} { set r "You are violating channel rules." }
  if {[info procs bannick:ban] != ""} {
    bannick:ban $n global $d $r $l
  } elseif {[info procs newban:ban] != ""} {
    if {![newban:ban $m $d $r $l]} { return 0 }
  } else { newban $m $d $r $l }
  set t "ban $n ($u) ${l}m :$r"
  set h "ban $u"; set h [string tolower $h]; set h [md5 $h]
  if {[info procs ircconsole] != ""} { ircconsole $c i CENSOR $t $h }
}


####################################################
# censor:chanban
####################################################
proc censor:chanban { n u c r } {
  if {[matchban $n!$u $c]} { return 0 }
  set m "*!$u"; set d censor.tcl
  if {[string match "~*@*" $u] || [string match "*.users.quakenet.org" $u]} { set m "*!*@[lindex [split $u @] 1]" }
  set r [split $r]; set l [lindex $r 0]; set r [join [lrange $r 1 end]]
  if {[string equal $r ""]} { set r "You are violating channel rules." }
  if {[info procs bannick:ban] != ""} {
    bannick:ban $n $c $d $r $l
  } elseif {[info procs newchanban:ban] != ""} {
    if {![newchanban:ban $c $m $d $r $l]} { return 0 }
  } else { newchanban $c $m $d $r $l }
  set t "ban $n ($u) ${l}m :$r"
  set h "ban $u"; set h [string tolower $h]; set h [md5 $h]
  if {[info procs ircconsole] != ""} { ircconsole $c j CENSOR $t $h }
}


####################################################
# censor:kick
####################################################
proc censor:kick { n u c r } {
  set x $r
  if {[string match "@*" $r]} { set r "" }
  if {[string equal $r ""]} { set r "You are violating channel rules." }
  if {![botisop $c] && ![botishalfop $c]} { return 0 }
  if {![botisop $c] && ([isop $n $c] || [ishalfop $n $c])} { return 0 }
  if {![censor:whichbot $c $n $r]} { return 0 }
  putkick $c $n $r
  set t "kick $n ($u) :$x"; set h "kick $u"; set h [md5 [string tolower $h]]
  if {![string equal [info procs ircconsole] ""]} { ircconsole $c k CENSOR $t $h }
}


####################################################
# censor:warn
####################################################
proc censor:warn { n u c r } {
  set x $r
  if {[string match "@*" $r]} { set r "" }
  if {[string equal $r ""]} { set r "You are violating channel rules." }
  if {![botisop $c] && ![botishalfop $c]} { return 0 }
  if {![botisop $c] && ([isop $n $c] || [ishalfop $n $c])} { return 0 }
  if {![censor:whichbot $c $n $r]} { return 0 }
  set m "\[$c\]: $r"
  if {![string equal [info procs cnotice] ""]} { cnotice $n $m puthelp
  } else { puthelp "NOTICE $n :$m" }
  set t "warn $n ($u) :$x"; set h "warn $u"; set h [md5 [string tolower $h]]
  if {![string equal [info procs ircconsole] ""]} { ircconsole $c l CENSOR $t $h }
}


####################################################
# censor:whichbot
# returns 1 if this bot should respond, 0 otherwise
####################################################
proc censor:whichbot { c n t } {
  if {[string equal [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 censor]} { continue }
    set h [nick2hand $b $c]
    if {![string equal [lsearch -exact $r $h] -1]} { continue }
    lappend r $h
  }
  set r [lrange [lsort $r] 0 15]; set x [expr [llength $r] -1]
  if {[string equal $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 {[string equal $b ${botnet-nick}]} { return 1 } else { return 0 }
}


####################################################
# censor:msg
####################################################
bind msg omn|omn censor censor:msg
proc censor: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 {[string equal $c ""]} { lappend o "censor: usage censor add|del|list|help"
  } elseif {[string equal $c "add"]} { set o [censor:add $h $t]
  } elseif {[string equal $c "list"]} { set o [censor:list $h $t]
  } elseif {[string equal $c "del"]} { set o [censor:del $h $t]
  } elseif {[string equal $c "help"]} { set o [censor:help $t]
  } else { lappend o "censor: usage censor add|del|list|help" }
  if {[string equal [info procs cnotice] ""]} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "censor: " } }
  return 1
}


####################################################
# censor:dcc
####################################################
bind dcc -|- censor censor:dcc
proc censor:dcc { h i t } {
  set t [split $t]; set c [string tolower [lindex $t 0]]; set t [join [lrange $t 1 end]]
  if {[string equal $c ""]} { lappend o "censor: usage censor add|del|list|help"
  } elseif {[string equal $c "add"]} { set o [censor:add $h $t]
  } elseif {[string equal $c "list"]} { set o [censor:list $h $t]
  } elseif {[string equal $c "del"]} { set o [censor:del $h $t]
  } elseif {[string equal $c "help"]} { set o [censor:help $t]
  } else { lappend o "censor: usage censor add|del|list|help" }
  foreach l $o { putidx $i $l }
  return 1
}


####################################################
# censor:add
####################################################
# handle <chan> <type> <action> <duration> "<pattern>" [lifetime] <reason>
proc censor:add { h z } {
  set z [split $z]
  set c [lindex $z 0]
  set t [string tolower [lindex $z 1]]
  set a [string tolower [lindex $z 2]]
  set d [lindex $z 3]
  if {![string equal $d ""]} { set d [censor:duration $d] }
  set p [join [lrange $z 4 end]]
  set l ""
  if {[string match \"*\"* $p]} {
    set p [string map {"\" " \n} $p]
    set p [split $p \n]
    set r [join [lrange $p 1 end] "\" "]
    set p [string range [lindex $p 0] 1 end]
  } else {
    set p [split $p]
    set r [join [lrange $p 1 end]]
    set p [lindex $p 0]
  }
  if {[string equal $a "ban"] || [string equal $a "chanban"]} {
    set r [split $r]
    set l [censor:duration [lindex $r 0]]
    set r [join [lrange $r 1 end]]
  }
  set m [join [split "\$CENSOR $t $a $p *!*@*"] "\004"]
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  if {[string equal -nocase $c "global"]} { set c [string tolower $c] }

# no chan
  if {[string equal $c ""]} {
    lappend output "censor: no chan specified"
    lappend output "censor: add <chan> <type> <action> <duration> \"<pattern>\" \[<lifetime>\] <reason>"
    lappend output "censor: use \"global\" for chan to add global entries"

# invalid chan
  } elseif {![validchan $c] && ![string equal $c "global"]} {
    lappend output "censor: no access or unknown channel $c"
    lappend output "censor: add <chan> <type> <action> <duration> \"<pattern>\" \[<lifetime>\] <reason>"

# no access global
  } elseif {[string equal $c "global"] && ![matchattr $h omn]} {
    lappend output "censor: no access to add global censor pattern"

# no access chan
  } elseif {![string equal $c "global"] && ![matchattr $h omn|omn $c]} {
    lappend output "censor: no access or unknown channel $c"
    lappend output "censor: add <chan> <type> <action> <duration> \"<pattern>\" \[<lifetime>\] <reason>"

# invalid type
  } elseif {[string equal [lsearch -exact "all pub msg action ctcp notice quit part private pmsg paction pctcp pnotice" $t] "-1"]} {
    if {[string equal $t ""]} { lappend output "censor: no type specified"
    } else { lappend output "censor: invalid type $t" }
    lappend output "censor: add <chan> <type> <action> <duration> \"<pattern>\" \[<lifetime>\] <reason>"
    lappend output "censor: valid types are: all pub msg action ctcp notice quit part private pmsg paction pctcp pnotice"

# invalid action
  } elseif {[string equal [lsearch -exact "ban chanban kick warn" $a] "-1"]} {
    if {[string equal $a ""]} { lappend output "censor: no action specified"
    } else { lappend output "censor: invalid action $a" }
    lappend output "censor: add <chan> <type> <action> <duration> \"<pattern>\" \[<lifetime>\] <reason>"
    lappend output "censor: valid actions are: ban chanban kick warn"

# cannot warn or kick (except for hostchanges) users that quit
  } elseif {[string equal $t "quit"] && [string equal [lsearch -exact "kick warn" $a] "-1"]} {
    lappend output "censor: invalid combination of action $a and type $t"

# cannot kick users that part, allow ban/chanban/warn
  } elseif {[string equal $t "part"] && [string equal $a "kick"]} {
    lappend output "censor: invalid combination of action $a and type $t"

# no access to use ban
  } elseif {[string equal $a "ban"] && ![matchattr $h omn]} {
    lappend output "censor: no access to use action ban"

# duration
  } elseif {[string equal $d ""] || $d < 0} {
    if {[string equal $d ""]} { lappend output "censor: no duration specified"
    } else { lappend output "censor: invalid duration" }
    lappend output "censor: add <chan> <type> <action> <duration> \"<pattern>\" \[<lifetime>\] <reason>"
    lappend output "censor: valid format is XyXmXwXdXhXnXs where y=year, m=month, w=week, d=day, h=hour, n=minute, s=second. Duration 0 is perm, minimal duration is 5 minutes."

# no pattern
  } elseif {[string equal $p ""]} {
    lappend output "censor: no pattern specified"
    lappend output "censor: add <chan> <type> <action> <duration> \"<pattern>\" \[<lifetime>\] <reason>"

# no or invalid lifetime
  } elseif {![string equal [lsearch -exact "ban chanban" $a] "-1"] && $l < 0} {
    lappend output "censor: invalid lifetime $l"
    lappend output "censor: add <chan> <type> <action> <duration> \"<pattern>\" \[<lifetime>\] <reason>"
    lappend output "censor: valid format is XyXmXwXdXhXnXs where y=year, m=month, w=week, d=day, h=hour, n=minute, s=second. Duration 0 is perm, minimal duration is 5 minutes."

# no reason
  } elseif {[string equal $r ""]} {
    lappend output "censor: no reason specified"
    lappend output "censor: add <chan> <type> <action> <duration> \"<pattern>\" \[<lifetime>\] <reason>"

# add global ban
  } elseif {[string equal $c "global"]} {
    if {[string equal $d 0]} { set d2 perm } else { set d2 [censor:ts $d] }
    if {![string equal [lsearch -exact "ban chanban" $a] "-1"]} {
      if {[string equal $l 0]} { set l2 perm } else { set l2 [censor:ts $l] }
      if {($l > 1440 || [string equal $l 0]) && ![matchattr $h mn]} {
        set l 1440
        set l2 [censor:ts $l]
        lappend output "censor: using max allowed lifetime of $l2"
      }

      lappend output "censor: added global pattern=\"$p\" type=$t action=$a duration=$d2  lifetime=$l2 reason=$r"
      putloglev c * "CENSOR: $h ADD $c $t $a $d2 $p $l2 $r"
      if {![string equal [info procs ircconsole] ""]} {
        ircconsole * S CENSOR "$h ADD $t $a $d2 \"$p\" $l2 :$r"
      }
    } else {
      lappend output "censor: added global pattern=\"$p\" type=$t action=$a duration=$d2 reason=$r"
      putloglev c * "CENSOR: $h ADD $c $t $a $d2 $p $r"
      if {![string equal [info procs ircconsole] ""]} { 
        ircconsole * S CENSOR "$h ADD $t $a $d2 \"$p\" :$r"
      }
    }

    lappend output "censor: added pattern=\"$p\" type=$t action=$a duration=$d2 reason=$r"
    if {![string equal $l ""]} { set reason "$l $r" }

    if {![string equal $l ""]} { set r "$l $r" }
    set x ""; set y ""
    foreach x [channels] {
      if {[channel get $x dynamicexempts]} { continue }
      lappend y $x; channel set $x +dynamicexempts
    }
    newexempt $m $h $r $d
    foreach x $y { channel set $x -dynamicexempts }

# add chan exempt
  } else {
    if {[string equal $d 0]} { set d2 perm } else { set d2 [censor:ts $d] }
    if {![string equal [lsearch -exact "ban chanban" $a] "-1"]} {
      if {[string equal $l 0]} { set l2 perm } else { set l2 [censor:ts $l] }
      if {($l > 1440 || [string equal $l 0]) && ![matchattr $h mn|mn $c]} {
        set l 1440
        set l2 [censor:ts $l]
        lappend output "censor: using max allowed lifetime of $l2"
      }

      lappend output "censor: added pattern=\"$p\" type=$t action=$a chan=$c duration=$d2  lifetime=$l2 reason=$r"
      putloglev c $c "CENSOR: $h ADD $c $t $a $d2 $p $l2 $r"
      if {![string equal [info procs ircconsole] ""]} { 
        ircconsole $c O CENSOR "$h ADD $t $a $d2 \"$p\" $l2 :$r"
      }
    } else {
      lappend output "censor: added pattern=\"$p\" type=$t action=$a chan=$c duration=$d2 reason=$r"
      putloglev c $c "CENSOR: $h ADD $c $t $a $d2 $p $r"
      if {![string equal [info procs ircconsole] ""]} { 
        ircconsole $c O CENSOR "$h ADD $t $a $d2 \"$p\" :$r"
      }
    }

    if {![string equal $l ""]} { set r "$l $r" }
    set e 0
    if {![channel get $c dynamicexempts]} { set e 1; channel set $c +dynamicexempts }
    newchanexempt $c $m $h $r $d
    if {$e} { channel set $c -dynamicexempts }
  }
  return $output
}


####################################################
# censor:list
####################################################
# handle <chan> [<type>] [<action>] ["<pattern>"] [<reason>] [<range>]
proc censor:list { h text } {
# check chan and access
# $CENSOR type action pattern *!*@*
  set max 3
  set text [split $text]

  set range [lindex $text end]
  if {[string is digit $range]} {
    set text [lrange $text 0 end-1]
  } else { set range 1 }

  set c [lindex $text 0]
  set t [string tolower [lindex $text 1]]
  set a [string tolower [lindex $text 2]]
  set p [join [lrange $text 3 end]]
  if {[string match \"*\"* $p]} {
    set p [string map {"\" " \n} $p]
    set p [split $p \n]
    set r [join [lrange $p 1 end] "\" "]
    set p [string range [lindex $p 0] 1 end]
  } else {
    set p [split $p]
    set r [join [lrange $p 1 end]]
    set p [lindex $p 0]
  }
  if {[string equal $a ""]} { set a * }
  if {[string equal $p ""]} { set p * }
  if {[string equal $r ""]} { set r * }
  set m "\$CENSOR $t $a $p *!*@*"
  regsub -all {[][\\]} $m {\\\0} m
  regsub -all {[][\\]} $r {\\\0} r
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  if {[string equal -nocase $c "global"]} { set c [string tolower $c] }

  set g 0
  if {[matchattr $h omn]} { set g 1 }
  if {[string equal $c "global"] && ![matchattr $h omn]} {
    foreach chan [channels] {
      if {![matchattr $h -|omn $chan]} { continue }
      set g 1
      break
    }
  }

# no chan
  if {[string equal $c ""]} {
    lappend output "censor: no chan specified"
    lappend output "censor: list <chan> \[<type> <action> \"<pattern>\" <reason>\] \[<range>\]"
    lappend output "censor: use \"global\" for chan to list global entries"

# invalid chan
  } elseif {![string equal $c "global"] && ![validchan $c]} {
    lappend output "censor: no access or unknown channel $c"
    lappend output "censor: list <chan> \[<type> <action> \"<pattern>\" <reason>\] \[<range>\]"

# no access global
  } elseif {[string equal $c "global"] && ![string equal $g "1"]} {
    lappend output "censor: no access to list global censor pattern"
    lappend output "censor: list <chan> \[<type> <action> \"<pattern>\" <reason>\] \[<range>\]"

# no access
  } elseif {![string equal $c "global"] && ![matchattr $h omn|omn $c]} {
    lappend output "censor: no access or unknown channel $c"
    lappend output "censor: list <chan> \[<type> <action> \"<pattern>\" <reason>\] \[<range>\]"

# nothing else
  } elseif {[string equal $t ""]} {
    lappend output "censor: list <chan> \[<type> <action> \"<pattern>\" <reason>\] \[<range>\]"

# list
  } else {
    if {[string equal $range "0"]} {
      set range2 ""
    } else {
      set range2 " - range [expr ($range -1) * $max +1]-[expr ($range -1) * $max + $max]"
    }
    if {[string equal $c "global"]} {
      set exempts [exemptlist]
      lappend output "censor: listing global entries$range2"
    } else {
      set exempts [exemptlist $c]
      lappend output "censor: listing entries for chan $c$range2"
    }
    set x 0
    set y 0
    set now [clock seconds]
    foreach exempt $exempts {
      set ho [lindex $exempt 0]
      set ho [join [split $ho \004]]
# not a censor exempt
      if {![string match -nocase "\$CENSOR *" $ho]} { continue }
      incr x 1
# no match on type action pattern
      if {![string match -nocase $m $ho]} { continue }
      incr y 1
      set ho [split $ho]
      set a2 "[string tolower [lindex $ho 2]]"
      set l2 ""
      set co [lindex $exempt 1]
      if {[string equal $a2 "ban"] || [string equal $a2 "chanban"]} {
        set co [split $co]
        set l2 [lindex $co 0]
        set l2 "ban for [censor:ts $l2]"
        set a2 "$a2   $l2"
        set co [join [lrange $co 1 end]]
      }
# no match on reason
      if {![string match -nocase $r $co]} { continue }

# right range
      if {$y < [expr ($range -1) * $max +1]} { continue }
      if {$y > [expr ($range -1) * $max + $max]} { continue }

      set ex [censor:ts [expr ([lindex $exempt 2] - $now) / 60]]
      if {[string equal $ex "0"]} { set ex "expires never" } else { set ex "expires in $ex" }
      set ta "added [censor:ts [expr ($now - [lindex $exempt 3]) / 60]] ago"
      set cr [lindex $exempt 5]
      set t2 "[string tolower [lindex $ho 1]]"
      set p2 "pattern \"[join [lrange $ho 3 end-1]]\""
      set r2 "$co"
      lappend output "censor: #$x   $t2   $a2   $p2   $ta   $ex   $cr: $r2"
    }
    if {![string equal $max 0] && $y > $max && $y < $x} {
      lappend output "censor: use range [expr $range +1] to list next $max entries"
    }
    lappend output "censor: $y / $x entries match type=$t action=$a pattern=\"$p\" reason=$r"
  }
  return $output
}


####################################################
# censor:del
####################################################
# handle <chan> N1 N2 N3 ... NN [<hash>]
proc censor:del { h text } {
  set text [split $text]
  set c [lindex $text 0]
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  if {[string equal -nocase $c "global"]} { set c [string tolower $c] }
  set hash [lindex $text end]
  set num [lrange $text 1 end]
  set md5i [string range [md5 "[string tolower $c] [lrange $text 1 end-1]"] 0 5]
  set md5o [string range [md5 "[string tolower $c] [lrange $text 1 end]"] 0 5]
  if {[string equal $hash $md5i]} { set num [lrange $text 1 end-1] }


  if {[string equal $c "global"]} {
    set exempts [exemptlist]
  } elseif {[validchan $c]} {
    set exempts [exemptlist $c]
  }
  if {[string equal $c "global"] || [validchan $c]} {
    set max [llength $exempts]
    set err ""
    set nums ""
    foreach n $num {
# not a digit
      if {![string is digit $n]} {
        set err "censor: $n is not a digit"
        break
# not a censor exempt
      } elseif {![string match -nocase "\$CENSOR*" [lindex [lindex $exempts [expr $n -1]] 0]]} {
        set err "censor: $n is not a censor pattern"
        break
      }
      if {[string equal [lsearch -exact $nums $n] "-1"]} { lappend nums $n }
    }
    set nums [join [lsort -decreasing -dictionary $nums]]
  }


# no chan
  if {[string equal $c ""]} {
    lappend output "censor: no chan specified"
    lappend output "censor: del <chan> <N1> \[<N2> <N3> ...\]"
    lappend output "censor: use \"global\" for chan to del global entries"

# invalid chan
  } elseif {![string equal $c "global"] && ![validchan $c]} {
    lappend output "censor: no access or unknown channel $c"
    lappend output "censor: del <chan> <N1> \[<N2> <N3> ...\]"

# no access global
  } elseif {[string equal $c "global"] && ![matchattr $h omn]} {
    lappend output "censor: no access to del global censor pattern"
    lappend output "censor: del <chan> <N1> \[<N2> <N3> ...\]"

# no access
  } elseif {![string equal $c "global"] && ![matchattr $h omn|omn $c]} {
    lappend output "censor: no access or unknown channel $c"
    lappend output "censor: del <chan> <N1> \[<N2> <N3> ...\]"

# nothing else
  } elseif {[string equal $num ""]} {
    lappend output "censor: del <chan> <N1> \[<N2> <N3> ...\]"

# error
  } elseif {![string equal $err ""]} {
    lappend output $err

# show 
  } elseif {![string equal $md5i $hash]} {
    lappend output "censor: to remove these patterns, use censor del [join $text] $md5o"

# delete
  } else {
    foreach n $nums {
      set exempt [lindex $exempts [expr $n -1]]
      set m [lindex $exempt 0]

      set ho [lindex $exempt 0]
      set ho [split $ho \004]
      set ty [lindex $ho 1]
      set ac [lindex $ho 2]
      set pa "\"[join [lrange $ho 3 end-1]]\""
      set co "\"[lindex $exempt 1]\""
      set et [lindex $exempt 2]
      set ta [lindex $exempt 3]
      set cr [lindex $exempt 5]
      lappend output "censor: #$n   $ty   $ac   $pa   $co"

      if {[string equal $c "global"]} {
        putloglev c * "CENSOR: $h DEL $c $ty $ac $pa :$co"
        killexempt $m
        if {![string equal [info procs ircconsole] ""]} {
          ircconsole * S CENSOR "$h DEL $ty $ac $pa :$co"
        }
      } else {
        putloglev c $c "CENSOR: $h DEL $c $ty $ac $pa :$co"
        killchanexempt $c $m
        if {![string equal [info procs ircconsole] ""]} {
          ircconsole $c O CENSOR "$h DEL $ty $ac $pa :$co"
        }
      }
    }
    if {[string equal $c "global"]} {
      lappend output "censor: deleted $nums global censor patterns"
    } else {
      lappend output "censor: deleted $nums censor patterns on $c"
    }
  }
  return $output
}


####################################################
# censor:help
####################################################
proc censor:help { t } {
  set c [string tolower [lindex [split $t] 0]]
  if {[string equal $c "general"]} {
    lappend o "censor: censors patterns and warns/kicks/bans users for it, channel settings: \002+censor\002 (enable)   \002+censor-op\002 (ignore opped users)   \002+censor-voice\002 (ignore voiced users)   \002+censor-pm\002 (enable private censors), users with any of fvlomn flags for chan or global are ignored."

  } elseif {[string equal $c "add"]} {
    lappend o "censor: add <chan> <type> <action> <duration> \"<pattern>\" \[<lifetime>\] <reason>"
    lappend o "censor: <type> is the type of message to censor: all=everyting     pub=everything in chan     msg=msg in chan     action=action in chan     ctcp=chan ctcp     notice=chan notice     quit=quit msg     part=part msg     private=everything in private     pmsg=private msg     paction=private action     pctcp=private ctcp    pnotice=private notice"
    lappend o "censor: <action> is the type of action to take: ban=global ban     chanban=chan ban     kick=kick     warn=warn user by notice    <duration> how long the entry should exist     <pattern> is the pattern to censor, enclose in \"\" if it contains spaces"
    lappend o "censor: <lifetime> is only needed when action is ban or chanban, it specifies how long to ban the user     <reason> is the ban/kick/warn message used, prefix it with a @ to make it hidden, users get ban/kick/warn with \"You are violating channel rules.\""
    lappend o "censor: use \"global\" for chan to add global entries"

  } elseif {[string equal $c "list"]} {
    lappend o "censor: list <chan> <type> \[<action> \"<pattern>\" <reason>\] \[<range>\]"
    lappend o "censor: list entries matching the given parameters, when the results exceed the maximum allowed output, use range to view the next set (default 1)"
    lappend o "censor: use \"global\" for chan to list global entries"

  } elseif {[string equal $c "del"]} {
    lappend o "censor: del <chan> <N1> \[<N2> <N3> ...\]"
    lappend o "censor: deletes the given censor entry numbers obtained from censor list"
    lappend o "censor: use \"global\" for chan to del global entries"

  } else {
    lappend o "censor: help add|list|del|general"
    lappend o "censor: shows help in general or for the given subcommand"
  }
  return $o
}


####################################################
# censor:duration
# transforms XyXMXwXdXhXmXs to minutes
####################################################
proc censor:duration { duration } {
  set d 0
  set e 0
  set durating [string tolower $duration]
  set duration [string map "s SECOND n MINUTE h HOUR d DAY w WEEK m MONTH y YEAR" $duration]
  if {[string equal $duration 0]} { return $d }
  foreach part [split $duration] {
    if {[catch {incr d [expr [clock scan $part] - [clock seconds]]}]} { set e 1 }
  }
  if {$d < "300" && $d > "0"} { set d 5 } else { set d [expr $d / 60] }
  if {[string equal $d$e 01]} { set d -1 }
  return $d
}


####################################################
# censor:ts
# give minutes, returns XyXwXhXmXs
####################################################
proc censor:ts { t } {
  if {![string is digit $t]} { return 0 }
  set t [duration [expr $t * 60]]
  set t [string map "seconds s second s minutes m minute m hours h hour h" $t]
  set t [string map "days d day d weeks w week w years y year y" $t]
  return [join [lrange [split $t] 0 1] ""]
}


set ::scriptdb(censor) {
  "censors patterns and warns/kicks/bans users for it, channel settings: +censor (enable)   +censor-op (ignore opped users)   +censor-voice (ignore voiced users) +censor-pm (enable private censors), users with any of fvlomn flags for chan or global are ignored."
}

