####################################################
# by wiebe @ QuakeNet
#
# script makes the bots linked on a botnet
# request/give invite/voice/(half)op from/to eachother
# script verifies the bot's nick!user@host, performs a /WHOIS if needed
# can voice/(half)op a bot even when hidden on the channel by chan mode +D/+d
# incoming and outgoing requests are reported on console mode +b (botnet)
#
# ask for:
#  op when this bot has +o for chan/global
#  halfop when this bot has +l for chan/global
#  voice when this bot has +v for chan/global
#  invite when this bot has any of +fvlo for chan/global
#
# give:
#  op when bot has +o for chan/global
#  halfop when bot has +l for chan/global
#  voice when bot has +v for chan/global
#  invite when bot has any of +fvlo for chan/global
#
# if the bots should have access and ops on all their shared channels,
# give all bots global +fo (also themselves)
#
# see .chattr and .help chattr on the partyline to modify flags
####################################################

# internal notes on how the script works.
# botnet protocol
#   invite:  bnn i 'i <chan> <nick!user@host>'
#    voice:  bnn v 'v <chan> <nick!user@host>'
#   halfop:  bnn l 'l <chan> <nick!user@host>'
#       op:  bnn o 'o <chan> <nick!user@host>'
# add chan:  bnn a 'a <chan> [<bot>]'
# del chan:  bnn d 'd [<chan>]'
# 'text' may be encrypted, key set in botnetneed:encrypt and decrypt procs

# array syntax
#  chan:  bnndb(c,<chan>)
#   bot:  bnndb(b,<bot>,<chan>)
# whois:  bnndb(w,<nick>) 'nick user@host bot' 'chan type' 'chan type'

####################################################
# botnetneed:evnt
####################################################
bind evnt -|- init-server botnetneed:evnt; bind evnt -|- userfile-loaded botnetneed:evnt
proc botnetneed:evnt { t } {
  if {[string equal $t "init-server"]} {
    global bnndb
    foreach e [array names bnndb] { if {[string match {[cw],*} $e]} { unset bnndb($e) } }
    putallbots "bnn d [botnetneed:encrypt d]"
  } elseif {[string equal $t "userfile-loaded"]} {
    set m ${::botnet-nick}
    if {![validuser $m]} { addbot $m "" }
  }
}


####################################################
# botnetneed:time
####################################################
bind time -|- * botnetneed:time
proc botnetneed:time { n h d m y } {
  global bnndb; set o ""; set m ${::botnet-nick}
  foreach c [channels] {
    set c [botnetneed:lower $c]
    if {[botisop $c] || [botishalfop $c]} {
      if {![info exists bnndb(c,$c)]} { set bnndb(c,$c) 0 }
      if {[string equal $bnndb(c,$c) "1"]} { continue }
      set bnndb(c,$c) 1
      lappend o "a [botnetneed:encrypt "a $c"]"
    } elseif {[info exists bnndb(c,$c)]} {
      unset bnndb(c,$c)
      lappend o "d [botnetneed:encrypt "d $c"]"
    }
  }
  foreach e [array names bnndb] {
    if {![string match "c,*" $e]} { continue }
    set c [lindex [split $e ,] 1]
    if {[validchan $c]} { continue }
    unset bnndb($e)
    lappend o "d [botnetneed:encrypt "d $c"]"
  }
  foreach l $o { putallbots "bnn $l" }
}


####################################################
# botnetneed:link
####################################################
bind link -|- * botnetneed:link
proc botnetneed:link { b v } {
  global bnndb; set m ${::botnet-nick}; set o ""
  if {![string equal $b $m] && ![string equal $v $m]} { return 0 }
  foreach e [array names bnndb] {
    if {[string match "c,*" $e]} {
      set c [lindex [split $e ,] 1]
      lappend o "a [botnetneed:encrypt "a $c"]"
    } elseif {[string match "b,*,*" $e]} {
      set e [split $e ,]; set d [lindex $e 1]; set c [lindex $e 2]
      lappend o "a [botnetneed:encrypt "a $c $d"]"
    }
  }
  foreach l $o { putbot $b "bnn $l" }
}


