####################################################
# by wiebe @ QuakeNet
#
# script provides ban command
#
# script can use:
#  bannick.tcl chase.tcl dnsdb.tcl irc.tcl ircconsole.tcl isupport.tcl newban.tcl
#  newchanban.tcl rule.tcl term.tcl whichchan.tcl whowas.tcl whox.tcl
# script provides info for: script.tcl
#
####################################################

# relay to dnsbans, whox, etc?
# call the appropriate proc to set the type of ban

# ban add (override) from internal banlist, log details?

####################################################
# flags
####################################################
setudef flag public


####################################################
# ban:help:pubm
####################################################
bind pubm lomn|lomn "% ${botnet-nick} help ban" ban:help:pubm
proc ban:help:pubm { n u h c t } {
  if {[matchattr $h bkZ]} { return 0 }
  lappend o "ban: usage ban \[<chan>\] add|del|list|reset|clear|stick|unstick|help"
  ban:cnotice $n $o
  putloglev c $c "help: $n $u $h $c ban"
  return 1
}


####################################################
# ban:help:msg
####################################################
bind msgm lomn|lomn "help ban" ban:help:msgm
proc ban:help:msgm { n u h t } {
  if {[matchattr $h bkZ]} { return 0 }
  lappend o "ban: usage ban \[<chan>\] add|del|list|reset|clear|stick|unstick|help"
  ban:cnotice $n $o
  putcmdlog "($n!$u) !$h! help ban"
  return 1
}


####################################################
# ban:pubm
####################################################
bind pubm lomn|lomn "% ${botnet-nick} ban" ban:pubm
bind pubm lomn|lomn "% ${botnet-nick} ban *" ban:pubm
proc ban:pubm { n u h c t } {
  if {[matchattr $h bkZ]} { return 0 }
  if {![validchan $c]} { return 0 }
  set t [lrange [split $t] 2 end]; set d [string tolower [lindex $t 0]]
  if {[lsearch -exact "add del list reset clear stick unstick help" $d] == -1} {
    set d [string tolower [lindex $t 1]]; if {$d == ""} { set d [string tolower [lindex $t 0]] }
  }
  set t [join $t]
  if {$d == "add"} { set o [ban:add $h $t $c]
  } elseif {$d == "del"} { set o [ban:del $h $t $c]
  } elseif {$d == "list"} { set o [ban:list $h $t $c]
  } elseif {$d == "reset"} { set o [ban:reset $h $t $c]
  } elseif {$d == "clear"} { set o [ban:clear $h $t $c]
  } elseif {$d == "stick"} { set o [ban:stick $h $t $c]
  } elseif {$d == "unstick"} { set o [ban:unstick $h $t $c]
  } elseif {$d == "help"} { set o [ban:help $t]
  } else {
    if {$d != ""} { lappend o "ban: unknown subcommand $d" }
    lappend o "ban: usage ban \[<chan>\] add|del|list|reset|clear|stick|unstick|help"
    lappend o "ban: the current (console) channel is used, unless one is specified. specify 'global' for channel to force the operation for global bans. if there is no current channel and no channel specified, the operation is for global bans."
  }
  ban:cnotice $n $o
  putloglev c $c "ban: $n $u $h $c $t"
  return 1
}


####################################################
# ban:msg
####################################################
bind msg lomn|lomn ban ban:msg
proc ban:msg { n u h t } {
  if {[matchattr $h bkZ]} { return 0 }
  set t [split $t]; set d [string tolower [lindex $t 0]]
  if {[lsearch -exact "add del list reset clear stick unstick help" $d] == -1} {
    set d [string tolower [lindex $t 1]]; if {$d == ""} { set d [string tolower [lindex $t 0]] }
  }
  set t [join $t]
  if {$d == "add"} { set o [ban:add $h $t]
  } elseif {$d == "del"} { set o [ban:del $h $t]
  } elseif {$d == "list"} { set o [ban:list $h $t]
  } elseif {$d == "reset"} { set o [ban:reset $h $t]
  } elseif {$d == "clear"} { set o [ban:clear $h $t]
  } elseif {$d == "stick"} { set o [ban:stick $h $t]
  } elseif {$d == "unstick"} { set o [ban:unstick $h $t]
  } elseif {$d == "help"} { set o [ban:help $t]
  } else {
    if {$d != ""} { lappend o "ban: unknown subcommand $d" }
    lappend o "ban: usage ban \[<chan>\] add|del|list|reset|clear|stick|unstick|help"
    lappend o "ban: the current (console) channel is used, unless one is specified. specify 'global' for channel to force the operation for global bans. if there is no current channel and no channel specified, the operation is for global bans."
  }
  ban:cnotice $n $o
  return 1
}


####################################################
# ban:dcc
####################################################
bind dcc -|- ban ban:dcc
proc ban:dcc { h i t } {
  if {![valididx $i]} { return 0 }
  set c [lindex [split [console $i]] 0]
  if {![validchan $c]} { set c "" }
  set t [split $t]; set d [string tolower [lindex $t 0]]
  if {[lsearch -exact "add del list reset clear stick unstick help" $d] == -1} {
    set d [string tolower [lindex $t 1]]; if {$d == ""} { set d [string tolower [lindex $t 0]] }
  }
  set t [join $t]
  if {$d == "add"} { set o [ban:add $h $t $c]
  } elseif {$d == "del"} { set o [ban:del $h $t $c]
  } elseif {$d == "list"} { set o [ban:list $h $t $c 1]
  } elseif {$d == "reset"} { set o [ban:reset $h $t $c]
  } elseif {$d == "clear"} { set o [ban:clear $h $t $c]
  } elseif {$d == "stick"} { set o [ban:stick $h $t $c]
  } elseif {$d == "unstick"} { set o [ban:unstick $h $t $c]
  } elseif {$d == "help"} { set o [ban:help $t]
  } else {
    if {$d != ""} { lappend o "ban: unknown subcommand $d" }
    lappend o "ban: usage ban \[<chan>\] add|del|list|reset|clear|stick|unstick|help"
    lappend o "ban: the current (console) channel is used, unless one is specified. specify 'global' for channel to force the operation for global bans. if there is no current channel and no channel specified, the operation is for global bans."
  }
  foreach l $o { putidx $i $l }
  return 1
}


