####################################################
# by wiebe @ QuakeNet
#
####################################################

# remove bot flags 12, check for g, keep deleted items for sometime, just burst
# loading, new entries, sent over botnet?

# ?? [<chan>] <entry> <nick>
#	when <nick> is a channel op, the script assumes an error
#	and does not say it to <nick>

# channel flags
# +term		set ?? trigger on/off
# +term-noglobal	global terms are ignored
# +term-globaloverride	global terms override channel terms
# +term-silent		unknown users get a private reply
# +term-nounknown	unknown users are ignored completly
# +term-op		any @ and % can redirect to a nick
# +term-voice		any +, % and @ can redirect to a nick
# +term-msg		sent replies with privmsg instead of by notice
# +term-share		channel terms can  be shown on other chans
#  term-flood		a term will not be repeated in the channel for this duration in seconds
# +term-redirect	auto use channel specified with term-redirectchan
#  term-redirectchan	when +term-redirect, and no chan is used, auto use this chan
# +term-announce	set on/off channel announcements (use "botnick term announce")
#  term-announcedelay	time in minutes between announcements


# botnet
# bot flag 1 = share database
# bot flag 2 = bot acts as hub
#
# Bot A set with 12 (on itself and Bot B)
# Bot B set with 1 (on itself and Bot A)
# Bot A and B share entries when linked
# when bots A and B link, Bot A sends database to Bot B


####################################################
# settings
####################################################
# file to save database
set termsetting(file) "termdb.txt"

# max allowed lines per entry (item item_2 .. item_N)
set termsetting(maxlines) "5"

# key to use for encrypting data sent over botnet
set termsetting(key) "7c386761e1e5a7765e41c9de8864abe7"

# reload database on every check? 1=yes, 0=no
# only needed if the database can be altered by other means than this script
set termsetting(reload) "0"

####################################################
# create flags
####################################################
setudef flag term
setudef flag term-announce
setudef flag term-globaloverride
setudef flag term-noglobal
setudef flag term-silent
setudef flag term-nounknown
setudef flag term-msg
setudef flag term-voice
setudef flag term-op
setudef flag term-share
setudef flag term-redirect
setudef int term-flood
setudef int term-announcedelay
setudef str term-redirectchan


####################################################
# term:help:pubm
####################################################
bind pubm vlomn|vlomn "% ${botnet-nick} help term" term:help:pubm
proc term:help:pubm { n u h c t } {
  if {[matchattr $h bkZ]} { return 0 }
  lappend o "term: usage term \[<chan>\] show|add|mod|del|info|list|listfull|announce|help"
  if {[string equal [info procs cnotice] ""]} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "term: " } }
  putloglev c $c "help: $n $u $h $c term"
  return 1
}


####################################################
# term:help:msgm
####################################################
bind msgm vlomn|vlomn "help term" term:help:msgm
proc term:help:msgm { n u h t } {
  if {[matchattr $h bkZ]} { return 0 }
  lappend o "term: usage term \[<chan>\] show|add|mod|del|info|list|listfull|announce|help"
  if {[string equal [info procs cnotice] ""]} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "term: " } }
  putcmdlog "($n!$u) !$h! help term"
  return 1
}


####################################################
# term:help
####################################################
proc term:help { t } {
  set c [string tolower [lindex [split $t] 1]]
  if {[string equal $c "general"]} {
    lappend o "term: handles term entries"
    lappend o "term: channel settings: \002+term\002 (enable)   \002+term-globaloverride\002 (global entries override channel entries)   \002+term-noglobal\002 (global entries are ignored)   \002+term-silent\002 (unknown users get a private reply)   \002+term-nounknown\002 (unknown users are ignored)   \002+term-op\002 (opped user can redirect)   \002+term-voice\002 (opped/voiced user can redirect)"
    lappend o "term: channel settings: \002+term-share\002 (channel entries can be shown on other channels)   \002+term-redirect\002 (use entries from another channel by default)   \002term-redirectchan\002 (used together with +term-redirect, this specifies the channel to use)   \002term-flood\002 (do not repeat the same entry in the channel for X seconds)"
  } elseif {[string equal $c "add"]} {
    lappend o "term: term \[<chan>\] add <term> <description>"
    lappend o "term: adds an entry to the term database for the list with the given description"
    lappend o "term: channel name \"global\" can be used for the global list. entries can have multiple lines (item, item_2, item_3, etc.)"
  } elseif {[string equal $c "mod"]} {
    lappend o "term: term \[<chan>\] mod <term> <description>"
    lappend o "term: modifies an entry in the term database"
    lappend o "term: channel name \"global\" can be used for the global list"
  } elseif {[string equal $c "del"]} {
    lappend o "term: term \[<chan>\] del <term>"
    lappend o "term: deletes an entry from the term database"
    lappend o "term: channel name \"global\" can be used for the global list"
  } elseif {[string equal $c "info"]} {
    lappend o "term: term \[<chan>\] info <term>"
    lappend o "term: shows info for an entry in the term database"
    lappend o "term: channel name \"global\" can be used for the global list"
  } elseif {[string equal $c "list"]} {
    lappend o "term: term \[<chan>\] list <term> \[<description>\]"
    lappend o "term: lists matching items and the number of times they have been used, wildcards are supported in <term> and <description>"
    lappend o "term: channel name \"global\" can be used for the global list"
  } elseif {[string equal $c "listfull"]} {
    lappend o "term: term \[<chan>\] listfull <term> \[<description>\]"
    lappend o "term: lists matching items, wildcards are supported in <term> and <description>"
    lappend o "term: channel name \"global\" can be used for the global list"
  } elseif {[string equal $c "show"]} {
  # change to "bot ?? term.."  ?
    lappend o "term: term \[<chan>\] show <term> \[<nick>\]"
    lappend o "term: same as the \"??\" command, but forces this bot to respond (only works in channel)"
    lappend o "term: channel name \"global\" can be used for the global list"
  } elseif {[string equal $c "announce"]} {
    lappend o "term: term \[<chan>\] announce on|off \[<delay>\]"
    lappend o "term: enables or disables announcements. add the text to announce under entry \"announce\" (supports multiple lines, see add). delay is given in minutes"
  } else {
    lappend o "term: help show|add|mod|del|info|list|listfull|announce|general"
    lappend o "term: shows help in general or for the given subcommand"
  }
  return $o
}


