####################################################
# by wiebe @ QuakeNet
#
####################################################

# topicdb(t,chan,n) ts nick user@host handle topic

####################################################
# topic:help:pubm
####################################################
bind pubm lomn|lomn "% ${botnet-nick} help topic" topic:help:pubm
proc topic:help:pubm { n u h c t } {
  if {[matchattr $h bkZ]} { return 0 }
  lappend o "topic: usage topic \[<chan>\] set|add|del|insert|replace|clear|refresh|list|show|help"
  if {[string equal [info procs cnotice] ""]} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "topic: " } }
  putloglev c $c "help: $n $u $h $c topic"
  return 1
}


####################################################
# topic:help:msg
####################################################
bind msgm lomn|lomn "help topic" topic:help:msgm
proc topic:help:msgm { n u h t } {
  if {[matchattr $h bkZ]} { return 0 }
  lappend o "topic: usage topic \[<chan>\] set|add|del|insert|replace|clear|refresh|list|show|help"
  if {[string equal [info procs cnotice] ""]} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "topic: " } }
  putcmdlog "($n!$u) !$h! help topic"
  return 1
}


####################################################
# topic:pubm
####################################################
bind pubm lomn|lomn "% ${botnet-nick} topic" topic:pubm
bind pubm lomn|lomn "% ${botnet-nick} topic *" topic:pubm
proc topic: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 {[string equal [lsearch -exact "set add del insert replace clear refresh list show help" $d] "-1"]} {
    set d [string tolower [lindex $t 1]]
    if {[string equal $d ""]} { set d [string tolower [lindex $t 0]] }
  }
  if {[string equal $d "set"]} { set o [topic:set $h $t $n $u $c]
  } elseif {[string equal $d "add"]} { set o [topic:add $h $t $n $u $c]
  } elseif {[string equal $d "del"]} { set o [topic:del $h $t $n $u $c]
  } elseif {[string equal $d "insert"]} { set o [topic:insert $h $t $n $u $c]
  } elseif {[string equal $d "replace"]} { set o [topic:replace $h $t $n $u $c]
  } elseif {[string equal $d "clear"]} { set o [topic:clear $h $t $n $u $c]
  } elseif {[string equal $d "refresh"]} { set o [topic:refresh $h $t $c]
  } elseif {[string equal $d "list"]} { set o [topic:list $h $t $c]
  } elseif {[string equal $d "show"]} { set o [topic:show $h $t $c $n $c]
  } elseif {[string equal $d "help"]} { set o [topic:help $t]
  } else {
    if {![string equal $d ""]} { lappend o "topic: unknown subcommand $d" }
    lappend o "topic: usage topic \[<chan>\] set|add|del|insert|replace|clear|refresh|list|show|help"
  }
  if {[string equal [info procs cnotice] ""]} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "topic: " } }
  putloglev c $c "TOPIC: $n $u $h $c $t"
  return 1
}


####################################################
# topic:msg
####################################################
bind msg lomn|lomn topic topic:msg
proc topic:msg { n u h t } {
  if {[matchattr $h bkZ]} { return 0 }
  set t [split $t]; set d [string tolower [lindex $t 0]]
  if {[string equal [lsearch -exact "set add del insert replace clear refresh list show help" $d] "-1"]} {
    set d [string tolower [lindex $t 1]]
    if {[string equal $d ""]} { set d [string tolower [lindex $t 0]] }
  }
  if {[string equal $d "set"]} { set o [topic:set $h $t $n $u ""]
  } elseif {[string equal $d "add"]} { set o [topic:add $h $t $n $u ""]
  } elseif {[string equal $d "del"]} { set o [topic:del $h $t $n $u ""]
  } elseif {[string equal $d "insert"]} { set o [topic:insert $h $t $n $u ""]
  } elseif {[string equal $d "replace"]} { set o [topic:replace $h $t $n $u ""]
  } elseif {[string equal $d "clear"]} { set o [topic:clear $h $t $n $u ""]
  } elseif {[string equal $d "refresh"]} { set o [topic:refresh $h $t ""]
  } elseif {[string equal $d "list"]} { set o [topic:list $h $t ""]
  } elseif {[string equal $d "show"]} { set o [topic:show $h $t $n $n ""]
  } elseif {[string equal $d "help"]} { set o [topic:help $t]
  } else {
    if {![string equal $d ""]} { lappend o "topic: unknown subcommand $d" }
    lappend o "topic: usage topic \[<chan>\] set|add|del|insert|replace|clear|refresh|list|show|help"
  }
  if {[string equal [info procs cnotice] ""]} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "topic: " } }
  return 1
}