####################################################
# ban:add
####################################################
# <hand> "[<chan>] add <mask> [[<mask> .. <mask>] [%<duration>] :]<reason>" [<chan>]
proc ban:add { h t {c ""} } {
  set t [split $t]; set d [lindex $t 0]; set t [lrange $t 1 end]
  if {![string equal -nocase $d "add"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c]} { set c [ban:whichchan $h $c] }
  if {[string equal -nocase $c "global"]} { set c "global" }
  
  if {$c == ""} {
    set d ""
    foreach c [channels] { if {[channel get $c public]} { lappend d $c } }
    if {[llength $d] == 1} { set c [lindex $d 0] } else { set c "" }
  }
  #if {$c == ""} { set c "global" }

# reason
  set p [lsearch -glob $t ":*"]
  if {$p == 0} {
    set t ""; set r ""
  } elseif {$p > -1} {
    set r [string range [join [lrange $t $p end]] 1 end]
    set t [lrange $t 0 [expr $p -1]]
  } else { set r [join [lrange $t 1 end]]; set t [split [lindex $t 0]] }
  if {[string match "\\?\\? ?*" $r] && [info procs term:export] != ""} { 
    set r [term:export $c [join [lrange [split $r] 1 end]]]
  }
  if {[info procs rule:get] != ""} { set r [rule:get $r] }
  if {$r == ""} { set r "You are violating channel rules." }
  
# duration
  set l "240"; set p [lsearch -glob $t \%*]
  if {$p > -1} {
    set l [string range [lindex $t $p] 1 end]
    set t [lreplace $t $p $p]; set l [ban:dur $l]
    if {$l == -1} {
      set l "240"
      lappend o "ban: unable to convert given duration, using default duration ([ban:ts $l])"
    }
  }

  if {$c == "global" && ![matchattr $h omn]} {
    lappend o "ban: no access to add global bans"
  } elseif {$c != "global" && (![validchan $c] || ![matchattr $h lomn|lomn $c])} {
    lappend o "ban: no access or unknown channel $c"
  } elseif {[llength $t] == 0} {
    lappend o "ban: no mask specified"
    lappend o "ban: usage ban \[<chan>\] add <mask> \]\[<mask> .. <mask>\] \[%<duration> :\]\]\[@\]<reason>"
  } else {
    if {($l > 43200 || $l == 0) && [validchan $c] && ![matchattr $h mn|mn $c]} {
      if {[matchattr $h o|o $c]} { set l 43200 } else { set l 720 }
      lappend o "ban: using max allowed duration ([ban:ts $l])"
    }
    set s ""; set f ""; set n ""
    foreach e $t {
      if {$e == ""} { continue }
      if {[ban:validnick $e] && [info procs bannick:ban] != ""} {
        lappend n $e
      } else {
        set e [ban:mask $e]
        if {$c == "global"} {
          if {[info procs newban:ban] == "" || ![newban:ban $e $h $r $l]} { newban $e $h $r $l }
        } else {
          if {[info procs newchanban:ban] == "" || ![newchanban:ban $c $e $h $r $l]} {
            newchanban $c $e $h $r $l
          }
        }
      }
      lappend s $e
    }
    if {$n != ""} { bannick:ban [join $n ,] $c $h $r $l }
    if {$f != ""} { lappend o "ban: failed to ban [join $f "   "]" }
    if {$s != ""} {
      if {$l > 10080} { lappend o "ban: adding bans with excessive long duration!" }
      if {$l == 0} { set l "perm" } else { set l [ban:ts $l] }
      if {$c == "global"} {
        lappend o "ban: banned '[join $s]' $l :$r"
        ban:ircconsole $c 4 BAN "$h ADD '[join $s]' $l :$r"
      } else {
        lappend o "ban: banned on $c '[join $s]' $l :$r"
        ban:ircconsole $c b BAN "$h ADD '[join $s]' $l :$r"
      }
    }
  }
  return $o
}