####################################################
# term:pubm
####################################################
bind pubm omn|vlomn "% ${botnet-nick} term" term:pubm
bind pubm omn|vlomn "% ${botnet-nick} term *" term:pubm
proc term: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 "add mod del info list listfull announce 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 "add"] } { set o [term:add $h $t $c]
  } elseif {[string equal $d "mod"]} { set o [term:mod $h $t $c]
  } elseif {[string equal $d "del"]} { set o [term:del $h $t $c]
  } elseif {[string equal $d "info"]} { set o [term:info $h $t $c]
  } elseif {[string equal $d "list"]} { set o [term:list $h $t 200 $c]
  } elseif {[string equal $d "listfull"]} { set o [term:listfull $h $t 10 $c]
  } elseif {[string equal $d "show"]} {
# ?
    if {![channel get $c term]} { return 0 }
    term:check $n $u $h $c [join [lrange [split $t] 2 end]] 1
    set o ""
  } elseif {[string equal $d "announce"]} { set o [term:announce $h $t $c]
  } elseif {[string equal $d "help"]} { set o [term:help $t]
  } else { lappend o "term: usage term \[<chan>\] show|add|mod|del|info|list|listfull|announce|help" }
  if {[string equal [info procs cnotice] ""]} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "term: " } }
  putloglev c $c "term: $n $u $h $c $d $t"
  return 0
}


####################################################
# term:msg
####################################################
bind msg omn|vlomn term term:msg
proc term: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 "add mod del info list listfull announce 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 "add"]} { set o [term:add $h $t]
  } elseif {[string equal $d "mod"]} { set o [term:mod $h $t]
  } elseif {[string equal $d "del"]} { set o [term:del $h $t]
  } elseif {[string equal $d "info"]} { set o [term:info $h $t]
  } elseif {[string equal $d "list"]} { set o [term:list $h $t 200]
  } elseif {[string equal $d "listfull"]} { set o [term:listfull $h $t 10]
  } elseif {[string equal $d "announce"]} { set o [term:announce $h $t]
  } elseif {[string equal $d "help"]} { set o [term:help $t]
  } else { lappend o "term: usage term \[<chan>\] add|mod|del|info|list|listfull|announce|help" }
  if {[string equal [info procs cnotice] ""]} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "term: " } }
  return 1
}


####################################################
# term:dcc
####################################################
bind dcc -|- term term:dcc
proc term: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 "add mod del info list listfull announce 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 "add"]} { set o [term:add $h $t $c]
  } elseif {[string equal $d "mod"]} { set o [term:mod $h $t $c]
  } elseif {[string equal $d "del"]} { set o [term:del $h $t $c]
  } elseif {[string equal $d "info"]} { set o [term:info $h $t $c]
  } elseif {[string equal $d "list"]} { set o [term:list $h $t 500 $c]
  } elseif {[string equal $d "listfull"]} { set o [term:listfull $h $t 150 $c]
  } elseif {[string equal $d "announce"]} { set o [term:announce $h $t $c]
  } elseif {[string equal $d "help"]} { set o [term:help $t]
  } else { lappend o "term: usage term \[<chan>\] add|mod|del|info|list|listfull|announce|help" }
  foreach l $o { putidx $i $l }
  return 1
}