####################################################
# botnetneed:bot
####################################################
bind bot -|- bnn botnetneed:bot
proc botnetneed:bot { b c t } {
  if {![string equal $c "bnn"]} { return 0 }
  global bnndb; set t [split $t]; set c [lindex $t 0]; set t [join [lrange $t 1 end]]
  set t [split [botnetneed:decrypt $t]]; set d [lindex $t 0]; set t [lrange $t 1 end]
  if {![string equal $c $d]} { return 0 }
# add
  if {[string equal $c "a"]} {
    set c [lindex $t 0]; set r [lindex $t 1]
# dont second guess what we receive - and support all channel types, not just # and & channels    
    #if {![string match {[#&]*} $c]} { return 0 }
    if {[string equal $r ""]} { set bnndb(b,$b,$c) 1 } else { set bnndb(b,$r,$c) 1 }
# del
  } elseif {[string equal $c "d"]} {
    set c [lindex $t 0]; set o ""
    if {[string equal $c ""]} {
      foreach e [array names bnndb] {
        if {[string match "b,*,*" $e]} {
          set r [lindex [split $e ,] 1]
          if {[string equal $b $r]} { unset bnndb($e) }
        }
        if {[string match "c,*" $e]} {
          set c [lindex [split $e ,] 1]
          lappend o "a [botnetneed:encrypt "a $c"]"
        }
      }
      foreach l $o { putbot $b "bnn $l" }
    } elseif {[info exists bnndb(b,$b,$c)]} { unset bnndb(b,$b,$c) }
# voice/halfop/op
  } elseif {[string match {[vlo]} $c]} {
    set c [lindex $t 0]; set n [split [lindex $t 1] !]; set u [lindex $n 1]; set n [lindex $n 0]
    if {![validchan $c]} { return 0 }
    if {![botisop $c] && ![botishalfop $c]} { return 0 }
    if {[string match {[ol]} $d] && ![botisop $c]} { return 0 }
    if {[string equal $d "o"] && ![matchattr $b o|o $c]} { return 0 }
    if {[string equal $d "l"] && ![matchattr $b l|l $c]} { return 0 }
    if {[string equal $d "v"] && ![matchattr $b v|v $c]} { return 0 }
    if {[onchan $n $c] && ![onchansplit $n $c]} {
      if {[string equal $d "o"] && [isop $n $c]} { return 0 }
      if {[string equal $d "l"] && [ishalfop $n $c]} { return 0 }
      if {[string equal $d "v"] && [isvoice $n $c]} { return 0 }
    }
    if {[string equal $d "l"]} {
      set p "-1"
      if {![string equal [info procs isupport] ""]} { set p [isupport prefix] }
      if {[string equal $p "-1"]} { set p "vo" }
      if {![string match "*h*" $p]} { return 0 }
    }
    if {[onchan $n] && ![onchansplit $n] && ![string equal $u [getchanhost $n]]} { return 0 }
    if {[string equal $d "l"]} { set d h }
    set m [lindex [split [getchanmode $c]] 0]
# on chan, not split, uhost ok
    if {[onchan $n $c] && ![onchansplit $n $c] && [string equal $u [getchanhost $n $c]]} {
      pushmode $c +$d $n
      putloglev b $c "BNN: giving $n ($u) !$b! +$d on $c"
# +d/D, not on chan
    } elseif {[string match -nocase "*d*" $m] && ![onchan $n $c]} {
# on other chan, uhost ok
      if {[onchan $n] && ![onchansplit $n] && [string equal $u [getchanhost $n]]} {
        puthelp "MODE $c +$d $n"
        putloglev b $c "BNN: giving $n ($u) !$b! +$d on $c"
# whois
      } else {
        set m [botnetneed:lower $n]
        if {![info exists bnndb(w,$m)]} {
          lappend bnndb(w,$m) "$n $u $b"; lappend bnndb(w,$m) "$c $d"; puthelp "WHOIS $n"
        } else { lappend bnndb(w,$m) "$c $d" }
      }
    }
# invite
  } elseif {[string equal $c "i"]} {
    set c [lindex $t 0]; set n [split [lindex $t 1] !]; set u [lindex $n 1]; set n [lindex $n 0]
    if {![validchan $c]} { return 0 }
    if {![matchattr $b fvlo|fvlo $c]} { return 0 }
    if {![botisop $c] && ![botishalfop $c]} { return 0 }
    if {[onchan $n $c] && ![onchansplit $n $c]} { return 0 }
    if {[onchan $n] && ![onchansplit $n]} {
      if {![string equal $u [getchanhost $n]]} { return 0 }
      puthelp "INVITE $n $c"
      putloglev b $c "BNN: giving $n ($u) !$b! invite to $c"
# whois
    } else {
      set m [botnetneed:lower $n]
      if {![info exists bnndb(w,$m)]} {
        lappend bnndb(w,$m) "$n $u $b"; lappend bnndb(w,$m) "$c $d"; puthelp "WHOIS $n"
      } else { lappend bnndb(w,$m) "$c $d" }
    }
  }
}