####################################################
# ban:del
####################################################
proc ban:del { h t {c ""} } {
  set t [split $t]; set d [lindex $t 0]; set t [lrange $t 1 end]
  if {![string equal -nocase $d "del"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c]} { set c [ban:whichchan $h $c] }
  if {[string equal -nocase $c "global"]} { set c "global" }
  
  if {$c == ""} {
    set d ""
    foreach c [channels] { if {[channel get $c public]} { lappend d $c } }
    if {[llength $d] == 1} { set c [lindex $d 0] } else { set c "" }
  }
  #if {$c == ""} { set c "global" }

  if {$c == "global" && ![matchattr $h omn]} {
    lappend o "ban: no access to delete global bans"
  } elseif {$c != "global" && $c != "*" && (![validchan $c] || ![matchattr $h lomn|lomn $c])} {
    lappend o "ban: no access or unknown channel $c"
  } else {
    if {[join $t] == ""} { set o [ban:dellast $h $c]
    } else {
      set r ""; set f ""
      set x [ban:deldigit $c $t]
      set t [lindex $x 0]; lappend r [join [lindex $x 1]]; lappend f [join [lindex $x 2]]
      foreach e [lrange $x 3 end] {
        set e [split $e]; set y [lindex $e 0]; set z [join [lrange $e 1 end]]; lappend g($y) $z
      }
      set x [ban:delnick $h $c $t]
      set t [lindex $x 0]; lappend r [join [lindex $x 1]]; lappend f [join [lindex $x 2]]
      foreach e [lrange $x 3 end] {
        set e [split $e]; set y [lindex $e 0]; set z [join [lrange $e 1 end]]; lappend g($y) $z
      }
      set x [ban:delmask $h $c $t]; lappend r [join [lindex $x 0]]; lappend f [join [lindex $x 1]]
      foreach e [lrange $x 2 end] {
        set e [split $e]; set y [lindex $e 0]; set z [join [lrange $e 1 end]]; lappend g($y) $z
      }
      foreach e [array names g] {
        set z [join [split [join $g($e)]] "  "]
        if {[validchan $e]} { ban:ircconsole $e b BAN "$h DEL $z"
        } elseif {[string equal -nocase $e "global"]} { ban:ircconsole $e 4 BAN "$h DEL $z" }
      }
      set a ""; set b ""
      foreach e $r { if {$e != ""} { lappend a $e } }
      foreach e $f { if {$e != ""} { lappend b $e } }
      set r [join $a]; set f [join $b]
      if {$r != ""} { lappend o "ban: removed bans '$r' ($c)" }
      if {$f != ""} { lappend o "ban: failed to remove bans '$f' ($c)" }
    }
  }
  return $o
}


####################################################
# ban:dellast
####################################################
proc ban:dellast { h c } {
  if {$c == "*"} {
    lappend o "ban: cannot remove youngest ban on all channels"
  } elseif {[string equal -nocase $c "global"]} {
    lappend o "ban: cannot remove youngest global ban"
  } elseif {![validchan $c]} {
    lappend o "ban: unknown channel $c"
  } elseif {![botonchan $c]} {
    lappend o "ban: I am not on $c"
  } elseif {[llength [chanbans $c]] == 0} {
    lappend o "ban: no bans active on $c"
  } else {
    set t [lindex [chanbans $c] end 2]; set r ""; set z ""
    for {set p [expr [llength [chanbans $c]] -1]} {$p >= 0} {incr p -1} {
      set e [lindex [chanbans $c] $p]; set b [lindex $e 0]; set s [lindex $e 2]
      if {$s > [expr $t +10]} { break }
      if {![isban $b $c]} {
        if {[botisop $c] || [botishalfop $c]} { pushmode $c -b $b }
        lappend r $b
        continue
      }
      foreach e [banlist $c] {
        set d [lindex $e 0]
        if {![string equal -nocase $b $d]} { continue }
        set u [lindex $e 3]
        foreach e [banlist $c] {
          set d [lindex $e 0]; set v [lindex $e 3]
          if {$v > [expr $u +10] || $v < [expr $u -10]} { continue }
          set R [lindex $e 1]; set E [lindex $e 2]; set L [lindex $e 4]
          set C [lindex $e 5]; set v [ban:dur2 $v]
          if {$E == 0} { set E perm } else { set E [ban:dur2 $E] }
          if {$L == 0} { set L never } else { set L [ban:dur2 $L] }
          putloglev k $c "ban: DEL $c $d    $v/$L/$E   $C: $R"
          lappend r $d; lappend z $d; killchanban $c $d
        }
      }
    }
    lappend o "ban: bans removed from $c [join $r "   "]"
    if {$z != ""} { ban:ircconsole $c b BAN "$h DEL [join $z "   "]" }
  }
  return $o
}

####################################################
# ban:deldigit
####################################################
proc ban:deldigit { c t } {
  set t [lsort -dictionary -decreasing $t]; set f ""; set s ""; set r ""
  if {$c == "*"} {
    set s $t
  } elseif {$c == "global"} {
    foreach e $t {
      if {$e == ""} { continue }
      if {[string is digit $e]} {
        if {$e > [llength [banlist]]} { lappend f $e; continue }
        set e [lindex [banlist] [expr $e -1]]
        set B [lindex $e 0]; set R [lindex $e 1]; set E [lindex $e 2]
        set A [lindex $e 3]; set L [lindex $e 4]; set C [lindex $e 5]; set A [ban:dur2 $A]
        if {$E == 0} { set E perm } else { set E [ban:dur2 $E] }
        if {$L == 0} { set L never } else { set L [ban:dur2 $L] }
        lappend r $B
        killban $B
        putloglev k * "ban: DEL $B    $A/$L/$E   $C: $R"
      } else { lappend s $e }
    }
  } else {
    foreach e $t {
      if {$e == ""} { continue }
      if {[string is digit $e]} {
        if {$e > [llength [banlist $c]]} { lappend f $e; continue }
        set e [lindex [banlist $c] [expr $e -1]]
        set B [lindex $e 0]; set R [lindex $e 1]; set E [lindex $e 2]
        set A [lindex $e 3]; set L [lindex $e 4]; set C [lindex $e 5]; set A [ban:dur2 $A]
        if {$E == 0} { set E perm } else { set E [ban:dur2 $E] }
        if {$L == 0} { set L never } else { set L [ban:dur2 $L] }
        lappend r $B
        killchanban $c $B
        putloglev k $c "ban: DEL $c $B    $A/$L/$E   $C: $R"
      } else { lappend s $e }
    }
  }
  lappend o $s; lappend o $r; lappend o $f; set r [join $r]
  if {$r != ""} { lappend o "$c $r" }
  return $o
}

