#by wiebe @ QuakeNet

#script to make eggdrop work with accounts
#script saves info from /who (IP server realname account flags distance idletime)
#only works on ircu (UnderNet, QuakeNet, GameSurge)

#script uses WHOX (extended who, or specific who) to get the info
#it updates every 2 minutes
#updates as long as no account is known for the user

#it simply adds NICK!USER@HOST to the handle matching the account the user has
#it adds the mask in upper case, so it wont remove masks it shouldnt remove
#still allowing you to add nick!user@host masks to a handle
#users with a global U flag are excluded from this

#script checks for account bans, like *!*@<account>.users.quakenet.org
#script checks for ip bans, even if the user is resolved on irc.
#script checks for k flag on for the user.
#script can set bans on realname, for this set a ban in the format of nick!user@host$realname
#  where the nick user host fields can be anything

#this script has no commands, nothing. you add users like you always do.

#importent to remember, ACCOUNT IS HANDLE
#meaning if your handle on the bot is "abc", anyone registered as abc has your access
#so your handle should always be the same as your account




#whox proc
#whox <nick> <type>
#where <nick> is a nick on a channel with the bot
#where <type> is one of the following:
#u for user (uses getchanhost)
#h for host (uses getchanhost)
#n for nick (simply returns the nick given)
#i for ip
#s for server
#f for flags (H means here, G means gone, * means IRC Operator)
#d for distance
#l for idle
#a for account (0 means none)
#r for realname
#all for all




#  registered to the network, auth'd as, logged in with X, etc. whatever you know it as
#  handle as in username on the bot
#  account as in username on the network's service




#about WHOX
#format: WHO <mask1> [[<flags>][%[<fields>[,<query-type>]]] [:<mask2>]]

#<mask>
#can be a nick/user/host/server/realname/ip and wildcards are supported,
#or a comma seperated list of nicks & channels.

#if <mask2> is specified, <mask1> is ignored.
#<mask2> allows searching with a mask with spaces since a : can be used to mark the last parameter.

#<query-type> can be a number 0-999.
#default <flags> are nuhsr (multiple flags can be used),
#default <fields> are cuhsnfdr. the o flag allows filtering results for IRC Operator status.

#<flags> can be:
#n=nick u=user h=host i=ip s=server o=oper
#x=extended visibility of information for opers

#<fields> can be:
#t=query-type c=channel u=user i=ip h=host s=server
#n=nick f=flags d=distance l=idletime a=account r=realname

#add
#options to enable ip bans, accounts bans, host adding
#realname bans, option to enable, ban on account if possible

##################################################
#settings
##################################################


#vhost account string
#.users.quakenet.org on QuakeNet
#.users.undernet.org on UnderNet
set whox(vhost) ".users.quakenet.org"

#fake ip users get when having a fake vhost
#on quakenet/undernet is this 127.0.0.1
#on bircd this is 127.0.0.1
set whox(ip) "127.0.0.1"

#maximum amount of results in 1 WHO command
#if the bot quits IRC with error "max sendQ exceeded"
#you may need to set this to a lower value
set whox(maxusers) "700"

#maximum length of WHO request
#400 should be fine
set whox(maxlength) "400"

#query number to use in WHO
#valid setting 0-999
#you probably dont need to change this
set whox(query) "123"


#add new users?
#adds registered users to the bot if they arent added yet
#not recommended, the userlist grows fast this way..
#0 is disabled, 1 is enabled
set whox(newusers) "0"


##################################################
#code
##################################################


#check to see if there is a common channel left
#used for when a user parts or is kicked
#returns 1 if a channel is still shared, else 0 is returned
proc whox:comchan { nick chan } {
  if { ![validchan $chan] } { return 0 }
  foreach c [channels] { if { [botonchan $c] && [onchan $nick $c] && ![string equal -nocase $c $chan] } { return 1 } }
  return 0
}


#check all accounts saved, and remove those who are nolonger with us, re-add users still there
proc whox:check { } {
  global whoxdb
  foreach name [array names whoxdb] {
    set nick $name
    set account [whox $nick a]
    if { ![onchan $nick] } {
      whox:unset $nick
      if { [validuser $account] && ![string equal $account 0] } {
        foreach host [getuser $account hosts] {
          if { [string match *?!*?@*? $host] && ![regexp {\*|\?} $host] && [string equal $host [string toupper $host]] } {
            whox:remove $account $host
          }
        }
      }
    } elseif { [validuser $account] } {
      set mask $nick![getchanhost $nick]
      whox:add $account $mask
    }
  }
}


