#by wiebe @ QuakeNet


#channel names sent over the botnet are encrypted with md5,
#  so spying eyes on the botnet cant really tell what channel it is

#  bots will op/invite/unban eachother when needed
#  no need to share keys or raise limits, invite overrides such chanmodes
#  invite overrides bans too, however a channel service may enforce bans set (such as Q)
#  when banned, the bot checks the banlist and compares it to its own lists of hosts
#      and requests the removal of the matching bans
#  triggers need bind for raw 477 (not registered) and requests invite
#  bot needs o (either global or for the channel) to gain op
#  bot needs one of fvlo flags (either global or for the channel) to get invite/unban
#  keeps a record of the channels other bots are (half)opped on and shares his own over the botnet
#  can work with hidden users (chanmode D/d)
#  gives op even if the requesting bot looks opped
#      may be spammy on lagged network, in terms of requests on the botnet and mode commands sent
#  script uses extended who (WHOX) supported on ircu
#      to check the bots user@host if it is not known, before invite/unban/giving op
#  wont give/request op, when it has d flag (either global or for the channel)
#  requests op when it sees a linked bot become opped
#  requests op 10 seconds after joining a channel

#add voice / halfop request?


#request op
#bnn o <md5.hash.lower.chan> nick user@host

#request invite
#bnn i <md5.hash.lower.chan> nick user@host

#request unban
#bnn u <md5.hash.lower.chan> banmask

#chanlist where bot is (half)opped
#bnn c [md5.hash.lower.channels]



bind need - * botnetneed:need

proc botnetneed:need { chan type } {
  set chan [string tolower $chan]
  set md5 [md5 $chan]
  if { ![validchan $chan] } { return 0 }
  if { [channel get $chan inactive] } { return 0 }

  global bnn
  set type [string tolower $type]
  if { [lsearch -exact "op unban invite limit key" $type] == "-1" } { return 0 }
  if { [string equal op $type] && [botisop $chan] } { return 0 }
  if { [string equal op $type] && [ischanjuped $chan] } { return 0 }
  if { [string equal [bots] ""] } { return 0 }

  global botnet-nick botnick botname
  set uhost [lindex [split $botname !] 1]
  if { ![matchattr ${botnet-nick} o|o $chan] } { return 0 }
  if { [string equal op $type] && [matchattr ${botnet-nick} d|d $chan] } { return 0 }

  if { [string equal op $type] } {
    foreach b [chanlist $chan b] {
      set bot [nick2hand $b]
      if { [isop $b $chan] && [matchattr $bot o|o $chan] && [islinked $bot] } {
        putbot $bot "bnn o $md5 $botnick $uhost"
        botnetneed:dcc "request op from $bot on $chan"
      }
    }
  } elseif { [string equal unban $type] } {
    global bnn
    set bnn(chan,$chan) 1
    puthelp "MODE $chan +b"
  } else {
    foreach bot [bots] {
      if { [matchattr $bot fvlo|fvlo $chan] && [botnetneed:check $bot $chan] } {
        putbot $bot "bnn i $md5 $botnick $uhost"
        botnetneed:dcc "request invite from $bot for $chan"
      }
    }
  }
  return 0
}


#RAW 477 - ERR_NEEDREGGEDNICK
#<source> 477 <target> <channel> :Cannot join channel (+r)

bind raw - "477" botnetneed:raw477

proc botnetneed:raw477 { nick numeric rest } {
  set chan [lindex [split $rest] 1]
  if { [validchan $chan] && [ischanjuped $chan] } {
    botnetneed:need $chan invite
  }
  return 0
}


bind bot - bnn botnetneed:bot