####################################################
# topic:dcc
####################################################
bind dcc -|- topic topic:dcc
proc topic: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 {[string equal [lsearch -exact "set add del insert replace clear refresh list show help" $d] "-1"]} {
    set d [string tolower [lindex $t 1]]
    if {[string equal $d ""]} { set d [string tolower [lindex $t 0]] }
  }
  if {[string equal $d "set"]} { set o [topic:set $h $t * * $c]
  } elseif {[string equal $d "add"]} { set o [topic:add $h $t * * $c]
  } elseif {[string equal $d "del"]} { set o [topic:del $h $t * * $c]
  } elseif {[string equal $d "insert"]} { set o [topic:insert $h $t * * $c]
  } elseif {[string equal $d "replace"]} { set o [topic:replace $h $t * * $c]
  } elseif {[string equal $d "clear"]} { set o [topic:clear $h $t * * $c]
  } elseif {[string equal $d "refresh"]} { set o [topic:refresh $h $t $c]
  } elseif {[string equal $d "list"]} { set o [topic:list $h $t $c 1]
  } elseif {[string equal $d "show"]} { set o [topic:show $h $t $i "" $c]
  } elseif {[string equal $d "help"]} { set o [topic:help $t]
  } else {
    if {![string equal $d ""]} { lappend o "topic: unknown subcommand $d" }
    lappend o "topic: usage topic \[<chan>\] set|add|del|insert|replace|clear|refresh|list|show|help"
  }
  foreach l $o { putidx $i $l }
  return 1
}