#removes the given mask from the user, and informs the botnet
proc whox:remove { handle mask } {
  if { ![validuser $handle] } { return 0 }
  if { [matchattr $handle Uu] } { return 0 }
  set mask [string toupper $mask]
  foreach host [getuser $handle hosts] {
    if { [string equal $host $mask] } {
      delhost $handle $mask
      foreach bot [bots] {
        if { [string match *g* [botattr $bot]] } {
          putbot $bot "ac r $handle $mask"
        }
      }
    }
  }
  return 0
}


#deletes the given mask from the user, and does not inform the botnet
proc whox:del { handle mask } {
  if { ![validuser $handle] } { return 0 }
  if { [matchattr $handle U] } { return 0 }
  set mask [string toupper $mask]
  foreach host [getuser $handle hosts] {
    if { [string equal $host $mask] } {
      delhost $handle $mask
    }
  }
  return 0
}


#adds the given mask to the user
proc whox:add { handle mask } {
  global whox whoxdb
  set mask [string toupper $mask]
  if { [info exists whox(newusers)] && [string equal $whox(newusers) 1] && ![validuser $handle] } {
    adduser $handle $mask
  }
  if { ![validuser $handle] } { return 0 }
  if { [matchattr $handle U] } { return 0 }
  if { ![string equal -nocase [finduser $mask] $handle] } { setuser $handle hosts $mask }
  if { ![info exists whox(vhost)] } { return 0 }
  set vhost $whox(vhost)
  if { ![string equal $vhost ""] && [string equal [finduser *!*@$handle$vhost] *] } { setuser $handle hosts *!*@$handle$vhost }
  return 0
}


#finds the matching ban and returns ban + reason
proc whox:matchban { mask chan } {
  if { [matchban $mask $chan] } {
    foreach list [banlist $chan] {
      set ban [lindex $list 0]
      set reason [lindex $list 1]

#hide reason if prefixed with @
      if { [string match @* $reason] } { set reason "You are banned" }
      if { [string match -nocase $ban $mask] } {
        return "$ban $reason"
      }
    }
  } elseif { [matchban $mask] } {
    foreach ban [banlist] {
      set ban [lindex $list 0]
      set reason [lindex $list 1]

#hide reason if prefixed with @
      if { [string match @* $reason] } { set reason "You are banned" }
      if { [string match -nocase $ban $mask] } {
        return "$ban $reason"
      }
    }
  }
  return 0
}