####################################################
# botnetneed:disc
####################################################
bind disc -|- * botnetneed:disc
proc botnetneed:disc { b } {
  global bnndb
  foreach e [array names bnndb] {
    if {![string match "b,*,*" $e]} { continue }
    set r [lindex [split $e ,] 1]
    if {[string equal $b $r]} { unset bnndb($e) }
  }
}


####################################################
# botnetneed:raw
####################################################
bind raw -|- 311 botnetneed:raw; bind raw -|- 477 botnetneed:raw
proc botnetneed:raw { s n t } {
  set t [lrange [split $t] 1 end]
  if {[string equal $n "311"]} {
    global bnndb
    set n [botnetneed:lower [lindex $t 0]]
    if {![info exists bnndb(w,$n)]} { return 0 }
    set d $bnndb(w,$n); unset bnndb(w,$n)
    set u [split [lindex $d 0]]; set n [lindex $u 0]; set b [lindex $u 2]; set u [lindex $u 1]
    set h [join [lrange $t 1 2] @]; set d [lrange $d 1 end]
    if {![string equal -nocase $u $h]} { return 0 }
    foreach e $d {
      set e [split $e]; set c [lindex $e 0]; set e [lindex $e 1]
      if {![validchan $c]} { continue }
      if {![botisop $c] && ![botishalfop $c]} { continue }
      if {![string match {[ivho]} $e]} { continue }
      if {[string match {[ho]} $e] && ![botisop $c]} { continue }
      if {![onchansplit $n $c]} {
        if {[isop $n $c]} { continue }
        if {[string equal $e "h"] && [ishalfop $n $c]} { continue }
        if {[string equal $e "v"] && [isvoice $n $c]} { continue }
        if {[string equal $e "i"] && [onchan $n $c]} { continue }
      }
      if {[string match {[vho]} $e]} {
        set m [lindex [split [getchanmode $c]] 0]
        if {[string match -nocase "*d*" $m] && ![onchan $n $c]} {
          puthelp "MODE $c +$e $n"
        } else { pushmode $c +$e $n }
        putloglev b $c "BNN: giving $n ($u) !$b! +$e on $c"
      } elseif {[string equal $e "i"]} {
        puthelp "INVITE $n $c"
        putloglev b $c "BNN: giving $n ($u) !$b! invite to $c"
      }
    }
  } elseif {[string equal $n "401"]} {
    global bnndb
    set n [botnetneed:lower [lindex $t 0]]
    if {[info exists bnndb(w,$n)]} { unset bnndb(w,$n) }
  } elseif {[string equal $n "477"]} {
    set c [lindex $t 0]
    botnetneed:need $c invite
  }
}