####################################################
# ban:delnick
####################################################
proc ban:delnick { h c t } {
  set f ""; set s ""; set r ""; set o ""
  if {$c == "global"} { set d ""
  } elseif {$c == "*"} { set d [channels]
  } else { lappend d $c }
  foreach n $t {
    if {![ban:validnick $n]} { lappend s $n; continue }
    set p 0
    foreach c $d {
      if {![matchattr $h lomn|lomn $c]} { continue }
      set m ""
      if {![onchan $n]} { if {[info procs chase] != "" && [set i [chase $n]] != ""} { set n $i } }
      if {[onchan $n] && ![onchansplit $n]} {
        set u [split [getchanhost $n] @]; set j [lindex $u 1]; set u [lindex $u 0]
        lappend m "$n!$u@$j"
        if {[info procs dnsdb:get] != ""} {
          set j [dnsdb:get $n h]; if {$j != ""} { lappend m "$n!$u@$j" }
          set j [dnsdb:get $n i]; if {$j != ""} { lappend m "$n!$u@$j" }
        }
        if {[info procs whox:get] != ""} {
          set j [whox:get $n a]
          if {$j != "" && $j != 0} { lappend m "$n!$u@$j.users.quakenet.org" }
        }
        # get uhost, host, ip, account
      } else {
        if {[info procs whowas:get] != ""} {
          set j [whowas:get $n uhost $c]
          if {$j == ""} { set j [whowas:get $n uhost] }
          # failed
          if {$j == ""} { lappend f $n; continue }
          set j [split $j @]; set u [lindex $j 0]; set j [lindex $j 1]
          lappend m "$n!$u@$j"
          set j [whowas:get $n dnsdbh $c]
          if {$j == ""} { set j [whowas:get $n dnsdbh] }
          if {$j != ""} { lappend m "$n!$u@$j" }
          set j [whowas:get $n dnsdbi $c]
          if {$j == ""} { set j [whowas:get $n dnsdbi] }
          if {$j != ""} { lappend m "$n!$u@j" }
          set j [whowas:get $n whox $c]
          if {$j == ""} { set j [whowas:get $n whox] }
          set j [split $j]; set j [string range [lindex $j [lsearch -glob $j "a=?*"]] 2 end]
          if {$j != "" && $j != 0} { lappend m "$n!$u@$j.users.quakenet.org" }
        }
        # check whowas, get uhost, host, ip, account
      }
      foreach e [chanbans $c] {
        set B [lindex $e 0]
        if {[isban $B $c]} { continue }
        regsub -all {[][\\]} $B {\\\0} b
        foreach l $m {
          if {![string match -nocase $b $l]} { continue }
          if {![botisop $c] && ![botishalfop $c]} { continue }
          lappend g($c) $B
          pushmode $c -b $B
          set p 1
        }
      }
      foreach e [banlist $c] {
        set B [lindex $e 0]; set R [lindex $e 1]; set E [lindex $e 2]
        set A [lindex $e 3]; set L [lindex $e 4]; set C [lindex $e 5]; set A [ban:dur2 $A]
        if {$E == 0} { set E perm } else { set E [ban:dur2 $E] }
        if {$L == 0} { set L never } else { set L [ban:dur2 $L] }
        regsub -all {[][\\]} $B {\\\0} b
        foreach l $m {
          if {![string match -nocase $b $l]} { continue }
          set p 1
          killchanban $c $B
          putloglev k $c "ban: DEL $c $B    $A/$L/$E   $C: $R"
          lappend g($c) $B
          break
        }
      }
    }
    if {$p} { lappend r $n } else { lappend f $n }
  }
  lappend o $s; lappend o $r; lappend o $f
  foreach c $d { if {[info exist g($c)]} { lappend o "$c [join $g($c)]" } }
  return $o
}

####################################################
# ban:delmask
####################################################
proc ban:delmask { h c t } {
  set f ""; set r ""; set o ""
  if {$c == "*"} { set d [channels] } else { lappend d $c }
  if {$d == "global"} {
    foreach e $t {
      if {$e == ""} { continue }
      set e [ban:mask $e]
      if {[isban $e]} {
        foreach b [banlist] {
          set B [lindex $b 0]; set R [lindex $b 1]; set E [lindex $b 2]
          if {![string equal -nocase $e $B]} { continue }
          set A [lindex $b 3]; set L [lindex $b 4]; set C [lindex $b 5]; set A [ban:dur2 $A]
          if {$E == 0} { set E perm } else { set E [ban:dur2 $E] }
          if {$L == 0} { set L never } else { set L [ban:dur2 $L] }
          lappend r $e
          putloglev k * "ban: DEL $B    $A/$L/$E   $C: $R"
          killban $e
          lappend g($d) $e
          break; continue
        }
      } else { lappend f $e }
    }
  } else {
    set p 0
    foreach e $t {
      if {$e == ""} { continue }
      set e [ban:mask $e]
      foreach c $d {
        if {![matchattr $h lomn|lomn $c]} { continue }
        if {[isban $e $c]} {
          foreach b [banlist $c] {
            set B [lindex $b 0]; set R [lindex $b 1]; set E [lindex $b 2]
            if {![string equal -nocase $e $B]} { continue }
            set A [lindex $b 3]; set L [lindex $b 4]; set C [lindex $b 5]; set A [ban:dur2 $A]
            if {$E == 0} { set E perm } else { set E [ban:dur2 $E] }
            if {$L == 0} { set L never } else { set L [ban:dur2 $L] }
            putloglev k $c "ban: DEL $c $B    $A/$L/$E   $C: $R"
            killchanban $c $e; set p 1; lappend g($c) $e; break
          }
        } elseif {([botisop $c] || [botishalfop $c]) && [ischanban $e $c]} {
          set p 1; pushmode $c -b $e; lappend g($c) $e
        }        
      }
      if {$p} { lappend r $e } else { lappend f $e }
    }
  }
  lappend o $r; lappend o $f
  foreach c $d { if {[info exists g($c)]} { lappend o "$c [join $g($c)]" } }
  return $o
}