proc botnetneed:bot { bot cmd text } {
  if { ![string equal -nocase bnn $cmd] } { return 0 }

  set type [string tolower [lindex [split $text] 0]]
  if { [lsearch -exact "o i u c" $type] == "-1" } { return 0 }

  global bnn
  set chan [lindex [split $text] 1]
  set chan [botnetneed:findchan $chan]
  set nick [lindex [split $text] 2]
  set uhost [lindex [split $text] 3]

  if { [string equal c $type] } {
    set bnn(bot,$bot) "[join [lrange [split $text] 1 end]]"

  } elseif { [string equal o $type] } {
    if { [string equal $nick ""] } { return 0 }
    if { ![validchan $chan] } { return 0 }
    if { ![botisop $chan] } { return 0 }
    if { [matchattr $bot d|d $chan] } { return 0 }
    if { [string equal $nick ""] } { return 0 }
    if { [onchan $nick] && ![string equal $uhost [getchanhost $nick]] } { return 0 }
    if { ![isop $nick $chan] && [onchan $nick $chan] } {
      pushmode $chan +o $nick
      botnetneed:dcc "opping $nick ($bot) on $chan"
    } elseif { [isop $nick $chan] } {
      puthelp "MODE $chan +o $nick"
    } elseif { [string match -nocase *d* [lindex [split [getchanmode $chan]] 0]] } {
      global bnn
      if { ![info exists bnn(nick,$nick)] } { set bnn(nick,$nick) "" }
      if { [string equal $bnn(nick,$nick) ""] } {
        set bnn(nick,$nick) "$bot $uhost o,$chan"
        puthelp "WHO $nick n%nuht,234"
      } else {
        append bnn(nick,$nick) " o,$chan"
      }
    }

  } elseif { [string equal i $type] } {
    if { [string equal $nick ""] } { return 0 }
    if { ![validchan $chan] } { return 0 }
    if { ![matchattr $bot o|o $chan] } { return 0 }
    if { ![botisop $chan] } { return 0 }

    if { [string equal $uhost ""] } { return 0 }
    if { [onchan $nick $chan] } { return 0 }
    if { ![onchan $nick] } {
      global bnn
      if { ![info exists bnn(nick,$nick)] } { set bnn(nick,$nick) "" }
      if { [string equal $bnn(nick,$nick) ""] } {
        set bnn(nick,$nick) "$bot $uhost i,$chan"
        puthelp "WHO $nick n%nuht,234"
      } else {
        append bnn(nick,$nick) " i,$chan"
      }
    } elseif { [string equal [getchanhost $nick] $uhost] && [string equal [nick2hand $nick] $bot] } {
      puthelp "INVITE $nick $chan"
      botnetneed:dcc "inviting $nick ($bot) to $chan"
    }

  } elseif { [string equal u $type] } {
    if { ![validchan $chan] } { return 0 }
    if { ![matchattr $bot o|o $chan] } { return 0 }
    if { ![botisop $chan] } { return 0 }
    set ban [lindex [split $text] 2]
    if { ![ischanban $ban $chan] } { return 0 }
    pushmode $chan -b $ban
    botnetneed:dcc "unban $ban for $bot on $chan"
  }
  return 0
}


bind raw -|- "354" botnetneed:raw354

proc botnetneed:raw354 { source numeric text } {
  set t [lindex [split $text] 1]
  if { ![string equal $t 234] } { return 0 }
  global bnn
  set u [lindex [split $text] 2]
  set h [lindex [split $text] 3]
  set n [lindex [split $text] 4]
  if { ![info exists bnn(nick,$n)] } { return 0 }
  set bot [lindex [split $bnn(nick,$n)] 0]
  set uhost [lindex [split $bnn(nick,$n)] 1]
  if { ![string equal $uhost $u@$h] } { return 0 }
  set chans [join [lrange [split $bnn(nick,$n)] 2 end]]
  if { [string equal $chans ""] } { return 0 }
  foreach chan $chans {
    set type [lindex [split $chan ,] 0]
    set chan [lindex [split $chan ,] 1]
    if { [botisop $chan] } {
      if { [string equal $type i] && ![onchan $n $chan] } {
        puthelp "INVITE $n $chan"
        botnetneed:dcc "inviting $n ($bot) to $chan"
      } elseif { [string equal $type o] && ![onchan $n $chan] && [string match -nocase *d* [lindex [split [getchanmode $chan]] 0]] } {
        puthelp "MODE $chan +o $n"
        botnetneed:dcc "opping $n ($bot) on $chan"
      }
    }
  }
  unset bnn(nick,$n)
  return 0
}


bind raw -|- "315" botnetneed:raw315

proc botnetneed:raw315 { source numeric text } {
  set nick [lindex [split $text] 1]
  global bnn
  if { [info exists bnn(nick,$nick)] } { unset bnn(nick,$nick) }
}


#<source> 367 <target> <chan> <banmask> <nick> <time>
bind raw -|- "367" botnetneed:raw367