#checks if the user is banned, either on fake host, ip or +k flag
proc whox:ban { chan nick } {
  if { ![validchan $chan] } { return 1 }
  if { ![botisop $chan] && ![botishalfop $chan] } { return 2 }
  set handle [nick2hand $nick]
  if { [matchattr $handle lomn|lomn $chan] && [channel get $chan dontkickops] } { return 3 }
  global whox whoxdb
  set account [whox $nick a]
  set ip [whox $nick i]
  if { ![info exists whox(ip)] || [string equal $ip $whox(ip)] } { set ip "" }
  if { [info exists whox(vhost)] } { set vhost $whox(vhost) } else { set vhost "" }
  set user [lindex [split [getchanhost $nick] @] 0]
  set host [lindex [split [getchanhost $nick] @] 1]
  set amask $nick!$user@$account$vhost
  set aresult [whox:matchban $amask $chan]
  set imask $nick!$user@$ip
  set iresult [whox:matchban $imask $chan]
  set realname [whox $nick r]
  set realname [string map [list " " "_" "search" "replace" "search" "replace"] $realname]
  set realname [string map [list "\017" "" "search" "replace" "search" "replace"] $realname]
  set realname [stripcodes bcru $realname]
  if { [string equal $realname *] } { set realname "" }
  set rmask $nick!$user@$host\$$realname
  set rresult [whox:matchban $rmask $chan]
  set ramask $amask\$$realname
  set raresult [whox:matchban $ramask $chan]
  set rimask $imask\$$realname
  set riresult [whox:matchban $rimask $chan]
  if { ![string equal $aresult 0] && ![string equal $account ""] && ![string equal $account 0] && ![string equal $vhost ""] } {
    set ban [lindex [split $aresult] 0]
    set reason [join [lrange [split $aresult] 1 end]]
    pushmode $chan +b $ban
    putkick $chan $nick "Banned: $reason"
  } elseif { ![string equal $iresult 0] && ![string equal $ip ""] } {
    set ban [lindex [split $iresult] 0]
    set reason [join [lrange [split $iresult] 1 end]]
    pushmode $chan +b $ban
    putkick $chan $nick "Banned: $reason"
  } elseif { [matchattr $account k|k $chan] && ![string equal $account ""] && ![string equal $account 0] } {
    pushmode $chan +b $mask
    putkick $chan $nick "banned"
  } elseif { ![string equal $rresult 0] && ![string equal $realname ""] } {
    set reason [join [lrange [split $rresult] 1 end]]
    if { [string match *@*$vhost [getchanhost $nick]] } {
      #newchanban $chan *!*@$host whox_realname_ban $reason 60
    } elseif { ![string equal $account 0] && ![string equal $account ""] } {
      set host *!*@$account$vhost
      #newchanban $chan *!*@$host whox_realname_ban $reason 60
    } else {
      #newchanban $chan *!$user@$host whox_realname_ban $reason 30
    }
  } elseif { ![string equal $raresult 0] && ![string equal $account ""] && ![string equal $account 0] && ![string equal $realname ""] } {
    set reason [join [lrange [split $raresult] 1 end]]
    if { [string match *@*$vhost [getchanhost $nick]] } {
      #newchanban $chan *!*@$host whox_realname_ban $reason 60
    } elseif { ![string equal $account 0] && ![string equal $account ""] } {
      set host *!*@$account$vhost
      #newchanban $chan *!*@$host whox_realname_ban $reason 60
    } else {
      #newchanban $chan *!$user@$host whox_realname_ban $reason 30
    }
  } elseif { ![string equal $riresult 0] && ![string equal $ip ""] && ![string equal $realname ""] } {
    set reason [join [lrange [split $riresult] 1 end]]
    if { [string match *@*$vhost [getchanhost $nick]] } {
      #newchanban $chan *!*@$host whox_realname_ban $reason 60
    } elseif { ![string equal $account 0] && ![string equal $account ""] } {
      set host *!*@$account$vhost
      #newchanban $chan *!*@$host whox_realname_ban $reason 60
    } else {
      #newchanban $chan *!$user@$host whox_realname_ban $reason 30
    }
  }
}


bind time - "* * * * *" whox:update

#updates every 2 minutes
proc whox:update { mi ho da mo ye } {
  if { [string equal $::botname $::botnick] } { return 0 }
  set mi [lindex [split $mi ""] 1]
  if { [string equal [lsearch -exact "0 2 4 6 8" $mi] -1] } { return 0 }
  global whox server
  if { [string equal $server ""] } { return 0 }
  whox:check
  set who ""
  set users 0
  set length 0
  set maxusers $whox(maxusers)
  set maxlength $whox(maxlength)
  set query $whox(query)
  foreach chan [channels] {
    if { [botonchan $chan] && [whox:checkchan $chan] && [llength [chanlist $chan]] <= $maxusers } {
      if { [expr $users + [llength [chanlist $chan]]] >= $maxusers || [expr $length + [llength [split $chan ""]]] >= $maxlength } {
        putserv "WHO $who %isnfdlart,$query"
        set who "$chan"
        set users [llength [chanlist $chan]]
        set length [llength [split $chan ""]]
      } else {
        if { [string equal $who ""] } { set who $chan } else { set who "$who,$chan" }
        set users [expr $users + [llength [chanlist $chan]]]
        set length [expr $length + [llength [split $chan ""]]]
      }
    }
  }
  if { ![string equal $who ""] } { putserv "WHO $who %isnfdlart,$query" }
}


#checks if the channel needs updating
proc whox:checkchan { chan } {
  if { ![validchan $chan] } { return 0 }
  global whoxdb
  set do 0
  foreach nick [chanlist $chan] {
    set account [whox $nick a]
    if { ![isbotnick $nick] && ([string equal $account ""] || ![string equal $account 0]) } { set do 1 }
    whox:ban $chan $nick
  }
  return $do
}


bind raw -|- "354" whox:raw