####################################################
# ban:list
####################################################
proc ban:list { h t {c ""} {z 0} } {
  set t [split $t]; set d [lindex $t 0]; set t [lrange $t 1 end]
  if {![string equal -nocase $d "list"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c]} { set c [ban:whichchan $h $c] }
  if {[string equal -nocase $c "global"]} { set c "global" }
  
  if {$c == ""} {
    set d ""
    foreach c [channels] { if {[channel get $c public]} { lappend d $c } }
    if {[llength $d] == 1} { set c [lindex $d 0] } else { set c "" }
  }
  #if {$c == ""} { set c "global" }

  set m [lindex $t 0]; set r [join [lrange $t 1 end]]; set p 0
  if {![matchattr $h lomn]} {
    foreach c [channels] { if {[matchattr $h lomn|lomn $c]} { set p 1; break } }
  } else { set p 1 }
  if {$c == "global" && !$p} {
    lappend o "ban: no access to view global bans"
  } elseif {$c != "global" && (![validchan $c] || ![matchattr $h lomn|lomn $c])} {
    lappend o "ban: no access or unknown channel $c"
  } elseif {$m == ""} {
    lappend o "ban: no mask specified"
    lappend o "ban: usage ban \[<chan>\] list <mask> \[<creator>:<reason>\|active]"
  } elseif {$c == "global" && [string equal -nocase $r "active"]} {
    lappend o "ban: cannot list active bans for 'global', specify a channel"
  } elseif {$c != "global" && [string equal -nocase $r "active"] && ![botonchan $c]} {
    lappend o "ban: unable to list active bans on $c (not on channel)"
  } else {
    set n $m; set s $r; set x 0; set y 0
    regsub -all {[][\\]} $n {\\\0} n; regsub -all {[][\\]} $s {\\\0} s
    if {$r == ""} { set s *; set r * }
    if {$c == "global"} {
      foreach e [banlist] {
        incr x
        set B [lindex $e 0]; set R [lindex $e 1]; set E [lindex $e 2]
        set A [lindex $e 3]; set L [lindex $e 4]; set C [lindex $e 5]
        if {![string match -nocase $n $B]} { continue }
        if {![string match -nocase $s $C:$R]} { continue }
        incr y
        if {$y == 1} {
          lappend o "ban: listing global bans matching '$m $r'"
          lappend o "#: mask    'added/last used/expires'    creator: reason (sticky)"
        }
        set A [ban:dur2 $A]
        if {$E == 0} { set E perm } else { set E [ban:dur2 $E] }
        if {$L == 0} { set L never } else { set L [ban:dur2 $L] }
        if {!$z && $y > 10} {
          if {$y == 10} { lappend o "ban: too many matches, restricting output" }
          continue
        }
        set S ""; if {[isbansticky $B]} { set S " (sticky)" }
        lappend o "$x: $B    $A/$L/$E    $C: $R$S"
      }
      if {$y == 0} { lappend o "ban: no global bans found matching '$m $r'"
      } else { lappend o "ban: end of list ($y matches on $x bans)" }
    } else {
      if {[string equal -nocase $r "active"]} {
        lappend o "ban: listing active channel bans on $c matching '$m'"
        foreach e [chanbans $c] {
          set b [lindex $e 0]; set w [lindex $e 1]; set a [lindex $e 2]
          if {![string match -nocase $n $b]} { continue }
          if {!$z && $y > 10} {
            if {$y == 10} { lappend o "ban: too many matches, restricting output" }
            break
          }
          incr y
          if {[isban $b $c]} {
            set x 0
            foreach e [banlist $c] {
              incr x
              set B [lindex $e 0]; set R [lindex $e 1]; set E [lindex $e 2]
              set A [lindex $e 3]; set L [lindex $e 4]; set C [lindex $e 5]
              if {![string equal -nocase $b $B]} { continue }
              set A [ban:dur2 $A]
              if {$E == 0} { set E perm } else { set E [ban:dur2 $E] }
              if {$L == 0} { set L never } else { set L [ban:dur2 $L] }
              set S ""; if {[isbansticky $B $c]} { set S " (sticky)" }
              lappend o "$x: $B    $A/$L/$E   $C: $R$S"
            }
            set x 0
            foreach e [banlist] {
              incr x
              set B [lindex $e 0]; set R [lindex $e 1]; set E [lindex $e 2]
              set A [lindex $e 3]; set L [lindex $e 4]; set C [lindex $e 5]
              if {![string equal -nocase $b $B]} { continue }
              set A [ban:dur2 $A]
              if {$E == 0} { set E perm } else { set E [ban:dur2 $E] }
              if {$L == 0} { set L never } else { set L [ban:dur2 $L] }
              set S ""; if {[isbansticky $B]} { set S " (sticky)" }
              lappend o "$x: $B    $A/$L/$E   $C: $R (global)$S"
            }
          } else {
            set a [duration $a]
            set a [string map "seconds s second s minutes m minute m hours h hour h" $a]
            set a [string map "days d day d weeks w week w years y year y" $a]
            set a [join [lrange [split $a] 0 3] ""]
            lappend o "-: $b $w $a"
          }
        }
        lappend o "ban: end of list ($y matches on [llength [chanbans $c]] bans)"
      } else {
        foreach e [banlist $c] {
          incr x
          set B [lindex $e 0]; set R [lindex $e 1]; set E [lindex $e 2]
          set A [lindex $e 3]; set L [lindex $e 4]; set C [lindex $e 5]
          if {![string match -nocase $n $B]} { continue }
          if {![string match -nocase $s $C:$R]} { continue }
          incr y
          if {$y == 1} {
            lappend o "ban: listing channel bans on $c matching '$m $r'"
            lappend o "#: mask    'added/last used/expires'    creator: reason (sticky)"
          }
          set A [ban:dur2 $A]
          if {$E == 0} { set E perm } else { set E [ban:dur2 $E] }
          if {$L == 0} { set L never } else { set L [ban:dur2 $L] }
          if {!$z && $y > 10} {
            if {$y == 10} { lappend o "ban: too many matches, restricting output" }
            continue
          }
          set S ""; if {[isbansticky $B $c]} { set S " (sticky)" }
          lappend o "$x: $B    $A/$L/$E   $C: $R$S"
        }
        if {$y == 0} { lappend o "ban: no channel bans found on $c matching '$m $r'"
        } else { lappend o "ban: end of list ($y matches on $x bans)" }
      }
    }
  }
  return $o
}