proc botnetneed:raw367 { source numeric text } {
  set chan [lindex [split $text] 1]
  set chan [string tolower $chan]
  set md5 [md5 $chan]
  if { ![validchan $chan] } { return 0 }
  if { [channel get $chan inactive] } { return 0 }
  if { [botonchan $chan] } { return 0 }
  if { [string equal [bots] ""] } { return 0 }
  global bnn botnet-nick botname
  if { ![info exists bnn(chan,$chan)] } { return 0 }
  set ban [lindex [split $text] 2]
  regsub -all {\[} $ban {\\[} ban
  regsub -all {\]} $ban {\\]} ban
  set hosts "[getuser ${botnet-nick} HOSTS] $botname"
  foreach host $hosts {
    if { [string match -nocase $ban $host] } {
      foreach bot [bots] {
        if { [matchattr $bot fvlo|fvlo $chan] && [botnetneed:check $bot $chan] } {
          putbot $bot "bnn u $md5 $ban"
          botnetneed:dcc "request unban $ban from $bot on $chan"
        }
      }
    }
  }
  return 0
}


#<source> 368 <target> <chan> :End of Channel Ban List.
bind raw -|- "368" botnetneed:raw368

proc botnetneed:raw368 { source numeric text } {
  set chan [lindex [split $text] 1]
  if { ![validchan $chan] } { return 0 }
  if { [channel get $chan inactive] } { return 0 }
  if { [botonchan $chan] } { return 0 }
  if { [string equal [bots] ""] } { return 0 }
  global bnn
  if { ![info exists bnn(chan,$chan)] } { return 0 }
  unset bnn(chan,$chan)
  botnetneed:need $chan invite
  return 0
}


bind evnt - disconnect-server botnetneed:evnt

proc botnetneed:evnt { type } {
  global bnn
  if { ![info exists bnn] } { return 0 }
  foreach name [array names bnn] {
    if { ![string match -nocase bot,* $name] } {
      unset bnn($name)
    }
  }
  putallbots "bnn c"
}


bind join b|- * botnetneed:join

proc botnetneed:join { nick uhost handle chan } {
  if { ![validchan $chan] } { return 0 }
  if { [channel get $chan inactive] } { return 0 }
#if { [isbotnick $nick] } { utimer 10 [list botnetneed:need $chan op] }
}


bind mode -|- "% +o" botnetneed:op

proc botnetneed:op { nick uhost handle chan mode victim } {
  if { ![validchan $chan] } { return 0 }
  if { [channel get $chan inactive] } { return 0 }
  if { [botisop $chan] } { return 0 }
  if { [matchattr [nick2hand $victim] b] && [islinked [nick2hand $victim]] } { botnetneed:need $chan op }
}


proc botnetneed:dcc { text } {
  foreach chat [dcclist chat] {
    set id "[lindex $chat 0]"
    if { [string match *b* [lindex [console $id] 1]] } {
      putdcc $id "BOTNETNEED: $text"
    }
  }
}


bind link - * botnetneed:link

proc botnetneed:link { bot via } {
  if { ![islinked $bot] } { return 0 }
  global bnn
  putbot $bot "c $bnn(chans)"
}


bind disc - * botnetneed:disc

proc botnetneed:disc { bot } {
  if { [islinked $bot] } { return 0 }
  global bnn
  if { [info exists bnn(bot,$bot)] } {
    unset bnn(bot,$bot)
  }
}


bind time - "* * * * *" botnetneed:time

proc botnetneed:time { mi ho da mo ye } {
  global bnn
  if { [botnetneed:chans] } {
    putallbots "bnn c $bnn(chans)"
  }
}


proc botnetneed:chans { } {
  global bnn
  set chans ""
  foreach chan [channels] {
    set chan [string tolower $chan]
    if { [botisop $chan] || [botishalfop $chan] } {
      if { [string equal $chans ""] } { set chans [md5 $chan] } else { append chans " [md5 $chan]" }
    }
  }
  if { ![info exists bnn(chans)] || ![string equal $chans $bnn(chans)] } {
    set bnn(chans) $chans
    return 1
  }
  return 0
}


proc botnetneed:check { bot chan } {
  global bnn
  set chan [string tolower $chan]
  set md5 [md5 $chan]
  if { ![info exists bnn(bot,$bot)] } { return 0 }
  set chans $bnn(bot,$bot)
  if { [lsearch -exact $chans $md5] >= 0 } { return 1 }
  return 0
}


proc botnetneed:findchan { hash } {
  foreach chan [channels] {
    set chan [string tolower $chan]
    set md5 [md5 $chan]
    if { [string equal $md5 $hash] } {
      return $chan
    }
  }
  return 0
}
