222 lines
9.6 KiB
C++
222 lines
9.6 KiB
C++
#include "ts_client.hpp"
|
|||
|
|
|
||
|
|
#include "helpers.hpp"
|
||
|
|
|
||
|
|
#include <teamspeak/clientlib.h>
|
||
|
|
#include <teamspeak/public_errors.h>
|
||
|
|
|
||
|
|
#include <algorithm>
|
||
|
|
#include <cstdint>
|
||
|
|
#include <cstring>
|
||
|
|
#include <iostream>
|
||
|
|
#include <memory>
|
||
|
|
|
||
|
|
namespace com::teamspeak
|
||
|
|
{
|
||
|
|
/*static*/ std::unique_ptr<TS_Client> TS_Client::ts_client;
|
||
|
|
|
||
|
|
TS_Client::TS_Client(std::string_view path, bool& success)
|
||
|
|
{
|
||
|
|
success = true;
|
||
|
|
/* Create struct for callback function pointers */
|
||
|
|
struct ClientUIFunctions funcs;
|
||
|
|
|
||
|
|
/* Initialize all callbacks with NULL */
|
||
|
|
memset(&funcs, 0, sizeof(struct ClientUIFunctions));
|
||
|
|
|
||
|
|
/* Callback function pointers */
|
||
|
|
/* It is sufficient to only assign those callback functions you are using. When adding more callbacks, add those function pointers here. */
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Callback for connection status change.
|
||
|
|
* Connection status switches through the states STATUS_DISCONNECTED, STATUS_CONNECTING, STATUS_CONNECTED and STATUS_CONNECTION_ESTABLISHED.
|
||
|
|
*
|
||
|
|
* Parameters:
|
||
|
|
* serverConnectionHandlerID - Server connection handler ID
|
||
|
|
* newStatus - New connection status, see the enum ConnectStatus in public_definitions.h
|
||
|
|
* errorNumber - Error code. Should be zero when connecting or actively disconnection.
|
||
|
|
* Contains error state when losing connection.
|
||
|
|
*/
|
||
|
|
funcs.onConnectStatusChangeEvent = [](uint64_t connection_id, int32_t status, uint32_t error)
|
||
|
|
{
|
||
|
|
if (TS_Client::ts_client)
|
||
|
|
TS_Client::ts_client->on_connect_status_change(connection_id, static_cast<ConnectStatus>(status), error);
|
||
|
|
};
|
||
|
|
funcs.onClientMoveEvent = [](uint64 connection_id, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility, const char* /*msg*/)
|
||
|
|
{
|
||
|
|
if (TS_Client::ts_client)
|
||
|
|
TS_Client::ts_client->on_client_move_common(connection_id, clientID, oldChannelID, newChannelID, static_cast<Visibility>(visibility));
|
||
|
|
};
|
||
|
|
funcs.onClientMoveSubscriptionEvent = [](uint64 connection_id, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility)
|
||
|
|
{
|
||
|
|
if (TS_Client::ts_client)
|
||
|
|
TS_Client::ts_client->on_client_move_common(connection_id, clientID, oldChannelID, newChannelID, static_cast<Visibility>(visibility));
|
||
|
|
};
|
||
|
|
funcs.onClientMoveTimeoutEvent = [](uint64 connection_id, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility, const char* /*msg*/)
|
||
|
|
{
|
||
|
|
if (TS_Client::ts_client)
|
||
|
|
TS_Client::ts_client->on_client_move_common(connection_id, clientID, oldChannelID, newChannelID, static_cast<Visibility>(visibility));
|
||
|
|
};
|
||
|
|
funcs.onClientMoveMovedEvent = [](uint64 connection_id, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility, anyID /*moverID*/, const char* /*moverName*/, const char* /*moverUniqueIdentifier*/, const char* /*msg*/)
|
||
|
|
{
|
||
|
|
if (TS_Client::ts_client)
|
||
|
|
TS_Client::ts_client->on_client_move_common(connection_id, clientID, oldChannelID, newChannelID, static_cast<Visibility>(visibility));
|
||
|
|
};
|
||
|
|
funcs.onClientKickFromChannelEvent = [](uint64 connection_id, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility, anyID /*kickerID*/, const char* /*kickerName*/, const char* /*kickerUniqueIdentifier*/, const char* /*msg*/)
|
||
|
|
{
|
||
|
|
if (TS_Client::ts_client)
|
||
|
|
TS_Client::ts_client->on_client_move_common(connection_id, clientID, oldChannelID, newChannelID, static_cast<Visibility>(visibility));
|
||
|
|
};
|
||
|
|
funcs.onClientKickFromServerEvent = [](uint64 connection_id, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility, anyID /*kickerID */, const char* /*kickerName*/, const char* /*kickerUniqueIdentifier*/, const char* /*msg*/)
|
||
|
|
{
|
||
|
|
if (TS_Client::ts_client)
|
||
|
|
TS_Client::ts_client->on_client_move_common(connection_id, clientID, oldChannelID, newChannelID, static_cast<Visibility>(visibility));
|
||
|
|
};
|
||
|
|
/*
|
||
|
|
* This event is called when a client starts or stops talking.
|
||
|
|
*
|
||
|
|
* Parameters:
|
||
|
|
* serverConnectionHandlerID - Server connection handler ID
|
||
|
|
* status - 1 if client starts talking, 0 if client stops talking
|
||
|
|
* isReceivedWhisper - 1 if this event was caused by whispering, 0 if caused by normal talking
|
||
|
|
* clientID - ID of the client who announced the talk status change
|
||
|
|
*/
|
||
|
|
funcs.onTalkStatusChangeEvent = [](uint64 serverConnectionHandlerID, int status, int isReceivedWhisper, anyID clientID)
|
||
|
|
{
|
||
|
|
char* name = nullptr;
|
||
|
|
/* Query client nickname from ID */
|
||
|
|
if (ts3client_getClientVariableAsString(serverConnectionHandlerID, clientID, CLIENT_NICKNAME, &name) != ERROR_ok)
|
||
|
|
return;
|
||
|
|
|
||
|
|
auto status_str = std::string();
|
||
|
|
switch (status)
|
||
|
|
{
|
||
|
|
case TalkStatus::STATUS_TALKING:
|
||
|
|
status_str = "starts";
|
||
|
|
break;
|
||
|
|
case TalkStatus::STATUS_NOT_TALKING:
|
||
|
|
status_str = "stops";
|
||
|
|
break;
|
||
|
|
case TalkStatus::STATUS_TALKING_WHILE_DISABLED:
|
||
|
|
status_str = "starts (while disabled)";
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
std::cout << "Client " << name << " " << status_str << " talking." << std::endl;
|
||
|
|
/* Release dynamically allocated memory only if function succeeded */
|
||
|
|
ts3client_freeMemory(name);
|
||
|
|
};
|
||
|
|
funcs.onServerErrorEvent = [](uint64 connection_id, const char* error_msg, uint32_t error, const char* /*return_code*/, const char* extra_msg)
|
||
|
|
{
|
||
|
|
auto msg = std::string("onServerError: ");
|
||
|
|
if (error_msg)
|
||
|
|
msg += error_msg;
|
||
|
|
|
||
|
|
if (extra_msg)
|
||
|
|
{
|
||
|
|
auto extra = std::string(extra_msg);
|
||
|
|
if (!extra.empty())
|
||
|
|
msg += " Extra Msg: " + extra;
|
||
|
|
}
|
||
|
|
if (error == ERROR_ok)
|
||
|
|
ts3client_logMessage(msg.c_str(), LogLevel::LogLevel_DEBUG, "", connection_id);
|
||
|
|
else
|
||
|
|
print_error(error, msg, connection_id);
|
||
|
|
};
|
||
|
|
funcs.onIgnoredWhisperEvent = [](uint64 connection_id, anyID client_id)
|
||
|
|
{
|
||
|
|
print_error(ts3client_allowWhispersFrom(connection_id, client_id), "Error allowing whisper", connection_id);
|
||
|
|
};
|
||
|
|
|
||
|
|
if (auto error = ts3client_initClientLib(&funcs, nullptr, LogType_FILE | LogType_CONSOLE | LogType_USERLOGGING, nullptr, path.data()); error != ERROR_ok)
|
||
|
|
{
|
||
|
|
print_error(error, "Error initialzing clientlib", 0);
|
||
|
|
success = false;
|
||
|
|
}
|
||
|
|
_funcs = funcs;
|
||
|
|
|
||
|
|
if (success)
|
||
|
|
{
|
||
|
|
auto error = uint32_t{ ERROR_ok };
|
||
|
|
_custom_device = std::make_unique<Custom_Device>(error);
|
||
|
|
if (ERROR_ok != error)
|
||
|
|
_custom_device = {};
|
||
|
|
|
||
|
|
success = ERROR_ok == error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TS_Client::~TS_Client()
|
||
|
|
{
|
||
|
|
if (auto error = ts3client_destroyClientLib(); error != ERROR_ok)
|
||
|
|
{
|
||
|
|
print_error(error, "Failed to destroy clientlib", 0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*static*/ bool TS_Client::create(std::string_view path)
|
||
|
|
{
|
||
|
|
if (TS_Client::ts_client)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
auto success = false;
|
||
|
|
auto result = std::make_unique<TS_Client>(path, success);
|
||
|
|
if (!success)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
success = result->log_clientlib_version();
|
||
|
|
if (!success)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
TS_Client::ts_client.swap(result);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool TS_Client::log_clientlib_version()
|
||
|
|
{
|
||
|
|
char* version = nullptr;
|
||
|
|
if (auto error = ts3client_getClientLibVersion(&version); error != ERROR_ok)
|
||
|
|
{
|
||
|
|
print_error(error, "Failed to get clientlib version", 0);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
auto msg = "Client lib version: " + std::string(version);
|
||
|
|
ts3client_freeMemory(version); /* Release dynamically allocated memory */
|
||
|
|
version = nullptr;
|
||
|
|
ts3client_logMessage(msg.c_str(), LogLevel_INFO, "", 0);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void TS_Client::on_client_move_common(uint64_t connection_id, uint16_t client_id, uint64_t oldChannelID, uint64_t newChannelID, Visibility visibility)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
void TS_Client::on_connect_status_change(uint64_t connection_id, ConnectStatus status, uint32_t error)
|
||
|
|
{
|
||
|
|
{
|
||
|
|
auto msg = std::string("Connect status changed: ") + std::to_string(connection_id) + " " + std::to_string(status);
|
||
|
|
ts3client_logMessage(msg.c_str(), LogLevel_INFO, "", connection_id);
|
||
|
|
}
|
||
|
|
/* Failed to connect ? */
|
||
|
|
if (status == STATUS_DISCONNECTED && error == ERROR_failed_connection_initialisation)
|
||
|
|
{
|
||
|
|
ts3client_logMessage("Looks like there is no server running.\n", LogLevel_INFO, "", connection_id);
|
||
|
|
}
|
||
|
|
print_error(error, "onConnectStatusChange", connection_id);
|
||
|
|
|
||
|
|
if (_shutting_down)
|
||
|
|
return;
|
||
|
|
|
||
|
|
/* pass the event on to the connection instance */
|
||
|
|
if (auto it = std::find_if(std::begin(_connections), std::end(_connections), [connection_id](auto&& connection)
|
||
|
|
{
|
||
|
|
return connection && connection_id == connection->_connection_id;
|
||
|
|
}); it != std::end(_connections))
|
||
|
|
{
|
||
|
|
auto&& connection = *it;
|
||
|
|
connection->on_connect_status_change(status, error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|