####################################################
# ban:reset
####################################################
proc ban:reset { h t {c ""} } {
  set t [split $t]; set d [lindex $t 0]; set t [lrange $t 1 end]
  if {![string equal -nocase $d "reset"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c]} { set c [ban:whichchan $h $c] }
  if {[string equal -nocase $c "global"]} { set c "global" }
  
  if {$c == ""} {
    set d ""
    foreach c [channels] { if {[channel get $c public]} { lappend d $c } }
    if {[llength $d] == 1} { set c [lindex $d 0] } else { set c "" }
  }
  #if {$c == ""} { set c "global" }

  if {$c == "global" && ![matchattr $h omn]} {
    lappend o "ban: no access to reset bans for all channels"
  } elseif {$c != "global" && (![validchan $c] || ![matchattr $h lomn|lomn $c])} {
    lappend o "ban: no access or unknown channel $c"
  } elseif {$c != "global" && ![botonchan $c]} {
    lappend o "ban: I am not on $c"
  } elseif {$c != "global" && ![botisop $c] && ![botishalfop $c]} {
    lappend o "ban: I am not opped on $c"
  } else {
    if {$c == "global"} {
      foreach x [channels] { if {[botisop $x] || [botishalfop $x]} { resetbans $x } }
      lappend o "ban: reset bans on all channels"
      ban:ircconsole $c 4 BAN "$h RESET"
    } else {
      resetbans $c
      lappend o "ban: reset bans on $c"
      ban:ircconsole $c b BAN "$h RESET"
    }
  }
  return $o
}


####################################################
# ban:clear
####################################################
proc ban:clear { h t {c ""} } {
  set t [split $t]; set d [lindex $t 0]; set t [lrange $t 1 end]
  if {![string equal -nocase $d "clear"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c]} { set c [ban:whichchan $h $c] }
  if {[string equal -nocase $c "global"]} { set c "global" }
  
  if {$c == ""} {
    set d ""
    foreach c [channels] { if {[channel get $c public]} { lappend d $c } }
    if {[llength $d] == 1} { set c [lindex $d 0] } else { set c "" }
  }
  #if {$c == ""} { set c "global" }

  if {$c == "global" && ![matchattr $h omn]} {
    lappend o "ban: no access to clear global bans"
  } elseif {$c != "global" && (![validchan $c] || ![matchattr $h omn|omn $c])} {
    lappend o "ban: no access or unknown channel $c"
  } elseif {[llength $t] == 0} {
    lappend o "ban: no mask specified"
    lappend o "ban: usage ban \[<chan>\] clear <mask> \[<creator>:<reason>\]"
  } else {
    set m [lindex $t 0]; set r [join [lrange $t 1 end]]; set n $m; set s $r; set x 0; set y 0
    regsub -all {[][\\]} $n {\\\0} n; regsub -all {[][\\]} $s {\\\0} s
    if {$r == ""} { set s *; set r * }
    if {$c == "global"} {
      foreach e [banlist] {
        incr x
        set B [lindex $e 0]; set R [lindex $e 1]; set E [lindex $e 2]
        set A [lindex $e 3]; set L [lindex $e 4]; set C [lindex $e 5]
        if {![string match -nocase $n $B]} { continue }
        if {![string match -nocase $s $C:$R]} { continue }
        set A [ban:dur2 $A]
        if {$E == 0} { set E perm } else { set E [ban:dur2 $E] }
        if {$L == 0} { set L never } else { set L [ban:dur2 $L] }
        incr y
        killban $B
        putloglev k * "ban: CLEAR $B    $A/$L/$E   $C: $R"
      }
      lappend o "ban: cleared $y bans matching '$m $r'"
      if {$y > 0} { ban:ircconsole $c 4 BAN "$h CLEAR '$m $r' ($y bans cleared)" }
    } else {
      foreach e [banlist $c] {
        incr x
        set B [lindex $e 0]; set R [lindex $e 1]; set E [lindex $e 2]
        set A [lindex $e 3]; set L [lindex $e 4]; set C [lindex $e 5]
        if {![string match -nocase $n $B]} { continue }
        if {![string match -nocase $s $C:$R]} { continue }
        set A [ban:dur2 $A]
        if {$E == 0} { set E perm } else { set E [ban:dur2 $E] }
        if {$L == 0} { set L never } else { set L [ban:dur2 $L] }
        incr y
        killchanban $c $B
        putloglev k * "ban: CLEAR $c $B    $A/$L/$E   $C: $R"
      }
      lappend o "ban: cleared $y bans from $c matching '$m $r'"
      if {$y > 0} { ban:ircconsole $c b BAN "$h CLEAR '$m $r' ($y bans cleared)" }
    }
  }
  return $o
}