#here we get the raw replies from the server
proc whox:raw { server raw ar } {
  global whox whoxdb whoxdbchase
  if { [llength [split $ar]] < 10 } { return 0 }
  if { [string equal $raw 354] } {
    set query [lindex [split $ar] 1]
    set querynr $whox(query)
    if { [string equal -nocase $query $querynr] } {
      set ip [lindex [split $ar] 2]
      set server [lindex [split $ar] 3]
      set nick [string tolower [lindex [split $ar] 4]]
      set flags [lindex [split $ar] 5]
      set distance [lindex [split $ar] 6]
      set idle [lindex [split $ar] 7]
      set account [lindex [split $ar] 8]
      set realname [join [lrange [split $ar] 9 end]]
      set realname [string range $realname 1 end]
      set uhost [getchanhost $nick]
      set mask [string toupper $nick!$uhost]
      if { [onchan $nick] } {
        set nick [string tolower $nick]
        set whoxdb($nick) "$ip $server $flags $distance $idle $account $realname"
        if { [info exists whoxdbchase($nick)] } { unset whoxdbchase($nick) }
        if { ![string equal $account 0] && ![isbotnick $nick] } {
          whox:add $account $mask
        }
        foreach chan [channels] {
          if { [onchan $nick $chan] } {
            whox:ban $chan $nick
          }
        }
      }
    }
  }
}


bind evnt -|- init-server whox:connect

#on connect, logout all users, send message over botnet to re-add logged in users
proc whox:connect { type } {
  if { [string equal -nocase $type init-server] } {
    global whoxdb
    if { [info exists whoxdb] } { unset whoxdb }
    foreach user [userlist] {
      foreach host [getuser $user hosts] {
        set nick [lindex [split $host !] 0]
        set uhost [lindex [split $host !] 1]
        if { [string match *?!*?@*? $host] && ![regexp {\*|\?} $host] && [string equal $host [string toupper $host]] && ![matchattr $user U] } {
          delhost $user $host
        }
      }
    }
    foreach bot [bots] {
      if { [string match *g* [botattr $bot]] } {
        putbot $bot "ac a"
      }
    }
  }
}


bind bot - ac whox:bot

#messages from other bots
proc whox:bot { bot cmd text } {
  global server
  if { [string equal $server ""] } { return 0 }
  if { ![string equal -nocase $cmd ac] } { return 0 }
  if { [string equal -nocase $text a] } {
    whox:check
  } elseif { [string match -nocase "r *? *?!*?@*?" $text] } {
    set handle [lindex [split $text] 1]
    set mask [lindex [split $text] 2]
    set nick [lindex [split $mask !] 0]
    if { [onchan $nick] && [validuser $handle] } {
      whox:add $handle $mask
    }
  }
}


bind part -|- * whox:part

#part
proc whox:part { nick uhost handle chan {msg ""} } {
  global whoxdb
  if { ![validchan $chan] } { return 0 }
  if { [isbotnick $nick] } {
    foreach n [chanlist $chan] {
      set h [nick2hand $n]
      if { ![isbotnick $n] && ![whox:comchan $n $chan] && [validuser $h] && [info exists whoxdb($n)] } {
        set mask [getchanhost $n]
        whox:remove $h $mask
        whox:unset $n
      }
    }
  } elseif { [info exists whoxdb($nick)] && ![whox:comchan $nick $chan] } {
    set account [whox $nick a]
    if { [validuser $account] && ![string equal $account 0] } {
      whox:remove $account $nick!$uhost
    }
    whox:unset $nick
  }
}


bind kick -|- * whox:kick
proc whox:kick { nick uhost handle chan target reason } {
  whox:part $target [getchanhost $target] [nick2hand $target] $chan $reason
}


bind sign -|- * whox:sign

#quit
proc whox:sign { nick uhost handle chan reason } {
  global whoxdb
  set nick [string tolower $nick]
  if { ![validchan $chan] } { return 0 }
  set account [whox $nick a]
  if { [string equal $account ""] } { return 0 }
  if { [validuser $account] && ![string equal $account 0] } {
    whox:del $account $nick!$uhost
  }
  if { [string equal -nocase $reason "host change"] || [string equal -nocase $reason "registered"] } {
    set whoxdb(join,$nick) $whoxdb($nick)
  }
  whox:unset $nick
}


bind splt -|- * whox:splt

#split
proc whox:splt { nick uhost handle chan } {
  global whoxdb
  if { ![validchan $chan] } { return 0 }
  set account [whox $nick a]
  if { [string equal $account ""] } { return 0 }
  if { [validuser $account] && ![string equal $account 0] } {
    whox:remove $account $nick!$uhost
  }
  whox:unset $nick
}


bind nick -|- * whox:nick

#nick change
proc whox:nick { nick uhost handle chan newnick } {
  global whoxdb
  if { ![validchan $chan] } { return 0 }
  if { [info exists whoxdb($nick)] } {
    set whoxdb($newnick) $whoxdb($nick)
    set account [whox $nick a]
    whox:unset $nick
    if { ![string equal -nocase $nick $newnick] && [validuser $account] && ![string equal $account 0] } {
      set mask $newnick!$uhost
      whox:add $account $mask
      set mask [string toupper $nick!$uhost]
      whox:del $account $mask
    }
  }
}


