<?php
/***************************************************************************************************
 * BeBot - An Anarchy Online & Age of Conan Chat Automaton
 * Copyright (C) 2004 Jonas Jax
 * Copyright (C) 2005-2010 Thomas Juberg, ShadowRealm Creations and the BeBot development team.
 *
 * Developed by:
 * - Alreadythere (RK2)
 * - Blondengy (RK1)
 * - Blueeagl3 (RK1)
 * - Glarawyn (RK1)
 * - Khalem (RK1)
 * - Naturalistic (RK1)
 * - Temar (RK1)
 *
 * See Credits file for all acknowledgements.
 ***************************************************************************************************
 * TeamSpeak 3 module for Bebot
 * This module allows users to display server info and channel list with clients connected to a TS3 server.
 *
 * Author:    Kentarii [Ragnarok] @ EN Fury PvP
 * E-mail:    Does not take a rocket scientist to figure out..
 * Website:    http://aoc.is-better-than.tv/
 ***************************************************************************************************
 * Installation:
 *        Download TS3Admin.class from http://ts3admin.par0noid.info/
 *        Extract and copy ts3admin.class.php to <bebot_dir>/extra/ts3admin.class.php
 *        Copy TeamSpeak3.phps to <bebot_dir>/custom/modules/TeamSpeak3.php and restart bot
 * Configuration:
 *        You have to configure the module under '!settings TeamSpeak3' before you can use it.
 *        If a guest user does not have permissions to use ServerQuery methods, you have to provide
 *        both QueryUser and QueryPassword to get this module to function.
 *        It's probably a good idea to add the IP of your bot to the TS3 query_ip_whitelist.txt file
 *        which can be found in the root dir of the TeamSpeak3 server installation if you have access
 *        to that.
 * Disable module:
 *        Rename TeamSpeak3.php to _TeamSpeak3.php and restart bot
 * Uninstallation:
 *        Delete <bebot_dir>/custom/modules/TeamSpeak3.php and restart bot
 ***************************************************************************************************
 * Dependencies:
 *        This module uses the TS3Admin.class provided by par0noid
 *        Website: http://ts3admin.par0noid.info/
 ***************************************************************************************************
 * Changelog:
 *    2011-08-11    0.0.3    Add channel id and client id to output
 *    2011-02-05    0.0.2    Add utf8_decode to support special chars in output.
 *    2011-02-04    0.0.1    First version
 ***************************************************************************************************
 */

// if you use windows, you probably have to change this to x:\\path\to\bebot\extra\ts3admin.class.php
require_once('./extra/ts3admin.class.php');
$TeamSpeak3 = new TeamSpeak3($bot);