####################################################
# ban:stick
####################################################
proc ban:stick { h t {c ""} } {
  set t [split $t]; set d [lindex $t 0]; set t [lrange $t 1 end]
  if {![string equal -nocase $d "stick"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c]} { set c [ban:whichchan $h $c] }
  if {[string equal -nocase $c "global"]} { set c "global" }
  
  if {$c == ""} {
    set d ""
    foreach c [channels] { if {[channel get $c public]} { lappend d $c } }
    if {[llength $d] == 1} { set c [lindex $d 0] } else { set c "" }
  }
  #if {$c == ""} { set c "global" }

  if {$c == "global" && ![matchattr $h omn]} {
    lappend o "ban: no access to stick global bans"
  } elseif {$c != "global" && (![validchan $c] || ![matchattr $h omn|omn $c])} {
    lappend o "ban: no access or unknown channel $c"
  } elseif {[llength $t] == 0} {
    lappend o "ban: no mask specified"
    lappend o "ban: usage ban \[<chan>\] stick <mask> \[<mask> .. <mask>\]"
  } else {
    set s ""; set f ""
    if {$c == "global"} {
      foreach e $t {
        if {[string is digit $e]} {
          if {$e <= [llength [banlist]] && $e > 0} {
            stick [set e [lindex [banlist] [expr $e -1] 0]]]; lappend s $e
            foreach z [channels] {
              if {![botisop $z] && ![botishalfop $z]} { continue }
              if {[ischanban $e $z]} { continue }
              pushmode $z +b $e
            }
          } else { lappend f $e }
        } elseif {[stick $e] || [stick [set e [ban:mask $e]]]} {
          lappend s $e
          foreach z [channels] {
            if {![botisop $z] && ![botishalfop $z]} { continue }
            if {[ischanban $e $z]} { continue }
            pushmode $z +b $e
          }
        } else { lappend f $e }
      }
      if {$s != ""} {
        lappend o "ban: stick global bans '[join $s "   "]'"
        ban:ircconsole $c 4 BAN "$h STICK '[join $s]'"
      }
      if {$f != ""} { lappend o "ban: failed to stick, no such global bans '[join $f "   "]'" }
    } else {
      foreach e $t {
        if {[string is digit $e]} {
          if {$e <= [llength [banlist $c]] && $e > 0} {
            stick [set e [lindex [banlist $c] [expr $e -1] 0]] $c; lappend s $e
            if {([botisop $c] || [botishalfop $c]) && ![ischanban $e $c]} { pushmode $c +b $e }
          } else { lappend f $e }
        } elseif {[stick $e $c] || [stick [set e [ban:mask $e]] $c]} {
          lappend s $e
          if {([botisop $c] || [botishalfop $c]) && ![ischanban $e $c]} { pushmode $c +b $e }
        } else { lappend f $e }
      }
      if {$s != ""} {
        lappend o "ban: stick channel bans on $c '[join $s "   "]'"
        ban:ircconsole $c b BAN "$h STICK '[join $s]'"
      }
      if {$f != ""} { lappend o "ban: failed to stick, no such channel bans on $c '[join $f "   "]'" }
    }
  }
  return $o
}


####################################################
# ban:unstick
####################################################
proc ban:unstick { h t {c ""} } {
  set t [split $t]; set d [lindex $t 0]; set t [lrange $t 1 end]
  if {![string equal -nocase $d "unstick"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c]} { set c [ban:whichchan $h $c] }
  if {[string equal -nocase $c "global"]} { set c "global" }
  
  if {$c == ""} {
    set d ""
    foreach c [channels] { if {[channel get $c public]} { lappend d $c } }
    if {[llength $d] == 1} { set c [lindex $d 0] } else { set c "" }
  }
  #if {$c == ""} { set c "global" }

  if {$c == "global" && ![matchattr $h omn]} {
    lappend o "ban: no access to unstick global bans"
  } elseif {$c != "global" && (![validchan $c] || ![matchattr $h omn|omn $c])} {
    lappend o "ban: no access or unknown channel $c"
  } elseif {[llength $t] == 0} {
    lappend o "ban: no mask specified"
    lappend o "ban: usage ban \[<chan>\] unstick <mask> \[<mask> .. <mask>\]"
  } else {
    set s ""; set f ""
    if {$c == "global"} {
      foreach e $t {
        if {[string is digit $e]} {
          if {$e <= [llength [banlist]] && $e > 0} {
            unstick [set e [lindex [banlist] [expr $e -1] 0]]]; lappend s $e
          } else { lappend f $e }
        } elseif {[unstick $e] || [unstick [set e [ban:mask $e]]]} { lappend s $e
        } else { lappend f $e }
      }
      if {$s != ""} {
        lappend o "ban: unstick global bans '[join $s "   "]'"
        ban:ircconsole $c 4 BAN "$h UNSTICK '[join $s]'"
      }
      if {$f != ""} { lappend o "ban: failed to unstick, no such global bans '[join $f "   "]'" }
    } else {
      foreach e $t {
        if {[string is digit $e]} {
          if {$e <= [llength [banlist $c]] && $e > 0} {
            unstick [set e [lindex [banlist $c] [expr $e -1] 0]] $c; lappend s $e
          } else { lappend f $e }
        } elseif {[unstick $e $c] || [unstick [set e [ban:mask $e]] $c]} { lappend s $e
        } else { lappend f $e }
      }
      if {$s != ""} {
        lappend o "ban: unstick channel bans on $c '[join $s "   "]'"
        ban:ircconsole $c b BAN "$h UNSTICK '[join $s]'"
      }
      if {$f != ""} { lappend o "ban: failed to unstick, no such channel bans on $c '[join $f "   "]'" }
    }
  }
  return $o
}


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