####################################################
# botnetneed:need
####################################################
bind need -|- * botnetneed:need
proc botnetneed:need { c t } {
  if {![validchan $c]} { return 0 }
  if {[string equal $t "op"]} {
    botnetneed:op $c
  } elseif {![string equal [lsearch -exact "invite key unban limit" $t] "-1"]} {
    botnetneed:invite $c
  }
}


####################################################
# botnetneed:join
####################################################
bind join -|- * botnetneed:join
proc botnetneed:join { n u h c } {
  if {![validchan $c]} { return 0 }
  if {[channel get $c inactive]} { return 0 }
  if {![isbotnick $n]} { return 0 }
  botnetneed:op $c
}


####################################################
# botnetneed:mode
####################################################
bind mode -|- "% +o" botnetneed:mode; bind mode -|- "% +h" botnetneed:mode
proc botnetneed:mode { n u h c m v } {
  if {![validchan $c]} { return 0 }
  if {[channel get $c inactive]} { return 0 }
  if {[botisop $c]} { return 0 }
  set b [nick2hand $v $c]
  if {![matchattr $b b]} { return 0 }
  if {![islinked $b]} { return 0 }
  botnetneed:op $c $b
}


####################################################
# botnetneed:op
####################################################
proc botnetneed:op { c {b ""} } {
  set m ${::botnet-nick}
  if {![botonchan $c]} { return 0 }
  if {[botisop $c]} { return 0 }
  if {[matchattr $m o|o $c]} { set a "o" } elseif {[matchattr $m l|l $c]} { set a "l"
  } elseif {[matchattr $m v|v $c]} { set a "v" } else { return 0 }
  if {[string equal $a "l"] && [botishalfop $c]} { return 0 }
  if {[string equal $a "v"] && [botisvoice $c]} { return 0 }
  global bnndb; set c [botnetneed:lower $c]
  set t "bnn $a [botnetneed:encrypt "$a $c $::botname"]"
  if {![string equal $b ""]} { putbot $b $t
  } else { foreach b [bots] { if {[info exists bnndb(b,$b,$c)]} { putbot $b $t } } }
}


####################################################
# botnetneed:invite
####################################################
proc botnetneed:invite { c } {
  set m ${::botnet-nick}
  if {[botonchan $c]} { return 0 }
  if {[channel get $c inactive]} { return 0 }
  if {![matchattr $m fvlo|fvlo $c]} { return 0 }
  global bnndb; set c [botnetneed:lower $c]
  set t "bnn i [botnetneed:encrypt "i $c $::botname"]"
  foreach b [bots] { if {[info exists bnndb(b,$b,$c)]} { putbot $b $t } }
}


####################################################
# botnetneed:encrypt, k = key
# need the same key on all bots that should work together!
####################################################
proc botnetneed:encrypt { t } { global bnn; set k ""; set t [encrypt $k $t]; return $t }


####################################################
# botnetneed:decrypt, k = key
# need same key as in encrypt proc
####################################################
proc botnetneed:decrypt { t } { global bnn; set k ""; set t [decrypt $k $t]; return $t }


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


####################################################
# set info for script.tcl
####################################################
set ::scriptdb(botnetneed) {
  "Script makes the bots linked on a botnet request/give invite/voice/(half)op from/to eachother.  Script verifies the bot's nick!user@host, performs a /WHOIS if needed.  Can voice/(half)op a bot even when hidden on the channel by chan mode +D/+d.  Incoming and outgoing requests are reported on console mode +b (botnet)."
  "Ask for:  op when this bot has +o for chan/global,  halfop when this bot has +l for chan/global,  voice when this bot has +v for chan/global,  invite when this bot has any of +fvlo for chan/global."
  "Give:  op when bot has +o for chan/global,  halfop when bot has +l for chan/global,  voice when bot has +v for chan/global,  invite when bot has any of +fvlo for chan/global."
  "If the bots should have access and ops on all their shared channels, give all bots global +fo (also themselves).   See .chattr and .help chattr on the partyline to modify flags."
}