class 
TeamSpeak3 Extends BaseActiveModule {
    var 
$bot;
    var 
$version;

    function 
__construct (&$bot) {
        
parent::__construct(&$botget_class($this));

        
$this -> version '0.0.3';

        
$this -> bot -> core('settings') -> create('TeamSpeak3''Host''''TS3 server IP or hostname.'''false1);
        
$this -> bot -> core('settings') -> create('TeamSpeak3''Port'9987'TS3 server port.'''false2);
        
$this -> bot -> core('settings') -> create('TeamSpeak3''QueryPort'10011'TS3 ServerQuery port.'''false3);
        
$this -> bot -> core('settings') -> create('TeamSpeak3''QueryUser''''TS3 ServerQuery user.'''false4);
        
$this -> bot -> core('settings') -> create('TeamSpeak3''QueryPassword''''TS3 ServerQuery password.'''false5);
        
$this -> bot -> core('settings') -> create('TeamSpeak3''ShowServerInfo'true'Show server info in output.'''false6);
        
$this -> bot -> core('settings') -> create('TeamSpeak3''ShowChannelList'true'Show channel list with users in output.'''false7);
        
$this -> bot -> core('settings') -> create('TeamSpeak3''ShowChannelID'true'Show channel id in channel list.'''false8);
        
$this -> bot -> core('settings') -> create('TeamSpeak3''ShowClientID'true'Show client id in channel list.'''false9);
        
$this -> bot -> core('settings') -> create('TeamSpeak3''DebugWhoAmI'false'Show WhoAmI in output. Used for debugging purposes.'''false10);
        
$this -> bot -> core('settings') -> create('TeamSpeak3''DebugServerInfo'false'Show server info in output. Used for debugging purposes.'''false11);

        
$this -> register_command('all''ts3''MEMBER');

        
$this -> help['description'] = 'This module allows users to display server info and channel list with clients connected to a TS3 server.';
        
$this -> help['command']['ts3'] = 'Show server info and channel list with clients.';
        
$this -> help['notes'] = sprintf("TeamSpeak3 v##lightbeige##%s##end##, created by Kentarii [Ragnarok] @ EN Fury PvP 2008-%d\n"$this -> versiondate('Y'));
    }

    function 
command_handler($name$msg$origin) {
        if (
preg_match('/^ts3$/i'$msg)) {
            
$rv $this -> show_ts3_info();
        }
        else { 
// show help
            
$this -> bot -> send_help($name'TeamSpeak3');
            return 
false;
        }
        return 
$this -> prefix_output($rv);
    }

    function 
prefix_output($rv) {
        return (
$rv) ? "##white##<font color='#CAD1DE'>[-TS3-]</font> :: "$rv "##end##" false;
    }

    
/**
     * Create a standard header with optional $title
     */
    
function blob_header($title null) {
        if (
$title) return "<font face='hyborianlarge' color='#CAD1DE'>TeamSpeak3 :: $title</font>\n";
        else return 
"<font face='hyborianlarge' color='#CAD1DE'>TeamSpeak3</font>\n";
    }

    
/**
     * Create a section header with $title
     */
    
function blob_section_header($title) {
        return 
"<b></b>\n<font face='hyboriansmall' color='#909FBC'>$title</font>\n";
    }

    
/**
     * Wrapper for make_blob
     * @param string $title
     * @param string $content
     * @return string
     */
    
function make_blob($title$content$attributes '') {
        
// Using " inside a blob will end the blob.
        // Convert opening and closing tags with " to '
        // Convert any other " to HTML entities.
        
$content str_replace("=\"""='"$content);
        
$content str_replace("\">""'>"$content);
        
$content str_replace("\"""&quot;"$content);
        return 
"<a href=\"text://##white##" $content "##end##\"". ($attributes ' '$attributes '') .">" $title "</a>";
    }

    function 
seconds_to_age($seconds) {
        
$d floor($seconds 86400);
        
$h floor(($seconds - ($d 86400)) / 3600);
        
$m floor(($seconds - ($d 86400) - ($h 3600)) / 60);
        
$s $seconds - ($d 86400) - ($h 3600) - ($m 60);
        if (
$d 0) return sprintf("%dd %02dh %02dm %02ds"$d$h$m$s);
        else if (
$h 0) return sprintf("%02dh %02dm %02ds"$h$m$s);
        else if (
$m 0) return sprintf("%02dm %02ds"$m$s);
        else return 
sprintf("%02ds"$s);
    }

    function 
char_cmp($a$b) {
        return 
strcasecmp($a['client_nickname'], $b['client_nickname']);
    }

    function 
validate_settings() {
        if (
$this -> bot -> core('settings') -> get('TeamSpeak3''Host') == '') return false;
        else if (!
is_numeric($this -> bot -> core('settings') -> get('TeamSpeak3''Port'))) return false;
        else if (!
is_numeric($this -> bot -> core('settings') -> get('TeamSpeak3''QueryPort'))) return false;
# Commented out since some servers might work without needing to login..
//        else if ($this -> bot -> core('settings') -> get('TeamSpeak3', 'QueryUser') == '') return false;
//        else if ($this -> bot -> core('settings') -> get('TeamSpeak3', 'QueryPassword') == '') return false;
        
return true;
    }

    function 
indent($count$indent '--') {
        
$output '';
        for (
$i 0$i $count$i++)
            
$output .= $indent;
        return 
$output;
    }

    function 
show_ts3_info() {
        
// Put result in a cache for one minute to prevent excessive hammering on TS3 server.
        // TODO: should cache be configurable?
        
static $cache = array();
        
$cache_id date("YmdHi");

        if (!isset(
$cache[$cache_id])) {
            
$cache = array(); // reset cache
            
if (!$this -> validate_settings()) {
                return 
"##red##Please configure the module by running <pre>settings TeamSpeak3##end##";
            }
            else {
                
// create ts3admin object
                
$ts3 = new ts3admin($this -> bot -> core('settings') -> get('TeamSpeak3''Host'), $this -> bot -> core('settings') -> get('TeamSpeak3''QueryPort'));
                
$output $this -> blob_header();

                
// Connect to TS3 ServerQuery
                
if (!$ts3->getElement('success'$ts3->connect())) {
                    
$output .= $this -> blob_section_header('Connection Info');
                    
$output .= sprintf("Host: ##seablue##%s##end##\n"$this -> bot -> core('settings') -> get('TeamSpeak3''Host'));
                    
$output .= sprintf("Port: ##seablue##%s##end##\n"$this -> bot -> core('settings') -> get('TeamSpeak3''Port'));
                    
$output .= sprintf("QueryPort: ##seablue##%s##end##\n"$this -> bot -> core('settings') -> get('TeamSpeak3''QueryPort'));
                    
$output .= sprintf("QueryUser: ##seablue##%s##end##\n"$this -> bot -> core('settings') -> get('TeamSpeak3''QueryUser'));
                    
$cache[$cache_id] = sprintf("##red##Error connecting to server %s:%s :: %s##end##"$this -> bot -> core('settings') -> get('TeamSpeak3''Host'), $this -> bot -> core('settings') -> get('TeamSpeak3''Port'), $this -> make_blob('Show'$output));
                }
                else {
                    
// some local variables used in final output
                    
$server_name '';
                    
$max_clients 0;
                    
$clients_online 0;
                    
$active_channels 0;

                    
// Login to TS3 ServerQuery if user/pass is provided, otherwise try using guest connection
                    
if ($this -> bot -> core('settings') -> get('TeamSpeak3''QueryUser') && $this -> bot -> core('settings') -> get('TeamSpeak3''QueryPassword')) {
                        
$result $ts3->login($this -> bot -> core('settings') -> get('TeamSpeak3''QueryUser'), $this -> bot -> core('settings') -> get('TeamSpeak3''QueryPassword'));
                        if (!
$result['success']) {
                            
$output .= "##red##Error logging on to server, make sure password is correct!\n";
                            foreach (
$result['errors'] as $msg) {
                                
$output .= sprintf("##red##%s##end##\n"$msg);
                            }
                            
$cache[$cache_id] = sprintf("##red##Error logging on to server, make sure QueryUser/QueryPassword is correct! :: %s##end##"$this -> make_blob('Show'$output));
                            return 
$cache[$cache_id];
                        }
                    }

                    
// Select server running on defined port in settings
                    
$result $ts3->selectServer($this -> bot -> core('settings') -> get('TeamSpeak3''Port'));
                    if (!
$result['success']) {
                        
$output .= "##red##Error selecting server on port: "$this -> bot -> core('settings') -> get('TeamSpeak3''Port') ." :: %s##end##\n";
                        foreach (
$result['errors'] as $msg) {
                            
$output .= sprintf("##red##%s##end##\n"$msg);
                        }
                        
$cache[$cache_id] = sprintf("##red##Error selecting server on port: %s :: %s##end##"$this -> bot -> core('settings') -> get('TeamSpeak3''Port'), $this -> make_blob('Show'$output));
                        return 
$cache[$cache_id];
                    }

                    
// get server info, mostly for debug
                    
$result $ts3->serverInfo();
                    if (
$result['success']) {
                        
$server_name utf8_decode($result['data']['virtualserver_name']);
                        
$max_clients $result['data']['virtualserver_maxclients'];
                        
$clients_online $result['data']['virtualserver_clientsonline'];

                        if (
$this -> bot -> core('settings') -> get('TeamSpeak3''ShowServerInfo')) {
                            
$output .= $this -> blob_section_header('Server Info');
                            
$output .= sprintf("Name: ##seablue##%s##end##\n"$server_name);
                            
$output .= sprintf("Welcome Message: ##seablue##%s##end##\n"trim(str_replace('\n'' 'utf8_decode($result['data']['virtualserver_welcomemessage']))));
                            
//$output .= sprintf("Host: ##seablue##%s##end##\n", $result['data']['virtualserver_ip']); // Better to use the one provided in settings since this resolves ip
                            
$output .= sprintf("Host: ##seablue##%s##end##\n"$this -> bot -> core('settings') -> get('TeamSpeak3''Host'));
                            
$output .= sprintf("Port: ##seablue##%s##end##\n"$result['data']['virtualserver_port']);
                            
$output .= sprintf("Ping: ##seablue##%s##end##\n"$result['data']['virtualserver_total_ping']);
                            
$output .= sprintf("Version: ##seablue##%s##end##\n"$result['data']['virtualserver_version']);
                            
$output .= sprintf("Max Clients: ##seablue##%s##end##\n"$result['data']['virtualserver_maxclients']);
                            
$output .= sprintf("Client count: ##seablue##%s##end##\n"$result['data']['virtualserver_clientsonline']);
                            
$output .= sprintf("Channel count: ##seablue##%s##end##\n"$result['data']['virtualserver_channelsonline']);
                            
$output .= sprintf("Uptime: ##seablue##%s##end##\n"$this->seconds_to_age($result['data']['virtualserver_uptime']));
                        }
                    }
                    else {
                        foreach (
$result['errors'] as $msg) {
                            
$output .= sprintf("##red##%s##end##\n"$msg);
                        }
                    }

                    if (
$this -> bot -> core('settings') -> get('TeamSpeak3''ShowChannelList')) {
                        
$channel_users = array(); // put users into channel

                        // get client list and put them into an assoc with channel id as key
                        
$result $ts3->clientList();
                        if (
$result['success']) {
                            
/*
                             * Array {
                             *  [clid] => 1
                             *  [cid] => 1
                             *  [client_database_id] => 2
                             *  [client_nickname] => Par0noid
                             *  [client_type] => 0
                             * }
                             */
                            
foreach($result['data'] as $client) {
                                if (!isset(
$channel_users[$client['cid']])) $channel_users[$client['cid']] = array();
                                
$channel_users[$client['cid']][] = $client;
                            }
                        }
                        else {
                            foreach (
$result['errors'] as $msg) {
                                
$output .= sprintf("##red##%s##end##\n"$msg);
                            }
                        }

                        
// get channel list
                        
$result $ts3->channelList();
                        if (
$result['success']) {
                            
/* Array {
                             *  [cid] => 1
                             *  [pid] => 0
                             *  [channel_order] => 0
                             *  [channel_name] => Default Channel
                             *  [total_clients] => 2
                             *  [channel_needed_subscribe_power] => 0
                             * }
                             */
                            
$output .= $this -> blob_section_header('Channel List');
                            
$output .= sprintf("##seablue##%s##end##\n"$server_name);

                            
$indent = array(); // used to add virtual tree structure in output based on parent channel
                            
foreach($result['data'] as $ch) {
                                if (
$ch['pid'] == 0$indent[$ch['cid']] = 1;
                                else 
$indent[$ch['cid']] = $indent[$ch['pid']] + 1;

                                if (
$this -> bot -> core('settings') -> get('TeamSpeak3''ShowChannelID'))
                                    
$output .= sprintf("%s ##%s##%s##end## (%d)[%d]\n"$this->indent($indent[$ch['cid']]), (($ch['total_clients'] > 0) ? 'seablue' 'gray'), utf8_decode($ch['channel_name']), $ch['total_clients'], $ch['cid']);
                                else
                                    
$output .= sprintf("%s ##%s##%s##end## (%d)\n"$this->indent($indent[$ch['cid']]), (($ch['total_clients'] > 0) ? 'seablue' 'gray'), utf8_decode($ch['channel_name']), $ch['total_clients']);

                                if (
$ch['total_clients'] > && isset($channel_users[$ch['cid']])) {
                                    
$active_channels++;
                                    
usort($channel_users[$ch['cid']], array($this'char_cmp'));
                                    foreach (
$channel_users[$ch['cid']] as $cu) {
                                        if (
$cu['client_type'] == 1) { // ServerQuery user
                                            
$output .= sprintf("%s ##silver##%s##end##\n"$this->indent($indent[$ch['cid']]+1), $this -> bot -> botname); // not really tested thoroughly... dunno if there might be other ServerQuery users online...
                                        
}
                                        else {
                                            if (
$this -> bot -> core('settings') -> get('TeamSpeak3''ShowClientID'))
                                                
$output .= sprintf("%s ##seagreen##%s##end## [%d]\n"$this->indent($indent[$ch['cid']]+1), utf8_decode($cu['client_nickname']), $cu['clid']);
                                            else
                                                
$output .= sprintf("%s ##seagreen##%s##end##\n"$this->indent($indent[$ch['cid']]+1), utf8_decode($cu['client_nickname']));
                                        }
                                    }
                                }
                            }
                        }
                        else {
                            foreach (
$result['errors'] as $msg) {
                                
$output .= sprintf("##red##%s##end##\n"$msg);
                            }
                        }
                    }

                    if (
$this -> bot -> core('settings') -> get('TeamSpeak3''DebugWhoAmI')) {
                        
// get WhoAmI info, mostly for debug
                        
$result $ts3->whoAmI();
                        if (
$result['success']) {
                            
$output .= $this -> blob_section_header('Debug :: WhoAmI');
                            foreach(
$result['data'] as $k => $v) {
                                
$output .= sprintf("%s: ##seablue##%s##end##\n"$k$v);
                            }
                        }
                        else {
                            foreach (
$result['errors'] as $msg) {
                                
$output .= sprintf("##red##%s##end##\n"$msg);
                            }
                        }
                    }

                    if (
$this -> bot -> core('settings') -> get('TeamSpeak3''DebugServerInfo')) {
                        
// get server info, mostly for debug
                        
$result $ts3->serverInfo();
                        if (
$result['success']) {
                            
$output .= $this -> blob_section_header('Debug :: Server Info');
                            foreach(
$result['data'] as $k => $v) {
                                
$output .= sprintf("%s: ##seablue##%s##end##\n"$k$v);
                            }
                        }
                        else {
                            foreach (
$result['errors'] as $msg) {
                                
$output .= sprintf("##red##%s##end##\n"$msg);
                            }
                        }
                    }
                    
$cache[$cache_id] = sprintf("Found ##seablue##%d / %d##end## client(s) in ##seablue##%d##end## channel(s) :: %s"$clients_online$max_clients$active_channels$this -> make_blob('Show'$output));
                    
$this -> bot -> log('TEAMSPEAK3''UPDATE'sprintf("Found %d / %d client(s) in %d channel(s)"$clients_online$max_clients$active_channels));
                }
            }
        }
        else {
            
$this -> bot -> log('TEAMSPEAK3''CACHE''Cache hit for: '$cache_id);
        }
        return 
$cache[$cache_id];
    }
}
?>