####################################################
# ban:dur2
# give ts, returns XyXwXhXmXs
####################################################
proc ban:dur2 { t } {
  if {![string is digit $t]} { return 0 }
  set t [expr $t - [clock seconds]]
  if {$t < 0} { set t [expr $t * -1] }
  set t [duration $t]
  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 3] ""]
}


####################################################
# ban:ts
# give minutes, returns XyXwXhXmXs
####################################################
proc ban: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 3] ""]
}


####################################################
# ban:mask
####################################################
proc ban:mask { m } {
  if {[string match "?*@?*" $m] && ![string match "?*!?*@?*" $m]} { return "*!$m"
  } elseif {[string match "*.*" $m] && ![string match "?*!?*@?*" $m]} { return "*!*@$m"
  } elseif {[string match "?*!?*" $m] && ![string match "?*!?*@?*" $m]} { return "$m@*"
  } elseif {[string match "\#?*" $m] && ![string match "?*!?*@?*" $m]} {
    return "*!*@[string range $m 1 end].users.quakenet.org"
  } elseif {![string match "?*!?*@?*" $m]} { return "$m!*@*" } else { return $m }
}


####################################################
# ban:validnick
####################################################
proc ban:validnick { n } {
  regsub -all {\*{2,}} $n * n; set l -1
  if {[info procs isupport] != ""} { set l [isupport nicklen] }
  if {$l == -1} { set l $::nicklen }
  if {[string length $n] > $l} { return 0 }
  set p ^(\[a-zA-Z\\^\\\[\\\]\\{\\}\\\\|`_\\?\\*\])(\[a-zA-Z0-9\\-\\^\\\[\\\]\\{\\}\\\\|`_\\?\\*\])*\$
  if {![regexp $p $n]} { return 0 }
  return 1
}


####################################################
# ban:help
####################################################
proc ban:help { t } {
  set c [string tolower [lindex [split $t] 1]]
  if {$c == "add"} {
    lappend o "ban: ban \[<chan>\] add <mask> \[\[<mask> .. <mask>\] \[%<duration> :\]\]\[@\]<reason>"
    lappend o "ban: adds bans to the ban list, multiple masks can be specified. use #<account> for mask to ban *!*@<account>.users.quakenet.org. if mask is a nick and on a channel with the bot, the user is banned on *!user@host or *!*@host, else nick!*@* is banned. prefix mask with @ to make the ban hidden, targets are banned on *!*@host (example @*!*baduser@*.isp.tld). for quick removal of users, use the 'out' or 'chanout' command."
    lappend o "ban: the ':' prefixing the reason is required when using multiple masks or duration. duration supports the following: s=second, n=minute, h=hour, d=day, w=week, m=month, y=year, example %1d2h. default is 4 hours, use %0 for perm bans. only +m or higher can set perm bans and bans longer than 4 weeks."
    lappend o "ban: the reason can be a custom reason, one predefined from the rule command or a term from the term database (use ?? \[<chan>\] <term>). prefix the reason with a '@' to hide it, targets get kicked with 'You are banned'."
  } elseif {$c == "del"} {
    lappend o "ban: ban \[<chan>\] del \[<mask> .. <mask>\]"
    lappend o "ban: deletes bans from the ban list. multiple masks can be specified, mask can be a banned mask (*!*@host.com), a ban number obtained from 'ban list', #account or a nick. without a mask specified, the youngest ban(s) are removed. use * for chan to take effect on all channels where you have access."
  } elseif {$c == "list"} {
    lappend o "ban: ban \[<chan>\] list <mask> \[<creator>:<reason>|active\]"
    lappend o "ban: lists matching bans from the ban list, on IRC max 10 results, on partyline no limit. when 'active' is used for '<creator>:<reason>' active channel bans are listed."
  } elseif {$c == "reset"} {
    lappend o "ban: ban \[<chan>\] reset"
    lappend o "ban: removes all bans on the channel that are not in the bot's ban list and refreshes any bans that should be on the channel but are not set."
  } elseif {$c == "clear"} {
    lappend o "ban: ban \[<chan>\] clear <mask> \[<creator>:<reason>\]"
    lappend o "ban: deletes all matching bans, useful for mass removal of bans. details of the bans deleted are logged."
  } elseif {$c == "stick"} {
    lappend o "ban: ban \[<chan>\] stick <mask> \[<mask> .. <mask>\]"
    lappend o "ban: makes bans 'sticky' on the channel(s), meaning they will not be removed even if +dynamicbans is set. multiple masks can be specified, mask can be a banned mask (*!*@host.com), or a ban number obtained from 'ban list'."
  } elseif {$c == "unstick"} {
    lappend o "ban: ban \[<chan>\] unstick <mask> \[<mask> .. <mask>\]"
    lappend o "ban: makes 'sticky' bans normal. multiple masks can be specified, mask can be a banned mask (*!*@host.com), or a ban number obtained from 'ban list'."
  } else {
    lappend o "ban: usage help add|del|list|reset|clear|stick|unstick"
    lappend o "ban: shows help in general or for the given subcommand"
  }
  return $o
}


####################################################
# ban:whichchan
####################################################
proc ban:whichchan { h c } {
  if {[info procs whichchan] == ""} { return $c }
  return [whichchan $h $c]
}


####################################################
# ban:ircconsole
####################################################
proc ban:ircconsole { c l y t {h ""} } {
  if {[info procs ircconsole] == ""} { return 0 }
  ircconsole $c $l $y $t $h
}


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


####################################################
# set info for script.tcl
####################################################
set ::scriptdb(ban) {
  "provides ban command"
}

