2023-10-28 20:48:55 +02:00

430 lines
12 KiB
PHP

<?php
/**
* TSStatus: Teamspeak 3 viewer for php5
* @author Sebastien Gerard <seb@sebastien.me>
* @see http://tsstatus.sebastien.me/
* @version 2013-08-31
**/
class TSStatus
{
private $_host;
private $_queryPort;
private $_serverDatas;
private $_channelDatas;
private $_userDatas;
private $_serverGroupFlags;
private $_channelGroupFlags;
private $_login;
private $_password;
private $_cacheFile;
private $_cacheTime;
private $_channelList;
private $_useCommand;
private $_javascriptName;
private $_socket;
private $showPasswordBox;
public $imagePath;
public $showNicknameBox;
public $timeout;
public $hideEmptyChannels;
public $hideParentChannels;
public function __construct($host, $queryPort = 10011)
{
$this->_host = $host;
$this->_queryPort = $queryPort;
$this->_socket = null;
$this->_serverDatas = array();
$this->_channelDatas = array();
$this->_userDatas = array();
$this->_serverGroupFlags = array();
$this->_channelGroupFlags = array();
$this->_login = false;
$this->_password = false;
$this->_cacheTime = 0;
$this->_cacheFile = __FILE__ . ".cache";
$this->_channelList = array();
$this->_useCommand = "use port=9987";
$this->imagePath = "img/";
$this->showNicknameBox = true;
$this->showPasswordBox = false;
$this->timeout = 2;
$this->hideEmptyChannels = false;
$this->hideParentChannels = false;
}
public function useServerId($serverId)
{
$this->_useCommand = "use sid=$serverId";
}
public function useServerPort($serverPort)
{
$this->_useCommand = "use port=$serverPort";
}
public function setLoginPassword($login, $password)
{
$this->_login = $login;
$this->_password = $password;
}
public function setCache($time, $file = false)
{
$this->_cacheTime = $time;
if($file !== false) $this->_cacheFile = $file;
}
public function clearServerGroupFlags()
{
$this->_serverGroupFlags = array();
}
public function setServerGroupFlag($serverGroupId, $image)
{
$this->_serverGroupFlags[$serverGroupId] = $image;
}
public function clearChannelGroupFlags()
{
$this->_channelGroupFlags = array();
}
public function setChannelGroupFlag($channelGroupId, $image)
{
$this->_channelGroupFlags[$channelGroupId] = $image;
}
public function limitToChannels()
{
$this->_channelList = func_get_args();
}
private function ts3decode($str, $reverse = false)
{
$find = array('\\\\', "\/", "\s", "\p", "\a", "\b", "\f", "\n", "\r", "\t", "\v");
$rplc = array(chr(92), chr(47), chr(32), chr(124), chr(7), chr(8), chr(12), chr(10), chr(3), chr(9), chr(11));
if(!$reverse) return str_replace($find, $rplc, $str);
return str_replace($rplc, $find, $str);
}
private function toHTML($string)
{
return htmlentities($string, ENT_QUOTES, "UTF-8");
}
private function sortUsers($a, $b)
{
if($a["client_talk_power"] != $b["client_talk_power"]) return $a["client_talk_power"] > $b["client_talk_power"] ? -1 : 1;
return strcasecmp($a["client_nickname"], $b["client_nickname"]);
}
private function parseLine($rawLine)
{
$datas = array();
$rawItems = explode("|", $rawLine);
foreach ($rawItems as $rawItem)
{
$rawDatas = explode(" ", $rawItem);
$tempDatas = array();
foreach($rawDatas as $rawData)
{
$ar = explode("=", $rawData, 2);
$tempDatas[$ar[0]] = isset($ar[1]) ? $this->ts3decode($ar[1]) : "";
}
$datas[] = $tempDatas;
}
return $datas;
}
private function sendCommand($cmd)
{
fputs($this->_socket, "$cmd\n");
$response = "";
do
{
$response .= fread($this->_socket, 8096);
}while(strpos($response, 'error id=') === false);
if(strpos($response, "error id=0") === false)
{
throw new Exception("TS3 Server returned the following error: " . $this->ts3decode(trim($response)));
}
return $response;
}
private function queryServer()
{
$this->_socket = @fsockopen($this->_host, $this->_queryPort, $errno, $errstr, $this->timeout);
if($this->_socket)
{
@socket_set_timeout($this->_socket, $this->timeout);
$isTs3 = trim(fgets($this->_socket)) == "TS3";
if(!$isTs3) throw new Exception("Not a Teamspeak 3 server/bad query port");
if($this->_login !== false)
{
$this->sendCommand("login client_login_name=" . $this->_login . " client_login_password=" . $this->_password);
}
$response = "";
$response .= $this->sendCommand($this->_useCommand);
$response .= $this->sendCommand("serverinfo");
$response .= $this->sendCommand("channellist -topic -flags -voice -limits");
$response .= $this->sendCommand("clientlist -uid -away -voice -groups");
$response .= $this->sendCommand("servergrouplist");
$response .= $this->sendCommand("channelgrouplist");
$this->disconnect();
return $response;
}
else throw new Exception("Socket error: $errstr [$errno]");
}
private function disconnect()
{
@fputs($this->_socket, "quit\n");
@fclose($this->_socket);
}
private function update()
{
$response = $this->queryServer();
$lines = explode("error id=0 msg=ok\n\r", $response);
if(count($lines) == 7)
{
$this->_serverDatas = $this->parseLine($lines[1]);
$this->_serverDatas = $this->_serverDatas[0];
$tmpChannels = $this->parseLine($lines[2]);
$hide = count($this->_channelList) > 0 || $this->hideEmptyChannels;
foreach ($tmpChannels as $channel)
{
$channel["show"] = !$hide;
$this->_channelDatas[$channel["cid"]] = $channel;
}
$tmpUsers = $this->parseLine($lines[3]);
usort($tmpUsers, array($this, "sortUsers"));
foreach ($tmpUsers as $user)
{
if($user["client_type"] == 0)
{
if(!isset($this->_userDatas[$user["cid"]])) $this->_userDatas[$user["cid"]] = array();
$this->_userDatas[$user["cid"]][] = $user;
}
}
$serverGroups = $this->parseLine($lines[4]);
foreach ($serverGroups as $sg) if($sg["iconid"] !== 0) $this->setServerGroupFlag($sg["sgid"], 'icon_' . unpack('V',pack('V',$sg["iconid"]))[1]);
$channelGroups = $this->parseLine($lines[5]);
foreach ($channelGroups as $cg) if($cg["iconid"] !== 0) $this->setChannelGroupFlag($cg["cgid"], 'icon_' . unpack('V',pack('V',$cg["iconid"]))[1]);
}
else throw new Exception("Invalid server response");
}
private function setShowFlag($channelIds)
{
if(!is_array($channelIds)) $channelIds = array($channelIds);
foreach ($channelIds as $cid)
{
if(isset($this->_channelDatas[$cid]))
{
$this->_channelDatas[$cid]["show"] = true;
if(!$this->hideParentChannels && $this->_channelDatas[$cid]["pid"] != 0)
{
$this->setShowFlag($this->_channelDatas[$cid]["pid"]);
}
}
}
}
private function getCache()
{
if($this->_cacheTime > 0 && file_exists($this->_cacheFile) && (filemtime($this->_cacheFile) + $this->_cacheTime >= time()) )
{
return file_get_contents($this->_cacheFile);
}
return false;
}
private function saveCache($content)
{
if($this->_cacheTime > 0)
{
if(!@file_put_contents($this->_cacheFile, $content))
{
throw new Exception("Unable to write to file: " . $this->_cacheFile);
}
}
}
private function renderFlags($flags)
{
$content = "";
foreach ($flags as $flag) $content .= '<img src="' . $this->imagePath . $flag . '" />';
return $content;
}
private function renderOptionBox($name, $label)
{
$key = "tsstatus-" . $this->_javascriptName . "-$name";
$value = isset($_COOKIE[$key]) ? htmlspecialchars($_COOKIE[$key]) : "";
return '<label>' . $label . ': <input type="text" id="' . $key . '" value="' . $value . '" /></label>';
}
private function renderUsers($channelId)
{
$content = "";
if(isset($this->_userDatas[$channelId]))
{
$imagePath = $this->imagePath;
foreach ($this->_userDatas[$channelId] as $user)
{
if($user["client_type"] == 0)
{
$name = $this->toHTML($user["client_nickname"]);
$icon = "16x16_player_off.png";
if($user["client_away"] == 1) $icon = "16x16_away.png";
else if($user["client_flag_talking"] == 1) $icon = "16x16_player_on.png";
else if($user["client_output_hardware"] == 0) $icon = "16x16_hardware_output_muted.png";
else if($user["client_output_muted"] == 1) $icon = "16x16_output_muted.png";
else if($user["client_input_hardware"] == 0) $icon = "16x16_hardware_input_muted.png";
else if($user["client_input_muted"] == 1) $icon = "16x16_input_muted.png";
$flags = array();
if(isset($this->_channelGroupFlags[$user["client_channel_group_id"]]))
{
$flags[] = $this->_channelGroupFlags[$user["client_channel_group_id"]];
}
$serverGroups = explode(",", $user["client_servergroups"]);
foreach ($serverGroups as $serverGroup)
{
if(isset($this->_serverGroupFlags[$serverGroup]))
{
$flags[] = $this->_serverGroupFlags[$serverGroup];
}
}
$flags = $this->renderFlags($flags);
$content .= <<<HTML
<div class="tsstatusItem">
<img src="$imagePath$icon" />$name
<div class="tsstatusFlags">
$flags
</div>
</div>
HTML;
}
}
}
return $content;
}
private function renderChannels($channelId)
{
$content = "";
$imagePath = $this->imagePath;
foreach ($this->_channelDatas as $channel)
{
if($channel["pid"] == $channelId)
{
if($channel["show"])
{
$name = $this->toHTML($channel["channel_name"]);
$title = $name . " [" . $channel["cid"] . "]";
$link = "javascript:tsstatusconnect('" . $this->_javascriptName . "'," . $channel["cid"] . ")";
$icon = "16x16_channel_green.png";
if( $channel["channel_maxclients"] > -1 && ($channel["total_clients"] >= $channel["channel_maxclients"])) $icon = "16x16_channel_red.png";
else if( $channel["channel_maxfamilyclients"] > -1 && ($channel["total_clients_family"] >= $channel["channel_maxfamilyclients"])) $icon = "16x16_channel_red.png";
else if($channel["channel_flag_password"] == 1) $icon = "16x16_channel_yellow.png";
$flags = array();
if($channel["channel_flag_default"] == 1) $flags[] = '16x16_default.png';
if($channel["channel_needed_talk_power"] > 0) $flags[] = '16x16_moderated.png';
if($channel["channel_flag_password"] == 1) $flags[] = '16x16_register.png';
$flags = $this->renderFlags($flags);
$users = $this->renderUsers($channel["cid"]);
$childs = $this->renderChannels($channel["cid"]);
$cid = $channel["cid"];
$content .= <<<HTML
<div class="tsstatusItem">
<a href="$link" title="$title">
<img src="$imagePath$icon" />$name
<div class="tsstatusFlags">
$flags
</div>
$users
</a>
$childs
</div>
HTML;
}
else $content .= $this->renderChannels($channel["cid"]);
}
}
return $content;
}
public function render()
{
try
{
$cache = $this->getCache();
if($cache != false) return $cache;
$this->update();
if($this->hideEmptyChannels && count($this->_channelList) > 0) $this->setShowFlag(array_intersect($this->_channelList, array_keys($this->_userDatas)));
else if($this->hideEmptyChannels) $this->setShowFlag(array_keys($this->_userDatas));
else if(count($this->_channelList) > 0) $this->setShowFlag($this->_channelList);
$host = $this->_host;
$port = $this->_serverDatas["virtualserver_port"];
$name = $this->toHTML($this->_serverDatas["virtualserver_name"]);
$icon = $this->imagePath . "16x16_server_green.png";
$this->_javascriptName = $javascriptName = preg_replace("#[^a-z-A-Z0-9]#", "-", $host . "-" . $port);
$options = "";
if ($this->showNicknameBox) $options .= $this->renderOptionBox("nickname", "Nickname");
if($this->showPasswordBox && isset($this->_serverDatas["virtualserver_flag_password"]) && $this->_serverDatas["virtualserver_flag_password"] == 1) $options .= $this->renderOptionBox("password", "Password");
$channels = $this->renderChannels(0);
$content = <<<HTML
<div class="tsstatus">
<input type="hidden" id="tsstatus-$javascriptName-hostport" value="$host:$port" />
$options
<div class="tsstatusItem tsstatusServer">
<a href="javascript:tsstatusconnect('$javascriptName')"><img src="$icon" />$name</a>
$channels
</div>
</div>
HTML;
$this->saveCache($content);
}
catch (Exception $ex)
{
$this->disconnect();
$content = '<div class="tsstatusError">' . $ex->getMessage() . '</div>';
}
return $content;
}
}
?>