####################################################
# topic:set
####################################################
proc topic:set { h t n u c } {
  set d [lindex $t 0]; set t [lrange $t 1 end]; set m -1
  if {![string equal -nocase $d "set"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  if {[string equal [info procs isupport] ""]} { set m [isupport topiclen] }
  if {[string equal $m "-1"]} { set m 250 }
  set t [join $t]; set x ""; set y ""
  if {[regexp {^\#(\d)+$} $t]} {
    set x [string trimleft $t \#]; set d [topic:lower $c]
    global topicdb
    if {[info exists topicdb(t,$d,$x)]} {
      set y [join [lrange [split $topicdb(t,$d,$x)] 4 end]]
    }
  }
  if {[string equal $c ""]} {
    lappend o "topic: no chan specified"
    lappend o "topic: topic \[<chan>\] set <topic>|#<N>"
  } elseif {![validchan $c] || ![matchattr $h omn|omn $c]} {
    lappend o "topic: no access or unknown channel $c"
    lappend o "topic: topic \[<chan>\] set <topic>|#<N>"
  } elseif {[string equal $t ""]} {
    lappend o "topic: no topic specified"
    lappend o "topic: topic \[<chan>\] set <topic>|#<N>"
  } elseif {[string length $t] > 250} {
    lappend o "topic: topic exceeds topiclen $m"
    lappend o "topic: [string range $t 0 [expr $m -1]]"
  } elseif {![string equal $x ""] && [string equal $y ""]} {
    lappend o "topic: no topic with number $x found"
    lappend o "topic: topic \[<chan>\] set <topic>|#<N>"
  } elseif {[string equal $x "1"]} {
    lappend o "topic: topic with number 1 is already set"
    lappend o "topic: topic \[<chan>\] set <topic>|#<N>"
  } else {
    if {![string equal $y ""]} { set t $y }
    if {![string equal [info procs ircconsole] ""]} { ircconsole $c t TOPIC "$h SET '$t\017'" }
    topic:new $n $u $h $c $t
    topic:setnew $c
    lappend o "topic: new topic on $c '$t\017' [string length $t]/$m chars"
  }
  return $o
}


####################################################
# topic:add
####################################################
proc topic:add { h t n u c } {
  set d [lindex $t 0]; set t [lrange $t 1 end]; set s " | "; set m -1
  if {![string equal -nocase $d "add"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  if {[string equal [info procs isupport] ""]} { set m [isupport topiclen] }
  if {[string equal $m "-1"]} { set m 250 }
  if {[validchan $c]} {
    set t [join $t]; set x [topic $c]
    if {![string equal $x ""]} { set x $x$s$t } else { set x $t }
  }
  if {[string equal $c ""]} {
    lappend o "topic: no chan specified"
    lappend o "topic: topic \[<chan>\] add <topic>"
  } elseif {![validchan $c] || ![matchattr $h omn|omn $c]} {
    lappend o "topic: no access or unknown channel $c"
    lappend o "topic: topic \[<chan>\] add <topic>"
  } elseif {[string equal $t ""]} {
    lappend o "topic: no topic specified"
    lappend o "topic: topic \[<chan>\] add <topic>"
  } elseif {[string length $x] > 250} {
    lappend o "topic: topic exceeds topiclen $m"
    lappend o "topic: [string range $x 0 [expr $m -1]]"
  } else {
    lappend o "topic: add '$t\017'"
    if {![string equal [info procs ircconsole] ""]} { ircconsole $c t TOPIC "$h ADD '$t\017'" }
    topic:new $n $u $h $c $x
    topic:setnew $c
    lappend o "topic: new topic on $c '$x\017' [string length $x]/$m chars"
  }
  return $o
}


####################################################
# topic:del
####################################################
proc topic:del { h t n u c } {
  set d [lindex $t 0]; set t [lrange $t 1 end]; set s " | "; set z ""; set m -1
  if {![string equal -nocase $d "del"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  if {[string equal [info procs isupport] ""]} { set m [isupport topiclen] }
  if {[string equal $m "-1"]} { set m 250 }
  set t [join $t]
  if {[validchan $c] && ![string equal $t ""]} {
    set x [split [string map [list $s \n] [topic $c]] \n]; set k [llength $x];set e ""; set r ""
    foreach y [split $t] {
      if {![regexp {^-{0,1}\d+$} $y]} { set e "$y is not a number"; break }
      if {$y < 0 && $y >= -$k} { set y [expr $y + $k] } else { incr y -1 }
      if {$y < $k} { if {[string equal [lsearch $z $y] "-1"]} {lappend z $y; continue } }
      set e "$y is out of range (1-$k)"; break
    }
  }
  set z [lsort -decreasing -dictionary $z]
  if {[string equal $c ""]} {
    lappend o "topic: no chan specified"
    lappend o "topic: topic \[<chan>\] del <N> \[<N> <N> ...\]"
  } elseif {![validchan $c] || ![matchattr $h omn|omn $c]} {
    lappend o "topic: no access or unknown channel $c"
    lappend o "topic: topic \[<chan>\] del <N> \[<N> <N> ...\]"
  } elseif {[string equal $t ""]} {
    lappend o "topic: no topic specified"
    lappend o "topic: topic \[<chan>\] del <N> \[<N> <N> ...\]"
  } elseif {![string equal $e ""]} {
    lappend o "topic: $e"
    lappend o "topic: topic \[<chan>\] del <N> \[<N> <N> ...\]"
  } else {
    foreach y $z { set r "'[lindex $x $y]\017'\n$r"; set x [lreplace $x $y $y] }
    set x [join $x $s]; set r [join [split $r \n] "     "]
    topic:new $n $u $h $c $x
    topic:setnew $c
    lappend o "topic: del $r"
    if {![string equal [info procs ircconsole] ""]} { ircconsole $c t TOPIC "$h DEL $r" }
    if {[string equal $x ""]} { lappend o "topic: topic unset on $c"
    } else { lappend o "topic: new topic on $c '$x\017' [string length $x]/$m chars" }
  }
  return $o
}


####################################################
# topic:insert
####################################################
proc topic:insert { h t n u c } {
  set d [lindex $t 0]; set t [lrange $t 1 end]; set s " | "; set m -1
  if {![string equal -nocase $d "insert"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  set y [lindex $t 0]; set t [join [lrange $t 1 end]]
  if {[string equal [info procs isupport] ""]} { set m [isupport topiclen] }
  if {[string equal $m "-1"]} { set m 250 }
  if {[validchan $c] && ![string equal $t ""]} {
    set x [topic $c]; set l [expr [string length $x$s$t] - $m]
    set x [split [string map [list $s \n] $x] \n]; set k [llength $x]
  }
  if {[string equal $c ""]} {
    lappend o "topic: no chan specified"
    lappend o "topic: topic \[<chan>\] insert <N> <topic>"
  } elseif {![validchan $c] || ![matchattr $h omn|omn $c]} {
    lappend o "topic: no access or unknown channel $c"
    lappend o "topic: topic \[<chan>\] insert <N> <topic>"
  } elseif {[string equal $y ""]} {
    lappend o "topic: no number specified"
    lappend o "topic: topic \[<chan>\] insert <N> <topic>"
  } elseif {![regexp {^-{0,1}\d+$} $y]} {
    lappend o "topic: invalid number $y"
    lappend o "topic: topic \[<chan>\] insert <N> <topic>"
  } elseif {$y > [expr $k +1] || $y < -$k} {
    lappend o "topic: no topic part $y"
    lappend o "topic: topic \[<chan>\] insert <N> <topic>"
  } elseif {[string equal $t ""]} {
    lappend o "topic: no topic specified"
    lappend o "topic: topic \[<chan>\] insert <N> <topic>"
  } elseif {$l > 0} {
    lappend o "topic: topic exceeds topiclen $m"
    lappend o "topic: [string range $t 0 end-$l]"
  } else {
    if {$y <= 0} { incr y $k } else { incr y -1 }
    lappend o "topic: insert '$t\017' in position $y"
    if {![string equal [info procs ircconsole] ""]} {
      ircconsole $c t TOPIC "$h INSERT $y '$t\017'"
    }
    set x [join [linsert $x $y $t] $s]
    topic:new $n $u $h $c $x
    topic:setnew $c
    lappend o "topic: new topic on $c '$x\017' [string length $x]/$m chars"
  }
  return $o
}


####################################################
# topic:replace
####################################################
proc topic:replace { h t n u c } {
  set d [lindex $t 0]; set t [lrange $t 1 end]; set s " | "; set m -1
  if {![string equal -nocase $d "replace"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  set y [lindex $t 0]; set t [join [lrange $t 1 end]]
  if {[string equal [info procs isupport] ""]} { set m [isupport topiclen] }
  if {[string equal $m "-1"]} { set m 250 }
  if {[validchan $c] && ![string equal $t ""]} {
    set x [topic $c]; set l [expr [string length $x$t] - $m]; set z $y
    set x [split [string map [list $s \n] $x] \n]; set k [llength $x]
    if {[regexp {^-{0,1}\d+$} $z]} {
      if {$z <= 0} { incr z $k } else { incr z -1 }
      if {$z > 0 && $z <= $k} { incr l -[string length [lindex $x $z]] }
    }
  }
  if {[string equal $c ""]} {
    lappend o "topic: no chan specified"
    lappend o "topic: topic \[<chan>\] replace <N> <topic>"
  } elseif {![validchan $c] || ![matchattr $h omn|omn $c]} {
    lappend o "topic: no access or unknown channel $c"
    lappend o "topic: topic \[<chan>\] replace <N> <topic>"
  } elseif {[string equal $y ""]} {
    lappend o "topic: no number specified"
    lappend o "topic: topic \[<chan>\] replace <N> <topic>"
  } elseif {![regexp {^-{0,1}\d+$} $y]} {
    lappend o "topic: invalid number $y"
    lappend o "topic: topic \[<chan>\] replace <N> <topic>"
  } elseif {$y > $k || $y < -$k} {
    lappend o "topic: no topic part $y"
    lappend o "topic: topic \[<chan>\] replace <N> <topic>"
  } elseif {[string equal $t ""]} {
    lappend o "topic: no topic specified"
    lappend o "topic: topic \[<chan>\] replace <N> <topic>"
  } elseif {$l > 0} {
    lappend o "topic: topic exceeds topiclen $m"
    lappend o "topic: [string range $t 0 end-$l]"
  } else {
    lappend o "topic: replace '[lindex $x $z]\017' with '$t\017'"
    if {![string equal [info procs ircconsole] ""]} {
      ircconsole $c t TOPIC "$h REPLACE '[lindex $x $z]\017' with '$t\017'"
    }
    set x [join [lreplace $x $z $z $t] $s]
    topic:new $n $u $h $c $x
    topic:setnew $c
    lappend o "topic: new topic on $c '$x\017' [string length $x]/$m chars"
  }
  return $o
}


####################################################
# topic:clear
####################################################
proc topic:clear { h t n u c } {
  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] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  if {[string equal $c ""]} {
    lappend o "topic: no chan specified"
    lappend o "topic: topic \[<chan>\] clear"
  } elseif {![validchan $c] || ![matchattr $h omn|omn $c]} {
    lappend o "topic: no access or unknown channel $c"
    lappend o "topic: topic \[<chan>\] clear"
  } else {
    if {![string equal [info procs ircconsole] ""]} { ircconsole $c t TOPIC "$h CLEAR" }
    topic:new $n $u $h $c ""
    topic:setnew $c
    lappend o "topic: topic unset on $c"
  }
  return $o
}


####################################################
# topic:refresh
####################################################
proc topic:refresh { h t c } {
  set d [lindex $t 0]; set t [lrange $t 1 end]; set m -1
  if {![string equal -nocase $d "refresh"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  if {[string equal [info procs isupport] ""]} { set m [isupport topiclen] }
  if {[string equal $m "-1"]} { set m 250 }
  if {[validchan $c]} { set t [topic $c] }
  if {[string equal $c ""]} {
    lappend o "topic: no chan specified"
    lappend o "topic: topic \[<chan>\] refresh"
  } elseif {![validchan $c] || ![matchattr $h lomn|lomn $c]} {
    lappend o "topic: no access or unknown channel $c"
    lappend o "topic: topic \[<chan>\] refresh"
  } elseif {[string equal $t ""]} {
    lappend o "topic: no topic set here"
    putallbots "TOPC R $c"
  } elseif {![botisop $c] && ![botishalfop $c] && [string match "*t*" $m]} {
    lappend o "topic: unable to change topic (mode +t)"
    putallbots "TOPC R $c"
  } else {
    topic:change $c $t
    lappend o "topic: refreshed topic on $c '$t\017' [string length $t]/$m chars"
  }
  return $o
}


####################################################
# topic:list
####################################################
proc topic:list { h t c {z 0} } {
  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] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  set p [lrange $t 0 end]; set v 3; set m -1
  if {[string equal [info procs isupport] ""]} { set m [isupport topiclen] }
  if {[string equal $m "-1"]} { set m 250 }
  if {[string is digit [lindex $p end]] && [llength $p] > 1} {
    set x [lindex $p end]; set p [join [lrange $t 1 end-1]]
  } else { set x "" }
  if {[string equal $c ""]} {
    lappend o "topic: no chan specified"
    lappend o "topic: topic \[<chan>\] list <pattern> \[<range>\]"
  } elseif {![validchan $c] || ![matchattr $h lomn|lomn $c]} {
    lappend o "topic: no access or unknown channel $c"
    lappend o "topic: topic \[<chan>\] list <pattern> \[<range>\]"
  } elseif {[string equal $p ""]} {
    lappend o "topic: no pattern specified"
    lappend o "topic: topic \[<chan>\] list <pattern> \[<range>\]"
  } else {
    if {[string equal $x ""]} { set x 1 }
    global topicdb; set d [topic:lower $c]; set y 0; set k 0
    regsub -all {[][\\]} $p {\\\0} q
    for {set g 1} {$g <= 12} {incr g 1} {
      if {![info exists topicdb(t,$d,$g)]} { continue }
      set e [split $topicdb(t,$d,$g)]; set s [lindex $e 0]; set i [lindex $e 1]; incr k 1
      set r [lindex $e 2]; set a [lindex $e 3]; set t [join [lrange $e 4 end]]; set b ""
      if {![string match -nocase $q $t]} { continue }
      incr y 1
      if {!$z} {
        if {$y < [expr ($x -1) * $v +1]} { continue }
        if {$y > [expr ($x -1) * $v + $v]} { continue }
      }
      if {![string equal $i$r "**"]} { lappend b "$i ($r)" }
      if {![string equal $a "*"]} { lappend b !$a! }
      if {[string equal $b ""]} { lappend b "???" }
      set b [join $b]; set l "[string length $t]/$m chars"
      lappend o "topic: #$g '$t\017' $l    by $b"
    }
    if {$y > $v && $y < $g && !$z} { lappend o "topic: use range [expr $x +1] to list next $v entries" }
    lappend o "topic: $y / $k entries match pattern '$p\017' for $c"
  }
  return $o
}


####################################################
# topic:show
####################################################
proc topic:show { h t p n c } {
  set d [lindex $t 0]; set t [lrange $t 1 end]; set m -1
  if {![string equal -nocase $d "show"]} { set c $d; set t [lrange $t 1 end] }
  set d [topic:lower $c]; set t [join $t]
  if {[string equal [info procs isupport] ""]} { set m [isupport topiclen] }
  if {[string equal $m "-1"]} { set m 250 }
  if {[string equal $c ""]} {
    lappend o "topic: no chan specified"
    lappend o "topic: topic \[<chan>\] show"
  } elseif {![regexp {^\#[^,\002\003\009\017\026\037]*$} $c] || [string length $c] > 200} {
    lappend o "topic: invalid chan specified"
    lappend o "topic: topic \[<chan>\] show"
  } elseif {[validchan $c]} {
    global topicdb
    if {[string match *s* [lindex [split [getchanmode $c]] 0]] && ![string equal $d [topic:lower $p]] && ([validchan $p] || ![matchattr $h lomn|lomn $c])} {
      set z "topic: unable to retrieve topic from $c (secret)"
    } elseif {![info exists topicdb(t,$d,1)]} {
      set z "topic: no topic set on $c"
    } else {
      set e [split $topicdb(t,$d,1)]; set s [lindex $e 0]; set i [lindex $e 1]
      set r [lindex $e 2]; set a [lindex $e 3]; set t [join [lrange $e 4 end]]; set b ""
      if {![string equal $i$r "**"]} { lappend b "$i ($r)" }
      if {![string equal $a "*"]} { lappend b !$a! }
      if {[string equal $b ""]} { lappend b "???" }
      set b [join $b]; set s "[ctime $s] ([topic:ts $s] ago)"; set l "[string length $t]/$m chars"
      set z "topic: topic on $c is '$t\017' $l     set by $b     on $s"
    }
    if {[validchan $p] && (![string match *m* [lindex [split [getchanmode $p]] 0]] || [botisvoice $p] || [botishalfop $p] || [botisop $p])} {
      if {[string match *c* [lindex [split [getchanmode $p]] 0]]} { set z [stripcodes bcru $z] }
      if {[string equal [info procs privmsg] ""]} { puthelp "PRIVMSG $p :$z"
      } else { privmsg $p $z puthelp "topic: " }
      return ""
    } else { lappend o $z }
  } elseif {![validchan $p] && ![valididx $p] && ![onchan $p]} {
    lappend o "topic: you are not on any of my channels"
  } else {
    global topicdb; set x [clock seconds]
# topicdb(s,from) ts to1 to2 ..
    if {[info exists topicdb(s,$d)]} {
      set y [lindex [split $topicdb(s,$d)] 0]
      if {[string equal [lsearch $topicdb(s,$d) "$p $h $n"] "-1"]} {
        lappend topicdb(s,$d) "$p $h $n"
      }
      if {[expr $x - $y] > 60} { puthelp "TOPIC $d" }
    } else { lappend topicdb(s,$d) $x; lappend topicdb(s,$d) "$p $h $n"; puthelp "TOPIC $d" }
    return ""
  }
  return $o
}


####################################################
# topic:raw
# 331 RPL_NOTOPIC   332 RPL_TOPIC   333 RPL_TOPICWHOTIME
# 403 ERR_NOSUCHCHANNEL   442 ERR_NOTONCHANNEL
####################################################
bind raw -|- "331" topic:raw; bind raw -|- "332" topic:raw; bind raw -|- "333" topic:raw
bind raw -|- "403" topic:raw; bind raw -|- "442" topic:raw
proc topic:raw { s n t } {
  set t [split $t]; set c [lindex $t 1]; set t [join [lrange $t 2 end]]; set d [topic:lower $c]
  global topicdb; set m -1
  if {[string equal [info procs isupport] ""]} { set m [isupport topiclen] }
  if {[string equal $m "-1"]} { set m 250 }
  if {![info exists topicdb(s,$d)]} { return 0 }
  if {[string equal $n "332"]} { lappend topicdb(s,$d) [string range $t 1 end]; return 0 }
# no topic
  if {[string equal $n "331"]} {
    set z "topic: no topic set on $c"
# whotime
  } elseif {[string equal $n "333"]} {
    set t [split $t]; set w [lindex $t 0]; set s [lindex $t 1]
    set x [lindex $topicdb(s,$d) end]; set topicdb(s,$d) [lrange $topicdb(s,$d) 0 end-1]
    set s "[ctime $s] ([topic:ts $s] ago)"; set l "[string length $x]/$m chars"
    set z "topic: topic on $c is: '$x\017' $l     set by $w     on $s"
# no chan
  } elseif {[string equal $n "403"]} {
    set z "topic: no such channel $c"
# +s
  } elseif {[string equal $n "442"]} {
    set z "topic: channel $c is +s (secret)"
  }
  set E [lrange $topicdb(s,$d) 1 end]
  foreach e $E {
    set e [split $e]; set p [lindex $e 0]; set h [lindex $e 1]; set n [lindex $e 2]
    set y [stripcodes bcru $z]; set C ""; set D ""; set N ""
    if {[validchan $p]} {
      set k [lindex [split [getchanmode $p]] 0]
      if {![string match *m* $k] || [botisvoice $p] || [botishalfop $p] || [botisop $p]} {
      } elseif {[onchan $n $p]} { set p $n }
    }
    if {[validchan $p]} {
      if {![botonchan $p]} { continue }
      if {![string match *c* $k]} { lappend C $p } else { lappend D $p }
    } elseif {[valididx $p]} {
      if {![string equal $h [idx2hand $p]]} { continue }
      putidx $p $z
    } elseif {[onchan $p]} {
      if {![string equal $h [nick2hand $p]]} { continue }
      lappend N $p
    }
  }
  if {![string equal $C ""]} {
    set C [join $C ,]
    if {[string equal [info procs privmsg] ""]} { puthelp "PRIVMSG $C :$z"
    } else { privmsg $C $z puthelp "topic: " }
    }
  if {![string equal $D ""]} {
    set D [join $D ,]
    if {[string equal [info procs privmsg] ""]} { puthelp "PRIVMSG $D :$y"
    } else { privmsg $D $y puthelp "topic: " }
  }
  if {![string equal $N ""]} {
    set N [join $N ,]
    if {[string equal [info procs notice] ""]} { puthelp "NOTICE $N :$z"
    } else { notice $N $z puthelp "topic: " }
  }
  unset topicdb(s,$d)
}


####################################################
# topic:help
####################################################
proc topic:help { t } {
  set c [string tolower [lindex [split $t] 1]]; set s " | "
  if {[string equal $c "general"]} {
    lappend o "topic: handles the channel topic. info about the last 12 topics is saved and shared over the botnet. the topic is refreshed every 30 minutes or within 2 minutes when a user returns from a netsplit. the channel topic is divided in parts by \"$s\". the parts are numbered from left to right 1,2,3,..,N and from right to left -1,-2,-3,..,-N"
  } elseif {[string equal $c "set"]} {
    lappend o "topic: topic \[<chan>\] set <topic>|#<N>"
    lappend o "topic: changes the channel topic. using #N will change the channel topic to the Nth entry in the database. use the show subcommand to view the database"
  } elseif {[string equal $c "add"]} {
    lappend o "topic: topic \[<chan>\] add <topic>"
    lappend o "topic: adds the given topic to the end of the channel topic"
  } elseif {[string equal $c "del"]} {
    lappend o "topic: topic \[<chan>\] del <N> \[<N> <N> ...\]"
    lappend o "topic: deletes given positions from the channel topic. the channel topic is divided in parts by \"$s\". the parts are numbered from left to right 1,2,3,..,N and from right to left -1,-2,-3,..,-N"
  } elseif {[string equal $c "insert"]} {
    lappend o "topic: topic \[<chan>\] insert <N> <topic>"
    lappend o "topic: inserts the given topic in the Nth position. the channel topic is divided in parts by \"$s\". the parts are numbered from left to right 1,2,3,..,N and from right to left -1,-2,-3,..,-N. using 0 for N is the same as using the add subcommand"
  } elseif {[string equal $c "replace"]} {
    lappend o "topic: topic \[<chan>\] replace <N> <topic>"
    lappend o "topic: replaces the Nth position with the given topic. the channel topic is divided in parts by \"$s\". the parts are numbered from left to right 1,2,3,..,N and from right to left -1,-2,-3,..,-N"
  } elseif {[string equal $c "refresh"]} {
    lappend o "topic: topic \[<chan>\] refresh"
    lappend o "topic: refreshes the channel topic"
  } elseif {[string equal $c "list"]} {
    lappend o "topic: topic \[<chan>\] list <pattern> \[<range>\]"
    lappend o "topic: lists entries matching the given parameters, when the results exceed the maximum allowed output, use range to view the next set (default 1)"
  } elseif {[string equal $c "show"]} {
    lappend o "topic: topic \[<chan>\] show"
    lappend o "topic: shows the channel topic from the given channel"
  } else {
    lappend o "topic: usage help set|add|del|insert|replace|refresh|list|show|general"
    lappend o "topic: shows help in general or for the given subcommand"
  }
  return $o
}


####################################################
# topic:topc
# n = * u = * h = * when bot joins, t = empty if none set
####################################################
bind topc -|- * topic:topc
proc topic:topc { n u h c t } {
  if {![validchan $c]} { return 0 }
  if {![botonchan $c]} { return 0 }
  global topicdb; set d [topic:lower $c]
# bot joined chan
  if {[string equal $n$u "**"]} {
    if {[expr [clock seconds] - [getchanjoin $::botnick $c]] > 120} { return 0 }
    if {[string equal $t ""]} { topic:setnew $c; return 0 }
  }
  if {[isbotnick $n] && [info exists topicdb(t,$d,1)]} {
    set v [join [lrange [split $topicdb(t,$d,1)] 4 end]]
    if {![string equal -nocase $v $t]} { topic:change $c $v; return 0 }
  }
  topic:new $n $u $h $c $t
}


####################################################
# topic:setnew
####################################################
proc topic:setnew { c {b ""} } {
  if {[channel get $c inactive]} { return 0 }
  if {![botonchan $c]} { return 0 }
  global topicdb; set d [topic:lower $c]
  if {[info exists topicdb(t,$d,1)]} {
    set t [join [lrange [split $topicdb(t,$d,1)] 4 end]]
    if {[string equal [topic $c] $t]} { return 0 }
    if {[string equal $b ""]} { putallbots "TOPC T $c $topicdb(t,$d,1)" }
    topic:change $c $t
  }
}


####################################################
# topic:change
####################################################
proc topic:change { c t } {
  set m [lindex [split [getchanmode $c]] 0]
  if {![botisop $c] && ![botishalfop $c] && [string match "*t*" $m]} { return 0 }
  puthelp "TOPIC $c :$t"
}


####################################################
# topic:new
####################################################
proc topic:new { n u h c t } {
  global topicdb; set d [topic:lower $c]
  if {[info exists topicdb(t,$d,1)]} {
    set v [join [lrange [split $topicdb(t,$d,1)] 4 end]]
    if {[string equal $t $v]} { return 0 }
  }
  for {set p 11} {$p > 0} {incr p -1} {
    if {[info exists topicdb(t,$d,$p)]} { set topicdb(t,$c,[expr $p +1]) $topicdb(t,$d,$p) }
  }
  set topicdb(t,$d,1) "[clock seconds] $n $u $h $t"
  topic:save
  return 1
}


####################################################
# topic:mode
####################################################
bind mode -|- "% -t" topic:mode;bind mode -|- "% +h" topic:mode;bind mode -|- "% +o" topic:mode
proc topic:mode { n u h c m t } {
  if {[string equal $m "-t"] && ([botisop $c] || [botishalfop $c])} { return 0 }
  if {[string equal $m "+h"] && (![isbotnick $t] || [botisop $c])} { return 0 }
  if {[string equal $m "+o"] && (![isbotnick $t] || [botishalfop $c])} { return 0 }
  topic:setnew $c
}


####################################################
# topic:rejn
####################################################
bind rejn -|- * topic:rejn
proc topic:rejn { n u h c } { if {[validchan $c]} { global topicdb; set topicdb(r,$c) 1 } }


####################################################
# topic:time
####################################################
bind time -|- "* * * * *" topic:time
proc topic:time { n h d m y } {
  global topicdb; set s 1
  if {[expr round(fmod([clock seconds] / 60,60))] > 0} { set s 0 }
  foreach c [channels] {
    set g 0
    if {![botonchan $c]} { continue }
    if {![info exists topicdb(r,$c)] && !$s} { continue }
    if {[info exists topicdb(r,$c)]} { unset topicdb(r,$c); set g 1 }
    if {![topic:whichbot $c [string tolower "$h $c"]] && !$g} { continue }
    set t [topic $c]
    if {[string equal $t ""]} { continue }
    topic:change $c $t
  }
}


####################################################
# topic:whichbot
# returns 1 if this bot should respond, 0 otherwise
####################################################
proc topic:whichbot { c t } {
  if {[string equal [info procs script:check] ""]} { return 1 }
  set m [lindex [split [getchanmode $c]] 0]
  if {![botisop $c] && ![botishalfop $c] && [string match "*t*" $m]} { return 0 }
  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] && [string match "*t*" $m]} { continue }
    if {![script:check $b topic]} { 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 }
}


####################################################
# topic:save
####################################################
proc topic:save { } {
  global topicdb
  set f "topicdb.txt"
  set f [open "$f" w]
  if {[file exists $f]} {
    if {![file isfile $f]} { return 0 }
    if {![file writable $f]} { return 0 }
  }
  foreach n [lsort [array names topicdb]] {
    if {![string match -nocase "t,*" $n]} { continue }
    set c [lindex [split $n ,] 1]
    if {![validchan $c]} { continue }
    puts $f "$n $topicdb($n)"
  }
  close $f
}


####################################################
# topic:load
####################################################
proc topic:load { } {
  global topicdb
  if {[info exists topicdb]} { unset topicdb }
  set f "topicdb.txt"
  if {![file exists $f]} { return 0 }
  if {![file isfile $f]} { return 0 }
  if {![file readable $f]} { return 0 }
  set f [open "$f" r]; set d [read $f]; close $f
  foreach l [split $d \n] {
    if {[string equal $l ""]} { continue }
    set l [split $l]; set c [lindex [split [lindex $l 0] ,] 1]
    if {![validchan $c]} { continue }
    set topicdb([lindex $l 0]) [join [lrange $l 1 end]]
  }
}


####################################################
# topic:evnt
####################################################
bind evnt -|- userfile-loaded topic:evnt; bind evnt -|- init-server topic:evnt
proc topic:evnt { t } { topic:load }


####################################################
# topic:link
####################################################
bind link -|- * topic:link
proc topic:link { b v } {
  if {![string equal -nocase $v ${::botnet-nick}]} { return 0 }
  if {![string match "*g*" [botattr $b]]} { return 0 }
  global topicdb; set x 0
  foreach n [lsort [array names topicdb]] {
    if {![string match "t,*" $n]} { continue }
    set x 1; set c [lindex [split $n ,] 1]
    putbot $b "TOPC B $c $topicdb($n)"
  }
  if {$x} { putbot $b "TOPC E" }
}


####################################################
# topic:bot
####################################################
# topicdb(t,chan,n) ts nick user@host handle topic
bind bot -|- TOPC topic:bot
proc topic:bot { b c t } {
  if {![string equal $c "TOPC"]} { return 0 }
  if {![string match "*g*" [botattr $b]]} { return 0 }
  set z [lindex [split $t] 0]
  if {![string match {[BET]} $z]} { return 0 }
# B or T
  if {[string match {[BT]} $z]} {
    set t [lrange [split $t] 1 end]; set c [lindex $t 0]; set d [topic:lower $c]
    if {![validchan $c]} { return 0 }
    global topicdb; set t [join [lrange $t 1 end]]; lappend x $t
    for {set p 12} {$p > 0} {incr p -1} {
      if {[info exists topicdb(t,$d,$p)]} { lappend x $topicdb(t,$d,$p) }
    }
    set x [lsort -decreasing -dictionary $x]; set p 1
    foreach y $x { set topicdb(t,$d,$p) $y; incr p 1 }
  }
  if {[string equal $z "T"]} { topic:save; topic:setnew $c 1 }
# E
  if {[string equal $z "E"]} { topic:save; foreach c [channels] { topic:setnew $c 1 } }
# R
  if {[string equal $z "R"]} {
    set c [lindex [split $t] 0]
    if {![validchan $c] || ![botonchan $c]} { return 0 }
    set t [topic $c]
    if {[string equal $t ""]} { return 0 }
    topic:change $c $t
  }
}


####################################################
# topic:lower
####################################################
proc topic:lower { t } {
  global rfc-compliant
  if {[info exists rfc-compliant] && [string equal ${rfc-compliant} "1"]} {
    set t [string map "\\{ \[ \\} \] ~ ^ \\\\ |" $t]
  }
  set t [string tolower $t]
  return $t
}


####################################################
# topic:ts
####################################################
proc topic:ts { t } {
  if {![string is digit $t]} { return 0 }
  set n [clock seconds]; set t [expr $t - $n]; 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] ""]
}


set scriptdb(topic) {
  "provides topic command to set, add, del, insert, replace, refresh, list, show channel topics"
}