bind join -|- * whox:join

#join
proc whox:join { nick uhost handle chan } {
  global whoxdb
  if { ![validchan $chan] } { return 0 }
  set nick [string tolower $nick]
  whox:ban $chan $nick
  if { [info exists whoxdb(join,$nick)] } {
    set whoxdb($nick) $whoxdb(join,$nick)
    unset whoxdb(join,$nick)
    set account [whox $nick a]
    if { ![string equal $account ""] && ![string equal $account 0] } {
      whox:add $account $nick!$uhost
    }
  }
}


bind link - * whox:link

proc whox:link { bot via } {
  whox:check
}


#returns requested info
proc whox { nick type } {
  global whoxdb
  if { [string equal -nocase $type u] } { return [lindex [split [getchanhost $nick] @] 0] }
  if { [string equal -nocase $type h] } { return [lindex [split [getchanhost $nick] @] 1] }
  if { [string equal -nocase $type n] } { return $nick }
  foreach name [array names whoxdb] { if { [string equal -nocase $name $nick] } { set nick $name } }
  if { ![info exists whoxdb($nick)] } { return }
  set info $whoxdb($nick)
  if { [string equal -nocase $type i] } { return [lindex [split $info] 0] }
  if { [string equal -nocase $type s] } { return [lindex [split $info] 1] }
  if { [string equal -nocase $type f] } { return [lindex [split $info] 2] }
  if { [string equal -nocase $type d] } { return [lindex [split $info] 3] }
  if { [string equal -nocase $type l] } { return [lindex [split $info] 4] }
  if { [string equal -nocase $type a] } { return [lindex [split $info] 5] }
  if { [string equal -nocase $type r] } { return [join [lrange [split $info] 6 end]] }
  if { [string equal -nocase $type all] } { return $info }
  error "invalid type, should be: u for user, h for host, n for nick, i for ip, s for server, f for flags, d for distance, l for idle, a for account, r for realname, all for all"
}


bind mode - "% +b" whox:mode

proc whox:mode { nick uhost handle chan mode ban } {
  if { ![botisop $chan] && ![botishalfop $chan] } { return 0 }
  if { [string match *!*@*\$* $ban] && [isbotnick $nick] } { utimer 2 [list pushmode $chan -b $ban] }
  if { [string match *!*@* $ban] && ![string match *!*@*\$* $ban] } {
  }
}


proc whox:unset { nick } {
  global whoxdb whoxdbchase
  set nick [string tolower $nick]
  if { [info exists whoxdb($nick)] } {
    set whoxdbchase($nick) "[unixtime] $whoxdb($nick)"
    unset whoxdb($nick)
  }
}


#time
bind time -|- * whoxchase:time

proc whoxchase:time { mi ho da mo ye } {
  global whoxdbchase
  foreach name [array names whoxdbchase] {
    if { [expr [unixtime] - [lindex [split $whoxdbchase($name)] 0]] > "86400" } {
      unset whoxdbchase($name)
    }
  }
}



#returns requested info
proc whox:chase { nick type } {
  global whoxdbchase
  if { [string equal -nocase $type n] } { return $nick }
  set nick [string tolower $nick]
  if { ![info exists whoxdbchase($nick)] } { return }
  if { [expr [unixtime] - [lindex [split $whoxdbchase($nick)] 0]] > "86400" } {
    unset whoxdbchase($nick)
    return
  }
  set info [join [lrange [split $whoxdbchase($nick)] 1 end]]
  if { [string equal -nocase $type i] } { return [lindex [split $info] 0] }
  if { [string equal -nocase $type s] } { return [lindex [split $info] 1] }
  if { [string equal -nocase $type f] } { return [lindex [split $info] 2] }
  if { [string equal -nocase $type d] } { return [lindex [split $info] 3] }
  if { [string equal -nocase $type l] } { return [lindex [split $info] 4] }
  if { [string equal -nocase $type a] } { return [lindex [split $info] 5] }
  if { [string equal -nocase $type r] } { return [join [lrange [split $info] 6 end]] }
  if { [string equal -nocase $type all] } { return $info }
  error "invalid type, should be: n for nick, i for ip, s for server, f for flags, d for distance, l for idle, a for account, r for realname, all for all"
}