####################################################
# term:add
# parameter t is '[channel] add term description'
####################################################
proc term:add { h t {c ""} } {
  global termdb termdbi
  term:reload
  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] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  set b [term:lower $c]; set u [lindex $t 0]
  set v [string tolower $u]; set d [join [lrange $t 1 end]]
  if {[string equal $b "global"]} { set l "global list" } else { set l "list of $c" }
  set e "term: term \[<chan>\] add <term> <description>"
  if {[string equal $b ""]} {
    lappend o "term: no channel specified"; lappend o $e
  } elseif {![string match \#* $b] && ![string equal $b "global"]} {
    lappend o "term: invalid channel specified" lappend o $e
  } elseif {[string equal $v ""]} {
    lappend o "term: no term specified"; lappend o $e
  } elseif {[string equal $v "global"] || [string match \#* $v]} {
    lappend o "term: invalid term specified (\"global\" is not allowed and can not start with \"#\")"
  } elseif {[string equal $d ""]} {
    lappend o "term: no description specified"; lappend o $e
  } elseif {[string length $v] > "30"} {
    lappend o "term: term too long, max allowed is 30 chars"; lappend o $e
  } elseif {[string equal $b "global"] && ![matchattr $h omn]} {
    lappend o "term: no access to add global entries"
  } elseif {![string equal $b "global"] && (![validchan $b] || ![matchattr $h omn|omn $b])} {
    lappend o "term: no access or unknown channel $c"; lappend o $e
  } elseif {[info exists termdb($b,$v)]} {
    lappend o "term: an entry with the name \"$v\" already exists on $l"
  } else {
    set termdb($b,$u) $d; set termdbi($b,$u) "$h [clock seconds] 0 0"
    term:send $b $u add; term:save
    lappend o "term: added \"$v\" to $l with description:"
    lappend o "term: $d"
  }
  return $o
}


####################################################
# term:mod
# parameter t is '[channel] mod term description'
####################################################
proc term:mod { h t {c ""} } {
  global termdb termdbi
  term:reload
  set d [lindex $t 0]; set t [lrange $t 1 end]
  if {![string equal -nocase $d "mod"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  set b [term:lower $c]; set v [lindex $t 0]
  set u [string tolower $v]; set d [join [lrange $t 1 end]]
  if {[string equal $b "global"]} { set l "global list" } else { set l "list of $c" }
  set e "term: term \[<chan>\] mod <term> <description>"
  if {[string equal $b ""]} {
    lappend o "term: no channel specified"; lappend o $e
  } elseif {![string match \#* $b] && ![string equal $b "global"]} {
    lappend o "term: invalid channel specified"; lappend o $e
  } elseif {[string equal $u ""]} {
    lappend o "term: no term specified"; lappend o $e
  } elseif {[string equal $d ""]} {
    lappend o "term: no description specified"; lappend o $e
  } elseif {[string equal $b "global"] && ![matchattr $h omn]} {
    lappend o "term: no access to mod global entries"
  } elseif {![string equal $b "global"] && (![validchan $b] || ![matchattr $h omn|omn $b])} {
    lappend o "term: no access or unknown channel $c"; lappend o $e
  } elseif {![info exists termdb($b,$u)]} {
    lappend o "term: an entry with the name \"$v\" does not exist on $l"
  } else {
    lappend o "term: modified \"$v\" in $l with description:"
    lappend o "term: from: $termdb($b,$u)"
    lappend o "term: to: $d"
    set termdb($b,$u) $d
    set termdbi($b,$u) "$h [clock seconds] [join [lrange [split $termdbi($b,$u)] 2 end]]"
    term:send $b $u add; term:save
  }
  return $o
}


####################################################
# term:del
# parameter text is '[channel] del term'
####################################################
proc term:del { h t {c ""} } {
  global termdb termdbi
  term:reload
  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] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  set b [term:lower $c]; set v [lindex $t 0]; set u [string tolower $v]
  if {[string equal $b "global"]} {  set l "global list" } else { set l "list of $c" }
  set e "term: term \[<chan>\] del <term>"
  if {[string equal $b ""]} {
    lappend o "term: no channel specified"; lappend o $e
  } elseif {![string match \#* $b] && ![string equal $b "global"]} {
    lappend o "term: invalid channel specified"; lappend o $e
  } elseif {[string equal $u ""]} {
    lappend o "term: no term specified"; lappend o $e
  } elseif {[string equal $b "global"] && ![matchattr $h omn]} {
    lappend o "term: no access to delete global entries"
  } elseif {![string equal $b "global"] && (![validchan $b] || ![matchattr $h omn|omn $b]) } {
    lappend o "term: no access or unknown channel $c"; lappend o $e
  } elseif {![info exists termdb($b,$u)]} {
    lappend o "term: an entry with the name \"$v\" does not exist on $l"; lappend o $e
  } else {
    lappend o "term: deleted \"$v\" from $l with description:"
    foreach f [term:findall $b $u] {
      lappend o "term: $termdb($b,$f)"
      unset termdb($b,$f); unset termdbi($b,$f); term:send $b $f del
    }
    term:save
  }
  return $o
}


####################################################
# term:info
# parameter text is '[channel] info term'
####################################################
proc term:info { h t {c ""} } {
  global termdb termdbi
  term:reload
  set d [lindex $t 0]; set t [lrange $t 1 end]
  if {![string equal -nocase $d "info"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  set b [term:lower $c]; set v [lindex $t 0]; set u [string tolower $v]
  if {[string equal $b "global"] } { set l "global list" } else { set l "list of $c" }
  set e "term: term \[<chan>\] info <chan> <term>"
  if {[string equal $b ""]} {
    lappend o "term: no channel specified"; lappend o $e
  } elseif {![string match \#* $b] && ![string equal $b "global"] } {
    lappend o "term: invalid channel specified"; lappend o $e
  } elseif {[string equal $u ""]} {
    lappend o "term: no term specified"; lappend o $e
  } elseif {[string equal $b "global"] && ![matchattr $h omn]} {
    lappend o "term: no access to view info of global entries"
  } elseif {![string equal $b "global"] && (![validchan $b] || ![matchattr $h omn|omn $b]) } {
    lappend o "term: no access or unknown channel $c"; lappend o $e
  } elseif {![info exists termdb($b,$u)] && [string equal [set u [term:findbest $b $u]] ""] } {
    lappend o "term: an entry with the name \"$v\" does not exist on $l"; lappend o $e
  } else {
    if {![string equal -nocase $v $u]} { set v $u }
    set d $termdb($b,$u)
    set x [split $termdbi($b,$u)]
    set h [lindex $x 0]; set s [lindex $x 1]; set u [lindex $x 2]; set z [lindex $x 3]
    set s "on [clock format $s -format %d/%m/%y] ([term:ts $s] ago)"
    if {[string equal $u 0]} { set u "never used"
    } else { set u "last used on [clock format $u -format %d/%m/%y] ([term:ts $u] ago)" }
    lappend o "term: entry \"$v\" on $l was added by $h $s     used $z times     $u"
  }
  return $o
}


####################################################
# term:list
# parameter text is '[channel] list term description'
####################################################
proc term:list { h t z {c ""} } {
  global termdb termdbi
  term:reload
  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 b [term:lower $c]; set v [lindex $t 0]
  set u [string tolower $v]; set d [join [lrange $t 1 end]]
  if {[string equal $d ""]} { set d * }
  set e $d; set f "term: term \[<chan>\] list <term> \[<description>\]"
  if {[string equal $b "global"]} { set l "global list" } else { set l "list of $c" }
  if {[string equal $b ""]} {
    lappend o "term: no channel specified"; lappend o $f
  } elseif {![string match \#* $b] && ![string equal $b "global"]} {
    lappend o "term: no valid channel specified"; lappend o $f
  } elseif {[string equal $u ""]} {
    lappend o "term: no term specified"; lappend o $f
  } elseif {[string equal $b "global"] && ![matchattr $h omn]} {
    lappend o "term: no access to search the global list"
  } elseif {![string equal $b "global"] && (![validchan $b] || ![matchattr $h omn|vlomn $b]) } {
    lappend o "term: no access or unknown channel $c"; lappend o $f
  } else {
    regsub -all {[][\\]} $u {\\\0} u; regsub -all {[][\\]} $e {\\\0} e
    set x 0; set r ""; set k ""
    foreach n [lsort -dictionary [array names termdb]] {
      if {[string match $b,$u $n] && [string match -nocase $e $termdb($n)]} {
        set h [join [lrange [split $n ,] 1 end] ,]; set y [lindex [split $termdbi($n)] 3]
        if {[string equal $x "0"]} { lappend o "term: listing entries" }
        incr x 1
        if {[string equal $x [expr $z +1]] && ![string equal $z "0"]} {
          set k "term: more than $z results, aborting.."; break
        }
        lappend r "${h}($y)"
      }
    }
    if {![string equal $r ""]} { lappend o "term: [join $r "   "]" }
    if {![string equal $k ""]} { lappend o $k }
    if {[string equal $x "0"]} {
      lappend o "term: no entries found matching entry name \"$v\" and description \"$d\" on $l"
    } else {
      lappend o "term: end of list for entries matching entry name \"$v\" and description \"$d\" on $l"
    }
  }
  return $o
}


####################################################
# term:listfull
# parameter text is '[channel] listfull term description'
####################################################
proc term:listfull { h t z {c ""} } {
  global termdb
  term:reload
  set d [lindex $t 0]; set t [lrange $t 1 end]
  if {![string equal -nocase $d "listfull"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  set b [term:lower $c]; set v [lindex $t 0]
  set u [string tolower $v]; set d [join [lrange $t 1 end]]
  if {[string equal $d ""]} { set d * }
  set e $d; set f "term: term \[<chan>\] listfull <term> \[<description>\]"
  if {[string equal $b "global"]} { set l "global list" } else { set l "list of $c" }
  if {[string equal $b ""]} {
    lappend o "term: no channel specified"; lappend o $f
  } elseif {![string match \#* $b] && ![string equal $b "global"]} {
    lappend o "term: no valid channel specified"; lappend o $f
  } elseif {[string equal $u ""]} {
    lappend o "term: no term specified"; lappend o $f
  } elseif {[string equal $b "global"] && ![matchattr $h omn]} {
    lappend o "term: no access to search the global list"
  } elseif {![string equal $b "global"] && (![validchan $b] || ![matchattr $h omn|vlomn $b]) } {
    lappend o "term: no access or unknown channel $c"; lappend o $f
  } else {
    regsub -all {[][\\]} $u {\\\0} u; regsub -all {[][\\]} $e {\\\0} e
    set x 0
    foreach n [lsort [array names termdb]] {
      if {[string match $b,$u $n] && [string match -nocase $e $termdb($n)]} {
        set h [join [lrange [split $n ,] 1 end] ,]; set i $termdb($n)
        if {[string equal $x "0"]} { lappend o "term: listing entries" }
        incr x 1
        if {[string equal $x [expr $z +1]] && ![string equal $z "0"]} {
          lappend o "term: more than $z results, aborting.."
          break
        }
        lappend o "term: $h - $i"
      }
    }
    if {[string equal $x "0"]} {
      lappend o "term: no entries found matching entry name \"$v\" and description \"$d\" on $l"
    } else {
      lappend o "term: end of list for entries matching entry name \"$v\" and description \"$d\" on $l"
    }
  }
  return $o
}


####################################################
# term:announce
# parameter text is '[channel] announce on|off delay'
####################################################
proc term:announce { h t {c ""} } {
  global termdb
  set d [lindex $t 0]; set t [lrange $t 1 end]
  if {![string equal -nocase $d "announce"]} { set c $d; set t [lrange $t 1 end] }
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  set b [term:lower $c]; set s [string tolower [lindex $t 0]];  set d [lindex $t 1]
  if {[string equal $d ""] && [validchan $c]} {
    set d [channel get $c term-announcedelay]
    if {$d < "5"} { set d 5 }
  }
  if {[string equal $c ""]} {
    lappend o "term: no channel specified"
    lappend o "term: term \[<chan>\] announce on|off \[<delay>\]"
  } elseif {![validchan $c] || ![matchattr $h omn|omn $c]} {
    lappend o "term: no access or unknown channel $c"
  } elseif {[string equal $s ""]} {
    lappend o "term: no status specified."
  } elseif {![string equal $s "on"] && ![string equal $s "off"]} {
    lappend o "term: invalid value for status specified (must be on or off)"
  } elseif {[string equal $s "on"] && ![string is int $d]} {
    lappend o "term: invalid delay specified (delay is given in minutes)"
  } elseif {[string equal $s "off"]} {
    channel set $c -term-announce
    lappend o "term: announcement disabled on $c"
  } elseif {[string equal $s "on"]} {
    term:reload
    if {![info exists termdb($b,announce)]} {
      lappend o "term: no entry with name \"announce\" exists on term list for $c"
    } else {
      channel set $c +term-announce
      channel set $c term-announcedelay $d
      lappend o "term: announcement enabled with delay $d minutes on $c"
      lappend o "term: announcing following text:"
      foreach n [term:findall $b announce] { lappend result "term: $termdb($b,$n)" }
    }
  }
  return $o
}


####################################################
# term:announcetime
####################################################
bind time - "* * * * *" term:announcetime
proc term:announcetime { n h d m y} {
  global termdb
  set t [expr [clock seconds] / 60]
  foreach c [channels] {
    set c [term:lower $c]
    if {[channel get $c inactive]} { continue }
    if {![botonchan $c]} { continue }
    if {![channel get $c term-announce]} { continue }
    set d [channel get $c term-announcedelay]
    if {$d <= "5"} { set d 5 }
    if {![info exists termdb($c,announce)]} { channel set $c -term-announce; continue }
    if {![term:whichbot $c $t]} { continue }
    if {![string equal [expr round(fmod($t,$d))] 0]} { continue }
    set g 0; if {[string match *c* [lindex [split [getchanmode $c]] 0]]} { set g 1 }
    foreach n [term:findall $c announce] {
      set l $termdb($c,$n)
      if {$g} { set l [stripcodes bcru $l] }
      if {[string equal [info procs privmsg] ""]} { puthelp "PRIVMSG $c :$l"
      } else { privmsg $c $l puthelp "" }
    }
  }
}


####################################################
# term:check ??
####################################################
bind pubm -|- "% \\?\\? *" term:check
proc term:check { n u h c t {b 0} } {
  if {[matchattr $h bkZ]} { return 0 }
  if {![string equal [info procs matchignore] ""] && [matchignore $n!$u]} { return 0 }
  if {![validchan $c]} { return 0 }
  if {![channel get $c term]} { return 0 }
# unknown user joined less than 10s ago
  if {![matchattr $h fvlomn|fvlomn $c] && [expr [clock seconds] - [getchanjoin $n $c]] < 10} { return 0 }
# multibot support, should this bot respond?
  if {![term:whichbot $c $t] && !$b} { return 0 }
# redirect(2), channel(1) or private(0)
  set w 0
  if {[matchattr $h omn|fvlomn $c]} { set w 2 }
  if {[channel get $c term-op] && ([isop $n $c] || [ishalfop $n $c])} { set w 2 }
  if {[channel get $c term-voice] && ([isop $n $c] || [ishalfop $n $c] || [isvoice $n $c])} { set w 2 }
  if {![channel get $c term-silent] && ![string equal $w 2]} { set w 1 }
  if {[channel get $c term-nounknown] && [string equal $w 0]} { return 0 }
  set x [lindex [split [getchanmode $c]] 0]
  if {[string match *m* $x] && ![botisop $c] && ![botishalfop $c] && ![botisvoice $c]} { set w 0 }
# get entry
  set v [join [lrange [split $t] 1 end]]; set v [term:findentry $c $v]
  set f [lindex $v 0]; set e [lindex $v 1]; set u [lindex $v 3]; set r [lindex $v 4]
  set v [lindex $v 2]
# sort users
  set y ""; set r [lrange [split $r] 0 5]
  foreach z $r {
    if {![onchan $z $c]} { if {[string equal [info procs chase] ""]} { set z [chase $z] } }
    if {![onchan $z $c]} { continue }
    if {[onchansplit $z $c]} { continue }
    if {[isop $z $c] || [ishalfop $z $c]} { continue }
    if {[isbotnick $z]} { continue }
    if {[string equal -nocase $n $z]} { continue }
    lappend y $z
  }
  set y [join $y]; set r [string trimright [join $r] " "]
# unknown user writing crap, "?? raw bla bla bla"
  if {![string equal $w "2"] && ![string equal $y $r]} { return 0 }
# get all entries for u
  if {![string equal $u ""]} { set u [term:findall $f $u] }
# no match found
  global termdb
  if {[string equal $u ""]} {
    if {[matchattr $h omn|fvlomn $c]} {
      if {[string equal $v ""]} { lappend o "term: ?? \[<chan>\] <term> \[<nick>\]"
      } else { lappend o "term: no entry found for \"$v\"" }
    }
# other chan is used, but not valid, -term-share is set or user has no access there
  } elseif {![string equal -nocase $e $c] && ![string equal $e "global"] && (![validchan $e] || ![channel get $e term-share] || ![matchattr $h omn|vlomn $e])} {
    if {[matchattr $h omn|vlomn $c]} {
      lappend o "term: no access, unknown channel or channel is set -term-share $e"
    }
# send
  } else {
    if {![string equal $w 0]} {
      if {[term:flood $c $f $u]} { return 0 }
      set l "([lindex $u 0]) $termdb($f,[lindex $u 0])"
      if {[string equal $w "2"] && ![string equal $y ""]} { set l "$y: $l" }
      if {[string match *c* $x]} { set l [stripcodes bcru $l] }
      lappend o $l
      foreach l [lrange $u 1 end] {
        set l $termdb($f,$l)
        if {[string match *c* $x]} { set l [stripcodes bcru $l] }
        lappend o $l
      }
      if {[string equal [info procs privmsg] ""]} { foreach l $o { puthelp "PRIVMSG $c :$l" }
      } else { foreach l $o { privmsg $c $l puthelp "term: " } }
      putloglev c $c "[lindex [split $t] 0]: $n $u $h $c [join [lrange [split $t] 1 end]]"
      return 0
    } else {
      lappend o "([lindex $u 0]) $termdb($f,[lindex $u 0])"
      foreach l [lrange $u 1 end] { lappend o $termdb($f,$l) }

      set w [channel get $c term-msg]
      foreach l $o {
        if {$w} {
          if {[string equal [info procs cprivmsg] ""]} { puthelp "PRIVMSG $n :$l"
          } else { cprivmsg $n $l puthelp "" }
        } else {
          if {[string equal [info procs cnotice] ""]} { puthelp "NOTICE $n :$l"
          } else { cnotice $n $l puthelp "" }
        }
      }
      putloglev c $c "[lindex [split $t] 0]: $n $u $h $c [join [lrange [split $t] 1 end]]"
      return 0
    }
  }
  if {![info exists o]} { return 0 }
  if {[string equal [info procs cnotice] ""]} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "" } }
  return 1
}


####################################################
# term:msg
####################################################
bind msg omn|fvlomn ?? term:checkp
proc term:checkp { n u h t } {
  if {[matchattr $h bkZ]} { return 0 }
  
}


####################################################
# term:findentry
####################################################
proc term:findentry { c v } {
  global termdb termdbi
  term:reload

  set d $c
# check if "?? #chan term" is used or "?? global term"
  if {([string match \#* $v] || [string equal -nocase $v "global"]) && ![string equal [lindex [split $v] 1] ""]} {
    set v [split $v]; set d [lindex $v 0]; set v [join [lrange $v 1 end]]
# redirect flag
  } elseif {[channel get $c term-redirect]} {
    if {[validchan [channel get $c term-redirectchan]]} {
      set d [channel get $c term-redirectchan]
    } else { channel set $c -term-redirect }
  }

  set e [term:lower $d]; set f $e;

  if {[string match "\"* *\"*" $v]} {
    regsub {"$|" } $v \n v; set v [split $v \n]
    set r [join [lrange $v 1 end] "\" "]; set v [string range [lindex $v 0] 1 end]
  } else { set v [split $v]; set r [join [lrange $v 1 end]]; set v [lindex $v 0] }

  set u [string tolower $v]
  if {[string equal $u ""]} {
# ?? global term was used
  } elseif {[string equal $e "global"]} {
    if {![info exists termdb($e,$u)]} { set u [term:findbest $e $u] }
# no global, only check channel list
# if other channel is used, only check that, do not check current channel or global list
  } elseif {[channel get $c term-noglobal] || ![string equal -nocase $c $e]} {
    if {![info exists termdb($e,$u)]} { set u [term:findbest $e $u] }
# check global list first
  } elseif {[channel get $c term-globaloverride]} {
    if {[info exists termdb(global,$u)]} { set f "global"
    } elseif {![info exists termdb($e,$u)]} { set u [term:findbest $e $u] }
    if {[string equal $u ""]} {
      set u [string tolower $v]; set u [term:findbest "global" $u]; set f "global"
    }
# check channel list first
  } else {
    if {![info exists termdb($e,$u)] && [info exists termdb(global,$u)]} { set f "global"
    } elseif {![info exists termdb($e,$u)]} { set u [term:findbest $e $u]
      if {[string equal $u ""]} {
        set u [string tolower $v]; set u [term:findbest "global" $u]; set f "global"
      }
    }
  }
  if {[info exists termdbi($f,$u)] && ![string equal $u ""]} {
    set z [split $termdbi($f,$u)]
    set termdbi($f,$u) "[join [lrange $z 0 1]] [clock seconds] [expr [lindex $z 3] +1]"
  }
  lappend o $f; lappend o $e; lappend o $v; lappend o $u; lappend o $r
  return $o
}


####################################################
# term:findbest
####################################################
proc term:findbest { c t } {
  global termdb; set c [term:lower $c]; set t [string tolower $t]
  regsub -all {[][\\]} $t {\\\0} t; regsub -all {[][\\]} $c {\\\0} c; set x 2
  for {set x 2} {$x <= 8} {incr x 1} { set f($x) "" }
  foreach n [lsort [array names termdb]] {
    set d [string tolower $termdb($n)]; set e [join [lrange [split $n ,] 1 end] ,]
    if {[string match "$c,$t*" $n]} { return $e }
    if {[string match "$c,[string range $t 0 2]*" $n] && [string equal $f(2) ""]} { set f(2) $e }
    if {[string match "$c,*$t" $n] && [string equal $f(3) ""]} { set f(3) $e }
    if {[string match "$c,*$t*" $n] && [string equal $f(4) ""]} { set f(4) $e }
# matching entry names failed, match descriptions
    if {![string match "$c,*" $n]} { continue }
    if {[string match "* $t *" $d] && [string equal $f(5) ""]} { set f(5) $e }
    if {[string match "* $t*" $d] && [string equal $f(6) ""]} { set f(6) $e }
    if {[string match "*$t *" $d] && [string equal $f(7) ""]} { set f(7) $e }
    if {[string match "*$t*" $d] && [string equal $f(8) ""]} { set f(8) $e }
  }
  for {set x 2} {$x <= 8} {incr x 1} { if {![string equal $f($x) ""]} { return $f($x) } }
  return ""
}


####################################################
# term:findall
####################################################
proc term:findall { c t } {
  global termsetting termdb
  if {![info exists termdb($c,$t)]} { return "" }
  lappend r $t
  if {![info exists termsetting(maxlines)]} { set termsetting(maxlines) 4 }
  set m $termsetting(maxlines)
  if {![string is digit $m]} { set m 4 }
  for {set x 2} {$x <= $m} {incr x 1} {
    if {[info exists termdb($c,${t}_$x)]} { lappend r ${t}_$x }
  }
  return $r
}


####################################################
# term:flood
####################################################
proc term:flood { c d t } {
  global termfl
  set i [term:lower $c,$d,$t]; if {[string length $i] > 32} { set i [md5 $i] }
  set s [clock seconds]; set l [channel get $c term-flood]
  if {$l < 5} { set l 5 }
  if {![info exists termfl($i)]} { set termfl($i) $s; return 0
  } elseif {[expr $s - $termfl($i)] <= $l} { return 1
  } else { set termfl($i) $s; return 0 }
}


####################################################
# term:export
####################################################
proc term:export { c t } {
  if {![validchan $c] && ![string equal -nocase $c "global"]} { return "" }
  if {[string equal $t ""]} { return "" }
  global termdb
  set v [term:findentry $c $t]
  set f [lindex $v 0]; set u [lindex $v 3]
  if {![info exists termdb($f,$u)]} { return "" }
  return "($u) $termdb($f,$u)"
}


####################################################
# term:cleanup
####################################################
bind time - "?0 * * * *" term:cleanup
proc term:cleanup { n h d m y } {
  if {[string equal $::botname $::botnick]} { return 0 }
  global termfl; set t [clock seconds]
  foreach n [array names termfl] { if {[expr $t - $termfl($n)] > "120"} { unset termfl($n) } }
}


####################################################
# term:backup
####################################################
bind time - "00 04 * * *" term:backup
proc term:backup { n h d m y } {
  term:save
  global termsetting
  set s $termsetting(file)
  if {![file exists $s]} { return 0 }
  for {set x 15} {$x > 1} {incr x -1} {
    set f $s[expr $x -1]
    if {[file exists $f]} { file copy -force $f $s$x }
  }
  file copy -force $s $s$x
}


####################################################
# term:ts
####################################################
proc term: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] ""]
}


####################################################
# term:lower
####################################################
proc term: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
}


####################################################
# term:save
####################################################
proc term:save { } {
  global termsetting termdb termdbi
  set f $termsetting(file)
  if {[file isdirectory $f]} { return 0 }
  if {![file writable $f] && [file exists $f]} { return 0 }
  set f [open $f w]
  foreach n [lsort [array names termdb]] {
    set c [lindex [split $n ,] 0]; set t [join [lrange [split $n ,] 1 end] ,]; set d $termdb($n)
    set z [split $termdbi($c,$t)]
    set h [lindex $z 0]; set s [lindex $z 1]; set u [lindex $z 2]; set x [lindex $z 3]
    puts $f "$c $t $h $s $u $x $d"
  }
  close $f
}


####################################################
# term:load
####################################################
proc term:load { } {
  global termsetting termdb termdbi
  if {[info exists termdb]} { unset termdb }
  if {[info exists termdbi]} { unset termdbi }
  set f $termsetting(file)
  if {![file exists $f]} { return 0 }
  if {![file isfile $f]} { return 0 }
  if {[file isdirectory $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 ""]} {
      set l [split $l]; set c [lindex $l 0]; set t [string tolower [lindex $l 1]]
      set h [lindex $l 2]; set s [lindex $l 3]; set u [lindex $l 4]; set x [lindex $l 5];
      set e [join [lrange $l 6 end]]
      set termdb($c,$t) "$e"; set termdbi($c,$t) "$h $s $u $x"
    }
  }
}
term:load


####################################################
# term:reload
####################################################
proc term:reload { } {
  global termsetting
  if {![info exists termsetting(reload)]} { set termsetting(reload) 0 }
  if {[string equal $termsetting(reload) 1]} { term:load }
}


####################################################
# term:whichbot
# returns 1 if this bot should respond, 0 otherwise
####################################################
proc term:whichbot { c t } {
  if {![validchan $c]} { return 0 }
  if {[string equal $t ""]} { set t a }
  global botnet-nick; set a [chanlist $c b]; lappend r ${botnet-nick}
  foreach b $a {
    if {[onchansplit $b $c]} { continue }
    set h [nick2hand $b $c]
    if {![string match *1* [botattr $h]]} { continue }
    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 }
}


####################################################
# term:send
####################################################
proc term:send { c t a } {
  global termsetting termdb termdbi
  if {![info exists termsetting(key)]} { set termsetting(key) "" }
  set k $termsetting(key)
  if {[string equal $a "add"]} {
    set d $termdb($c,$t)
    set h [lindex [split $termdbi($c,$t)] 0]; set s [lindex [split $termdbi($c,$t)] 1]
    lappend o "a1 $c $t $h $s"
# break up lines, messages over botnet are limited to 400 chars!?
    while {![string equal [string length $d] 0]} {
      lappend o "a2 $c $t [string range $d 0 99]"; set d [string range $d 100 end]
    }
  } elseif {[string equal $a "del"]} { lappend o "d $c $t" } else { return 0 }
  foreach l $o {
    set l [encrypt $k $l]
    foreach b [bots] { if {[string match *1* [botattr $b]]} { putbot $b "term $l" } }
  }
}


####################################################
# term:bot
####################################################
bind bot - term term:bot
proc term:bot { b c t } {
  global termsetting termdb termdbi
  if {![string equal $c "term"]} { return 0 }
  if {![info exists termsetting(key)]} { set termsetting(key) "" }
  set k $termsetting(key); set t [decrypt $k $t]; set c [lindex [split $t] 0]
  set t [lrange [split $t] 1 end]
# add 1, create initial entry
  if {[string equal $c "a1"]} {
    set c [lindex $t 0]; set v [lindex $t 1]; set h [lindex $t 2]; set s [lindex $t 3]
    set termdb($c,$v) ""
    set termdbi($c,$v) "$h $s"
    term:save
# add 2, add info to existing entry
  } elseif {[string equal $c "a2"]} {
    set c [lindex $t 0]; set v [lindex $t 1]; set d [join [lrange $t 2 end]]
    if {![info exists termdb($c,$v)]} { set termdb($c,$v) "" }
    append termdb($c,$v) $d
    term:save
# del
  } elseif {[string equal $c "d"]} {
    set c [lindex $t 0]; set v [lindex $t 1]
    if {[info exists termdb($c,$v)]} { unset termdb($c,$v) }
    if {[info exists termdbi($c,$v)]} { unset termdbi($c,$v) }
    term:save
# add all 1, create initial entry
  } elseif {[string equal $c "aa1"] && [string match *2* [botattr $b]]} {
    set c [lindex $t 0]; set v [lindex $t 1]; set h [lindex $t 2]; set s [lindex $t 3]
    set termdb($c,$v) ""; set termdbi($c,$v) "$h $s"
    term:save
# add all 2, add to info to existing entry
  } elseif {[string equal $c "aa2"] && [string match *2* [botattr $b]]} {
    set c [lindex $t 0]; set v [lindex $t 1]; set d [join [lrange $t 2 end]]
    if {![info exists termdb($c,$v)]} { set termdb($c,$v) "" }
    append termdb($c,$v) $d
    term:save
# delete all
  } elseif {[string equal $c "da"] && [string match *2* [botattr $b]]} {
    if {[info exists termdb]} { unset termdb }
    if {[info exists termdbi]} { unset termdbi }
    term:save
  }
}


####################################################
# term:link
####################################################
bind link - * term:link
proc term:link { b z } {
  global botnick termsetting termdb termdbi
  if { [string match *1* [botattr $b]] && [string match *2* [botattr $botnick]] } {
    if {![info exists termsetting(key)]} { set termsetting(key) "" }
    set k $termsetting(key); set l "da"; set l [encrypt $k $l]
    putbot $b "term $l"
    foreach n [lsort [array names termdb]] {
      set c [lindex [split $n ,] 0]; set v [join [lrange [split $n ,] 1 end] ,]
      set h [lindex [split $termdbi($c,$v)] 0]; set s [lindex [split $termdbi($c,$v)] 1]
      set d $termdb($n); set o ""; lappend o "aa1 $c $v $h $s"
      while {![string equal [string length $d] 0]} {
        lappend o "aa2 $c $v [string range $d 0 99]"; set d [string range $d 100 end]
      }
      foreach l $o { set l [encrypt $k $l]; putbot $b "term $l" }
    }
  }
}


set scriptdb(term) {
  "provides term command for dealing with terms on the bot"
  "+term set ?? trigger on/off, +term-noglobal global terms are ignored, +term-globaloverride global terms override channel terms, +term-silent unknown users get a private reply, +term-nounknown unknown users are ignored completly, +term-op any @ and % can redirect to a nick"
  "+term-voice any +, % and @ can redirect to a nick, +term-msg sent replies with privmsg instead of by notice, +term-share channel terms can  be shown on other chans, term-flood a term will not be repeated in the channel for this duration in seconds"
  "+term-redirect auto use channel specified with term-redirectchan, term-redirectchan when +term-redirect, and no chan is used, auto use this chan, +term-announce set on/off channel announcements, term-announcedelay time in minutes between announcements"
}

