首次推送

This commit is contained in:
sansen
2026-07-20 19:01:03 +08:00
parent ea01b9cf99
commit ef1bf61f9f
4484 changed files with 937163 additions and 1 deletions
@@ -0,0 +1,121 @@
cmake_minimum_required(VERSION 3.21)
project("TeamSpeak SDK Samples" LANGUAGES C CXX)
set(CMAKE_SKIP_BUILD_RPATH FALSE)
set(CMAKE_MACOSX_RPATH TRUE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
if (APPLE)
set(CMAKE_INSTALL_RPATH "@executable_path")
else()
set(CMAKE_INSTALL_RPATH "\$ORIGIN")
endif()
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
set(BUILD_RPATH_USE_ORIGIN TRUE)
find_package(team_client CONFIG)
find_package(team_server CONFIG)
if(NOT team_client_FOUND AND NOT team_server_FOUND)
message(FATAL_ERROR
"Neither team_client nor team_server SDK found via CMAKE_PREFIX_PATH. "
"Pass -DCMAKE_PREFIX_PATH=... pointing at a stage directory.")
endif()
set(TS_SAMPLES
client
client_customdevice
client_minimal
client_minimal_filetransfer
client_multi
client_cpp_repeater
server
server_creation_params
server_filetransfer
server_minimal
server_permissions
)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# A sample's SDK flavor is given by its folder-name prefix: client* links the client SDK, server*
# the server SDK.
foreach(sample_folder IN LISTS TS_SAMPLES)
if(sample_folder MATCHES "^client")
set(sample_type client)
elseif(sample_folder MATCHES "^server")
set(sample_type server)
else()
message(FATAL_ERROR "Cannot infer SDK type from sample folder '${sample_folder}' (expected client*/server*)")
endif()
if("${sample_type}" STREQUAL "client" AND NOT team_client_FOUND)
continue()
endif()
if("${sample_type}" STREQUAL "server" AND NOT team_server_FOUND)
continue()
endif()
if("${sample_type}" STREQUAL "server"
AND CMAKE_SYSTEM_NAME STREQUAL "Linux"
AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86")
continue()
endif()
set(ts_sample_bin "ts_${sample_folder}")
include("${CMAKE_CURRENT_LIST_DIR}/${sample_folder}/sources.cmake")
add_executable(${ts_sample_bin} ${TS_SAMPLE_SRC})
set_target_properties(${ts_sample_bin} PROPERTIES CXX_STANDARD 17)
source_group("" FILES ${TS_SAMPLE_SRC})
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ide.cmake")
if(TS_SDK_IDE_FILES)
target_sources(${ts_sample_bin} PRIVATE ${TS_SDK_IDE_FILES})
source_group("teamspeak" FILES ${TS_SDK_IDE_FILES})
endif()
if("${sample_type}" STREQUAL "client")
target_link_libraries(${ts_sample_bin} PRIVATE teamspeak::client)
if(APPLE)
set_target_properties(${ts_sample_bin} PROPERTIES
INSTALL_RPATH "@executable_path"
)
endif()
find_package(Threads REQUIRED)
target_link_libraries(${ts_sample_bin} PRIVATE Threads::Threads)
set(ts_sdk_target teamspeak::client)
elseif("${sample_type}" STREQUAL "server")
target_link_libraries(${ts_sample_bin} PRIVATE teamspeak::server)
if(APPLE)
set_target_properties(${ts_sample_bin} PROPERTIES
INSTALL_RPATH "@executable_path"
)
endif()
set(ts_sdk_target teamspeak::server)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_libraries(${ts_sample_bin} PRIVATE dl)
endif()
# Copy the SDK shared library next to the sample so it runs from the build tree.
add_custom_command(TARGET ${ts_sample_bin} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE:${ts_sdk_target}>"
"$<TARGET_FILE_DIR:${ts_sample_bin}>"
)
install(TARGETS ${ts_sample_bin} RUNTIME DESTINATION bin)
endforeach()
set(_ts_imported_libs "")
if(team_client_FOUND)
list(APPEND _ts_imported_libs teamspeak::client)
endif()
if(team_server_FOUND)
list(APPEND _ts_imported_libs teamspeak::server)
endif()
if(_ts_imported_libs)
install(IMPORTED_RUNTIME_ARTIFACTS ${_ts_imported_libs}
RUNTIME DESTINATION bin
LIBRARY DESTINATION bin
)
endif()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
message("generating TeamSpeak SDK sample ${TS_SDK_SAMPLE}")
set (TS_SAMPLE_SRC
"${CMAKE_CURRENT_LIST_DIR}/main.c"
)
@@ -0,0 +1,107 @@
#include "connection_handler.hpp"
#include "ts_client.hpp"
namespace com::teamspeak
{
/* We'll be using the create() function instead */
Connection_Handler::Connection_Handler(uint64_t connection_id)
: _connection_id(connection_id)
{}
Connection_Handler::~Connection_Handler()
{
if (auto error = ts3client_destroyServerConnectionHandler(_connection_id); error != ERROR_ok)
print_error(error, "Error destroying connection", _connection_id);
}
/*static*/ std::unique_ptr<Connection_Handler> Connection_Handler::create()
{
auto connection_id = uint64_t{ 0 };
if (auto error = ts3client_spawnNewServerConnectionHandler(0, &connection_id); error != ERROR_ok)
{
print_error(error, "Error spawning server connection handler", 0);
return {};
}
return std::make_unique<Connection_Handler>(connection_id);
}
uint32_t Connection_Handler::connect()
{
/* Connect to server on localhost:9987 with nickname "client", no default channel, no default channel password and server password "secret" */
if (auto error = ts3client_startConnection(
_connection_id,
_connection_data.identity.c_str(),
_connection_data.address.c_str(),
_connection_data.port,
_connection_data.nick.c_str(),
nullptr,
"",
_connection_data.pw.c_str());
error != ERROR_ok)
{
print_error(error, "Error connecting to server", _connection_id);
return error;
}
return ERROR_ok;
}
uint32_t Connection_Handler::disconnect(std::string_view reason)
{
if (auto error = ts3client_stopConnection(_connection_id, reason.data()); error != ERROR_ok)
{
printf("Error stopping connection: %d\n", error);
return error;
}
return ERROR_ok;
}
void Connection_Handler::on_connect_status_change(ConnectStatus status, uint32_t error)
{
if (TS_Client::_do_autoreconnect && ConnectStatus::STATUS_DISCONNECTED == status)
{
// TODO: maybe should be timer-delayed?
connect();
}
}
uint32_t Connection_Handler::open_audio(Audio_IO audio_io, std::string_view mode, std::string_view device_id)
{
if (Audio_IO::Capture == audio_io)
{
if (auto error = ts3client_openPlaybackDevice(_connection_id, mode.data(), device_id.data()); error != ERROR_ok)
{
print_error(error, "Error opening playback device.", _connection_id);
return error;
}
}
else if (Audio_IO::Playback == audio_io)
{
if (auto error = ts3client_openCaptureDevice(_connection_id, mode.data(), device_id.data()); error != ERROR_ok)
{
print_error(error, "Error opening capture device.", _connection_id);
return error;
}
// Turn off any DSP, except a very low power based Voice Activity Detection
/* Adjust "vad_mode" value to use power */
print_error(
ts3client_setPreProcessorConfigValue(_connection_id, "vad_mode", "1"),
"Error setting vad_mode value to hybrid.", _connection_id);
/* Adjust "voiceactivation_level" value */
print_error(
ts3client_setPreProcessorConfigValue(_connection_id, "voiceactivation_level", "-50"),
"Error setting voiceactivation_level.", _connection_id);
/* turn on vad */
print_error(
ts3client_setPreProcessorConfigValue(_connection_id, "vad", "true"),
"Couldn't turn on VAD.", _connection_id);
// TODO: Turn off denoiser etc. no matter the default value
}
return ERROR_ok;
}
}
@@ -0,0 +1,44 @@
#pragma once
#include "helpers.hpp"
#include <teamspeak/clientlib.h>
#include <teamspeak/public_definitions.h>
#include <teamspeak/public_errors.h>
#include <cstdint>
#include <memory>
#include <string_view>
namespace com::teamspeak
{
class Connection_Handler
{
public:
/* We'll be using the create() function instead */
Connection_Handler(uint64_t connection_id);
Connection_Handler() = delete;
~Connection_Handler();
static std::unique_ptr<Connection_Handler> create();
uint32_t connect();
uint32_t disconnect(std::string_view reason = "leaving");
uint32_t open_audio(Audio_IO audio_io, std::string_view mode, std::string_view device_id);
void on_connect_status_change(ConnectStatus status, uint32_t error);
struct Connection_Data
{
std::string address = "";
uint16_t port = 9987;
std::string nick = "";
std::string identity = "";
std::string pw = "";
};
Connection_Data _connection_data;
const uint64_t _connection_id;
};
}
@@ -0,0 +1,27 @@
#include "custom_device.hpp"
#include "helpers.hpp"
#include <teamspeak/clientlib.h>
#include <teamspeak/public_errors.h>
namespace com::teamspeak
{
Custom_Device::Custom_Device(uint32_t& error)
{
error = ts3client_registerCustomDevice(custom_device, custom_device, 48000, 1, 48000, 1);
if (error != ERROR_ok)
{
print_error(error, "Error creating custom device.", 0);
}
}
Custom_Device::~Custom_Device()
{
/* Unregister the custom device. This automatically closes the device.*/
if (auto error = ts3client_unregisterCustomDevice(custom_device); error != ERROR_ok)
{
printf("Error unregistering custom device: %d\n", error);
}
}
}
@@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
namespace com::teamspeak
{
class Custom_Device
{
public:
Custom_Device(uint32_t& error);
~Custom_Device();
static constexpr const char* custom_mode = "custom";
static constexpr const char* custom_device = "loopback";
};
}
@@ -0,0 +1,50 @@
#include "helpers.hpp"
#include <teamspeak/clientlib.h>
#include <teamspeak/public_errors.h>
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#else
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#endif
namespace com::teamspeak {
void print_error(uint32_t error, const std::string& msg, uint64_t connection_id)
{
if (error == ERROR_ok)
return;
char* errormsg = nullptr;
if (ts3client_getErrorMessage(error, &errormsg) == ERROR_ok)
{
auto error_msg = msg + " " + std::string(errormsg);
ts3client_freeMemory(errormsg);
ts3client_logMessage(error_msg.c_str(), LogLevel_ERROR, "", connection_id);
return;
}
ts3client_logMessage(msg.c_str(), LogLevel_ERROR, "", connection_id);
}
auto create_identity() -> std::string
{
/* Create a new client identity */
/* In your real application you should do this only once, store the assigned identity locally and then reuse it. */
auto result = std::string();
char* identity = nullptr;
if (auto error = ts3client_createIdentity(&identity); error != ERROR_ok)
{
print_error(error, "Error creating identity", 0);
}
else
{
result = std::string(identity);
ts3client_freeMemory(identity); /* Release dynamically allocated memory */
identity = nullptr;
}
return result;
}
}
@@ -0,0 +1,17 @@
#pragma once
#include <cstdint>
#include <string>
namespace com::teamspeak {
enum Audio_IO : uint8_t
{
Playback = 0,
Capture
};
void print_error(uint32_t error, const std::string& msg, uint64_t connection_id = 0);
auto create_identity()->std::string;
}
@@ -0,0 +1,234 @@
/*
* TeamSpeak SDK client repeater sample
*
* Copyright (c) TeamSpeak-Systems
*/
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#else
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <stdio.h>
#include "custom_device.hpp"
#include "helpers.hpp"
#include "ts_client.hpp"
#include <teamspeak/public_definitions.h>
#include <teamspeak/public_errors.h>
#include <teamspeak/clientlib.h>
#include <chrono>
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#ifdef _WIN32
#define SLEEP(x) Sleep(x)
#define strdup(x) _strdup(x)
#else
#define SLEEP(x) usleep(x*1000)
#endif
struct Opts {
std::string from_ip = "";
uint16_t from_port = 0;
std::string to_ip = "";
uint16_t to_port = 0;
};
namespace {
char* programPath(char* programInvocation)
{
char* path;
char* end;
int length;
char pathsep;
if (programInvocation == NULL) return strdup("");
#ifdef _WIN32
pathsep = '\\';
#else
pathsep = '/';
#endif
end = strrchr(programInvocation, pathsep);
if (!end) return strdup("");
length = (end - programInvocation) + 2;
path = (char*)malloc(length);
strncpy(path, programInvocation, length - 1);
path[length - 1] = 0;
return path;
}
void print_usage()
{
std::cout << "usage: from_id from_port to_id to_port" << std::endl;
}
}
int main(int argc, char** argv)
{
// TODO: Decide on a proper header only options parser
auto opts = Opts();
if (argc != 5)
{
print_usage();
return -1;
}
for (auto i = decltype(argc){1}; i < argc; ++i)
{
switch (i)
{
case 1:
opts.from_ip = std::string(argv[i]);
break;
case 2:
try
{
opts.from_port = std::stoul(argv[i]);
}
catch (std::exception& e)
{
print_usage();
return -1;
}
break;
case 3:
opts.to_ip = std::string(argv[i]);
break;
case 4:
try
{
opts.to_port = std::stoul(argv[i]);
}
catch (std::exception& e)
{
print_usage();
return -1;
}
break;
}
}
std::cout << "listening to " << opts.from_ip.c_str() << ":" << opts.from_port << ", sending to " << opts.to_ip.c_str() << ":" << opts.to_port << std::endl;
using namespace com::teamspeak;
{
auto* path = programPath(argv[0]);
auto success = TS_Client::create(path);
free(path);
if (!success)
return 1;
}
auto&& ts_client = TS_Client::ts_client;
{
auto identity = create_identity();
if (identity.empty())
return 1;
ts_client->_identity = identity;
}
// We'll recycle them in case of disconnect, hence spawn these only once
{
auto connection = Connection_Handler::create();
if (!connection)
return 1;
ts_client->_connections[Audio_IO::Playback].swap(connection);
}
{
auto connection = Connection_Handler::create();
if (!connection)
return 1;
ts_client->_connections[Audio_IO::Capture].swap(connection);
}
auto&& connection_listen = ts_client->_connections[Audio_IO::Playback];
auto&& connection_broadcast = ts_client->_connections[Audio_IO::Capture];
connection_listen->open_audio(Audio_IO::Playback, Custom_Device::custom_mode, Custom_Device::custom_device);
connection_broadcast->open_audio(Audio_IO::Capture, Custom_Device::custom_mode, Custom_Device::custom_device);
connection_listen->_connection_data = Connection_Handler::Connection_Data{
opts.from_ip,
opts.from_port,
"repeater-listener",
ts_client->_identity
};
connection_listen->connect();
connection_broadcast->_connection_data = Connection_Handler::Connection_Data{
opts.to_ip,
opts.to_port,
"repeater-broadcaster",
ts_client->_identity
};
connection_broadcast->connect();
// 48000 kHz, 1ch, 20ms -> 960 samples
auto playback_buffer = std::array<int16_t, 960>();
auto custom_audio_thread = std::thread([&playback_buffer]()
{
while (!TS_Client::ts_client->_shutting_down)
{
for (;;)
{
/* Get playback data from the client lib */
if (auto error_playback = ts3client_acquireCustomPlaybackData(Custom_Device::custom_device, playback_buffer.data(), playback_buffer.size()); error_playback != ERROR_ok)
{
if (ERROR_sound_no_data == error_playback)
{
/* Not an error. The client lib has no playback data available.
Depending on your custom sound API, either pause playback for
performance optimization or send a buffer of zeros. */
// we're doing a no-op here
}
else
{
/* Error occured */
print_error(error_playback, "Failed to get playback data", 0);
}
break; // break draining (inner loop)
}
else
{
// we got playback data, loop it back to capture
/* Stream your capture data to the client lib */
if (auto error_capture = ts3client_processCustomCaptureData(Custom_Device::custom_device, playback_buffer.data(), playback_buffer.size()); ERROR_ok != error_capture)
{
print_error(error_capture, "Failed to process capture data", 0);
break; // break draining (inner loop)
}
}
}
using namespace std::chrono_literals;
std::this_thread::sleep_for(0.02s); // audio buffer size in our opus is 20ms
}
});
SLEEP(500);
/* Wait for user input */
printf("\n--- Press Return to disconnect from server and exit ---\n");
getchar();
/* Disconnect from servers */
ts_client->_shutting_down = true;
custom_audio_thread.join();
connection_listen->disconnect();
connection_broadcast->disconnect();
return 0;
}
@@ -0,0 +1,13 @@
message("generating TeamSpeak SDK sample ${TS_SDK_SAMPLE}")
set (TS_SAMPLE_SRC
"${CMAKE_CURRENT_LIST_DIR}/main.cpp"
"${CMAKE_CURRENT_LIST_DIR}/ts_client.hpp"
"${CMAKE_CURRENT_LIST_DIR}/ts_client.cpp"
"${CMAKE_CURRENT_LIST_DIR}/connection_handler.hpp"
"${CMAKE_CURRENT_LIST_DIR}/connection_handler.cpp"
"${CMAKE_CURRENT_LIST_DIR}/helpers.hpp"
"${CMAKE_CURRENT_LIST_DIR}/helpers.cpp"
"${CMAKE_CURRENT_LIST_DIR}/custom_device.hpp"
"${CMAKE_CURRENT_LIST_DIR}/custom_device.cpp"
)
@@ -0,0 +1,221 @@
#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);
}
}
}
@@ -0,0 +1,39 @@
#pragma once
#include "connection_handler.hpp"
#include "custom_device.hpp"
#include <teamspeak/public_definitions.h>
#include <array>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
namespace com::teamspeak
{
class TS_Client
{
public:
TS_Client(std::string_view path, bool& success);
~TS_Client();
static bool create(std::string_view path);
bool log_clientlib_version();
void on_client_move_common(uint64_t connection_id, uint16_t client_id, uint64_t old_channel_id, uint64_t new_channel_id, Visibility visibility);
void on_connect_status_change(uint64_t connection_id, ConnectStatus status, uint32_t error);
ClientUIFunctions _funcs;
std::string _identity = "";
std::array<std::unique_ptr<Connection_Handler>, 2> _connections;
bool _shutting_down = false;
static constexpr bool _do_autoreconnect{ true };
private:
std::unique_ptr<Custom_Device> _custom_device;
public:
static std::unique_ptr<TS_Client> ts_client;
};
}
@@ -0,0 +1,397 @@
/*
* TeamSpeak SDK client minimal sample with custom
* capture and record
*
* Copyright (c) TeamSpeak-Systems
*/
/* This example connects to a server and plays a wave file, while
recording incomming sound to output.wav */
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable : 4996)
#include <Windows.h>
#else
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <stdio.h>
#include <teamspeak/public_definitions.h>
#include <teamspeak/public_errors.h>
#include <teamspeak/clientlib.h>
#ifdef _WIN32
#define SLEEP(x) Sleep(x)
#else
#define SLEEP(x) usleep(x*1000)
#endif
#include "wave.h"
/*The client lib works at 48Khz internally.
It is therefore advisable to use the same for your project */
#define PLAYBACK_FREQUENCY 48000
#define PLAYBACK_CHANNELS 2
#define AUDIO_PROCESS_SECONDS 500
/*
* 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 clientlib_publicdefinitions.h
* errorNumber - Error code. Should be zero when connecting or actively disconnection.
* Contains error state when losing connection.
*/
void onConnectStatusChangeEvent(uint64 serverConnectionHandlerID, int newStatus, unsigned int errorNumber) {
printf("Connect status changed: %llu %d %u\n", (unsigned long long)serverConnectionHandlerID, newStatus, errorNumber);
/* Failed to connect ? */
if(newStatus == STATUS_DISCONNECTED && errorNumber == ERROR_failed_connection_initialisation) {
printf("Looks like there is no server running!\n");
}
}
/*
* Callback for current channels being announced to the client after connecting to a server.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* channelID - ID of the announced channel
* channelParentID - ID of the parent channel
*/
void onNewChannelEvent(uint64 serverConnectionHandlerID, uint64 channelID, uint64 channelParentID) {
/* Query channel name from channel ID */
char* name;
unsigned int error;
printf("onNewChannelEvent: %llu %llu %llu\n", (unsigned long long)serverConnectionHandlerID, (unsigned long long)channelID, (unsigned long long)channelParentID);
if((error = ts3client_getChannelVariableAsString(serverConnectionHandlerID, channelID, CHANNEL_NAME, &name)) == ERROR_ok) {
printf("New channel: %llu %s \n", (unsigned long long)channelID, name);
ts3client_freeMemory(name); /* Release dynamically allocated memory only if function succeeded */
} else {
char* errormsg;
if(ts3client_getErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error getting channel name in onNewChannelEvent: %s\n", errormsg);
ts3client_freeMemory(errormsg);
}
}
}
/*
* Callback for just created channels.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* channelID - ID of the announced channel
* channelParentID - ID of the parent channel
* invokerID - ID of the client who created the channel
* invokerName - Name of the client who created the channel
*/
void onNewChannelCreatedEvent(uint64 serverConnectionHandlerID, uint64 channelID, uint64 channelParentID, anyID invokerID, const char* invokerName, const char* invokerUniqueIdentifier) {
char* name;
/* Query channel name from channel ID */
if(ts3client_getChannelVariableAsString(serverConnectionHandlerID, channelID, CHANNEL_NAME, &name) != ERROR_ok)
return;
printf("New channel created: %s\n", name);
ts3client_freeMemory(name); /* Release dynamically allocated memory only if function succeeded */
}
/*
* Callback when a channel was deleted.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* channelID - ID of the deleted channel
* invokerID - ID of the client who deleted the channel
* invokerName - Name of the client who deleted the channel
*/
void onDelChannelEvent(uint64 serverConnectionHandlerID, uint64 channelID, anyID invokerID, const char* invokerName, const char* invokerUniqueIdentifier) {
printf("Channel ID %llu deleted by %s (%u)\n", (unsigned long long)channelID, invokerName, invokerID);
}
/*
* Called when a client joins, leaves or moves to another channel.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* clientID - ID of the moved client
* oldChannelID - ID of the old channel left by the client
* newChannelID - ID of the new channel joined by the client
* visibility - Visibility of the moved client. See the enum Visibility in clientlib_publicdefinitions.h
* Values: ENTER_VISIBILITY, RETAIN_VISIBILITY, LEAVE_VISIBILITY
*/
void onClientMoveEvent(uint64 serverConnectionHandlerID, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility, const char* moveMessage) {
printf("ClientID %u moves from channel %llu to %llu with message %s\n", clientID, (unsigned long long)oldChannelID, (unsigned long long)newChannelID, moveMessage);
}
/*
* Callback for other clients in current and subscribed channels being announced to the client.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* clientID - ID of the announced client
* oldChannelID - ID of the subscribed channel where the client left visibility
* newChannelID - ID of the subscribed channel where the client entered visibility
* visibility - Visibility of the announced client. See the enum Visibility in clientlib_publicdefinitions.h
* Values: ENTER_VISIBILITY, RETAIN_VISIBILITY, LEAVE_VISIBILITY
*/
void onClientMoveSubscriptionEvent(uint64 serverConnectionHandlerID, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility) {
char* name;
/* Query client nickname from ID */
if(ts3client_getClientVariableAsString(serverConnectionHandlerID, clientID, CLIENT_NICKNAME, &name) != ERROR_ok)
return;
printf("New client: %s\n", name);
ts3client_freeMemory(name); /* Release dynamically allocated memory only if function succeeded */
}
/*
* Called when a client drops his connection.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* clientID - ID of the moved client
* oldChannelID - ID of the channel the leaving client was previously member of
* newChannelID - 0, as client is leaving
* visibility - Always LEAVE_VISIBILITY
* timeoutMessage - Optional message giving the reason for the timeout
*/
void onClientMoveTimeoutEvent(uint64 serverConnectionHandlerID, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility, const char* timeoutMessage) {
printf("ClientID %u timeouts with message %s\n",clientID, timeoutMessage);
}
/*
* 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
*/
void onTalkStatusChangeEvent(uint64 serverConnectionHandlerID, int status, int isReceivedWhisper, anyID clientID) {
char* name;
/* Query client nickname from ID */
if(ts3client_getClientVariableAsString(serverConnectionHandlerID, clientID, CLIENT_NICKNAME, &name) != ERROR_ok)
return;
if(status == STATUS_TALKING) {
printf("Client \"%s\" starts talking.\n", name);
} else {
printf("Client \"%s\" stops talking.\n", name);
}
ts3client_freeMemory(name); /* Release dynamically allocated memory only if function succeeded */
}
void onServerErrorEvent(uint64 serverConnectionHandlerID, const char* errorMessage, unsigned int error, const char* returnCode, const char* extraMessage) {
printf("Error for server %llu: %s %s\n", (unsigned long long)serverConnectionHandlerID, errorMessage, extraMessage);
}
char* programPath(char* programInvocation){
char* path;
char* end;
int length;
char pathsep;
if (programInvocation == NULL) return strdup("");
#ifdef _WIN32
pathsep = '\\';
#else
pathsep = '/';
#endif
end = strrchr(programInvocation, pathsep);
if (!end) return strdup("");
length = (end - programInvocation) + 2;
path = (char*)malloc(length);
strncpy(path, programInvocation, length - 1);
path[length - 1] = 0;
return path;
}
int main(int argc, char** argv) {
uint64 scHandlerID;
unsigned int error;
char *version;
char *identity;
int captureFrequency;
int captureChannels;
short* captureBuffer;
int captureBufferSamples;
int audioPeriodCounter;
int captureAudioOffset;
int capturePeriodSize;
short* playbackBuffer;
int playbackAudioOffset;
int playbackPeriodSize;
char* path;
/* Create struct for callback function pointers */
struct ClientUIFunctions funcs;
/* Initialize all callbacks with NULL */
memset(&funcs, 0, sizeof(struct ClientUIFunctions));
/* Now assign the used callback function pointers */
funcs.onConnectStatusChangeEvent = onConnectStatusChangeEvent;
funcs.onNewChannelEvent = onNewChannelEvent;
funcs.onNewChannelCreatedEvent = onNewChannelCreatedEvent;
funcs.onDelChannelEvent = onDelChannelEvent;
funcs.onClientMoveEvent = onClientMoveEvent;
funcs.onClientMoveSubscriptionEvent = onClientMoveSubscriptionEvent;
funcs.onClientMoveTimeoutEvent = onClientMoveTimeoutEvent;
funcs.onTalkStatusChangeEvent = onTalkStatusChangeEvent;
funcs.onServerErrorEvent = onServerErrorEvent;
/* Read in the wave we are going to stream to the server */
if (!readWave("welcome_to_teamspeak.wav", &captureFrequency, &captureChannels, &captureBuffer, &captureBufferSamples))
return 1;
/* allocate AUDIO_PROCESS_SECONDS seconds worth of PLAYBACK_FREQUENCY 16bit PLAYBACK_CHANNELS channels */
playbackBuffer = (short*) malloc(AUDIO_PROCESS_SECONDS * PLAYBACK_FREQUENCY * sizeof(short) * PLAYBACK_CHANNELS);
if (!playbackBuffer){
printf("error: could not allocate memory for output wave\n");
return 1;
}
/* Initialize client lib with callbacks */
path = programPath(argv[0]);
if((error = ts3client_initClientLib(&funcs, NULL, LogType_FILE | LogType_CONSOLE, NULL, path)) != ERROR_ok) {
char* errormsg;
if(ts3client_getErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error initialzing serverlib: %s\n", errormsg);
ts3client_freeMemory(errormsg);
}
return 1;
}
/* register a new custom sound device, that captures at read wave freq+channels and plays PLAYBACK_CHANNELS channels at PLAYBACK_FREQUENCY */
if ((error = ts3client_registerCustomDevice("customWaveDeviceId", "Nice displayable wave device name", captureFrequency, captureChannels, PLAYBACK_FREQUENCY, PLAYBACK_CHANNELS)) != ERROR_ok) {
char* errormsg;
if(ts3client_getErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error registering custom sound device: %s\n", errormsg);
ts3client_freeMemory(errormsg);
}
}
/* Spawn a new server connection handler using the default port and store the server ID */
if((error = ts3client_spawnNewServerConnectionHandler(0, &scHandlerID)) != ERROR_ok) {
printf("Error spawning server connection handler: %d\n", error);
return 1;
}
/* Open capture device we created earlier */
if((error = ts3client_openCaptureDevice(scHandlerID, "custom", "customWaveDeviceId")) != ERROR_ok) {
printf("Error opening capture device: %d\n", error);
}
/* Open playback device we created earlier */
if((error = ts3client_openPlaybackDevice(scHandlerID, "custom", "customWaveDeviceId")) != ERROR_ok) {
printf("Error opening playback device: %d\n", error);
}
/* Create a new client identity */
/* In your real application you should do this only once, store the assigned identity locally and then reuse it. */
if((error = ts3client_createIdentity(&identity)) != ERROR_ok) {
printf("Error creating identity: %d\n", error);
return 1;
}
/* Connect to server on localhost:9987 with nickname "client", no default channel, no default channel password and server password "secret" */
if((error = ts3client_startConnection(scHandlerID, identity, "localhost", 9987, "client", NULL, "", "secret")) != ERROR_ok) {
printf("Error connecting to server: %d\n", error);
return 1;
}
ts3client_freeMemory(identity); /* Release dynamically allocated memory */
identity = NULL;
printf("Client lib initialized and running\n");
/* Query and print client lib version */
if((error = ts3client_getClientLibVersion(&version)) != ERROR_ok) {
printf("Failed to get clientlib version: %d\n", error);
return 1;
}
printf("Client lib version: %s\n", version);
ts3client_freeMemory(version); /* Release dynamically allocated memory */
version = NULL;
SLEEP(500);
printf("\n--- processing audio for %d seconds ---\n", AUDIO_PROCESS_SECONDS);
/*the clientlib works with 20ms packets internaly.
So the best is to feed is 20ms worth of sound at a time */
capturePeriodSize = (captureFrequency*20)/1000;
playbackPeriodSize = (PLAYBACK_FREQUENCY*20)/1000;
captureAudioOffset = 0;
playbackAudioOffset = 0;
for(audioPeriodCounter = 0; audioPeriodCounter < 50*AUDIO_PROCESS_SECONDS; ++audioPeriodCounter){ /*50*20=1000*/
/* wait 20 ms */
SLEEP(20);
/* make sure we dont stream past the end of our wave sample */
if (captureAudioOffset + capturePeriodSize > captureBufferSamples)
captureAudioOffset = 0;
/* stream capture data to the client lib */
if((error = ts3client_processCustomCaptureData("customWaveDeviceId", captureBuffer + captureAudioOffset*captureChannels, capturePeriodSize)) != ERROR_ok){
printf("Failed to get stream capture data: %d\n", error);
return 1;
}
/* get playback data from the client lib */
if((error = ts3client_acquireCustomPlaybackData("customWaveDeviceId", playbackBuffer + playbackAudioOffset*PLAYBACK_CHANNELS, playbackPeriodSize))!= ERROR_ok){
if(error != ERROR_sound_no_data) { //this error signals us to play silence
printf("Failed to get acquire playback data: %d\n", error);
return 1;
}
memset(playbackBuffer + playbackAudioOffset * PLAYBACK_CHANNELS, 0, playbackPeriodSize * PLAYBACK_CHANNELS * sizeof(short));
}
/*update buffer offsets */
captureAudioOffset += capturePeriodSize;
playbackAudioOffset += playbackPeriodSize;
}
/* Disconnect from server. ERROR_not_connected just means the peer is already
gone (e.g. the server was shut down) - nothing to do, so move on. */
if((error = ts3client_stopConnection(scHandlerID, "leaving")) != ERROR_ok && error != ERROR_not_connected)
printf("Error stopping connection: %d\n", error);
/* Destroy server connection handler */
if((error = ts3client_destroyServerConnectionHandler(scHandlerID)) != ERROR_ok)
printf("Error destroying ServerConnectionHandler: %d\n", error);
/* unregister the custom sound device */
if ((error = ts3client_unregisterCustomDevice("customWaveDeviceId")) != ERROR_ok)
printf("Error unregisterring custom sound device: %d\n", error);
/* Shutdown client lib */
if((error = ts3client_destroyClientLib()) != ERROR_ok)
printf("Failed to destroy clientlib: %d\n", error);
/* save the playback data */
writeWave("output.wav", PLAYBACK_FREQUENCY, PLAYBACK_CHANNELS, playbackBuffer, PLAYBACK_FREQUENCY*AUDIO_PROCESS_SECONDS);
/* release allocated memory */
free(captureBuffer);
free(playbackBuffer);
return 0;
}
@@ -0,0 +1,7 @@
message("generating TeamSpeak SDK sample ${TS_SDK_SAMPLE}")
set (TS_SAMPLE_SRC
"${CMAKE_CURRENT_LIST_DIR}/main.c"
"${CMAKE_CURRENT_LIST_DIR}/wave.c"
"${CMAKE_CURRENT_LIST_DIR}/wave.h"
)
@@ -0,0 +1,111 @@
#define _CRT_SECURE_NO_WARNINGS
#include "wave.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char riff[4] = { 'R', 'I', 'F', 'F' };
char wave[4] = { 'W', 'A', 'V', 'E' };
char fmt[4] = { 'f', 'm', 't', ' ' };
char dat[4] = { 'd', 'a', 't', 'a' };
void writeWave(const char* filename, int freq, int channels, short* buffer, int samples) {
struct WaveHeader wh;
int i;
int elemWritten;
FILE *f;
for(i=0; i<4; i++) {
wh.riffId[i] = riff[i];
wh.riffType[i] = wave[i];
wh.fmtId[i] = fmt[i];
wh.dataId[i] = dat[i];
}
/* Format chunk */
wh.fmtLen = 16;
wh.formatTag = 1; /* PCM */
wh.channels = channels;
wh.samplesPerSec = freq;
wh.avgBytesPerSec = freq * channels * sizeof(short);
wh.blockAlign = channels * sizeof(short);
wh.bitsPerSample = sizeof(short)*8;
wh.dataLen = samples * channels * sizeof(short);
wh.len = 36 + wh.dataLen;
f = fopen(filename,"wb");
if (!f) {
printf("error: could not write wave\n");
return;
}
elemWritten = fwrite(&wh, sizeof(wh), 1, f);
if (elemWritten) elemWritten = fwrite(buffer, wh.dataLen, 1, f);
fclose(f);
if (!elemWritten){
printf("error: could not write wave\n");
}
}
int readWave(const char* filename, int* freq, int* channels, short** buffer, int* samples) {
struct WaveHeader wh;
FILE *f;
int i;
int elemsRead;
memset(&wh, 0, sizeof(wh));
f = fopen(filename,"rb");
if (!f) {
printf("error: could not open wave %s\n",filename);
return 0;
}
fread(&wh, sizeof(wh), 1, f);
for(i=0; i<4; i++) {
if ((wh.riffId[i] != riff[i]) ||
(wh.riffType[i] != wave[i]) ||
(wh.fmtId[i] != fmt[i]) ||
(wh.dataId[i] != dat[i])){
goto closeError;
}
}
// Format chunk
if (wh.fmtLen != 16) goto closeError;
if (wh.formatTag != 1) goto closeError;
*channels = wh.channels;
if (*channels <1 || *channels >2) goto closeError;
*freq = wh.samplesPerSec;
if (wh.blockAlign != *channels * sizeof(short)) goto closeError;
*samples = wh.dataLen / (*channels * sizeof(short));
if (*samples < *freq) {
fclose(f);
printf("error: wave file is too short\n");
return 0;
}
(*buffer) = (short*) malloc(wh.dataLen);
if (!*buffer){
printf("error: could not allocate memory for wave\n");
return 0;
}
elemsRead = fread(*buffer, wh.dataLen, 1, f);
fclose(f);
if (elemsRead != 1){
printf("error: reading wave file\n");
return 0;
}
return 1;
closeError:
fclose(f);
printf("error: invalid wave file %s\n",filename);
return 0;
}
@@ -0,0 +1,29 @@
#ifndef WAVE_H
#define WAVE_H
struct WaveHeader {
// Riff chunk
char riffId[4]; // 'RIFF'
unsigned int len;
char riffType[4]; // 'WAVE'
// Format chunk
char fmtId[4]; // 'fmt '
unsigned int fmtLen;
unsigned short formatTag;
unsigned short channels;
unsigned int samplesPerSec;
unsigned int avgBytesPerSec;
unsigned short blockAlign;
unsigned short bitsPerSample;
// Data chunk
char dataId[4]; // 'data'
unsigned int dataLen;
};
#endif //WAVE_H
void writeWave(const char* filename, int freq, int channels, short* buffer, int samples);
//this reads a 16 bit 1 or 2 channel wave file. returns 0 on error, 1 on success
int readWave(const char* filename, int* freq, int* channels, short** buffer, int* samples);
@@ -0,0 +1,355 @@
/*
* TeamSpeak SDK client minimal sample
*
* Copyright (c) TeamSpeak-Systems
*/
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#else
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <stdio.h>
#include <teamspeak/public_definitions.h>
#include <teamspeak/public_errors.h>
#include <teamspeak/clientlib.h>
#ifdef _WIN32
#define SLEEP(x) Sleep(x)
#define strdup(x) _strdup(x)
#else
#define SLEEP(x) usleep(x*1000)
#endif
/*
* 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 clientlib_publicdefinitions.h
* errorNumber - Error code. Should be zero when connecting or actively disconnection.
* Contains error state when losing connection.
*/
void onConnectStatusChangeEvent(uint64 serverConnectionHandlerID, int newStatus, unsigned int errorNumber) {
printf("Connect status changed: %llu %d %u\n", (unsigned long long)serverConnectionHandlerID, newStatus, errorNumber);
/* Failed to connect ? */
if(newStatus == STATUS_DISCONNECTED && errorNumber == ERROR_failed_connection_initialisation) {
printf("Looks like there is no server running.\n");
}
}
/*
* Callback for current channels being announced to the client after connecting to a server.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* channelID - ID of the announced channel
* channelParentID - ID of the parent channel
*/
void onNewChannelEvent(uint64 serverConnectionHandlerID, uint64 channelID, uint64 channelParentID) {
/* Query channel name from channel ID */
char* name;
unsigned int error;
printf("onNewChannelEvent: %llu %llu %llu\n", (unsigned long long)serverConnectionHandlerID, (unsigned long long)channelID, (unsigned long long)channelParentID);
if((error = ts3client_getChannelVariableAsString(serverConnectionHandlerID, channelID, CHANNEL_NAME, &name)) == ERROR_ok) {
printf("New channel: %llu %s \n", (unsigned long long)channelID, name);
ts3client_freeMemory(name); /* Release dynamically allocated memory only if function succeeded */
} else {
char* errormsg;
if(ts3client_getErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error getting channel name in onNewChannelEvent: %s\n", errormsg);
ts3client_freeMemory(errormsg);
}
}
}
/*
* Callback for just created channels.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* channelID - ID of the announced channel
* channelParentID - ID of the parent channel
* invokerID - ID of the client who created the channel
* invokerName - Name of the client who created the channel
*/
void onNewChannelCreatedEvent(uint64 serverConnectionHandlerID, uint64 channelID, uint64 channelParentID, anyID invokerID, const char* invokerName, const char* invokerUniqueIdentifier) {
char* name;
/* Query channel name from channel ID */
if(ts3client_getChannelVariableAsString(serverConnectionHandlerID, channelID, CHANNEL_NAME, &name) != ERROR_ok)
return;
printf("New channel created: %s\n", name);
ts3client_freeMemory(name); /* Release dynamically allocated memory only if function succeeded */
}
/*
* Callback when a channel was deleted.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* channelID - ID of the deleted channel
* invokerID - ID of the client who deleted the channel
* invokerName - Name of the client who deleted the channel
*/
void onDelChannelEvent(uint64 serverConnectionHandlerID, uint64 channelID, anyID invokerID, const char* invokerName, const char* invokerUniqueIdentifier) {
printf("Channel ID %llu deleted by %s (%u)\n", (unsigned long long)channelID, invokerName, invokerID);
}
/*
* Called when a client joins, leaves or moves to another channel.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* clientID - ID of the moved client
* oldChannelID - ID of the old channel left by the client
* newChannelID - ID of the new channel joined by the client
* visibility - Visibility of the moved client. See the enum Visibility in clientlib_publicdefinitions.h
* Values: ENTER_VISIBILITY, RETAIN_VISIBILITY, LEAVE_VISIBILITY
*/
void onClientMoveEvent(uint64 serverConnectionHandlerID, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility, const char* moveMessage) {
printf("ClientID %u moves from channel %llu to %llu with message %s\n", clientID, (unsigned long long)oldChannelID, (unsigned long long)newChannelID, moveMessage);
}
/*
* Callback for other clients in current and subscribed channels being announced to the client.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* clientID - ID of the announced client
* oldChannelID - ID of the subscribed channel where the client left visibility
* newChannelID - ID of the subscribed channel where the client entered visibility
* visibility - Visibility of the announced client. See the enum Visibility in clientlib_publicdefinitions.h
* Values: ENTER_VISIBILITY, RETAIN_VISIBILITY, LEAVE_VISIBILITY
*/
void onClientMoveSubscriptionEvent(uint64 serverConnectionHandlerID, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility) {
char* name;
/* Query client nickname from ID */
if(ts3client_getClientVariableAsString(serverConnectionHandlerID, clientID, CLIENT_NICKNAME, &name) != ERROR_ok)
return;
printf("New client: %s\n", name);
ts3client_freeMemory(name); /* Release dynamically allocated memory only if function succeeded */
}
/*
* Called when a client drops his connection.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* clientID - ID of the moved client
* oldChannelID - ID of the channel the leaving client was previously member of
* newChannelID - 0, as client is leaving
* visibility - Always LEAVE_VISIBILITY
* timeoutMessage - Optional message giving the reason for the timeout
*/
void onClientMoveTimeoutEvent(uint64 serverConnectionHandlerID, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility, const char* timeoutMessage) {
printf("ClientID %u timeouts with message %s\n",clientID, timeoutMessage);
}
/*
* 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
*/
void onTalkStatusChangeEvent(uint64 serverConnectionHandlerID, int status, int isReceivedWhisper, anyID clientID) {
char* name;
/* Query client nickname from ID */
if(ts3client_getClientVariableAsString(serverConnectionHandlerID, clientID, CLIENT_NICKNAME, &name) != ERROR_ok)
return;
if(status == STATUS_TALKING) {
printf("Client \"%s\" starts talking.\n", name);
} else {
printf("Client \"%s\" stops talking.\n", name);
}
ts3client_freeMemory(name); /* Release dynamically allocated memory only if function succeeded */
}
/*
* This event is called when another client starts whispering to own client. Own client can decide to accept or deny
* receiving the whisper by adding the sending client to the whisper allow list. If not added, whispering is blocked.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* clientID - ID of the whispering client
*/
void onIgnoredWhisperEvent(uint64 serverConnectionHandlerID, anyID clientID) {
unsigned int error;
/* Add sending client to whisper allow list so own client will hear the voice data.
* It is sufficient to add a clientID only once, not everytime this event is called. However it won't
* hurt to add the same clientID to the allow list repeatedly, but is is not necessary. */
if((error = ts3client_allowWhispersFrom(serverConnectionHandlerID, clientID)) != ERROR_ok) {
printf("Error setting client on whisper allow list: %u\n", error);
} else {
printf("Added client %d to whisper allow list\n", clientID);
}
}
void onServerErrorEvent(uint64 serverConnectionHandlerID, const char* errorMessage, unsigned int error, const char* returnCode, const char* extraMessage) {
printf("Error for server %llu: %s %s\n", (unsigned long long)serverConnectionHandlerID, errorMessage, extraMessage);
}
char* programPath(char* programInvocation){
char* path;
char* end;
int length;
char pathsep;
if (programInvocation == NULL) return strdup("");
#ifdef _WIN32
pathsep = '\\';
#else
pathsep = '/';
#endif
end = strrchr(programInvocation, pathsep);
if (!end) return strdup("");
length = (end - programInvocation)+2;
path = (char*) malloc(length);
strncpy(path, programInvocation, length-1);
path[length-1] = 0;
return path;
}
int main(int argc, char **argv) {
uint64 scHandlerID;
unsigned int error;
char *version;
char *identity;
char * path;
/* 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. */
funcs.onConnectStatusChangeEvent = onConnectStatusChangeEvent;
funcs.onNewChannelEvent = onNewChannelEvent;
funcs.onNewChannelCreatedEvent = onNewChannelCreatedEvent;
funcs.onDelChannelEvent = onDelChannelEvent;
funcs.onClientMoveEvent = onClientMoveEvent;
funcs.onClientMoveSubscriptionEvent = onClientMoveSubscriptionEvent;
funcs.onClientMoveTimeoutEvent = onClientMoveTimeoutEvent;
funcs.onTalkStatusChangeEvent = onTalkStatusChangeEvent;
funcs.onServerErrorEvent = onServerErrorEvent;
funcs.onIgnoredWhisperEvent = onIgnoredWhisperEvent;
/* Initialize client lib with callbacks */
/* Resource path points to the SDK\bin directory to locate the soundbackends*/
path = programPath(argv[0]);
error = ts3client_initClientLib(&funcs, NULL, LogType_FILE | LogType_CONSOLE, NULL, path);
free(path);
if(error != ERROR_ok) {
char* errormsg;
if(ts3client_getErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error initializing serverlib: %s\n", errormsg);
ts3client_freeMemory(errormsg);
}
return 1;
}
/* Spawn a new server connection handler using the default port and store the server ID */
if((error = ts3client_spawnNewServerConnectionHandler(0, &scHandlerID)) != ERROR_ok) {
printf("Error spawning server connection handler: %d\n", error);
return 1;
}
/* Open default capture device */
/* Passing empty string for mode and NULL or empty string for device will open the default device */
if((error = ts3client_openCaptureDevice(scHandlerID, "", NULL)) != ERROR_ok) {
printf("Error opening capture device: %d\n", error);
}
/* Adjust "vad" preprocessor value to use vad by default */
if ((error = ts3client_setPreProcessorConfigValue(scHandlerID, "vad", "true")) != ERROR_ok) {
printf("Error toggling VAD: %d\n", error);
return 1;
}
/* Adjust "vad_mode" value to use hybrid by default */
if ((error = ts3client_setPreProcessorConfigValue(scHandlerID, "vad_mode", "2")) != ERROR_ok) {
printf("Error setting vad_mode value to hybrid: %d\n", error);
return 1;
}
/* Adjust "voiceactivation_level" value */
if ((error = ts3client_setPreProcessorConfigValue(scHandlerID, "voiceactivation_level", "-20")) != ERROR_ok) {
printf("Error setting voiceactivation_level: %d\n", error);
return 1;
}
/* Open default playback device */
/* Passing empty string for mode and NULL or empty string for device will open the default device */
if((error = ts3client_openPlaybackDevice(scHandlerID, "", NULL)) != ERROR_ok) {
printf("Error opening playback device: %d\n", error);
}
/* Create a new client identity */
/* In your real application you should do this only once, store the assigned identity locally and then reuse it. */
if((error = ts3client_createIdentity(&identity)) != ERROR_ok) {
printf("Error creating identity: %d\n", error);
return 1;
}
/* Connect to server on localhost:9987 with nickname "client", no default channel, no default channel password and server password "secret" */
if((error = ts3client_startConnection(scHandlerID, identity, "localhost", 9987, "client", NULL, "", "secret")) != ERROR_ok) {
printf("Error connecting to server: %d\n", error);
return 1;
}
ts3client_freeMemory(identity); /* Release dynamically allocated memory */
identity = NULL;
printf("Client lib initialized and running\n");
/* Query and print client lib version */
if((error = ts3client_getClientLibVersion(&version)) != ERROR_ok) {
printf("Failed to get clientlib version: %d\n", error);
return 1;
}
printf("Client lib version: %s\n", version);
ts3client_freeMemory(version); /* Release dynamically allocated memory */
version = NULL;
SLEEP(500);
/* Wait for user input */
printf("\n--- Press Return to disconnect from server and exit ---\n");
getchar();
/* Disconnect from server. ERROR_not_connected just means the peer is already
gone (e.g. the server was shut down) - nothing to do, so move on. */
if((error = ts3client_stopConnection(scHandlerID, "leaving")) != ERROR_ok && error != ERROR_not_connected)
printf("Error stopping connection: %d\n", error);
SLEEP(200);
/* Destroy server connection handler */
if((error = ts3client_destroyServerConnectionHandler(scHandlerID)) != ERROR_ok)
printf("Error destroying server connection handler: %d\n", error);
/* Shutdown client lib */
if((error = ts3client_destroyClientLib()) != ERROR_ok)
printf("Failed to destroy clientlib: %d\n", error);
return 0;
}
@@ -0,0 +1,5 @@
message("generating TeamSpeak SDK sample ${TS_SDK_SAMPLE}")
set (TS_SAMPLE_SRC
"${CMAKE_CURRENT_LIST_DIR}/main.c"
)
@@ -0,0 +1,629 @@
/*
* TeamSpeak SDK client minimal sample for filetransfer
*
* Copyright (c) TeamSpeak-Systems
*/
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#else
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <stdio.h>
#include <teamspeak/public_definitions.h>
#include <teamspeak/public_errors.h>
#include <teamspeak/clientlib.h>
#define DEFAULT_VIRTUAL_SERVER 1
#define NAME_BUFSIZE 1024
#define CHANNEL_PASSWORD_BUFSIZE 1024
#ifdef _WIN32
#define SLEEP(x) Sleep(x)
#define strdup(x) _strdup(x)
#else
#define SLEEP(x) usleep(x*1000)
#endif
char* gProgramPath;
void emptyInputBuffer() {
int c;
while((c = getchar()) != '\n' && c != EOF);
}
uint64 enterChannelID() {
uint64 channelID;
int n;
printf("\nEnter channel ID: ");
n = scanf("%llu", (unsigned long long*)&channelID);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return 0;
}
return channelID;
}
anyID enterTransferID() {
anyID transferID;
int n;
printf("\nEnter transferID: ");
n = scanf("%hu", (anyID*)&transferID);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return 0;
}
return transferID;
}
uint64 enterBWLimit(const char* section) {
uint64 limit;
int n;
printf("Enter bandwidth limit for %s (or u for unlimited): ", section);
n = scanf("%llu", (unsigned long long*)&limit);
emptyInputBuffer();
if(n == 0) {
return BANDWIDTH_LIMIT_UNLIMITED;
}
return limit;
}
void enterName(const char* text, char *name) {
char *s;
printf("\n%s: ", text);
fgets(name, NAME_BUFSIZE, stdin);
s = strrchr(name, '\n');
if(s) {
*s = '\0';
}
}
void enterPassword(char *password) {
char *s;
printf("\nEnter password: ");
fgets(password, CHANNEL_PASSWORD_BUFSIZE, stdin);
s = strrchr(password, '\n');
if(s) {
*s = '\0';
}
}
char* programPath(char* programInvocation){
char* path;
char* end;
int length;
char pathsep;
if(programInvocation == NULL) return strdup("");
#ifdef _WIN32
pathsep = '\\';
#else
pathsep = '/';
#endif
end = strrchr(programInvocation, pathsep);
if(!end) return strdup("");
length = (end - programInvocation)+2;
path = (char*) malloc(length);
strncpy(path, programInvocation, length-1);
path[length-1] = 0;
return path;
}
/*
* 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 clientlib_publicdefinitions.h
* errorNumber - Error code. Should be zero when connecting or actively disconnection.
* Contains error state when losing connection.
*/
void onConnectStatusChangeEvent(uint64 serverConnectionHandlerID, int newStatus, unsigned int errorNumber) {
printf("Connect status changed: %llu %d %u\n", (unsigned long long)serverConnectionHandlerID, newStatus, errorNumber);
/* Failed to connect ? */
if(newStatus == STATUS_DISCONNECTED && errorNumber == ERROR_failed_connection_initialisation) {
printf("Looks like there is no server running, terminate!\n");
}
}
/*
* This event is called when another client starts whispering to own client. Own client can decide to accept or deny
* receiving the whisper by adding the sending client to the whisper allow list. If not added, whispering is blocked.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* clientID - ID of the whispering client
*/
void onIgnoredWhisperEvent(uint64 serverConnectionHandlerID, anyID clientID) {
unsigned int error;
/* Add sending client to whisper allow list so own client will hear the voice data.
* It is sufficient to add a clientID only once, not everytime this event is called. However it won't
* hurt to add the same clientID to the allow list repeatedly, but is is not necessary. */
if((error = ts3client_allowWhispersFrom(serverConnectionHandlerID, clientID)) != ERROR_ok) {
printf("Error setting client on whisper allow list: %u\n", error);
} else {
printf("Added client %d to whisper allow list\n", clientID);
}
}
/*
* This event is called when the server determines an error.
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* errorMessage - String containing a verbose error message
* error - Error code as explained on public_errors.h
* returnCode - Return code if it has been set by the Client Lib function call which caused this error event
* extraMessage - Can contain additional information about the occurred error, otherwise it is an empty string
*/
void onServerErrorEvent(uint64 serverConnectionHandlerID, const char* errorMessage, unsigned int error, const char* returnCode, const char* extraMessage) {
printf("Error for server %llu: %s %s\n", (unsigned long long)serverConnectionHandlerID, errorMessage, extraMessage ? extraMessage : "");
}
/*
* This event is periodically called when a filetransfer is active
*
* Parameters:
* transferID - Transfer ID for which filetransfer this event was called
* status - Filetransfer status code as explained on public_errors.h
* statusMessage - String containing a verbose status message
* remotefileSize - Size in bytes of the remote file
* scHandlerID - Server connection handler ID
*/
void onFileTransferStatusEvent(anyID transferID, unsigned int status, const char* statusMessage, uint64 remotefileSize, uint64 scHandlerID) {
printf("onFileTransferStatusEvent transferID: %d status: %d statusMessage: %s\n", transferID, status, statusMessage);
}
/*
* This event is called when the fileList was requested (ts3client_requestFileList)
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* channelID - To which channel ID the remote file contains
* path - Path of the remote file or directory
* name - Name of the remote file or directory
* size - Size in bytes of the remote file. If it is a directory this value is 0
* datetime - Timestamp of the remote file or directory
* type - Type of the remote item (0 is a folder or 1 is a file)
* incompleteSize - If the file is not completely uploaded yet, this value contains the current available size
* returnCode - Return code if it has been set by the Client Lib function call which caused this error event
*/
void onFileListEvent(uint64 serverConnectionHandlerID, uint64 channelID, const char* path, const char* name, uint64 size, uint64 datetime, int type, uint64 incompletesize, const char* returnCode) {
printf("onFileListEvent channelID: %llu path: %s filename: %s type:%s\n", channelID, path, name, type == 1 ? "file" : "dir");
}
/*
* This event is called when the fileList request is finished
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* channelID - Server has finished sending events regarding this channel ID
* path - Path of the remote directory
*/
void onFileListFinishedEvent(uint64 serverConnectionHandlerID, uint64 channelID, const char* path) {
printf("onFileListFinishedEvent: %llu\n", (unsigned long long)channelID);
}
/*
* This event is called when a requestFileInfo call was completed on the server
*
* Parameters:
* serverConnectionHandlerID - Server connection handler ID
* channelID - Server has finished sending events regarding this channel ID
* name - name of the file
* size - size in bytes of the file
* datetime - file date/time in unix time
*/
void onFileInfoEvent(uint64 serverConnectionHandlerID, uint64 channelID, const char* name, uint64 size, uint64 datetime){
printf("onFileInfoEvent channelID: %llu filename: %s size:%llu date:%llu\n", channelID, name, size, datetime);
}
/*
* Print all channels of the given virtual server
*/
void showChannels(uint64 serverConnectionHandlerID) {
uint64 *ids;
int i;
unsigned int error;
printf("\nList of channels on virtual server %llu:\n", (unsigned long long)serverConnectionHandlerID);
if((error = ts3client_getChannelList(serverConnectionHandlerID, &ids)) != ERROR_ok) { /* Get array of channel IDs */
printf("Error getting channel list: %d\n", error);
return;
}
if(!ids[0]) {
printf("No channels\n\n");
ts3client_freeMemory(ids);
return;
}
for(i=0; ids[i]; i++) {
char* name;
if((error = ts3client_getChannelVariableAsString(serverConnectionHandlerID, ids[i], CHANNEL_NAME, &name)) != ERROR_ok) { /* Query channel name */
printf("Error getting channel name: %d\n", error);
break;
}
printf("%llu - %s\n", (unsigned long long)ids[i], name);
ts3client_freeMemory(name);
}
printf("\n");
ts3client_freeMemory(ids); /* Release array */
}
void showChannelDir(uint64 serverConnectionHandlerID) {
unsigned int error;
uint64 channelID = enterChannelID();
if(channelID) {
if((error = ts3client_requestFileList(serverConnectionHandlerID, channelID, "", "/", "")) != ERROR_ok) { /* Requesting the root directory of this channel ID */
printf("Error getting channel dir: %d\n", error);
}
}
}
void uploadFile(uint64 serverConnectionHandlerID) {
anyID transferID;
int overwrite = 1;
int resume = 0;
char filename[NAME_BUFSIZE];
uint64 channelID = enterChannelID();
printf("Uploading from predefined path: %s\n", gProgramPath);
enterName("Enter filename to upload (<serverPath><filename> like /testfile.txt)", filename);
if(channelID) {
if(ts3client_sendFile(serverConnectionHandlerID, channelID, "", filename, overwrite, resume, gProgramPath, &transferID, NULL) == ERROR_ok) {
printf("Sending file with transferID: %d\n", transferID);
}
}
}
void downloadFile(uint64 serverConnectionHandlerID) {
anyID transferID;
int overwrite = 1;
int resume = 0;
uint64 channelID;
char filename[NAME_BUFSIZE];
channelID = enterChannelID();
printf("Downloading in predefined path: %s\n", gProgramPath);
enterName("Enter filename to download (<serverPath><filename> like /testfile.txt)", filename);
if(ts3client_requestFile(serverConnectionHandlerID, channelID, "", filename, overwrite, resume, gProgramPath, &transferID, NULL) == ERROR_ok) {
printf("Recieving file with transferID: %d\n", transferID);
}
}
void deleteFile(uint64 serverConnectionHandlerID) {
unsigned int error;
uint64 channelID;
char* files[2];
char filename[NAME_BUFSIZE];
channelID = enterChannelID();
enterName("Enter filename to delete (<serverPath><filename> like /testfile.txt)", filename);
files[0] = filename;
files[1] = 0;
if(channelID) {
if((error = ts3client_requestDeleteFile(serverConnectionHandlerID, channelID, "", (const char**)files, NULL)) != ERROR_ok) {
printf("Error deleting file: %d\n", error);
}
}
}
void renameFile(uint64 serverConnectionHandlerID) {
unsigned int error;
uint64 channelID;
char oldName[NAME_BUFSIZE];
char newName[NAME_BUFSIZE];
channelID = enterChannelID();
enterName("Enter old name (<serverPath><filename> like /testfile.txt)", oldName);
enterName("Enter new name (<serverPath><filename> like /new_testfile.txt)", newName);
if(channelID) {
if((error = ts3client_requestRenameFile(serverConnectionHandlerID, channelID, "", channelID, "", oldName, newName, NULL)) != ERROR_ok) {
printf("Error renaming file: %d\n", error);
}
}
}
void createDirectory(uint64 serverConnectionHandlerID) {
unsigned int error;
uint64 channelID;
char dirName[NAME_BUFSIZE];
channelID = enterChannelID();
enterName("Enter new directory name (<serverPath> like /subdir)", dirName);
if(channelID) {
if((error = ts3client_requestCreateDirectory(serverConnectionHandlerID, channelID, "", dirName, NULL)) != ERROR_ok) {
printf("Error renaming file: %d\n", error);
}
}
}
void fileInfo(uint64 serverConnectionHandlerID) {
uint64 channelID;
char filename[NAME_BUFSIZE];
unsigned int error;
channelID = enterChannelID();
enterName("Enter filename to get info on (<serverPath><filename> like /testfile.txt)", filename);
if((error=ts3client_requestFileInfo(serverConnectionHandlerID, channelID, "", filename, NULL)) != ERROR_ok) {
printf("error getting file info: %d\n", error);
}
}
void bandwidth(uint64 serverConnectionHandlerID) {
unsigned int error;
uint64 instanceUpLimit;
uint64 instanceDownLimit;
uint64 schUpLimit;
uint64 schDownLimit;
if((error=ts3client_getInstanceSpeedLimitUp(&instanceUpLimit)) != ERROR_ok){
printf("error during ts3client_getInstanceSpeedLimitUp: %d\n", error);
instanceUpLimit=0;
}
if((error=ts3client_getInstanceSpeedLimitDown(&instanceDownLimit)) != ERROR_ok){
printf("error during ts3client_getInstanceSpeedLimitDown: %d\n", error);
instanceDownLimit=0;
}
if((error=ts3client_getServerConnectionHandlerSpeedLimitUp(serverConnectionHandlerID, &schUpLimit)) != ERROR_ok){
printf("error during ts3client_getServerConnectionHandlerSpeedLimitUp: %d\n", error);
schUpLimit=0;
}
if((error=ts3client_getServerConnectionHandlerSpeedLimitDown(serverConnectionHandlerID, &schDownLimit)) != ERROR_ok){
printf("error during ts3client_getServerConnectionHandlerSpeedLimitDown: %d\n", error);
schDownLimit=0;
}
printf("current limits: instanceUp: %llu instanceDown: %llu schUp: %llu schDown: %llu\n", instanceUpLimit, instanceDownLimit, schUpLimit, schDownLimit);
instanceUpLimit = enterBWLimit("instanceUp");
if((error=ts3client_setInstanceSpeedLimitUp(instanceUpLimit)) != ERROR_ok){
printf("error during ts3client_setInstanceSpeedLimitUp: %d\n", error);
}
instanceDownLimit = enterBWLimit("instanceDown");
if((error=ts3client_setInstanceSpeedLimitDown(instanceDownLimit)) != ERROR_ok){
printf("error during ts3client_setInstanceSpeedLimitDown: %d\n", error);
}
schUpLimit = enterBWLimit("schUp");
if((error=ts3client_setServerConnectionHandlerSpeedLimitUp(serverConnectionHandlerID, schUpLimit)) != ERROR_ok){
printf("error during ts3client_setServerConnectionHandlerSpeedLimitUp: %d\n", error);
}
schDownLimit = enterBWLimit("schDown");
if((error=ts3client_setServerConnectionHandlerSpeedLimitDown(serverConnectionHandlerID, schDownLimit)) != ERROR_ok){
printf("error during ts3client_setServerConnectionHandlerSpeedLimitDown: %d\n", error);
}
}
void cancelTransfer(uint64 serverConnectionHandlerID){
anyID transferID;
unsigned int error;
transferID = enterTransferID();
if(transferID){
if((error=ts3client_haltTransfer(serverConnectionHandlerID, transferID, 1, NULL))!=ERROR_ok){
printf("error during ts3client_haltTransfer: %d\n", error);
}
}
}
void transferStats(uint64 serverConnectionHandlerID){
unsigned int error;
uint64 bytesRecievedBandwidth;
uint64 bytesSentBandwidth;
uint64 bytesRecieved;
uint64 bytesSent;
if((error=ts3client_getConnectionVariableAsUInt64(serverConnectionHandlerID, 0, CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, &bytesRecievedBandwidth))!=ERROR_ok){
printf("error getting bytesRecievedBandwidth: %d\n", error);
bytesRecievedBandwidth=0;
}
if((error=ts3client_getConnectionVariableAsUInt64(serverConnectionHandlerID, 0, CONNECTION_FILETRANSFER_BANDWIDTH_SENT, &bytesSentBandwidth))!=ERROR_ok){
printf("error getting bytesSentBandwidth: %d\n", error);
bytesSentBandwidth=0;
}
if((error=ts3client_getConnectionVariableAsUInt64(serverConnectionHandlerID, 0, CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL, &bytesRecieved))!=ERROR_ok){
printf("error getting bytesRecieved: %d\n", error);
bytesRecieved=0;
}
if((error=ts3client_getConnectionVariableAsUInt64(serverConnectionHandlerID, 0, CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL, &bytesSent))!=ERROR_ok){
printf("error getting bytesSent: %d\n", error);
bytesSent=0;
}
printf("Transfer statistics: BW recv: %llu BW send %llu bytes recv: %llu bytes sent: %llu\n", bytesRecievedBandwidth, bytesSentBandwidth, bytesRecieved, bytesSent);
}
void showHelp() {
printf("\n");
printf("[q] - Disconnect from server\n");
printf("[h] - Show this help\n");
printf("[c] - Show channels\n");
printf("[s] - Show directory of a channel\n");
printf("[u] - Upload file\n");
printf("[d] - Download file\n");
printf("[x] - Delete file\n");
printf("[r] - Rename file\n");
printf("[f] - create directory file\n");
printf("[i] - get file information\n");
printf("[k] - cancel transfer\n");
printf("[l] - edit transfer bandwidth limits\n");
printf("[j] - get connection transfer stats\n");
}
int main(int argc, char **argv) {
uint64 scHandlerID;
unsigned int error;
char *version;
char *identity;
short abort = 0;
/* 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. */
funcs.onConnectStatusChangeEvent = onConnectStatusChangeEvent;
funcs.onServerErrorEvent = onServerErrorEvent;
funcs.onFileListEvent = onFileListEvent;
funcs.onFileListFinishedEvent = onFileListFinishedEvent;
funcs.onFileTransferStatusEvent = onFileTransferStatusEvent; // crash when not defined!!!
funcs.onFileInfoEvent = onFileInfoEvent;
funcs.onIgnoredWhisperEvent = onIgnoredWhisperEvent;
/* Initialize client lib with callbacks */
/* Resource path points to the SDK\bin directory to locate the soundbackends*/
gProgramPath = programPath(argv[0]);
error = ts3client_initClientLib(&funcs, NULL, LogType_FILE | LogType_CONSOLE | LogType_USERLOGGING, NULL, gProgramPath);
if(error != ERROR_ok) {
char* errormsg;
if(ts3client_getErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error initialzing serverlib: %s\n", errormsg);
ts3client_freeMemory(errormsg);
}
return 1;
}
/* Spawn a new server connection handler using the default port and store the server ID */
if((error = ts3client_spawnNewServerConnectionHandler(0, &scHandlerID)) != ERROR_ok) {
printf("Error spawning server connection handler: %d\n", error);
return 1;
}
/* Create a new client identity */
/* In your real application you should do this only once, store the assigned identity locally and then reuse it. */
if((error = ts3client_createIdentity(&identity)) != ERROR_ok) {
printf("Error creating identity: %d\n", error);
return 1;
}
/* Connect to server on localhost:9987 with nickname "client", no default channel, no default channel password and server password "secret" */
if((error = ts3client_startConnection(scHandlerID, identity, "localhost", 9987, "client", NULL, "", "secret")) != ERROR_ok) {
printf("Error connecting to server: %d\n", error);
return 1;
}
ts3client_freeMemory(identity); /* Release dynamically allocated memory */
identity = NULL;
printf("Client lib initialized and running\n");
/* Query and print client lib version */
if((error = ts3client_getClientLibVersion(&version)) != ERROR_ok) {
printf("Failed to get clientlib version: %d\n", error);
return 1;
}
printf("Client lib version: %s\n", version);
ts3client_freeMemory(version); /* Release dynamically allocated memory */
version = NULL;
SLEEP(500);
/* Simple commandline interface */
printf("\nTeamSpeak 3 client commandline interface\n");
showHelp();
/* Wait for user input */
while(!abort) {
int c = getc(stdin);
switch(c) {
case 'q':
printf("\nDisconnecting from server...\n");
abort = 1;
break;
case 'h':
showHelp();
break;
case 'c':
showChannels(DEFAULT_VIRTUAL_SERVER);
break;
case 's':
showChannelDir(DEFAULT_VIRTUAL_SERVER);
break;
case 'u':
uploadFile(DEFAULT_VIRTUAL_SERVER);
break;
case 'x':
deleteFile(DEFAULT_VIRTUAL_SERVER);
break;
case 'r':
renameFile(DEFAULT_VIRTUAL_SERVER);
break;
case 'd':
downloadFile(DEFAULT_VIRTUAL_SERVER);
break;
case 'f':
createDirectory(DEFAULT_VIRTUAL_SERVER);
break;
case 'i':
fileInfo(DEFAULT_VIRTUAL_SERVER);
break;
case 'k':
cancelTransfer(DEFAULT_VIRTUAL_SERVER);
break;
case 'l':
bandwidth(DEFAULT_VIRTUAL_SERVER);
break;
case 'j':
transferStats(DEFAULT_VIRTUAL_SERVER);
break;
}
SLEEP(50);
}
/* Disconnect from server. ERROR_not_connected just means the peer is already
gone (e.g. the server was shut down) - nothing to do, so move on. */
if((error = ts3client_stopConnection(scHandlerID, "leaving")) != ERROR_ok && error != ERROR_not_connected)
printf("Error stopping connection: %d\n", error);
SLEEP(200);
/* Destroy server connection handler */
if((error = ts3client_destroyServerConnectionHandler(scHandlerID)) != ERROR_ok)
printf("Error destroying server connection handler: %d\n", error);
/* Shutdown client lib */
if((error = ts3client_destroyClientLib()) != ERROR_ok)
printf("Failed to destroy clientlib: %d\n", error);
free(gProgramPath);
return 0;
}
@@ -0,0 +1,5 @@
message("generating TeamSpeak SDK sample ${TS_SDK_SAMPLE}")
set (TS_SAMPLE_SRC
"${CMAKE_CURRENT_LIST_DIR}/main.c"
)
@@ -0,0 +1,273 @@
/*
* TeamSpeak SDK client multi-connection sample
*
* Spawns multiple simultaneous server connection handlers from a single
* client application, all connecting to the same server. Intended to
* reproduce/diagnose the issue where secondary sessions are dropped after
* ~10 seconds with "10 resends of COMMAND packet" / ping timeout errors.
*
* Usage:
* ts_client_multi [host] [port] [serverPassword] [numConnections] [runSeconds] [staggerMs] [shareIdentity] [localPort]
*
* Defaults: localhost 9987 "secret" 3 60 0 0 0
*
* localPort: local UDP port passed to spawnNewServerConnectionHandler.
* 0 = ephemeral (a fresh port per connection handler). A fixed
* value makes all connection handlers request the SAME local port,
* mimicking applications that pin their source port.
*
* staggerMs: delay between issuing each connection (0 = all simultaneous).
* Use a value larger than the handshake time (e.g. 2000) to start
* each secondary session only after the previous one is fully
* established, matching the reported "secondary sessions initiated
* afterwards" scenario.
*
* Copyright (c) TeamSpeak-Systems
*/
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#else
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <stdio.h>
#include <time.h>
#include <teamspeak/public_definitions.h>
#include <teamspeak/public_errors.h>
#include <teamspeak/clientlib.h>
#ifdef _WIN32
#define SLEEP(x) Sleep(x)
#define strdup(x) _strdup(x)
#else
#define SLEEP(x) usleep((x)*1000)
#endif
#define MAX_CONNECTIONS 32
/* Track the connection handlers we created so the status callback can label them. */
static uint64 g_handlers[MAX_CONNECTIONS];
static int g_numHandlers = 0;
static time_t g_startTime;
static int handlerIndex(uint64 scHandlerID) {
for (int i = 0; i < g_numHandlers; ++i) {
if (g_handlers[i] == scHandlerID) return i;
}
return -1;
}
static double elapsed(void) {
return difftime(time(NULL), g_startTime);
}
static const char* statusName(int status) {
switch (status) {
case STATUS_DISCONNECTED: return "DISCONNECTED";
case STATUS_CONNECTING: return "CONNECTING";
case STATUS_CONNECTED: return "CONNECTED";
case STATUS_CONNECTION_ESTABLISHING:return "CONNECTION_ESTABLISHING";
case STATUS_CONNECTION_ESTABLISHED: return "CONNECTION_ESTABLISHED";
default: return "UNKNOWN";
}
}
/*
* Connection status change. This is where we expect to observe the secondary
* connections dropping back to DISCONNECTED with a non-zero error after ~10s.
*/
void onConnectStatusChangeEvent(uint64 serverConnectionHandlerID, int newStatus, unsigned int errorNumber) {
int idx = handlerIndex(serverConnectionHandlerID);
char* errormsg = NULL;
const char* errstr = "";
if (errorNumber != ERROR_ok && ts3client_getErrorMessage(errorNumber, &errormsg) == ERROR_ok)
errstr = errormsg;
printf("[%6.1fs] conn#%d (sch=%llu): %s (err=%u %s)\n",
elapsed(), idx, (unsigned long long)serverConnectionHandlerID,
statusName(newStatus), errorNumber, errstr);
fflush(stdout);
if (errormsg) ts3client_freeMemory(errormsg);
}
void onClientMoveTimeoutEvent(uint64 serverConnectionHandlerID, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility, const char* timeoutMessage) {
int idx = handlerIndex(serverConnectionHandlerID);
printf("[%6.1fs] conn#%d: clientID %u timed out: %s\n", elapsed(), idx, clientID, timeoutMessage ? timeoutMessage : "");
fflush(stdout);
}
void onServerErrorEvent(uint64 serverConnectionHandlerID, const char* errorMessage, unsigned int error, const char* returnCode, const char* extraMessage) {
int idx = handlerIndex(serverConnectionHandlerID);
printf("[%6.1fs] conn#%d: server error: %s %s\n", elapsed(), idx, errorMessage ? errorMessage : "", extraMessage ? extraMessage : "");
fflush(stdout);
}
char* programPath(char* programInvocation) {
char* path;
char* end;
int length;
char pathsep;
if (programInvocation == NULL) return strdup("");
#ifdef _WIN32
pathsep = '\\';
#else
pathsep = '/';
#endif
end = strrchr(programInvocation, pathsep);
if (!end) return strdup("");
length = (end - programInvocation) + 2;
path = (char*)malloc(length);
strncpy(path, programInvocation, length - 1);
path[length - 1] = 0;
return path;
}
int main(int argc, char** argv) {
unsigned int error;
char* version;
char* path;
/* --- arguments --- */
const char* host = (argc > 1) ? argv[1] : "localhost";
unsigned int port = (argc > 2) ? (unsigned int)atoi(argv[2]) : 9987;
const char* serverPassword = (argc > 3) ? argv[3] : "secret";
int numConnections = (argc > 4) ? atoi(argv[4]) : 3;
int runSeconds = (argc > 5) ? atoi(argv[5]) : 60;
int staggerMs = (argc > 6) ? atoi(argv[6]) : 0;
int shareIdentity = (argc > 7) ? atoi(argv[7]) : 0;
int localPort = (argc > 8) ? atoi(argv[8]) : 0;
if (numConnections < 1) numConnections = 1;
if (numConnections > MAX_CONNECTIONS) numConnections = MAX_CONNECTIONS;
printf("Multi-connection test: host=%s port=%u connections=%d runSeconds=%d staggerMs=%d shareIdentity=%d localPort=%d\n",
host, port, numConnections, runSeconds, staggerMs, shareIdentity, localPort);
/* Create struct for callback function pointers */
struct ClientUIFunctions funcs;
memset(&funcs, 0, sizeof(struct ClientUIFunctions));
funcs.onConnectStatusChangeEvent = onConnectStatusChangeEvent;
funcs.onClientMoveTimeoutEvent = onClientMoveTimeoutEvent;
funcs.onServerErrorEvent = onServerErrorEvent;
/* Initialize client lib with callbacks. Resource path points to the dir
* containing the soundbackends (next to the executable). */
path = programPath(argv[0]);
error = ts3client_initClientLib(&funcs, NULL, LogType_FILE | LogType_CONSOLE, NULL, path);
free(path);
if (error != ERROR_ok) {
char* errormsg;
if (ts3client_getErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error initializing clientlib: %s\n", errormsg);
ts3client_freeMemory(errormsg);
}
return 1;
}
if ((error = ts3client_getClientLibVersion(&version)) == ERROR_ok) {
printf("Client lib version: %s\n", version);
ts3client_freeMemory(version);
}
g_startTime = time(NULL);
/* When shareIdentity is set, all connections reuse a single identity (as a
* real application that stores and reuses one identity would). This mirrors
* "multiple sessions from the same client" and is a prime suspect for the
* handshake hardening rejecting secondary sessions from the same uid. */
char* sharedIdentity = NULL;
if (shareIdentity) {
if ((error = ts3client_createIdentity(&sharedIdentity)) != ERROR_ok) {
printf("Error creating shared identity: %u\n", error);
return 1;
}
}
/* Spawn N connection handlers and connect each to the same server.
* No audio devices are opened: this keeps the test focused on the
* network/command layer and avoids capture-device contention. */
for (int i = 0; i < numConnections; ++i) {
uint64 scHandlerID;
char* identity = NULL;
char nickname[64];
if ((error = ts3client_spawnNewServerConnectionHandler(localPort, &scHandlerID)) != ERROR_ok) {
printf("Error spawning server connection handler #%d: %u\n", i, error);
continue;
}
g_handlers[g_numHandlers++] = scHandlerID;
if (shareIdentity) {
identity = sharedIdentity;
} else if ((error = ts3client_createIdentity(&identity)) != ERROR_ok) {
printf("Error creating identity #%d: %u\n", i, error);
continue;
}
snprintf(nickname, sizeof(nickname), "multi_%d", i);
if ((error = ts3client_startConnection(scHandlerID, identity, host, port, nickname, NULL, "", serverPassword)) != ERROR_ok) {
printf("Error connecting conn#%d to server: %u\n", i, error);
} else {
printf("[%6.1fs] conn#%d (sch=%llu): startConnection issued as \"%s\"%s\n",
elapsed(), i, (unsigned long long)scHandlerID, nickname,
shareIdentity ? " [shared identity]" : "");
}
if (!shareIdentity && identity)
ts3client_freeMemory(identity);
/* Optionally wait before starting the next connection, so secondary
* sessions are initiated only after the previous one is up. */
if (staggerMs > 0 && i + 1 < numConnections) {
SLEEP(staggerMs);
}
}
if (sharedIdentity) ts3client_freeMemory(sharedIdentity);
printf("\n--- %d connection(s) started. Observing for %d seconds. ---\n", g_numHandlers, runSeconds);
printf("--- Watch for secondary connections dropping (resend/ping timeout). ---\n\n");
fflush(stdout);
/* Idle loop, printing a periodic status snapshot so drops are easy to spot. */
for (int t = 0; t < runSeconds; ++t) {
SLEEP(1000);
if ((t + 1) % 5 == 0) {
printf("[%6.1fs] status snapshot:\n", elapsed());
for (int i = 0; i < g_numHandlers; ++i) {
int status = 0;
ts3client_getConnectionStatus(g_handlers[i], &status);
printf(" conn#%d (sch=%llu): %s\n", i, (unsigned long long)g_handlers[i], statusName(status));
}
fflush(stdout);
}
}
/* Disconnect and clean up all connection handlers. */
printf("\nDisconnecting all connections...\n");
for (int i = 0; i < g_numHandlers; ++i) {
ts3client_stopConnection(g_handlers[i], "leaving");
}
SLEEP(500);
for (int i = 0; i < g_numHandlers; ++i) {
ts3client_destroyServerConnectionHandler(g_handlers[i]);
}
if ((error = ts3client_destroyClientLib()) != ERROR_ok) {
printf("Failed to destroy clientlib: %u\n", error);
return 1;
}
return 0;
}
@@ -0,0 +1,5 @@
message("generating TeamSpeak SDK sample ${TS_SDK_SAMPLE}")
set (TS_SAMPLE_SRC
"${CMAKE_CURRENT_LIST_DIR}/main.c"
)
@@ -0,0 +1,29 @@
# Collect a small set of SDK headers to show in the IDE solution.
# Depends on find_package(team_client|team_server) having run.
if("${sample_type}" STREQUAL "client")
get_target_property(_ts_sdk_inc_dir teamspeak::client INTERFACE_INCLUDE_DIRECTORIES)
elseif("${sample_type}" STREQUAL "server")
get_target_property(_ts_sdk_inc_dir teamspeak::server INTERFACE_INCLUDE_DIRECTORIES)
else()
set(_ts_sdk_inc_dir "")
endif()
set(TS_SDK_IDE_FILES "")
if(_ts_sdk_inc_dir)
file(GLOB _ts_log_headers "${_ts_sdk_inc_dir}/teamlog/*.h")
list(APPEND TS_SDK_IDE_FILES
${_ts_log_headers}
"${_ts_sdk_inc_dir}/teamspeak/public_definitions.h"
"${_ts_sdk_inc_dir}/teamspeak/public_errors.h"
)
if("${sample_type}" STREQUAL "client")
list(APPEND TS_SDK_IDE_FILES "${_ts_sdk_inc_dir}/teamspeak/clientlib.h")
elseif("${sample_type}" STREQUAL "server")
list(APPEND TS_SDK_IDE_FILES
"${_ts_sdk_inc_dir}/teamspeak/server_commands_file_transfer.h"
"${_ts_sdk_inc_dir}/teamspeak/serverlib.h"
"${_ts_sdk_inc_dir}/teamspeak/serverlib_publicdefinitions.h"
)
endif()
endif()
@@ -0,0 +1,10 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(triple aarch64-linux-gnueabi)
set(CMAKE_C_COMPILER clang)
set(CMAKE_C_COMPILER_TARGET ${triple})
set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_CXX_COMPILER_TARGET ${triple})
set(CMAKE_SYSROOT /usr/aarch64-linux-gnu)
set(CMAKE_EXE_LINKER_FLAGS "-fuse-ld=/usr/aarch64-linux-gnu/bin/ld" CACHE FILEPATH "" FORCE)
set(CMAKE_AR /usr/aarch64-linux-gnu/bin/ar CACHE FILEPATH "" FORCE)
@@ -0,0 +1,5 @@
pushd build
pushd win_x64
cmake -G "Visual Studio 17 2022" -A x64 ../..
popd
popd
@@ -0,0 +1,8 @@
pushd build
rm -rf linux_x64
mkdir linux_x64
pushd linux_x64
cmake -G Ninja ../..
ninja
popd
popd
@@ -0,0 +1,8 @@
pushd build
rm -rf mac
mkdir mac
pushd mac
cmake -G Ninja ../..
ninja
popd
popd
@@ -0,0 +1,46 @@
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "id_io.h"
int readKeyPairFromFile(const char *fileName, char *keyPair) {
FILE *file;
file = fopen(fileName, "r");
if(file == NULL) {
printf("Could not open file '%s' for reading keypair\n", fileName);
return -1;
}
fgets(keyPair, BUFSIZ, file);
if(ferror(file) != 0) {
fclose (file);
printf("Error reading keypair from file '%s'.\n", fileName);
return -1;
}
fclose (file);
printf("Read keypair '%s' from file '%s'.\n", keyPair, fileName);
return 0;
}
int writeKeyPairToFile(const char *fileName, const char* keyPair) {
FILE *file;
file = fopen(fileName, "w");
if(file == NULL) {
printf("Could not open file '%s' for writing keypair\n", fileName);
return -1;
}
fputs(keyPair, file);
if(ferror(file) != 0) {
fclose (file);
printf("Error writing keypair to file '%s'.\n", fileName);
return -1;
}
fclose (file);
printf("Wrote keypair '%s' to file '%s'.\n", keyPair, fileName);
return 0;
}
@@ -0,0 +1,7 @@
#ifndef ID_IO_H
#define ID_IO_H
int readKeyPairFromFile(const char *fileName, char *keyPair);
int writeKeyPairToFile(const char *fileName, const char* keyPair);
#endif
@@ -0,0 +1,996 @@
/*
* TeamSpeak SDK server sample
*
* Copyright (c) TeamSpeak-Systems
*/
#ifdef _WINDOWS
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#else
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <stdio.h>
#include <teamspeak/public_definitions.h>
#include <teamspeak/public_errors.h>
#include <teamspeak/serverlib_publicdefinitions.h>
#include <teamspeak/serverlib.h>
#include "id_io.h"
#define DEFAULT_VIRTUAL_SERVER_ID 1
/* Maximum number of clients allowed per virtual server */
#define MAX_CLIENTS 8
#ifdef _WINDOWS
#define SLEEP(x) Sleep(x)
#else
#define SLEEP(x) usleep(x*1000)
#endif
/* Enable to use server-side voice recording */
/* #define USE_VOICEDATAEVENT */
/* Enable to use custom encryption */
/* #define USE_CUSTOM_ENCRYPTION
#define CUSTOM_CRYPT_KEY 123 */
/* Uncomment "#define CUSTOM_PASSWORDS" to try custom passwords.
* Please note that you have to do the same on the client demo too */
/* #define CUSTOM_PASSWORDS */
#ifdef USE_VOICEDATAEVENT
#ifdef _WINDOWS
#include <io.h>
#else /* Unix compatibility */
#include <unistd.h>
#define _open open
#define _write write
#define _close close
#define _O_CREAT O_CREAT
#define _O_WRONLY O_WRONLY
#define _S_IREAD S_IREAD
#define _S_IWRITE S_IWRITE
#define _O_APPEND O_APPEND
#define _O_BINARY 0
#endif
#include <fcntl.h>
#include <sys/stat.h>
#endif
#define CHECK_ERROR(x) if((error = x) != ERROR_ok) { goto on_error; }
/*
* Callback when client has connected.
*
* Parameter:
* serverID - Virtual server ID
* clientID - ID of connected client
* channelID - ID of channel the client joined
*/
void onClientConnected(uint64 serverID, anyID clientID, uint64 channelID, unsigned int* removeClientError) {
char* clientName;
unsigned int error;
/* Query client nickname */
if((error = ts3server_getClientVariableAsString(serverID, clientID, CLIENT_NICKNAME, &clientName)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error querying client nickname: %s\n", errormsg);
ts3server_freeMemory(errormsg);
}
return;
}
printf("Client '%s' joined channel %llu on virtual server %llu\n", clientName, (unsigned long long) channelID, (unsigned long long)serverID);
/* Example: Kick clients with nickname "BlockMe from server */
if(!strcmp(clientName, "BlockMe")) {
printf("Blocking bad client!\n");
*removeClientError = ERROR_client_not_logged_in; /* Give a reason */
}
}
/*
* Callback when client has disconnected.
*
* Parameter:
* serverID - Virtual server ID
* clientID - ID of disconnected client
* channelID - ID of channel the client left
*/
void onClientDisconnected(uint64 serverID, anyID clientID, uint64 channelID) {
printf("Client %u left channel %llu on virtual server %llu\n", clientID, (unsigned long long)channelID, (unsigned long long)serverID);
}
/*
* Callback when client has moved.
*
* Parameter:
* serverID - Virtual server ID
* clientID - ID of moved client
* oldChannelID - ID of old channel the client left
* newChannelID - ID of new channel the client joined
*/
void onClientMoved(uint64 serverID, anyID clientID, uint64 oldChannelID, uint64 newChannelID) {
printf("Client %u moved from channel %llu to channel %llu on virtual server %llu\n", clientID, (unsigned long long)oldChannelID, (unsigned long long)newChannelID, (unsigned long long)serverID);
}
/*
* Callback when channel has been created.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who created the channel
* channelID - ID of the created channel
*/
void onChannelCreated(uint64 serverID, anyID invokerClientID, uint64 channelID) {
printf("Channel %llu created by %u on virtual server %llu\n", (unsigned long long)channelID, invokerClientID, (unsigned long long)serverID);
}
/*
* Callback when channel has been edited.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who edited the channel
* channelID - ID of the edited channel
*/
void onChannelEdited(uint64 serverID, anyID invokerClientID, uint64 channelID) {
printf("Channel %llu edited by %u on virtual server %llu\n", (unsigned long long)channelID, invokerClientID, (unsigned long long)serverID);
}
/*
* Callback when channel has been deleted.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who deleted the channel
* channelID - ID of the deleted channel
*/
void onChannelDeleted(uint64 serverID, anyID invokerClientID, uint64 channelID) {
printf("Channel %llu deleted by %u on virtual server %llu\n", (unsigned long long)channelID, invokerClientID, (unsigned long long)serverID);
}
/*
* Callback when a server text message has been received.
* Note that only server and channel chats are received, private client messages are not caught due to privacy reasons.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who sent the text message
* textMessage - Message text
*/
void onServerTextMessageEvent(uint64 serverID, anyID invokerClientID, const char* textMessage) {
char* invokerNickname;
unsigned int error;
/* Get invoker nickname */
if((error = ts3server_getClientVariableAsString(serverID, invokerClientID, CLIENT_NICKNAME, &invokerNickname)) != ERROR_ok) {
printf("Error getting client nickname: %d\n", error);
return;
}
printf("Text message in server chat by %s: %s\n", invokerNickname, textMessage);
ts3server_freeMemory(invokerNickname); /* Release previously allocated memory */
}
/*
* Callback when a channel text message has been received.
* Note that only server and channel chats are received, private client messages are not caught due to privacy reasons.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who sent the text message
* targetChannelID - ID of the channel in which the chat was sent
* textMessage - Message text
*/
void onChannelTextMessageEvent(uint64 serverID, anyID invokerClientID, uint64 targetChannelID, const char* textMessage) {
char* invokerNickname;
char* channelName;
unsigned int error;
/* Get invoker nickname */
if((error = ts3server_getClientVariableAsString(serverID, invokerClientID, CLIENT_NICKNAME, &invokerNickname)) != ERROR_ok) {
printf("Error getting client nickname: %d\n", error);
return;
}
/* Get channel name */
if((error = ts3server_getChannelVariableAsString(serverID, targetChannelID, CHANNEL_NAME, &channelName)) != ERROR_ok) {
printf("Error getting channel name: %d\n", error);
ts3server_freeMemory(invokerNickname);
return;
}
printf("Text message in channel '%s' by %s: %s\n", channelName, invokerNickname, textMessage);
ts3server_freeMemory(invokerNickname);
ts3server_freeMemory(channelName);
}
/*
* Callback for user-defined logging.
*
* Parameter:
* logMessage - Log message text
* logLevel - Severity of log message
* logChannel - Custom text to categorize the message channel
* logID - Virtual server ID giving the virtual server source of the log event
* logTime - String with the date and time the log entry occured
* completeLogString - Verbose log message including all previous parameters for convinience
*/
void onUserLoggingMessageEvent(const char* logMessage, int logLevel, const char* logChannel, uint64 logID, const char* logTime, const char* completeLogString) {
/* Your custom error display here... */
/* printf("LOG: %s\n", completeLogString); */
if(logLevel == LogLevel_CRITICAL) {
exit(1); /* Your custom handling of critical errors */
}
}
#ifdef USE_VOICEDATAEVENT
/*
* Callback triggered by the specified client sending voice data.
*
* Parameters:
* serverID - ID of the server sending the callback
* clientID - ID of the client sending the voice data
* voiceData - Voice data buffer. Format is 16 bit mono. Do not free this buffer.
* voiceDataSize - Size of the voiceData buffer
* frequency - Voice frequency
*/
void onVoiceDataEvent(uint64 serverID, anyID clientID, unsigned char* voiceData, unsigned int voiceDataSize, unsigned int frequency) {
int fd;
unsigned int error;
char* name;
/* Query client nickname as string */
if((error = ts3server_getClientVariableAsString(serverID, clientID, CLIENT_NICKNAME, &name)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error querying client nickname: %s\n", errormsg);
ts3server_freeMemory(errormsg);
}
return;
}
/* Open file with client nickname as filename and write voice data */
fd =_open(name, _O_CREAT | _O_APPEND | _O_BINARY | _O_WRONLY , _S_IREAD | _S_IWRITE);
if(fd == -1) {
printf("failed to open file\n");
ts3server_freeMemory(name);
exit(-1);
}
_write(fd, voiceData, voiceDataSize);
_close(fd);
/* Release string dynamically allocated in getClientVariableAsString */
ts3server_freeMemory(name);
}
#endif
#ifdef CUSTOM_PASSWORDS
/*
* Called to encrypt channel and server passwords
*
* In this example, we do not do any encryption. That way we have clear
* text passwords on the server.
*
* Parameters:
* serverID - Server ID
* plaintext - the clear text password
* encryptedText - pointer to a buffer to store the encrypted password
* encryptedTextByteSize - the size of the encryptedText buffer
*/
void onClientPasswordEncrypt(uint64 serverID, const char* plaintext, char* encryptedText, int encryptedTextByteSize){
int pt_len;
printf("onClientPasswordEncrypt called\n");
pt_len = strlen(plaintext);
if (encryptedTextByteSize < pt_len-1) pt_len = encryptedTextByteSize-1;
memcpy(encryptedText, plaintext, pt_len);
encryptedText[pt_len]=0;
}
/*
* Callback triggered for server password.
*
* Parameters:
* serverID - ID of the virtual server to which the client is connecting.
* client - Struct of client parameters like ident, nickname etc. who is connecting to the server. Please view public_definitions.h.
* password - Password provided by the client.
*
* Return ERROR_ok to indicate the password is correct. Return ERROR_server_invalid_password for incorrect.
*/
unsigned int onCustomServerPasswordCheck(uint64 serverID, const struct ClientMiniExport* client, const char* password){
if (strcmp(password, "secret") == 0) return ERROR_ok;
return ERROR_server_invalid_password;
}
/*
* Callback triggered for channel password.
*
* Parameters:
* serverID - ID of the virtual server on which the client is moving itself or other clients to a new channel.
* client - Struct of client parameters like ident, nickname etc. who is moving itself or other clients to the channel. Please view public_definitions.h.
* channelID - Channel ID of the channel being switched/moved to
* password - Password provided by client.
*
* Return ERROR_ok to indicate the password is correct. Return ERROR_channel_invalid_password for incorrect.
*/
unsigned int onCustomChannelPasswordCheck(uint64 serverID, const struct ClientMiniExport* client, uint64 channelID, const char* password){
if (strcmp(password, "channelpw") == 0) return ERROR_ok;
return ERROR_channel_invalid_password;
}
#endif
/*
* Callback triggered when the specified client starts talking.
*
* Parameters:
* serverID - ID of the server sending the callback
* clientID - ID of the client which started talking
*/
void onClientStartTalkingEvent(uint64 serverID, anyID clientID) {
printf("onClientStartTalkingEvent serverID=%llu, clientID=%u\n", (unsigned long long)serverID, clientID);
}
/*
* Callback triggered when the specified client stops talking.
*
* Parameters:
* serverID - ID of the server sending the callback
* clientID - ID of the client which stopped talking
*/
void onClientStopTalkingEvent(uint64 serverID, anyID clientID) {
printf("onClientStopTalkingEvent serverID=%llu, clientID=%u\n", (unsigned long long)serverID, clientID);
}
/*
* Callback triggered when a license error occurs.
*
* Parameters:
* serverID - ID of the virtual server on which the license error occured. This virtual server will be automatically
* shutdown. If the parameter is zero, all virtual servers are affected and have been shutdown.
* errorCode - Code of the occured error. Use ts3server_getGlobalErrorMessage() to convert to a message string
*/
void onAccountingErrorEvent(uint64 serverID, unsigned int errorCode) {
char* errorMessage;
if(ts3server_getGlobalErrorMessage(errorCode, &errorMessage) == ERROR_ok) {
printf("onAccountingErrorEvent serverID=%llu, errorCode=%u: %s\n", (unsigned long long)serverID, errorCode, errorMessage);
ts3server_freeMemory(errorMessage);
}
/* Your custom handling here. In a real application, you wouldn't stop the whole process because the TS3 server part went down.
* The whole idea of this callback is to let you gracefully handle the TS3 server failing to start and to continue your application. */
exit(1);
}
/*
* Callback allowing to apply custom encryption to outgoing packets.
* Using this function is optional. Do not implement if you want to handle the TeamSpeak SDK encryption.
*
* Parameters:
* dataToSend - Pointer to an array with the outgoing data to be encrypted
* sizeOfData - Pointer to an integer value containing the size of the data array
*
* Apply your custom encryption to the data array. If the encrypted data is smaller than sizeOfData, write
* your encrypted data into the existing memory of dataToSend. If your encrypted data is larger, you need
* to allocate memory and redirect the pointer dataToSend. You need to take care of freeing your own allocated
* memory yourself. The memory allocated by the SDK, to which dataToSend is originally pointing to, must not
* be freed.
*
*/
void onCustomPacketEncryptEvent(char** dataToSend, unsigned int* sizeOfData) {
#ifdef USE_CUSTOM_ENCRYPTION
unsigned int i;
for(i = 0; i < *sizeOfData; i++) {
(*dataToSend)[i] ^= CUSTOM_CRYPT_KEY;
}
#endif
}
/*
* Callback allowing to apply custom encryption to incoming packets
* Using this function is optional. Do not implement if you want to handle the TeamSpeak SDK encryption.
*
* Parameters:
* dataToSend - Pointer to an array with the received data to be decrypted
* sizeOfData - Pointer to an integer value containing the size of the data array
*
* Apply your custom decryption to the data array. If the decrypted data is smaller than dataReceivedSize, write
* your decrypted data into the existing memory of dataReceived. If your decrypted data is larger, you need
* to allocate memory and redirect the pointer dataReceived. You need to take care of freeing your own allocated
* memory yourself. The memory allocated by the SDK, to which dataReceived is originally pointing to, must not
* be freed.
*/
void onCustomPacketDecryptEvent(char** dataReceived, unsigned int* dataReceivedSize) {
#ifdef USE_CUSTOM_ENCRYPTION
unsigned int i;
for(i = 0; i < *dataReceivedSize; i++) {
(*dataReceived)[i] ^= CUSTOM_CRYPT_KEY;
}
#endif
}
void showHelp() {
printf("\n[q] - Quit\n[h] - Show this help\n[v] - List virtual servers\n[c] - Show channels of virtual server %d\n", DEFAULT_VIRTUAL_SERVER_ID);
printf("[l] - Show clients of virtual server %d\n[n] - Create new channel on virtual server %d with generated name\n[N] - Create new channel on virtual server %d with custom name\n", DEFAULT_VIRTUAL_SERVER_ID, DEFAULT_VIRTUAL_SERVER_ID, DEFAULT_VIRTUAL_SERVER_ID);
printf("[d] - Delete channel on virtual server %d\n[r] - Rename channel on virtual server %d\n[m] - Move client on virtual server %d\n", DEFAULT_VIRTUAL_SERVER_ID, DEFAULT_VIRTUAL_SERVER_ID, DEFAULT_VIRTUAL_SERVER_ID);
printf("[C] - Create new virtual server\n[E] - Edit virtual server\n[S] - Stop virtual server\n\n");
}
void emptyInputBuffer() {
int c;
while((c = getchar()) != '\n' && c != EOF);
}
void showVirtualServers() {
uint64* ids;
int i;
unsigned int error;
printf("\nList of virtual servers:\n");
if((error = ts3server_getVirtualServerList(&ids)) != ERROR_ok) { /* Get array of virtual server IDs */
printf("Error getting virtual server list: %d\n", error);
return;
}
if(!ids[0]) {
printf("No virtual servers\n\n");
ts3server_freeMemory(ids);
return;
}
for(i=0; ids[i]; i++) {
char* name;
int slotCount;
char* virtualServerUniqueIdentifier;
if((error = ts3server_getVirtualServerVariableAsString(ids[i], VIRTUALSERVER_NAME, &name)) != ERROR_ok) { /* Query server name */
printf("Error getting virtual server nickname: %d\n", error);
break;
}
if((error = ts3server_getVirtualServerVariableAsInt(ids[i], VIRTUALSERVER_MAXCLIENTS, &slotCount)) != ERROR_ok) {
printf("Error getting virtual server slot count: %d\n", error);
break;
}
if((error = ts3server_getVirtualServerVariableAsString(ids[i], VIRTUALSERVER_UNIQUE_IDENTIFIER, &virtualServerUniqueIdentifier)) != ERROR_ok) {
printf("Error getting virtual server unique identifier: %d\n", error);
break;
}
printf("ID=%llu NAME=\"%s\" CAPACITY=%d Unique Identifier=\"%s\"\n", (unsigned long long)ids[i], name, slotCount, virtualServerUniqueIdentifier);
ts3server_freeMemory(name); /* Do not free memory if above function returned an error */
ts3server_freeMemory(virtualServerUniqueIdentifier);
}
printf("\n");
ts3server_freeMemory(ids); /* Release array */
}
void showChannels(uint64 serverID) {
uint64* ids;
int i;
unsigned int error;
printf("\nList of channels on virtual server %llu:\n", (unsigned long long)serverID);
if((error = ts3server_getChannelList(serverID, &ids)) != ERROR_ok) { /* Get array of channel IDs */
printf("Error getting channel list: %d\n", error);
return;
}
if(!ids[0]) {
printf("No channels\n\n");
ts3server_freeMemory(ids);
return;
}
for(i=0; ids[i]; i++) {
char* name;
if((error = ts3server_getChannelVariableAsString(serverID, ids[i], CHANNEL_NAME, &name)) != ERROR_ok) { /* Query channel name */
printf("Error querying channel name: %d\n", error);
break;
}
printf("%llu - %s\n", (unsigned long long)ids[i], name);
ts3server_freeMemory(name); /* Do not free memory if above function returned an error */
}
printf("\n");
ts3server_freeMemory(ids); /* Release array */
}
void showClients(uint64 serverID) {
anyID* ids;
int i;
unsigned int error;
printf("\nList of clients on virtual server %llu:\n", (unsigned long long)serverID);
if((error = ts3server_getClientList(serverID, &ids)) != ERROR_ok) { /* Get array of client IDs */
printf("Error getting client list: %d\n", error);
return;
}
if(!ids[0]) {
printf("No clients\n\n");
ts3server_freeMemory(ids);
return;
}
for(i=0; ids[i]; i++) {
char* name;
if((error = ts3server_getClientVariableAsString(serverID, ids[i], CLIENT_NICKNAME, &name)) != ERROR_ok) { /* Query client nickname */
printf("Error querying client nickname: %d\n", error);
break;
}
printf("%u - %s\n", ids[i], name);
ts3server_freeMemory(name); /* Do not free memory if above function returned an error */
}
printf("\n");
ts3server_freeMemory(ids); /* Release array */
}
void createDefaultChannelName(char *name) {
static int i = 1;
sprintf(name, "Channel_%d", i++);
}
void enterName(char *name) {
char *s;
printf("\nEnter name: ");
fgets(name, BUFSIZ, stdin);
s = strrchr(name, '\n');
if(s) {
*s = '\0';
}
}
void createChannel(uint64 serverID, const char *name) {
unsigned int error;
uint64 newChannelID;
/* Set data of new channel. Use channelID of 0 for creating channels. */
CHECK_ERROR(ts3server_setChannelVariableAsString(serverID, 0, CHANNEL_NAME, name));
CHECK_ERROR(ts3server_setChannelVariableAsString(serverID, 0, CHANNEL_TOPIC, "Test channel topic"));
CHECK_ERROR(ts3server_setChannelVariableAsString(serverID, 0, CHANNEL_DESCRIPTION, "Test channel description"));
CHECK_ERROR(ts3server_setChannelVariableAsInt (serverID, 0, CHANNEL_FLAG_PERMANENT, 1));
CHECK_ERROR(ts3server_setChannelVariableAsInt (serverID, 0, CHANNEL_FLAG_SEMI_PERMANENT, 0));
CHECK_ERROR(ts3server_setChannelVariableAsInt (serverID, 0, CHANNEL_CODEC_QUALITY, 10));
/* Flush changes to server */
CHECK_ERROR(ts3server_flushChannelCreation(serverID, 0, &newChannelID)); /* Create as top-level channel */
printf("\nCreated channel: %llu\n\n", (unsigned long long)newChannelID);
return;
on_error:
printf("Error creating channel: %d\n", error);
}
void deleteChannel(uint64 serverID) {
uint64 channelID;
int n;
unsigned int error;
/* Query channel ID from user */
printf("\nEnter ID of channel to delete: ");
n = scanf("%llu", (unsigned long long*)&channelID);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
/* Delete channel */
if((error = ts3server_channelDelete(serverID, channelID, 0)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error deleting channel: %s\n\n", errormsg);
ts3server_freeMemory(errormsg);
}
}
}
void renameChannel(uint64 serverID) {
uint64 channelID;
int n;
unsigned int error;
char name[BUFSIZ];
/* Query channel ID from user */
printf("\nEnter ID of channel to rename: ");
n = scanf("%llu", (unsigned long long*)&channelID);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
/* Query new channel name from user */
enterName(name);
/* Change channel name and flush changes */
CHECK_ERROR(ts3server_setChannelVariableAsString(serverID, channelID, CHANNEL_NAME, name));
CHECK_ERROR(ts3server_flushChannelVariable(serverID, channelID));
printf("Renamed channel %llu\n\n", (unsigned long long)channelID);
return;
on_error:
printf("Error renaming channel: %d\n\n", error);
}
void moveClient(uint64 serverID) {
anyID clientIDArray[2]; /* We only want to move one client plus terminating null end-marker */
uint64 newChannelID; /* ID of channel to move the client into */
unsigned int error;
int n;
/* Query client ID from user */
printf("\nEnter ID of client to move: ");
n = scanf("%hu", &clientIDArray[0]);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
clientIDArray[1] = 0; /* Add end-marker */
/* Query channel ID from user */
printf("\nEnter ID of channel to move client into: ");
n = scanf("%llu", (unsigned long long*)&newChannelID);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
/* Move client and check for error */
if((error = ts3server_clientMove(serverID, newChannelID, clientIDArray)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error moving client: %s\n\n", errormsg);
ts3server_freeMemory(errormsg);
}
return;
}
printf("Client %d moved to channel %llu\n", clientIDArray[0], (unsigned long long)newChannelID);
}
uint64 createVirtualServer(const char* name, int port, unsigned int maxClients) {
char buffer[BUFSIZ] = { 0 };
char filename[BUFSIZ];
char port_str[20];
char *keyPair;
uint64 serverID;
unsigned int error;
/* Assemble filename: keypair_<port>.txt */
strcpy(filename, "keypair_");
sprintf(port_str, "%d", port);
strcat(filename, port_str);
strcat(filename, ".txt");
/* Try reading keyPair from file */
if(readKeyPairFromFile(filename, buffer) == 0) {
keyPair = buffer; /* Id read from file */
} else {
keyPair = ""; /* No Id saved, start virtual server with empty keyPair string */
}
/* Create the virtual server with specified port, name, keyPair and max clients */
printf("Create virtual server using keypair '%s'\n", keyPair);
printf("Create virtual server with %d slots\n", maxClients);
//listen on any address on ipv4 and ipv6 (it is also possible to enter multiple ipv4 and ipv6 addresses here)
if((error = ts3server_createVirtualServer(port, "0.0.0.0, ::", name, keyPair, maxClients, &serverID)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error creating virtual server: %s (%d)\n", errormsg, error);
ts3server_freeMemory(errormsg);
}
return 0;
}
/* If we didn't load the keyPair before, query it from virtual server and save to file */
if(!*buffer) {
if((error = ts3server_getVirtualServerKeyPair(serverID, &keyPair)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error querying keyPair: %s\n\n", errormsg);
ts3server_freeMemory(errormsg);
}
return 0;
}
/* Save keyPair to file "keypair_<port>.txt"*/
if(writeKeyPairToFile(filename, keyPair) != 0) {
ts3server_freeMemory(keyPair);
return 0;
}
ts3server_freeMemory(keyPair);
}
return serverID;
}
uint64 startVirtualServer() {
int n;
int port;
unsigned int maxClients;
char name[BUFSIZ];
/* Ask user for server port */
printf("\nEnter server port (default 9987): ");
n = scanf("%d", &port);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return 0;
}
printf("\nEnter server capacity (default %d): ", MAX_CLIENTS);
n = scanf("%d", &maxClients);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return 0;
}
/* Ask user for server name */
enterName(name);
return createVirtualServer(name, port, maxClients);
}
void editVirtualServer() {
int n;
uint64 serverID;
int currentSlotCount, newSlotCount;
unsigned int error;
printf("\nEnter ID of virtual server to edit: ");
n = scanf("%llu", (unsigned long long*)&serverID);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
if((error = ts3server_getVirtualServerVariableAsInt(serverID, VIRTUALSERVER_MAXCLIENTS, &currentSlotCount)) != ERROR_ok) {
printf("Error getting the current capcity of virtual server: %d\n\n", error);
return;
}
printf("\nEnter new capacity of virtual server (currently %d): ", currentSlotCount);
n = scanf("%d", &newSlotCount);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
if((error = ts3server_setVirtualServerVariableAsInt(serverID, VIRTUALSERVER_MAXCLIENTS, newSlotCount)) != ERROR_ok) {
printf("Error setting the new capacity: %d\n\n", error);
return;
}
if((error = ts3server_flushVirtualServerVariable(serverID)) != ERROR_ok) {
printf("Error flushing server variable updates %d\n\n", error);
return;
}
}
void stopVirtualServer() {
int n;
uint64 serverID;
unsigned int error;
printf("\nEnter ID of virtual server to stop: ");
n = scanf("%llu", (unsigned long long*)&serverID);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
if((error = ts3server_stopVirtualServer(serverID)) != ERROR_ok) {
printf("Error stopping virtual server: %d\n\n", error);
}
}
int main(int argc, char **argv) {
char *version;
short abort = 0;
uint64 serverID;
unsigned int error;
int unknownInput = 0;
uint64* ids;
int i;
/* Create struct for callback function pointers */
struct ServerLibFunctions funcs;
/* Initialize all callbacks with NULL */
memset(&funcs, 0, sizeof(struct ServerLibFunctions));
/* Now assign the used callback function pointers */
funcs.onClientConnected = onClientConnected;
funcs.onClientDisconnected = onClientDisconnected;
funcs.onClientMoved = onClientMoved;
funcs.onChannelCreated = onChannelCreated;
funcs.onChannelEdited = onChannelEdited;
funcs.onChannelDeleted = onChannelDeleted;
funcs.onServerTextMessageEvent = onServerTextMessageEvent;
funcs.onChannelTextMessageEvent = onChannelTextMessageEvent;
funcs.onUserLoggingMessageEvent = onUserLoggingMessageEvent;
funcs.onClientStartTalkingEvent = onClientStartTalkingEvent;
funcs.onClientStopTalkingEvent = onClientStopTalkingEvent;
funcs.onAccountingErrorEvent = onAccountingErrorEvent;
funcs.onCustomPacketEncryptEvent = onCustomPacketEncryptEvent;
funcs.onCustomPacketDecryptEvent = onCustomPacketDecryptEvent;
#ifdef USE_VOICEDATAEVENT
funcs.onVoiceDataEvent = onVoiceDataEvent;
#endif
#ifdef CUSTOM_PASSWORDS
funcs.onClientPasswordEncrypt = onClientPasswordEncrypt;
funcs.onCustomServerPasswordCheck = onCustomServerPasswordCheck;
funcs.onCustomChannelPasswordCheck = onCustomChannelPasswordCheck;
#endif
/* Initialize server lib with callbacks */
if((error = ts3server_initServerLib(&funcs, LogType_FILE | LogType_CONSOLE | LogType_USERLOGGING, NULL, argc, (const char* const *)argv)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error initialzing serverlib: %s\n", errormsg);
ts3server_freeMemory(errormsg);
}
return 1;
}
printf("Server running\n");
/* Query and print server lib version */
if((error = ts3server_getServerLibVersion(&version)) != ERROR_ok) {
printf("Error querying server lib version: %d\n", error);
return 1;
}
printf("Server lib version: %s\n", version);
ts3server_freeMemory(version); /* Release dynamically allocated memory */
/* Create a virtual server on localhost using default port 9987 with max 10 slots */
serverID = createVirtualServer("TS3 SDK Test Server", 9987, MAX_CLIENTS);
printf("Created virtual server\n");
/* Set welcome message */
if((error = ts3server_setVirtualServerVariableAsString(serverID, VIRTUALSERVER_WELCOMEMESSAGE, "Hello TeamSpeak")) != ERROR_ok) {
printf("Error setting server welcomemessage: %d\n", error);
return 1;
}
printf("Welcome message set.\n");
/* Set server password */
if((error = ts3server_setVirtualServerVariableAsString(serverID, VIRTUALSERVER_PASSWORD, "secret")) != ERROR_ok) {
printf("Error setting server password: %d\n", error);
return 1;
}
printf("Password set.\n");
/* Flush above two changes to server */
if((error = ts3server_flushVirtualServerVariable(serverID)) != ERROR_ok) {
printf("Error flushing server variables: %d\n", error);
return 1;
}
printf("Variables flushed.\n");
/* Set codec quality of channel(s) */
uint64* channelList = NULL;
if ((error = ts3server_getChannelList(serverID, &channelList)) != ERROR_ok) {
printf("Couldn't get channel list: %d\n", error);
return 1;
}
printf("Received channel list.\n");
uint64* channelIDPtr = NULL;
for (channelIDPtr = channelList; *channelIDPtr != (uint64)NULL; ++channelIDPtr) {
if ((error = ts3server_setChannelVariableAsInt(serverID, *channelIDPtr, CHANNEL_CODEC_QUALITY, 10)) != ERROR_ok) {
printf("Couldn't set channel codec quality: %d\n", error);
ts3server_freeMemory(channelList);
return 1;
}
if ((error = ts3server_flushChannelVariable(serverID, *channelIDPtr)) != ERROR_ok) {
if (error != ERROR_ok_no_update) {
printf("Error flushing channel variables: %d\n", error);
ts3server_freeMemory(channelList);
return 1;
}
}
}
ts3server_freeMemory(channelList);
/* Simple commandline interface */
printf("\nTeamSpeak 3 server commandline interface\n");
showHelp();
while(!abort) {
int c;
if(unknownInput == 0) {
printf("\nEnter Command (h for help)> ");
}
unknownInput = 0;
c = getchar();
switch(c) {
case 'q':
printf("\nShutting down server...\n");
abort = 1;
break;
case 'h':
showHelp();
break;
case 'v':
showVirtualServers();
break;
case 'c':
showChannels(serverID);
break;
case 'l':
showClients(serverID);
break;
case 'n':
{
char name[BUFSIZ];
createDefaultChannelName(name);
createChannel(serverID, name);
break;
}
case 'N':
{
char name[BUFSIZ];
emptyInputBuffer();
enterName(name);
createChannel(serverID, name);
break;
}
case 'd':
deleteChannel(serverID);
break;
case 'r':
renameChannel(serverID);
break;
case 'm':
moveClient(serverID);
break;
case 'C':
startVirtualServer();
break;
case 'E':
editVirtualServer();
break;
case 'S':
stopVirtualServer();
break;
default:
unknownInput = 1;
}
SLEEP(50);
}
/* Stop virtual servers to make sure connected clients are notified instead of dropped */
if((error = ts3server_getVirtualServerList(&ids)) != ERROR_ok) { /* Get array of virtual server IDs */
printf("Error getting virtual server list: %d\n", error);
} else {
for(i=0; ids[i]; i++) {
if((error = ts3server_stopVirtualServer(ids[i])) != ERROR_ok) {
printf("Error stopping virtual server: %d\n", error);
break;
}
}
ts3server_freeMemory(ids);
}
/* Shutdown server lib */
if((error = ts3server_destroyServerLib()) != ERROR_ok) {
printf("Error destroying server lib: %d\n", error);
return 1;
}
return 0;
}
@@ -0,0 +1,7 @@
message("generating TeamSpeak SDK sample ${TS_SDK_SAMPLE}")
set (TS_SAMPLE_SRC
"${CMAKE_CURRENT_LIST_DIR}/main.c"
"${CMAKE_CURRENT_LIST_DIR}/id_io.h"
"${CMAKE_CURRENT_LIST_DIR}/id_io.c"
)
@@ -0,0 +1,46 @@
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "id_io.h"
int readKeyPairFromFile(const char *fileName, char *keyPair) {
FILE *file;
file = fopen(fileName, "r");
if(file == NULL) {
printf("Could not open file '%s' for reading keypair\n", fileName);
return -1;
}
fgets(keyPair, BUFSIZ, file);
if(ferror(file) != 0) {
fclose (file);
printf("Error reading keypair from file '%s'.\n", fileName);
return -1;
}
fclose (file);
printf("Read keypair '%s' from file '%s'.\n", keyPair, fileName);
return 0;
}
int writeKeyPairToFile(const char *fileName, const char* keyPair) {
FILE *file;
file = fopen(fileName, "w");
if(file == NULL) {
printf("Could not open file '%s' for writing keypair\n", fileName);
return -1;
}
fputs(keyPair, file);
if(ferror(file) != 0) {
fclose (file);
printf("Error writing keypair to file '%s'.\n", fileName);
return -1;
}
fclose (file);
printf("Wrote keypair '%s' to file '%s'.\n", keyPair, fileName);
return 0;
}
@@ -0,0 +1,7 @@
#ifndef ID_IO_H
#define ID_IO_H
int readKeyPairFromFile(const char *fileName, char *keyPair);
int writeKeyPairToFile(const char *fileName, const char* keyPair);
#endif
@@ -0,0 +1,778 @@
/*
* TeamSpeak SDK server creation params sample
*
* Copyright (c) TeamSpeak-Systems
*/
#ifdef _WINDOWS
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#else
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <stdio.h>
#include <teamspeak/public_definitions.h>
#include <teamspeak/public_errors.h>
#include <teamspeak/serverlib_publicdefinitions.h>
#include <teamspeak/serverlib.h>
#include "id_io.h"
#define DEFAULT_VIRTUAL_SERVER_ID 1
/* Maximum number of clients allowed per virtual server */
#define MAX_CLIENTS 8
#ifdef _WINDOWS
#define SLEEP(x) Sleep(x)
#else
#define SLEEP(x) usleep(x*1000)
#endif
#define CHECK_ERROR(x) if((error = x) != ERROR_ok) { goto on_error; }
/*
* Callback when client has connected.
*
* Parameter:
* serverID - Virtual server ID
* clientID - ID of connected client
* channelID - ID of channel the client joined
*/
void onClientConnected(uint64 serverID, anyID clientID, uint64 channelID, unsigned int* removeClientError) {
char* clientName;
unsigned int error;
/* Query client nickname */
if((error = ts3server_getClientVariableAsString(serverID, clientID, CLIENT_NICKNAME, &clientName)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error querying client nickname: %s\n", errormsg);
ts3server_freeMemory(errormsg);
}
return;
}
printf("Client '%s' joined channel %llu on virtual server %llu\n", clientName, (unsigned long long) channelID, (unsigned long long)serverID);
}
/*
* Callback when client has disconnected.
*
* Parameter:
* serverID - Virtual server ID
* clientID - ID of disconnected client
* channelID - ID of channel the client left
*/
void onClientDisconnected(uint64 serverID, anyID clientID, uint64 channelID) {
printf("Client %u left channel %llu on virtual server %llu\n", clientID, (unsigned long long)channelID, (unsigned long long)serverID);
}
/*
* Callback when client has moved.
*
* Parameter:
* serverID - Virtual server ID
* clientID - ID of moved client
* oldChannelID - ID of old channel the client left
* newChannelID - ID of new channel the client joined
*/
void onClientMoved(uint64 serverID, anyID clientID, uint64 oldChannelID, uint64 newChannelID) {
printf("Client %u moved from channel %llu to channel %llu on virtual server %llu\n", clientID, (unsigned long long)oldChannelID, (unsigned long long)newChannelID, (unsigned long long)serverID);
}
/*
* Callback when channel has been created.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who created the channel
* channelID - ID of the created channel
*/
void onChannelCreated(uint64 serverID, anyID invokerClientID, uint64 channelID) {
printf("Channel %llu created by %u on virtual server %llu\n", (unsigned long long)channelID, invokerClientID, (unsigned long long)serverID);
}
/*
* Callback when channel has been edited.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who edited the channel
* channelID - ID of the edited channel
*/
void onChannelEdited(uint64 serverID, anyID invokerClientID, uint64 channelID) {
printf("Channel %llu edited by %u on virtual server %llu\n", (unsigned long long)channelID, invokerClientID, (unsigned long long)serverID);
}
/*
* Callback when channel has been deleted.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who deleted the channel
* channelID - ID of the deleted channel
*/
void onChannelDeleted(uint64 serverID, anyID invokerClientID, uint64 channelID) {
printf("Channel %llu deleted by %u on virtual server %llu\n", (unsigned long long)channelID, invokerClientID, (unsigned long long)serverID);
}
void showHelp() {
printf("\n[q] - Quit\n[h] - Show this help\n[v] - List virtual servers\n[c] - Show channels of virtual server %d\n", DEFAULT_VIRTUAL_SERVER_ID);
printf("[l] - Show clients of virtual server %d\n[n] - Create new channel on virtual server %d with generated name\n[N] - Create new channel on virtual server %d with custom name\n", DEFAULT_VIRTUAL_SERVER_ID, DEFAULT_VIRTUAL_SERVER_ID, DEFAULT_VIRTUAL_SERVER_ID);
printf("[d] - Delete channel on virtual server %d\n\n", DEFAULT_VIRTUAL_SERVER_ID);
printf("[C] - Create new virtual server\n[E] - Edit virtual server\n[S] - Stop virtual server\n\n");
}
void emptyInputBuffer() {
int c;
while((c = getchar()) != '\n' && c != EOF);
}
void showVirtualServers() {
uint64* ids;
int i;
unsigned int error;
printf("\nList of virtual servers:\n");
if((error = ts3server_getVirtualServerList(&ids)) != ERROR_ok) { /* Get array of virtual server IDs */
printf("Error getting virtual server list: %d\n", error);
return;
}
if(!ids[0]) {
printf("No virtual servers\n\n");
ts3server_freeMemory(ids);
return;
}
for(i=0; ids[i]; i++) {
char* name;
int slotCount;
char* virtualServerUniqueIdentifier;
if((error = ts3server_getVirtualServerVariableAsString(ids[i], VIRTUALSERVER_NAME, &name)) != ERROR_ok) { /* Query server name */
printf("Error getting virtual server nickname: %d\n", error);
break;
}
if((error = ts3server_getVirtualServerVariableAsInt(ids[i], VIRTUALSERVER_MAXCLIENTS, &slotCount)) != ERROR_ok) {
printf("Error getting virtual server slot count: %d\n", error);
break;
}
if((error = ts3server_getVirtualServerVariableAsString(ids[i], VIRTUALSERVER_UNIQUE_IDENTIFIER, &virtualServerUniqueIdentifier)) != ERROR_ok) {
printf("Error getting virtual server unique identifier: %d\n", error);
break;
}
printf("ID=%llu NAME=\"%s\" CAPACITY=%d Unique Identifier=\"%s\"\n", (unsigned long long)ids[i], name, slotCount, virtualServerUniqueIdentifier);
ts3server_freeMemory(name); /* Do not free memory if above function returned an error */
ts3server_freeMemory(virtualServerUniqueIdentifier);
}
printf("\n");
ts3server_freeMemory(ids); /* Release array */
}
void showChannels(uint64 serverID) {
uint64* ids;
int i;
unsigned int error;
printf("\nList of channels on virtual server %llu:\n", (unsigned long long)serverID);
if((error = ts3server_getChannelList(serverID, &ids)) != ERROR_ok) { /* Get array of channel IDs */
printf("Error getting channel list: %d\n", error);
return;
}
if(!ids[0]) {
printf("No channels\n\n");
ts3server_freeMemory(ids);
return;
}
for(i=0; ids[i]; i++) {
char* name;
if((error = ts3server_getChannelVariableAsString(serverID, ids[i], CHANNEL_NAME, &name)) != ERROR_ok) { /* Query channel name */
printf("Error querying channel name: %d\n", error);
break;
}
printf("%llu - %s\n", (unsigned long long)ids[i], name);
ts3server_freeMemory(name); /* Do not free memory if above function returned an error */
}
printf("\n");
ts3server_freeMemory(ids); /* Release array */
}
void showClients(uint64 serverID) {
anyID* ids;
int i;
unsigned int error;
printf("\nList of clients on virtual server %llu:\n", (unsigned long long)serverID);
if((error = ts3server_getClientList(serverID, &ids)) != ERROR_ok) { /* Get array of client IDs */
printf("Error getting client list: %d\n", error);
return;
}
if(!ids[0]) {
printf("No clients\n\n");
ts3server_freeMemory(ids);
return;
}
for(i=0; ids[i]; i++) {
char* name;
if((error = ts3server_getClientVariableAsString(serverID, ids[i], CLIENT_NICKNAME, &name)) != ERROR_ok) { /* Query client nickname */
printf("Error querying client nickname: %d\n", error);
break;
}
printf("%u - %s\n", ids[i], name);
ts3server_freeMemory(name); /* Do not free memory if above function returned an error */
}
printf("\n");
ts3server_freeMemory(ids); /* Release array */
}
void createDefaultChannelName(char *name) {
static int i = 11; /* We already have 10 channels in this example */
sprintf(name, "Channel_%d", i++);
}
void enterName(char *name) {
char *s;
printf("\nEnter name: ");
fgets(name, BUFSIZ, stdin);
s = strrchr(name, '\n');
if(s) {
*s = '\0';
}
}
void createChannel(uint64 serverID, const char *name) {
/* This code demonstrates how to use the new createChannel API. */
unsigned int error;
uint64 newChannelID;
struct TS3ChannelCreationParams* ccp;
struct TS3Variables* vars;
/* Create a new struct channel parameters struct. Memory is allocated in the clientlib,
* so the struct needs to be freed using ts3server_freeMemory when done */
error = ts3server_makeChannelCreationParams(&ccp);
if(error != ERROR_ok) {
printf("Failed to make channel creation params: %d\n", error);
goto leave;
}
/* Set essential channel paramters:
* parentID 0 -> create as top-level channel
* channelID 0 -> server will automatically assign a new channel ID. An existing channel ID would be an error */
error = ts3server_setChannelCreationParams(ccp, 0, 0);
if(error != ERROR_ok) {
printf("Failed to set channel creation params: %d\n", error);
goto leave;
}
/* Query a struct TS3Variables, used to set additional parameters below */
error = ts3server_getChannelCreationParamsVariables(ccp, &vars);
if(error != ERROR_ok) {
printf("Failed to get variables from channel creation params: %d\n", error);
goto leave;
}
/* Use above queried struct TS3Variables to set additional channel parameters */
/* Set codec quality */
error = ts3server_setVariableAsInt(vars, CHANNEL_CODEC_QUALITY, 10);
if (error != ERROR_ok) {
printf("Error setting channel quality: %d", error);
goto leave;
}
/* Set channel name */
error = ts3server_setVariableAsString(vars, CHANNEL_NAME, name);
if(error != ERROR_ok) {
printf("Failed to set channel name: %d\n", error);
goto leave;
}
/* Make channel permanent (important, otherwise the empty channel would be immediately deleted after creation) */
error = ts3server_setVariableAsInt(vars, CHANNEL_FLAG_PERMANENT, 1);
if(error != ERROR_ok) {
printf("Failed to set channel name: %d\n", error);
goto leave;
}
/* Set channel topic */
error = ts3server_setVariableAsString(vars, CHANNEL_TOPIC, "My topic");
if(error != ERROR_ok) {
printf("Failed to set channel topic: %d\n", error);
goto leave;
}
/* Finally create the channel. Write the ID of the created channel into newChannelID */
error = ts3server_createChannel(serverID, ccp, CHANNEL_CREATE_FLAG_NONE, &newChannelID);
if(error != ERROR_ok) {
printf("Failed to create channel: %d\n", error);
goto leave;
}
printf("\nCreated channel: %llu\n\n", (unsigned long long)newChannelID);
leave:
/* Cleanup struct TS3ChannelCreationParams */
ts3server_freeMemory(ccp);
}
void deleteChannel(uint64 serverID) {
uint64 channelID;
int n;
unsigned int error;
/* Query channel ID from user */
printf("\nEnter ID of channel to delete: ");
n = scanf("%llu", (unsigned long long*)&channelID);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
/* Delete channel */
if((error = ts3server_channelDelete(serverID, channelID, 0)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error deleting channel: %s\n\n", errormsg);
ts3server_freeMemory(errormsg);
}
}
}
void renameChannel(uint64 serverID) {
uint64 channelID;
int n;
unsigned int error;
char name[BUFSIZ];
/* Query channel ID from user */
printf("\nEnter ID of channel to rename: ");
n = scanf("%llu", (unsigned long long*)&channelID);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
/* Query new channel name from user */
enterName(name);
/* Change channel name and flush changes */
CHECK_ERROR(ts3server_setChannelVariableAsString(serverID, channelID, CHANNEL_NAME, name));
CHECK_ERROR(ts3server_flushChannelVariable(serverID, channelID));
printf("Renamed channel %llu\n\n", (unsigned long long)channelID);
return;
on_error:
printf("Error renaming channel: %d\n\n", error);
}
void moveClient(uint64 serverID) {
anyID clientIDArray[2]; /* We only want to move one client plus terminating null end-marker */
uint64 newChannelID; /* ID of channel to move the client into */
unsigned int error;
int n;
/* Query client ID from user */
printf("\nEnter ID of client to move: ");
n = scanf("%hu", &clientIDArray[0]);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
clientIDArray[1] = 0; /* Add end-marker */
/* Query channel ID from user */
printf("\nEnter ID of channel to move client into: ");
n = scanf("%llu", (unsigned long long*)&newChannelID);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
/* Move client and check for error */
if((error = ts3server_clientMove(serverID, newChannelID, clientIDArray)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error moving client: %s\n\n", errormsg);
ts3server_freeMemory(errormsg);
}
return;
}
printf("Client %d moved to channel %llu\n", clientIDArray[0], (unsigned long long)newChannelID);
}
/* Shows how to use the new server params method to create a virtual server */
uint64 createVirtualServer2(const char* name, int port, unsigned int maxClients) {
char buffer[BUFSIZ] = { 0 };
char filename[BUFSIZ];
char port_str[20];
char* keyPair;
unsigned int error;
int i;
struct TS3VirtualServerCreationParams* vscp;
struct TS3ChannelCreationParams* ccp;
struct TS3Variables* vars;
uint64 serverID = 0;
/* Assemble filename: keypair_<port>.txt */
strcpy(filename, "keypair_");
sprintf(port_str, "%d", port);
strcat(filename, port_str);
strcat(filename, ".txt");
/* Try reading keyPair from file */
if(readKeyPairFromFile(filename, buffer) == 0) {
keyPair = buffer; /* Id read from file */
} else {
keyPair = ""; /* No Id saved, start virtual server with empty keyPair string */
}
/* Create server creation params, write result into empty struct TS3VirtualServerCreationParams */
error = ts3server_makeVirtualServerCreationParams(&vscp);
if(error != ERROR_ok) {
printf("Error during makeVirtualServerCreationParams: %d\n", error);
return 0;
}
/* Set essential connection data to server creation params:
* port 9987, ip NULL (localhost), server keypair, max clients, channel count, virtual server ID */
error = ts3server_setVirtualServerCreationParams(vscp, port, NULL, keyPair, maxClients, 10, 1);
if(error != ERROR_ok) {
printf("Error during setVirtualServerCreationParams: %d\n", error);
goto leave;
}
/* Query the struct TS3Variables from the server creation params, to set some additional parameters */
error = ts3server_getVirtualServerCreationParamsVariables(vscp, &vars);
if(error != ERROR_ok) {
printf("Error during getVirtualServerCreationParamsVariables: %d\n", error);
goto leave;
}
/* Below we write some additional server parameters into the struct TS3Variables.
* These parameters are not part of the essential parameters, which we defined earlier
* in ts3server_setVirtualServerCreationParams */
/* Set virtual server name */
error = ts3server_setVariableAsString(vars, VIRTUALSERVER_NAME, "TeamSpeak3 SDK Server");
if(error != ERROR_ok) {
printf("Error setting server name: %d\n", error);
goto leave;
}
/* Set virtual server password */
error = ts3server_setVariableAsString(vars, VIRTUALSERVER_PASSWORD, "secret");
if(error != ERROR_ok) {
printf("Error setting server password: %d\n", error);
goto leave;
}
/* Create 10 channels, write them into the struct channel params, which we queried earlier */
for(i = 0; i < 10; ++i) {
/* Get the channel creation param for the channel index. This channel param structs are subobjects
* created inside the server creation params.
* The number of available channel params depends on the number of channels set above in
* ts3server_setVirtualServerCreationParams (10 in this sample)
* Write result into the reused struct TS3ChannelCreationParams. */
error = ts3server_getVirtualServerCreationParamsChannelCreationParams(vscp, i, &ccp);
if(error != ERROR_ok) {
printf("Error during getVirtualServerCreationParamsChannelCreationParams: %d\n", error);
goto leave;
}
/* Now fill the struct channel creation params with some channel data */
/* Set essential data: channel parent ID and channel ID.
* The idea here is to be able to restore a previously saved channel structure keeping the
* same channel ID over server restarts. If we would create channels the old way, it would not
* be possible to guarantee the channelID, as it would be assigned automatically by the server.
* Basically this allows to recreate snapshots of the channel tree. */
error = ts3server_setChannelCreationParams(ccp, 0, i + 1);
if(error != ERROR_ok) {
printf("Error during setChannelCreationParams: %d\n", error);
goto leave;
}
/* As above with the server, query a TS3Variables (reused) for this channel, which we
* can fill with some additional parameters that are not part of the essentials */
error = ts3server_getChannelCreationParamsVariables(ccp, &vars);
if(error != ERROR_ok) {
printf("Error during getChannelCreationParamsVariables: %d\n", error);
goto leave;
}
/* Now fill the queried struct TS3Variables with additional parameters */
/* Make first channel default */
if(i == 0) {
error = ts3server_setVariableAsInt(vars, CHANNEL_FLAG_DEFAULT, 1);
if(error != ERROR_ok) {
printf("Error setting channel %d default: %d", (i + 1), error);
goto leave;
}
}
/* Set codec quality */
error = ts3server_setVariableAsInt(vars, CHANNEL_CODEC_QUALITY, 10);
if (error != ERROR_ok) {
printf("Error setting channel quality: %d", error);
goto leave;
}
/* Set channel name as "channel #" */
sprintf(buffer, "channel %d", (i + 1));
error = ts3server_setVariableAsString(vars, CHANNEL_NAME, buffer);
if(error != ERROR_ok) {
printf("Error setting channel %d name: %d", (i + 1), error);
goto leave;
}
/* Make channel permanent */
error = ts3server_setVariableAsInt(vars, CHANNEL_FLAG_PERMANENT, 1);
if(error != ERROR_ok) {
printf("Error setting channel %d permanent: %d", (i + 1), error);
goto leave;
}
}
/* Finally create the virtual server, using the server parameters we setup earlier.
* In addition automatically create all channels we set in the channel parameters, which is
* included as part of the server parameters.
* The function writes the virtual server ID into serverID variable */
error = ts3server_createVirtualServer2(vscp, VIRTUALSERVER_CREATE_FLAG_NONE, &serverID);
if(error != ERROR_ok) {
printf("Error during createVirtualServer2: %d\n", error);
goto leave;
}
leave:
/* Cleanup struct virtual server param. The included struct channel param will be automatically
* freed when the virtual server param is freed, so you do not need to call freeMemory on
* the channel params, too. */
ts3server_freeMemory(vscp);
/* Finally return the virtual server ID of our just created virtual server */
return serverID;
}
uint64 startVirtualServer() {
int n;
int port;
unsigned int maxClients;
char name[BUFSIZ];
/* Ask user for server port */
printf("\nEnter server port (default 9987): ");
n = scanf("%d", &port);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return 0;
}
printf("\nEnter server capacity (default %d): ", MAX_CLIENTS);
n = scanf("%d", &maxClients);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return 0;
}
/* Ask user for server name */
enterName(name);
return createVirtualServer2(name, port, maxClients);
}
void editVirtualServer() {
int n;
uint64 serverID;
int currentSlotCount, newSlotCount;
unsigned int error;
printf("\nEnter ID of virtual server to edit: ");
n = scanf("%llu", (unsigned long long*)&serverID);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
if((error = ts3server_getVirtualServerVariableAsInt(serverID, VIRTUALSERVER_MAXCLIENTS, &currentSlotCount)) != ERROR_ok) {
printf("Error getting the current capcity of virtual server: %d\n\n", error);
return;
}
printf("\nEnter new capacity of virtual server (currently %d): ", currentSlotCount);
n = scanf("%d", &newSlotCount);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
if((error = ts3server_setVirtualServerVariableAsInt(serverID, VIRTUALSERVER_MAXCLIENTS, newSlotCount)) != ERROR_ok) {
printf("Error setting the new capacity: %d\n\n", error);
return;
}
if((error = ts3server_flushVirtualServerVariable(serverID)) != ERROR_ok) {
printf("Error flushing server variable updates %d\n\n", error);
return;
}
}
void stopVirtualServer() {
int n;
uint64 serverID;
unsigned int error;
printf("\nEnter ID of virtual server to stop: ");
n = scanf("%llu", (unsigned long long*)&serverID);
emptyInputBuffer();
if(n == 0) {
printf("Invalid input. Please enter a number.\n\n");
return;
}
if((error = ts3server_stopVirtualServer(serverID)) != ERROR_ok) {
printf("Error stopping virtual server: %d\n\n", error);
}
}
int main(int argc, char **argv) {
char *version;
short abort = 0;
uint64 serverID;
unsigned int error;
int unknownInput = 0;
uint64* ids;
int i;
/* Create struct for callback function pointers */
struct ServerLibFunctions funcs;
/* Initialize all callbacks with NULL */
memset(&funcs, 0, sizeof(struct ServerLibFunctions));
/* Now assign the used callback function pointers */
funcs.onClientConnected = onClientConnected;
funcs.onClientDisconnected = onClientDisconnected;
funcs.onClientMoved = onClientMoved;
funcs.onChannelCreated = onChannelCreated;
funcs.onChannelEdited = onChannelEdited;
funcs.onChannelDeleted = onChannelDeleted;
/* Initialize server lib with callbacks */
if((error = ts3server_initServerLib(&funcs, LogType_FILE | LogType_CONSOLE | LogType_USERLOGGING, NULL, argc, (const char* const *)argv)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error initialzing serverlib: %s\n", errormsg);
ts3server_freeMemory(errormsg);
}
return 1;
}
printf("Server running\n");
/* Query and print server lib version */
if((error = ts3server_getServerLibVersion(&version)) != ERROR_ok) {
printf("Error querying server lib version: %d\n", error);
return 1;
}
printf("Server lib version: %s\n", version);
ts3server_freeMemory(version); /* Release dynamically allocated memory */
/* Create a virtual server with the new server params method */
serverID = createVirtualServer2("TS3 SDK Test Server", 9987, MAX_CLIENTS);
/* Simple commandline interface */
printf("\nTeamSpeak 3 server commandline interface\n");
showHelp();
while(!abort) {
int c;
if(unknownInput == 0) {
printf("\nEnter Command (h for help)> ");
}
unknownInput = 0;
c = getchar();
switch(c) {
case 'q':
printf("\nShutting down server...\n");
abort = 1;
break;
case 'h':
showHelp();
break;
case 'v':
showVirtualServers();
break;
case 'c':
showChannels(serverID);
break;
case 'l':
showClients(serverID);
break;
case 'n':
{
char name[BUFSIZ];
createDefaultChannelName(name);
createChannel(serverID, name);
break;
}
case 'N':
{
char name[BUFSIZ];
emptyInputBuffer();
enterName(name);
createChannel(serverID, name);
break;
}
case 'd':
deleteChannel(serverID);
break;
case 'C':
startVirtualServer();
break;
case 'E':
editVirtualServer();
break;
case 'S':
stopVirtualServer();
break;
default:
unknownInput = 1;
}
SLEEP(50);
}
/* Stop virtual servers to make sure connected clients are notified instead of dropped */
if((error = ts3server_getVirtualServerList(&ids)) != ERROR_ok) { /* Get array of virtual server IDs */
printf("Error getting virtual server list: %d\n", error);
} else {
for(i=0; ids[i]; i++) {
if((error = ts3server_stopVirtualServer(ids[i])) != ERROR_ok) {
printf("Error stopping virtual server: %d\n", error);
break;
}
}
ts3server_freeMemory(ids);
}
/* Shutdown server lib */
if((error = ts3server_destroyServerLib()) != ERROR_ok) {
printf("Error destroying server lib: %d\n", error);
return 1;
}
return 0;
}
@@ -0,0 +1,7 @@
message("generating TeamSpeak SDK sample ${TS_SDK_SAMPLE}")
set (TS_SAMPLE_SRC
"${CMAKE_CURRENT_LIST_DIR}/main.c"
"${CMAKE_CURRENT_LIST_DIR}/id_io.h"
"${CMAKE_CURRENT_LIST_DIR}/id_io.c"
)
@@ -0,0 +1,564 @@
/*
* TeamSpeak SDK server sample
*
* Copyright (c) TeamSpeak-Systems
*/
//#define MINIMAL_EXAMPLE
#ifdef _WINDOWS
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#else
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <stdio.h>
#include <teamspeak/public_definitions.h>
#include <teamspeak/public_errors.h>
#include <teamspeak/serverlib_publicdefinitions.h>
#include <teamspeak/serverlib.h>
#ifndef MINIMAL_EXAMPLE
char FILE_BASE[] = "sdk_files";
/* Small helper function to compare string endings */
int endsWith(const char *str, const char *suffix)
{
size_t lenstr;
size_t lensuffix;
if (!str || !suffix) return 0;
lenstr = strlen(str);
lensuffix = strlen(suffix);
if (lensuffix > lenstr) return 0;
return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0;
}
/*
* Callback triggered when a file transfer status changes
*
* Parameters:
* data - The paramaters of the file transfer
*/
void onFileTransferEvent(const struct FileTransferCallbackExport* data){
printf("onFileTransferEvent clientID: %hu, transferID: %hu, remoteTransferID: %hu, status: %u, msg: %s, remoteFileSize: %llu, bytes: %llu, isSender: %i\n",
data->clientID, data->transferID, data->remoteTransferID, data->status, data->statusMessage, data->remotefileSize, data->bytes, data->isSender);
}
/*
* Callback triggered before a client tries to upload a file
*
* Parameters:
* serverID - ID of the virtual server on which upload is requested.
* client - Struct of client parameters like ident, nickname etc. who is uploading the file. See public_definitions.h.
* params - The paramaters of the file upload. See server_commands_file_transfer.h
*
* Note: You can deny the upload by returning ERROR_permissions
*/
unsigned int permFileTransferInitUpload(uint64 serverID, const struct ClientMiniExport* client, const struct ts3sc_ftinitupload* params){
/*log to screen*/
printf("permFileTransferInitUpload filename: %s, size: %llu, channel: %llu, overwrite: %d, resume: %d\n", params->d.fileName, params->d.fileSize, params->d.channelID, params->d.overwrite, params->d.resume);
/*just for fun, lets not permit uploading of .txt files*/
if (endsWith(params->d.fileName, ".txt")){
return ERROR_permissions;
}
/*note we also have the client parameter, so we could deny based on who is uploading*/
return ERROR_ok;
}
/*
* Callback triggered before a client tries to upload a file
*
* Parameters:
* serverID - ID of the virtual server on which upload is requested.
* client - Struct of client parameters like ident, nickname etc. who is uploading the file. See public_definitions.h.
* params - The paramaters of the file download. See server_commands_file_transfer.h
*
* Note: You can deny the download by returning ERROR_permissions
*/
unsigned int permFileTransferInitDownload(uint64 serverID, const struct ClientMiniExport* client, const struct ts3sc_ftinitdownload* params){
/*log to screen*/
printf("permFileTransferInitDownload filename: %s, channel: %llu\n", params->d.fileName, params->d.channelID);
/*just for fun, lets not permit downloading of .txt files*/
if (endsWith(params->d.fileName, ".txt")){
return ERROR_permissions;
}
return ERROR_ok;
}
/*
* Callback triggered before a client tries to get file information
*
* Parameters:
* serverID - ID of the virtual server on which upload is requested.
* client - Struct of client parameters like ident, nickname etc. who is uploading the file. See public_definitions.h.
* params - The paramaters of the information request. See server_commands_file_transfer.h
*
* Note: You can deny the whole request returning ERROR_permissions
*/
unsigned int permFileTransferGetFileInfo(uint64 serverID, const struct ClientMiniExport* client, const struct ts3sc_ftgetfileinfo* params){
int idx;
/*log to screen*/
printf("permFileTransferGetFileInfo \n");
for (idx= 0; idx < params->r_size; ++idx){
printf(" channel: %llu name:%s\n", params->r[idx].channelID, params->r[idx].fileName);
}
printf("\n");
return ERROR_ok;
}
/*
* Callback triggered before a client tries to get a directory listing
*
* Parameters:
* serverID - ID of the virtual server on which upload is requested.
* client - Struct of client parameters like ident, nickname etc. who is uploading the file. See public_definitions.h.
* params - The paramaters of the information request. See server_commands_file_transfer.h
*
* Note: You can deny the whole request returning ERROR_permissions
*/
unsigned int permFileTransferGetFileList(uint64 serverID, const struct ClientMiniExport* client, const struct ts3sc_ftgetfilelist* params){
/*log to screen*/
printf("permFileTransferGetFileList path: %s, channel: %llu\n", params->d.path, params->d.channelID);
return ERROR_ok;
}
/*
* Callback triggered before a client tries to delete files
*
* Parameters:
* serverID - ID of the virtual server on which upload is requested.
* client - Struct of client parameters like ident, nickname etc. who is uploading the file. See public_definitions.h.
* params - The paramaters of the delete request. See server_commands_file_transfer.h
*
* Note: You can deny the whole request returning ERROR_permissions
*/
unsigned int permFileTransferDeleteFile(uint64 serverID, const struct ClientMiniExport* client, const struct ts3sc_ftdeletefile* params){
int idx;
/*log to screen*/
printf("permFileTransferDeleteFile channel: %llu\n", params->d.channelID);
for (idx= 0; idx < params->r_size; ++idx){
printf(" name:%s\n", params->r[idx].fileName);
/*just for fun, lets not permit deleting of .txt files*/
if (endsWith(params->r[idx].fileName, ".txt")){
return ERROR_permissions;
}
}
printf("\n");
return ERROR_ok;
}
/*
* Callback triggered before a client tries to create a sub directory
*
* Parameters:
* serverID - ID of the virtual server on which upload is requested.
* client - Struct of client parameters like ident, nickname etc. who is uploading the file. See public_definitions.h.
* params - The paramaters of the request. See server_commands_file_transfer.h
*
* Note: You can deny the whole request returning ERROR_permissions
*/
unsigned int permFileTransferCreateDirectory(uint64 serverID, const struct ClientMiniExport* client, const struct ts3sc_ftcreatedir* params){
/*log to screen*/
printf("permFileTransferCreateDirectory dirname: %s, channel: %llu\n", params->d.dirname, params->d.channelID);
/*just for fun, lets not permit creation of .txt dirs*/
if (endsWith(params->d.dirname, ".txt")){
return ERROR_permissions;
}
return ERROR_ok;
}
/*
* Callback triggered before a client tries to rename a file
*
* Parameters:
* serverID - ID of the virtual server on which upload is requested.
* client - Struct of client parameters like ident, nickname etc. who is uploading the file. See public_definitions.h.
* params - The paramaters of the rename request. See server_commands_file_transfer.h
*
* Note: You can deny the whole request returning ERROR_permissions
*/
unsigned int permFileTransferRenameFile(uint64 serverID, const struct ClientMiniExport* client, const struct ts3sc_ftrenamefile* params){
/*log to screen*/
if (params->m.has_toChannelID){
printf("permFileTransferRenameFile oldname: %s, old channel: %llu new name: %s, new channel: %llu\n", params->d.oldFileName, params->d.fromChannelID, params->d.newFileName, params->d.toChannelID);
} else {
printf("permFileTransferRenameFile oldname: %s, old channel: %llu new name: %s\n", params->d.oldFileName, params->d.fromChannelID, params->d.newFileName);
}
return ERROR_ok;
}
/*
* Callback triggered after most file transfer request permissions have passed. It exists to let the server change the file
* name and/or directory of the request for special purposes.
*
* Parameters:
* serverID - ID of the virtual server on which upload is requested.
* invokerClientID - id of the client that is doing the request
* original - Struct of the request params like original file name, what kind of action is taken and . See public_definitions.h.
* result - Struct with the transformed parameters. See public_definitions.h.
*/
unsigned int onTransformFilePath(uint64 serverID, anyID invokerClientID, const struct TransformFilePathExport* original, struct TransformFilePathExportReturns* result){
const char* action;
size_t filenamelen;
size_t appendlen = strlen(".example");
switch(original->action){
case(FT_INIT_SERVER):
action = "FT_INIT_SERVER";
break;
case(FT_INIT_CHANNEL):
action = "FT_INIT_CHANNEL";
break;
case(FT_UPLOAD):
action = "FT_UPLOAD";
break;
case(FT_DOWNLOAD):
action = "FT_DOWNLOAD";
break;
case(FT_DELETE):
action = "FT_DELETE";
break;
case(FT_CREATEDIR):
action = "FT_CREATEDIR";
break;
case(FT_RENAME):
action = "FT_RENAME";
break;
case(FT_FILELIST):
action = "FT_FILELIST";
break;
case(FT_FILEINFO):
action = "FT_FILELIST";
break;
default:
action = "unknown action";
}
printf("onTransformFilePath filename: %s, action: %s\n", original->filename, action);
/*for FT_INIT_SERVER and FT_INIT_CHANNEL we use our own directory naming for the server/channel*/
if (original->action == FT_INIT_SERVER){
sprintf(result->channelPath, "%s/myvs_%llu", FILE_BASE, (unsigned long long)serverID);
return ERROR_ok;
}
if (original->action == FT_INIT_CHANNEL){
sprintf(result->channelPath, "%s/myvs_%llu/mychan_%llu", FILE_BASE, serverID, original->channel);
return ERROR_ok;
}
/* Here we can alter the filename and file path to the data on the server. The default values are already filled in in the result variable.
*
* For this example we will append ".example" to .doc files
*/
if (endsWith(original->filename, ".doc")){
filenamelen = strlen(original->filename);
if ((int)(filenamelen+appendlen) >= original->transformedFileNameMaxSize) {
/* the filename we want to return is larger than allowed. We return an error*/
return ERROR_parameter_invalid_size;
}
sprintf(result->transformedFileName, "%s.example", original->filename);
}
return ERROR_ok;
}
#endif
/*
* Callback for user-defined logging.
*
* Parameter:
* logMessage - Log message text
* logLevel - Severity of log message
* logChannel - Custom text to categorize the message channel
* logID - Virtual server ID giving the virtual server source of the log event
* logTime - String with the date and time the log entry occured
* completeLogString - Verbose log message including all previous parameters for convinience
*/
void onUserLoggingMessageEvent(const char* logMessage, int logLevel, const char* logChannel, uint64 logID, const char* logTime, const char* completeLogString) {
/* Your custom error display here... */
/* printf("LOG: %s\n", completeLogString); */
if(logLevel == LogLevel_CRITICAL) {
exit(1); /* Your custom handling of critical errors */
}
}
/*
* Callback triggered when a license error occurs.
*
* Parameters:
* serverID - ID of the virtual server on which the license error occured. This virtual server will be automatically
* shutdown. If the parameter is zero, all virtual servers are affected and have been shutdown.
* errorCode - Code of the occured error. Use ts3server_getGlobalErrorMessage() to convert to a message string
*/
void onAccountingErrorEvent(uint64 serverID, unsigned int errorCode) {
char* errorMessage;
if(ts3server_getGlobalErrorMessage(errorCode, &errorMessage) == ERROR_ok) {
printf("onAccountingErrorEvent serverID=%llu, errorCode=%u: %s\n", (unsigned long long)serverID, errorCode, errorMessage);
ts3server_freeMemory(errorMessage);
}
/* Your custom handling here. In a real application, you wouldn't stop the whole process because the TS3 server part went down.
* The whole idea of this callback is to let you gracefully handle the TS3 server failing to start and to continue your application. */
exit(1);
}
/*
* Read server key from file
*/
int readKeyPairFromFile(const char *fileName, char *keyPair) {
FILE *file;
file = fopen(fileName, "r");
if(file == NULL) {
printf("Could not open file '%s' for reading keypair\n", fileName);
return -1;
}
fgets(keyPair, BUFSIZ, file);
if(ferror(file) != 0) {
fclose (file);
printf("Error reading keypair from file '%s'.\n", fileName);
return -1;
}
fclose (file);
printf("Read keypair '%s' from file '%s'.\n", keyPair, fileName);
return 0;
}
/*
* Write server key to file
*/
int writeKeyPairToFile(const char *fileName, const char* keyPair) {
FILE *file;
file = fopen(fileName, "w");
if(file == NULL) {
printf("Could not open file '%s' for writing keypair\n", fileName);
return -1;
}
fputs(keyPair, file);
if(ferror(file) != 0) {
fclose (file);
printf("Error writing keypair to file '%s'.\n", fileName);
return -1;
}
fclose (file);
printf("Wrote keypair '%s' to file '%s'.\n", keyPair, fileName);
return 0;
}
int main(int argc, char **argv) {
char *version;
uint64 serverID;
unsigned int error;
char buffer[BUFSIZ] = { 0 };
char filename[BUFSIZ];
char port_str[20];
char *keyPair;
/* Create struct for callback function pointers */
struct ServerLibFunctions funcs;
/* Initialize all callbacks with NULL */
memset(&funcs, 0, sizeof(struct ServerLibFunctions));
/* Now assign the used callback function pointers */
funcs.onUserLoggingMessageEvent = onUserLoggingMessageEvent;
funcs.onAccountingErrorEvent = onAccountingErrorEvent;
#ifndef MINIMAL_EXAMPLE
funcs.permFileTransferInitUpload = permFileTransferInitUpload;
funcs.permFileTransferInitDownload = permFileTransferInitDownload;
funcs.permFileTransferGetFileInfo = permFileTransferGetFileInfo;
funcs.permFileTransferGetFileList = permFileTransferGetFileList;
funcs.permFileTransferDeleteFile = permFileTransferDeleteFile;
funcs.permFileTransferCreateDirectory = permFileTransferCreateDirectory;
funcs.permFileTransferRenameFile = permFileTransferRenameFile;
funcs.onFileTransferEvent = onFileTransferEvent;
funcs.onTransformFilePath = onTransformFilePath;
#endif
/* Initialize server lib with callbacks */
if((error = ts3server_initServerLib(&funcs, LogType_FILE | LogType_CONSOLE | LogType_USERLOGGING, NULL, argc, (const char* const *)argv)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error initialzing serverlib: %s\n", errormsg);
ts3server_freeMemory(errormsg);
}
return 1;
}
/* The call below is the only call that needs to be made to enable file transfers on the server.
There are how ever a lot of callbacks that can be hooked in to (above) to change
permissions or change file names */
/* Here we initialize file transfers to store everything on disk in a tree starting at "sdk_files".
All files for virtual server with id 1 will be in "sdk_files/virtualserver_1"
Files in channel 1 in virtual server 1 will be in "sdk_files/virtualserver_1/channel_1"
These directories will automatically be created by the server if they do not exist. It is the
responsibility of the application (not serverlib) to delete these directories when you are done
with them.
We supply NULL to the ips param. This is equivalent to:
char* ips[2]= {"0.0.0.0", NULL};
If the system is ipv6 capable it is equivalent to:
char* ips[3]= {"0.0.0.0", "::", NULL};
The port is free to choose. TeamSpeak defaults to 30033. Feel free to listen on an other port.
Finally we set no limits for the download and upload bandwidth.
*/
/* Initialize server file transfers */
if ((error=ts3server_enableFileManager(FILE_BASE, NULL, 30033, BANDWIDTH_LIMIT_UNLIMITED, BANDWIDTH_LIMIT_UNLIMITED)) != ERROR_ok){
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error initialzing filemanager: %s\n", errormsg);
ts3server_freeMemory(errormsg);
}
return 1;
}
/* Query and print server lib version */
if((error = ts3server_getServerLibVersion(&version)) != ERROR_ok) {
printf("Error querying server lib version: %d\n", error);
return 1;
}
printf("Server lib version: %s\n", version);
ts3server_freeMemory(version); /* Release dynamically allocated memory */
/* Attempt to load keypair from file */
/* Assemble filename: keypair_<port>.txt */
strcpy(filename, "keypair_");
sprintf(port_str, "%d", 9987); // Default port
strcat(filename, port_str);
strcat(filename, ".txt");
/* Try reading keyPair from file */
if(readKeyPairFromFile(filename, buffer) == 0) {
keyPair = buffer; /* Id read from file */
} else {
keyPair = ""; /* No Id saved, start virtual server with empty keyPair string */
}
/* Create virtual server using default port 9987 with max 10 slots */
/* Create the virtual server with specified port, name, keyPair and max clients */
printf("Create virtual server using keypair '%s'\n", keyPair);
//listen on any address on ipv4 and ipv6 (it is also possible to enter multiple ipv4 and ipv6 addresses here)
if((error = ts3server_createVirtualServer(9987, "0.0.0.0, ::", "TeamSpeak SDK Testserver", keyPair, 8, &serverID)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error creating virtual server: %s (%d)\n", errormsg, error);
ts3server_freeMemory(errormsg);
}
return 1;
}
/* If we didn't load the keyPair before, query it from virtual server and save to file */
if(!*buffer) {
if((error = ts3server_getVirtualServerKeyPair(serverID, &keyPair)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error querying keyPair: %s\n\n", errormsg);
ts3server_freeMemory(errormsg);
}
return 0;
}
/* Save keyPair to file "keypair_<port>.txt"*/
if(writeKeyPairToFile(filename, keyPair) != 0) {
ts3server_freeMemory(keyPair);
return 0;
}
ts3server_freeMemory(keyPair);
}
/* Set welcome message */
if((error = ts3server_setVirtualServerVariableAsString(serverID, VIRTUALSERVER_WELCOMEMESSAGE, "Hello TeamSpeak")) != ERROR_ok) {
printf("Error setting server welcome message: %d\n", error);
return 1;
}
/* Set server password */
if((error = ts3server_setVirtualServerVariableAsString(serverID, VIRTUALSERVER_PASSWORD, "secret")) != ERROR_ok) {
printf("Error setting server password: %d\n", error);
return 1;
}
/* Flush above two changes to server */
if((error = ts3server_flushVirtualServerVariable(serverID)) != ERROR_ok) {
printf("Error flushing server variables: %d\n", error);
return 1;
}
/* Set codec quality of channel(s) */
uint64* channelList = NULL;
if ((error = ts3server_getChannelList(serverID, &channelList)) != ERROR_ok) {
printf("Couldn't get channel list: %d\n", error);
return 1;
}
uint64* channelIDPtr = NULL;
for (channelIDPtr = channelList; *channelIDPtr != (uint64)NULL; ++channelIDPtr) {
if ((error = ts3server_setChannelVariableAsInt(serverID, *channelIDPtr, CHANNEL_CODEC_QUALITY, 10)) != ERROR_ok) {
printf("Couldn't set channel codec quality: %d\n", error);
ts3server_freeMemory(channelList);
return 1;
}
if ((error = ts3server_flushChannelVariable(serverID, *channelIDPtr)) != ERROR_ok) {
if (error != ERROR_ok_no_update) {
printf("Error flushing channel variables: %d\n", error);
ts3server_freeMemory(channelList);
return 1;
}
}
}
ts3server_freeMemory(channelList);
/* Wait for user input */
printf("\n--- Press Return to shutdown server and exit ---\n");
getchar();
/* Stop virtual server */
if((error = ts3server_stopVirtualServer(serverID)) != ERROR_ok) {
printf("Error stopping virtual server: %d\n", error);
return 1;
}
/* Shutdown server lib */
if((error = ts3server_destroyServerLib()) != ERROR_ok) {
printf("Error destroying server lib: %d\n", error);
return 1;
}
return 0;
}
@@ -0,0 +1,5 @@
message("generating TeamSpeak SDK sample ${TS_SDK_SAMPLE}")
set (TS_SAMPLE_SRC
"${CMAKE_CURRENT_LIST_DIR}/main.c"
)
@@ -0,0 +1,354 @@
/*
* TeamSpeak SDK server sample
*
* Copyright (c) TeamSpeak-Systems
*/
#ifdef _WINDOWS
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#else
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <stdio.h>
#include <teamspeak/public_definitions.h>
#include <teamspeak/public_errors.h>
#include <teamspeak/serverlib_publicdefinitions.h>
#include <teamspeak/serverlib.h>
/*
* Callback when client has connected.
*
* Parameter:
* serverID - Virtual server ID
* clientID - ID of connected client
* channelID - ID of channel the client joined
*/
void onClientConnected(uint64 serverID, anyID clientID, uint64 channelID, unsigned int* removeClientError) {
printf("Client %u joined channel %llu on virtual server %llu\n", clientID, (unsigned long long)channelID, (unsigned long long)serverID);
}
/*
* Callback when client has disconnected.
*
* Parameter:
* serverID - Virtual server ID
* clientID - ID of disconnected client
* channelID - ID of channel the client left
*/
void onClientDisconnected(uint64 serverID, anyID clientID, uint64 channelID) {
printf("Client %u left channel %llu on virtual server %llu\n", clientID, (unsigned long long)channelID, (unsigned long long)serverID);
}
/*
* Callback when client has moved.
*
* Parameter:
* serverID - Virtual server ID
* clientID - ID of moved client
* oldChannelID - ID of old channel the client left
* newChannelID - ID of new channel the client joined
*/
void onClientMoved(uint64 serverID, anyID clientID, uint64 oldChannelID, uint64 newChannelID) {
printf("Client %u moved from channel %llu to channel %llu on virtual server %llu\n", clientID, (unsigned long long)oldChannelID, (unsigned long long)newChannelID, (unsigned long long)serverID);
}
/*
* Callback when channel has been created.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who created the channel
* channelID - ID of the created channel
*/
void onChannelCreated(uint64 serverID, anyID invokerClientID, uint64 channelID) {
printf("Channel %llu created by %u on virtual server %llu\n", (unsigned long long)channelID, invokerClientID, (unsigned long long)serverID);
}
/*
* Callback when channel has been edited.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who edited the channel
* channelID - ID of the edited channel
*/
void onChannelEdited(uint64 serverID, anyID invokerClientID, uint64 channelID) {
printf("Channel %llu edited by %u on virtual server %llu\n", (unsigned long long)channelID, invokerClientID, (unsigned long long)serverID);
}
/*
* Callback when channel has been deleted.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who deleted the channel
* channelID - ID of the deleted channel
*/
void onChannelDeleted(uint64 serverID, anyID invokerClientID, uint64 channelID) {
printf("Channel %llu deleted by %u on virtual server %llu\n", (unsigned long long)channelID, invokerClientID, (unsigned long long)serverID);
}
/*
* Callback for user-defined logging.
*
* Parameter:
* logMessage - Log message text
* logLevel - Severity of log message
* logChannel - Custom text to categorize the message channel
* logID - Virtual server ID giving the virtual server source of the log event
* logTime - String with the date and time the log entry occured
* completeLogString - Verbose log message including all previous parameters for convinience
*/
void onUserLoggingMessageEvent(const char* logMessage, int logLevel, const char* logChannel, uint64 logID, const char* logTime, const char* completeLogString) {
/* Your custom error display here... */
/* printf("LOG: %s\n", completeLogString); */
if(logLevel == LogLevel_CRITICAL) {
exit(1); /* Your custom handling of critical errors */
}
}
/*
* Callback triggered when the specified client starts talking.
*
* Parameters:
* serverID - ID of the virtual server sending the callback
* clientID - ID of the client which started talking
*/
void onClientStartTalkingEvent(uint64 serverID, anyID clientID) {
printf("onClientStartTalkingEvent serverID=%llu, clientID=%u\n", (unsigned long long)serverID, clientID);
}
/*
* Callback triggered when the specified client stops talking.
*
* Parameters:
* serverID - ID of the virtual server sending the callback
* clientID - ID of the client which stopped talking
*/
void onClientStopTalkingEvent(uint64 serverID, anyID clientID) {
printf("onClientStopTalkingEvent serverID=%llu, clientID=%u\n", (unsigned long long)serverID, clientID);
}
/*
* Callback triggered when a license error occurs.
*
* Parameters:
* serverID - ID of the virtual server on which the license error occured. This virtual server will be automatically
* shutdown. If the parameter is zero, all virtual servers are affected and have been shutdown.
* errorCode - Code of the occured error. Use ts3server_getGlobalErrorMessage() to convert to a message string
*/
void onAccountingErrorEvent(uint64 serverID, unsigned int errorCode) {
char* errorMessage;
if(ts3server_getGlobalErrorMessage(errorCode, &errorMessage) == ERROR_ok) {
printf("onAccountingErrorEvent serverID=%llu, errorCode=%u: %s\n", (unsigned long long)serverID, errorCode, errorMessage);
ts3server_freeMemory(errorMessage);
}
/* Your custom handling here. In a real application, you wouldn't stop the whole process because the TS3 server part went down.
* The whole idea of this callback is to let you gracefully handle the TS3 server failing to start and to continue your application. */
exit(1);
}
/*
* Read server key from file
*/
int readKeyPairFromFile(const char *fileName, char *keyPair) {
FILE *file;
file = fopen(fileName, "r");
if(file == NULL) {
printf("Could not open file '%s' for reading keypair\n", fileName);
return -1;
}
fgets(keyPair, BUFSIZ, file);
if(ferror(file) != 0) {
fclose (file);
printf("Error reading keypair from file '%s'.\n", fileName);
return -1;
}
fclose (file);
printf("Read keypair '%s' from file '%s'.\n", keyPair, fileName);
return 0;
}
/*
* Write server key to file
*/
int writeKeyPairToFile(const char *fileName, const char* keyPair) {
FILE *file;
file = fopen(fileName, "w");
if(file == NULL) {
printf("Could not open file '%s' for writing keypair\n", fileName);
return -1;
}
fputs(keyPair, file);
if(ferror(file) != 0) {
fclose (file);
printf("Error writing keypair to file '%s'.\n", fileName);
return -1;
}
fclose (file);
printf("Wrote keypair '%s' to file '%s'.\n", keyPair, fileName);
return 0;
}
int main(int argc, char **argv) {
char *version;
uint64 serverID;
unsigned int error;
char buffer[BUFSIZ] = { 0 };
char filename[BUFSIZ];
char port_str[20];
char *keyPair;
/* Create struct for callback function pointers */
struct ServerLibFunctions funcs;
/* Initialize all callbacks with NULL */
memset(&funcs, 0, sizeof(struct ServerLibFunctions));
/* Now assign the used callback function pointers */
funcs.onClientConnected = onClientConnected;
funcs.onClientDisconnected = onClientDisconnected;
funcs.onClientMoved = onClientMoved;
funcs.onChannelCreated = onChannelCreated;
funcs.onChannelEdited = onChannelEdited;
funcs.onChannelDeleted = onChannelDeleted;
funcs.onUserLoggingMessageEvent = onUserLoggingMessageEvent;
funcs.onClientStartTalkingEvent = onClientStartTalkingEvent;
funcs.onClientStopTalkingEvent = onClientStopTalkingEvent;
funcs.onAccountingErrorEvent = onAccountingErrorEvent;
/* Initialize server lib with callbacks */
if((error = ts3server_initServerLib(&funcs, LogType_FILE | LogType_CONSOLE | LogType_USERLOGGING, NULL, argc, (const char* const *)argv)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error initialzing serverlib: %s\n", errormsg);
ts3server_freeMemory(errormsg);
}
return 1;
}
/* Query and print server lib version */
if((error = ts3server_getServerLibVersion(&version)) != ERROR_ok) {
printf("Error querying server lib version: %d\n", error);
return 1;
}
printf("Server lib version: %s\n", version);
ts3server_freeMemory(version); /* Release dynamically allocated memory */
/* Attempt to load keypair from file */
/* Assemble filename: keypair_<port>.txt */
strcpy(filename, "keypair_");
sprintf(port_str, "%d", 9987); // Default port
strcat(filename, port_str);
strcat(filename, ".txt");
/* Try reading keyPair from file */
if(readKeyPairFromFile(filename, buffer) == 0) {
keyPair = buffer; /* Id read from file */
} else {
keyPair = ""; /* No Id saved, start virtual server with empty keyPair string */
}
/* Create virtual server using default port 9987 with max 10 slots */
/* Create the virtual server with specified port, name, keyPair and max clients */
printf("Create virtual server using keypair '%s'\n", keyPair);
//listen on any address on ipv4 and ipv6 (it is also possible to enter multiple ipv4 and ipv6 addresses here)
if((error = ts3server_createVirtualServer(9987, "0.0.0.0, ::", "TeamSpeak SDK Testserver", keyPair, 8, &serverID)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error creating virtual server: %s (%d)\n", errormsg, error);
ts3server_freeMemory(errormsg);
}
return 1;
}
/* If we didn't load the keyPair before, query it from virtual server and save to file */
if(!*buffer) {
if((error = ts3server_getVirtualServerKeyPair(serverID, &keyPair)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error querying keyPair: %s\n\n", errormsg);
ts3server_freeMemory(errormsg);
}
return 0;
}
/* Save keyPair to file "keypair_<port>.txt"*/
if(writeKeyPairToFile(filename, keyPair) != 0) {
ts3server_freeMemory(keyPair);
return 0;
}
ts3server_freeMemory(keyPair);
}
/* Set welcome message */
if((error = ts3server_setVirtualServerVariableAsString(serverID, VIRTUALSERVER_WELCOMEMESSAGE, "Hello TeamSpeak")) != ERROR_ok) {
printf("Error setting server welcomemessage: %d\n", error);
return 1;
}
/* Set server password */
if((error = ts3server_setVirtualServerVariableAsString(serverID, VIRTUALSERVER_PASSWORD, "secret")) != ERROR_ok) {
printf("Error setting server password: %d\n", error);
return 1;
}
/* Flush above two changes to server */
if((error = ts3server_flushVirtualServerVariable(serverID)) != ERROR_ok) {
printf("Error flushing server variables: %d\n", error);
return 1;
}
/* Set codec quality of channel(s) */
uint64* channelList = NULL;
if ((error = ts3server_getChannelList(serverID, &channelList)) != ERROR_ok) {
printf("Couldn't get channel list: %d\n", error);
return 1;
}
uint64* channelIDPtr = NULL;
for (channelIDPtr = channelList; *channelIDPtr != (uint64)NULL; ++channelIDPtr) {
if ((error = ts3server_setChannelVariableAsInt(serverID, *channelIDPtr, CHANNEL_CODEC_QUALITY, 10)) != ERROR_ok) {
printf("Couldn't set channel codec quality: %d\n", error);
ts3server_freeMemory(channelList);
return 1;
}
if ((error = ts3server_flushChannelVariable(serverID, *channelIDPtr)) != ERROR_ok) {
if (error != ERROR_ok_no_update) {
printf("Error flushing channel variables: %d\n", error);
ts3server_freeMemory(channelList);
return 1;
}
}
}
ts3server_freeMemory(channelList);
/* Wait for user input */
printf("\n--- Press Return to shutdown server and exit ---\n");
getchar();
/* Stop virtual server */
if((error = ts3server_stopVirtualServer(serverID)) != ERROR_ok) {
printf("Error stopping virtual server: %d\n", error);
return 1;
}
/* Shutdown server lib */
if((error = ts3server_destroyServerLib()) != ERROR_ok) {
printf("Error destroying server lib: %d\n", error);
return 1;
}
return 0;
}
@@ -0,0 +1,5 @@
message("generating TeamSpeak SDK sample ${TS_SDK_SAMPLE}")
set (TS_SAMPLE_SRC
"${CMAKE_CURRENT_LIST_DIR}/main.c"
)
@@ -0,0 +1,725 @@
/*
* TeamSpeak SDK server permission sample
*
* Copyright (c) TeamSpeak-Systems
*/
#ifdef _WINDOWS
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#else
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <stdio.h>
#include <teamspeak/public_definitions.h>
#include <teamspeak/public_errors.h>
#include <teamspeak/serverlib_publicdefinitions.h>
#include <teamspeak/serverlib.h>
/*
* Callback when client has connected.
*
* Parameter:
* serverID - Virtual server ID
* clientID - ID of connected client
* channelID - ID of channel the client joined
*/
void onClientConnected(uint64 serverID, anyID clientID, uint64 channelID, unsigned int* removeClientError) {
printf("Client %u joined channel %llu on virtual server %llu\n", clientID, (unsigned long long)channelID, (unsigned long long)serverID);
}
/*
* Callback when client has disconnected.
*
* Parameter:
* serverID - Virtual server ID
* clientID - ID of disconnected client
* channelID - ID of channel the client left
*/
void onClientDisconnected(uint64 serverID, anyID clientID, uint64 channelID) {
printf("Client %u left channel %llu on virtual server %llu\n", clientID, (unsigned long long)channelID, (unsigned long long)serverID);
}
/*
* Callback when client has moved.
*
* Parameter:
* serverID - Virtual server ID
* clientID - ID of moved client
* oldChannelID - ID of old channel the client left
* newChannelID - ID of new channel the client joined
*/
void onClientMoved(uint64 serverID, anyID clientID, uint64 oldChannelID, uint64 newChannelID) {
printf("Client %u moved from channel %llu to channel %llu on virtual server %llu\n", clientID, (unsigned long long)oldChannelID, (unsigned long long)newChannelID, (unsigned long long)serverID);
}
/*
* Callback when channel has been created.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who created the channel
* channelID - ID of the created channel
*/
void onChannelCreated(uint64 serverID, anyID invokerClientID, uint64 channelID) {
printf("Channel %llu created by %u on virtual server %llu\n", (unsigned long long)channelID, invokerClientID, (unsigned long long)serverID);
}
/*
* Callback when channel has been edited.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who edited the channel
* channelID - ID of the edited channel
*/
void onChannelEdited(uint64 serverID, anyID invokerClientID, uint64 channelID) {
printf("Channel %llu edited by %u on virtual server %llu\n", (unsigned long long)channelID, invokerClientID, (unsigned long long)serverID);
}
/*
* Callback when channel has been deleted.
*
* Parameter:
* serverID - Virtual server ID
* invokerClientID - ID of the client who deleted the channel
* channelID - ID of the deleted channel
*/
void onChannelDeleted(uint64 serverID, anyID invokerClientID, uint64 channelID) {
printf("Channel %llu deleted by %u on virtual server %llu\n", (unsigned long long)channelID, invokerClientID, (unsigned long long)serverID);
}
/*
* Callback for user-defined logging.
*
* Parameter:
* logMessage - Log message text
* logLevel - Severity of log message
* logChannel - Custom text to categorize the message channel
* logID - Virtual server ID giving the virtual server source of the log event
* logTime - String with the date and time the log entry occured
* completeLogString - Verbose log message including all previous parameters for convinience
*/
void onUserLoggingMessageEvent(const char* logMessage, int logLevel, const char* logChannel, uint64 logID, const char* logTime, const char* completeLogString) {
/* Your custom error display here... */
/* printf("LOG: %s\n", completeLogString); */
if(logLevel == LogLevel_CRITICAL) {
exit(1); /* Your custom handling of critical errors */
}
}
/*
* Callback triggered when the specified client starts talking.
*
* Parameters:
* serverID - ID of the virtual server sending the callback
* clientID - ID of the client which started talking
*/
void onClientStartTalkingEvent(uint64 serverID, anyID clientID) {
printf("onClientStartTalkingEvent serverID=%llu, clientID=%u\n", (unsigned long long)serverID, clientID);
}
/*
* Callback triggered when the specified client stops talking.
*
* Parameters:
* serverID - ID of the virtual server sending the callback
* clientID - ID of the client which stopped talking
*/
void onClientStopTalkingEvent(uint64 serverID, anyID clientID) {
printf("onClientStopTalkingEvent serverID=%llu, clientID=%u\n", (unsigned long long)serverID, clientID);
}
/*
* Callback triggered when a license error occurs.
*
* Parameters:
* serverID - ID of the virtual server on which the license error occured. This virtual server will be automatically
* shutdown. If the parameter is zero, all virtual servers are affected and have been shutdown.
* errorCode - Code of the occured error. Use ts3server_getGlobalErrorMessage() to convert to a message string
*/
void onAccountingErrorEvent(uint64 serverID, unsigned int errorCode) {
char* errorMessage;
if(ts3server_getGlobalErrorMessage(errorCode, &errorMessage) == ERROR_ok) {
printf("onAccountingErrorEvent serverID=%llu, errorCode=%u: %s\n", (unsigned long long)serverID, errorCode, errorMessage);
ts3server_freeMemory(errorMessage);
}
/* Your custom handling here. In a real application, you wouldn't stop the whole process because the TS3 server part went down.
* The whole idea of this callback is to let you gracefully handle the TS3 server failing to start and to continue your application. */
exit(1);
}
/*
* Callback triggered before a client connects.
*
* Parameters:
* serverID - ID of the virtual server on which the client is connecting.
* client - Struct of client parameters like ident, nickname etc. Please view public_definitions.h.
*
* Note: You can deny the permission for special nicknames or identities here so the client connect is not allowed.
*/
unsigned int onPermClientCanConnect(uint64 serverID, const struct ClientMiniExport* client) {
printf("onPermClientCanConnect\n\tserverID=%llu\n"
"\tclientChannel=%llu\n\tclientClientID=%u\n\tclientIdent=%s\n\tclientNickname=%s\n",
(unsigned long long)serverID, (unsigned long long)client->channel, client->ID, client->ident, client->nickname);
// nickname of sdk client ist "client" so check it for a test
if (strcmp(client->nickname, "client") == 0) {
//return ERROR_permissions;
}
return ERROR_ok;
}
/*
* Callback triggered before a channel will be created.
*
* Parameters:
* serverID - ID of the virtual server where the client is requesting a channel creation.
* client - Struct of client parameters like ident, nickname etc. Please view public_definitions.h.
* parentChannelID - Parent ID of the channel which is going to be created.
* variables - Array of channel properties for the new channel.
*
* Note: You can deny the permission so creating the channel is not allowed.
*/
unsigned int onPermChannelCreate(uint64 serverID, const struct ClientMiniExport* client, uint64 parentChannelID, const struct VariablesExport* variables) {
int i=0;
printf("onPermChannelCreate\n\tserverID=%llu\n\tparentChannelID=%llu\n"
"\tclientChannel=%llu\n\tclientClientID=%u\n\tclientIdent=%s\n\tclientNickname=%s\n",
(unsigned long long)serverID, (unsigned long long)parentChannelID, (unsigned long long)client->channel, client->ID, client->ident, client->nickname);
// e.g. check nickname for admin and deny creation
if (strcmp(client->nickname, "admin") == 0) {
return ERROR_permissions;
}
for(; i<CHANNEL_ENDMARKER; ++i) {
struct VariablesExportItem item = variables->items[i];
if (item.itemIsValid) {
printf("\titem=%i itemIsValid=%i current=%s\n", i, item.itemIsValid, item.current);
if (item.proposedIsSet) {
printf("\titem=%i proposedIsSet=%i proposed=%s\n", i, item.proposedIsSet, item.proposed);
}
}
}
return ERROR_ok;
}
/*
* Callback triggered before a channel description will be sent.
*
* Parameters:
* serverID - ID of the virtual server on which the client is requesting the channel description.
* client - Struct of client parameters like ident, nickname etc. Please view public_definitions.h.
*
* Note: You can deny the permission so getting the channel descritpion is not allowed.
*/
unsigned int onPermClientCanGetChannelDescription(uint64 serverID, const struct ClientMiniExport* client) {
printf("onPermClientCanGetChannelDescription\n\tserverID=%llu\n"
"\tclientChannel=%llu\n\tclientClientID=%u\n\tclientIdent=%s\n\tclientNickname=%s\n",
(unsigned long long)serverID, (unsigned long long)client->channel, client->ID, client->ident, client->nickname);
// check nickname for admin and deny
if (strcmp(client->nickname, "admin") == 0) {
return ERROR_permissions;
}
return ERROR_ok;
}
/*
* Callback triggered before a client update
*
* Parameters:
* serverID - ID of the virtual server on which the client is requesting an udpate.
* clientID - ID of the client which triggered an update.
* variables - Array of client properties.
*
* Note: You can deny the permission so updating a client variable is not allowed.
*/
unsigned int onPermClientUpdate(uint64 serverID, anyID clientID, const struct VariablesExport* variables) {
int i=0;
printf("onPermClientUpdate\n\tserverID=%llu\n\tclientID=%u\n", (unsigned long long)serverID, clientID);
for(; i<CLIENT_ENDMARKER; ++i) {
struct VariablesExportItem item = variables->items[i];
if (item.itemIsValid) {
printf("\titem=%i itemIsValid=%i current=%s\n", i, item.itemIsValid, item.current);
if (item.proposedIsSet) {
printf("\titem=%i proposedIsSet=%i proposed=%s\n", i, item.proposedIsSet, item.proposed);
// e.g. check nickname for Admin and deny
if (i == CLIENT_NICKNAME && strcmp(item.proposed, "Admin") == 0) {
return ERROR_permissions;
}
}
}
}
return ERROR_ok;
}
/*
* Callback triggered before one or more clients will be kicked from channel.
*
* Parameters:
* serverID - ID of the virtual server on which the client is requesting the kick.
* client - Struct of client parameters like ident, nickname etc. who is causing the kick. Please view public_definitions.h.
* toKickCount - The number of clients to be kicked.
* toKickClients - Array of structs which clients are going to be kicked.
* reasonText - Optional reason text why the kick was initiated.
*
* Note: You can deny the permission so kicking is not allowed.
*/
unsigned int onPermClientKickFromChannel(uint64 serverID, const struct ClientMiniExport* client, int toKickCount, const struct ClientMiniExport* toKickClients, const char* reasonText) {
printf("onPermClientKickFromChannel\n\tserverID=%llu\n"
"\tclientChannel=%llu\n\tclientClientID=%u\n\tclientIdent=%s\n\tclientNickname=%s\n"
"\ttoKickCount=%i\n"
"\ttoKickClientsChannel=%llu\n\ttoKickClientsClientID=%u\n\ttoKickClientsIdent=%s\n\ttoKickClientsNickname=%s\n"
"\treasonText=%s\n",
(unsigned long long)serverID, (unsigned long long)client->channel, client->ID, client->ident, client->nickname, toKickCount, (unsigned long long)toKickClients->channel, toKickClients->ID, toKickClients->ident, toKickClients->nickname, reasonText);
// e.g. check nickname for admin and deny
if (strcmp(client->nickname, "admin") == 0) {
return ERROR_permissions;
}
return ERROR_ok;
}
/*
* Callback triggered before one or more clients will be kicked from server.
*
* Parameters:
* serverID - ID of the virtual server on which the client is requesting the kick.
* client - Struct of client parameters like ident, nickname etc. who is causing the kick. Please view public_definitions.h.
* toKickCount - The number of clients to be kicked.
* toKickClients - Array of structs which clients are going to be kicked.
* reasonText - Optional reason text why the kick was initiated.
*
* Note: You can deny the permission so kicking is not allowed.
*/
unsigned int onPermClientKickFromServer(uint64 serverID, const struct ClientMiniExport* client, int toKickCount, const struct ClientMiniExport* toKickClients, const char* reasonText) {
printf("onPermClientKickFromServer\n\tserverID=%llu\n"
"\tclientChannel=%llu\n\tclientClientID=%u\n\tclientIdent=%s\n\tclientNickname=%s\n"
"\ttoKickCount=%i\n"
"\ttoKickClientsChannel=%llu\n\ttoKickClientsClientID=%u\n\ttoKickClientsIdent=%s\n\ttoKickClientsNickname=%s\n"
"\treasonText=%s\n",
(unsigned long long)serverID, (unsigned long long)client->channel, client->ID, client->ident, client->nickname, toKickCount, (unsigned long long)toKickClients->channel, toKickClients->ID, toKickClients->ident, toKickClients->nickname, reasonText);
// check nickname for admin and deny
if (strcmp(client->nickname, "admin") == 0) {
return ERROR_permissions;
}
return ERROR_ok;
}
/*
* Callback triggered before one or more clients will be moved.
*
* Parameters:
* serverID - ID of the virtual server on which the client is requesting the client move.
* client - Struct of client parameters like ident, nickname etc. who is causing the move. Please view public_definitions.h.
* toMoveCount - The number of clients to be moved.
* toMoveClients - Array of structs which clients are going to be moved.
* newChannel - ID of the new channel.
* reasonText - The reason why the move was initiated.
*
* Note: You can deny the permission so moving is not allowed.
*/
unsigned int onPermClientMove(uint64 serverID, const struct ClientMiniExport* client, int toMoveCount, const struct ClientMiniExport* toMoveClients, uint64 newChannel, const char* reasonText) {
printf("onPermClientMove\n\tserverID=%llu\n"
"\tclientChannel=%llu\n\tclientClientID=%u\n\tclientIdent=%s\n\tclientNickname=%s\n"
"\ttoMoveCount=%i\n"
"\ttoMoveClientsChannel=%llu\n\ttoMoveClientsClientID=%u\n\ttoMoveClientsIdent=%s\n\ttoMoveClientsNickname=%s\n"
"\tnewChannel=%llu\n\treasonText=%s\n",
(unsigned long long)serverID, (unsigned long long)client->channel, client->ID, client->ident, client->nickname, toMoveCount, (unsigned long long)toMoveClients->channel, toMoveClients->ID, toMoveClients->ident, toMoveClients->nickname,(unsigned long long)newChannel, reasonText);
// check nickname for admin and deny
if (strcmp(client->nickname, "admin") == 0) {
return ERROR_permissions;
}
return ERROR_ok;
}
/*
* Callback triggered before a channel will be moved.
*
* Parameters:
* serverID - ID of the virtual server on which the client is requesting the channel move.
* client - Struct of client parameters like ident, nickname etc. who is causing the move. Please view public_definitions.h.
* channelID - ID of the current channel.
* newParentChannelID - ID to which parent channel the current channel is going to be moved.
*
* Note: You can deny the permission so moving is not allowed.
*/
unsigned int onPermChannelMove(uint64 serverID, const struct ClientMiniExport* client, uint64 channelID, uint64 newParentChannelID) {
printf("onPermChannelMove\n\tserverID=%llu\n"
"\tclientChannel=%llu\n\tclientClientID=%u\n\tclientIdent=%s\n\tclientNickname=%s\n"
"\tchannelID=%llu\n\tnewParentChannelID=%llu\n",
(unsigned long long)serverID, (unsigned long long)client->channel, client->ID, client->ident, client->nickname,(unsigned long long)channelID, (unsigned long long)newParentChannelID);
// check nickname for admin and deny
if (strcmp(client->nickname, "admin") == 0) {
return ERROR_permissions;
}
return ERROR_ok;
}
/*
* Callback triggered before message will be sent.
*
* Parameters:
* serverID - ID of the virtual server on which the client is requesting to sent a textmessage.
* client - Struct of client parameters like ident, nickname etc. who is sending the textmessage. Please view public_definitions.h.
* targetMode - The text message target mode if it is a server, channel or client message.
* targetClientOrChannel - ID of the target client or the target channel.
* textMessage - The sent text message.
*
* Note: You can deny the permission so sending a text message is not allowed.
*/
unsigned int onPermSendTextMessage(uint64 serverID, const struct ClientMiniExport* client, anyID targetMode, uint64 targetClientOrChannel, const char* textMessage) {
printf("onPermSendTextMessage\n\tserverID=%llu\n"
"\tclientChannel=%llu\n\tclientClientID=%u\n\tclientIdent=%s\n\tclientNickname=%s\n"
"\ttargetMode=%u\n\ttargetClientOrChannel=%llu\n"
"\ttextMessage=%s\n",
(unsigned long long)serverID, (unsigned long long)client->channel, client->ID, client->ident, client->nickname, targetMode, (unsigned long long)targetClientOrChannel, textMessage);
// check nickname for admin and deny
if (strcmp(client->nickname, "admin") == 0) {
return ERROR_permissions;
}
return ERROR_ok;
}
/*
* Callback triggered before server connection info will be sent.
*
* Parameters:
* serverID - ID of the virtual server on which the client is requesting to sent a textmessage.
* client - Struct of client parameters like ident, nickname etc. who is requesting the connection info. Please view public_definitions.h.
*
* Note: You can deny the permission so requesting the connection info is not allowed.
*/
unsigned int onPermServerRequestConnectionInfo(uint64 serverID, const struct ClientMiniExport* client) {
printf("onPermServerRequestConnectionInfo\n\tserverID=%llu\n"
"\tclientChannel=%llu\n\tclientClientID=%u\n\tclientIdent=%s\n\tclientNickname=%s\n",
(unsigned long long)serverID, (unsigned long long)client->channel, client->ID, client->ident, client->nickname);
// check nickname for admin and deny
if (strcmp(client->nickname, "admin") == 0) {
return ERROR_permissions;
}
return ERROR_ok;
}
/*
* Callback triggered before client connection info will be sent.
*
* Parameters:
* serverID - ID of the virtual server on which the client is requesting to sent a textmessage.
* client - Struct of client parameters like ident, nickname etc. who is requesting the connection info. Please view public_definitions.h.
* mayViewIpPort - Change it to 0 for decline and 1 to allow viewing ip and port. Default is allow. Also, this param is ignored if client==targetClient
* targetClient - Struct of target client parameters like ident, nickname etc. Please view public_definitions.h.
*
* Note: You can deny the permission so requesting the connection info is not allowed.
*/
unsigned int onPermSendConnectionInfo(uint64 serverID, const struct ClientMiniExport* client, int* mayViewIpPort, const struct ClientMiniExport* targetClient) {
*mayViewIpPort = 1;
printf("onPermSendConnectionInfo\n\tserverID=%llu\n"
"\tclientChannel=%llu\n\tclientClientID=%u\n\tclientIdent=%s\n\tclientNickname=%s\n"
"\ttargetClientChannel=%llu\n\ttargetClientClientID=%u\n\ttargetClientIdent=%s\n\ttargetClientNickname=%s\n",
(unsigned long long)serverID, (unsigned long long)client->channel, client->ID, client->ident, client->nickname, (unsigned long long)targetClient->channel, targetClient->ID, targetClient->ident, targetClient->nickname);
// check nickname for admin and deny
if (strcmp(client->nickname, "admin") == 0) {
return ERROR_permissions;
}
return ERROR_ok;
}
/*
* Callback triggered before channel will be edited.
*
* Parameters:
* serverID - ID of the virtual server on which the client is requesting to sent a textmessage.
* client - Struct of client parameters like ident, nickname etc. who is requesting the channel edit. Please view public_definitions.h.
* channelID - ID of the channel which is going to be edited.
* variables - Array of channel properties.
*
* Note: You can deny the permission so editing the channel is not allowed.
*/
unsigned int onPermChannelEdit(uint64 serverID, const struct ClientMiniExport* client, uint64 channelID, const struct VariablesExport* variables) {
int i=0;
printf("onPermChannelEdit\n\tserverID=%llu\n\tchannelID=%llu\n"
"\tclientChannel=%llu\n\tclientClientID=%u\n\tclientIdent=%s\n\tclientNickname=%s\n",
(unsigned long long)serverID, (unsigned long long)channelID, (unsigned long long)client->channel, client->ID, client->ident, client->nickname);
for(; i<CHANNEL_ENDMARKER; ++i) {
struct VariablesExportItem item = variables->items[i];
if (item.itemIsValid) {
printf("\titem=%i itemIsValid=%i current=%s\n", i, item.itemIsValid, item.current);
if (item.proposedIsSet) {
printf("\titem=%i proposedIsSet=%i proposed=%s\n", i, item.proposedIsSet, item.proposed);
// check channel name for admin and deny
if (i == CHANNEL_NAME && strcmp(item.proposed, "admin") == 0) {
return ERROR_permissions;
}
}
}
}
return ERROR_ok;
}
/*
* Callback triggered before channel will be deleted.
*
* Parameters:
* serverID - ID of the virtual server on which the client is requesting to sent a textmessage.
* client - Struct of client parameters like ident, nickname etc. who is requesting the channel edit. Please view public_definitions.h.
* channelID - ID of the channel which is going to be edited.
*
* Note: You can deny the permission so deleting the channel is not allowed.
*/
unsigned int onPermChannelDelete(uint64 serverID, const struct ClientMiniExport* client, uint64 channelID) {
printf("onPermChannelDelete\n\tserverID=%llu\n"
"\tclientChannel=%llu\n\tclientClientID=%u\n\tclientIdent=%s\n\tclientNickname=%s\n"
"\tchannelID=%llu\n",
(unsigned long long)serverID, (unsigned long long)client->channel, client->ID, client->ident, client->nickname,(unsigned long long)channelID);
// check nickname for admin and deny
if (strcmp(client->nickname, "admin") == 0) {
return ERROR_permissions;
}
return ERROR_ok;
}
/*
* Callback triggered before channel will be subscribed.
*
* Parameters:
* serverID - ID of the virtual server on which the client is requesting to sent a textmessage.
* client - Struct of client parameters like ident, nickname etc. who is requesting the channel edit. Please view public_definitions.h.
* channelID - ID of the channel which is going to be edited.
*
* Note: You can deny the permission so subscribing the channel is not allowed.
*/
unsigned int onPermChannelSubscribe(uint64 serverID, const struct ClientMiniExport* client, uint64 channelID) {
printf("onPermChannelSubscribe\n\tserverID=%llu\n"
"\tclientChannel=%llu\n\tclientClientID=%u\n\tclientIdent=%s\n\tclientNickname=%s\n"
"\tchannelID=%llu\n",
(unsigned long long)serverID, (unsigned long long)client->channel, client->ID, client->ident, client->nickname,(unsigned long long)channelID);
// check nickname for admin and deny
if (strcmp(client->nickname, "admin") == 0) {
return ERROR_permissions;
}
return ERROR_ok;
}
/*
* Read server key from file
*/
int readKeyPairFromFile(const char *fileName, char *keyPair) {
FILE *file;
file = fopen(fileName, "r");
if(file == NULL) {
printf("Could not open file '%s' for reading keypair\n", fileName);
return -1;
}
fgets(keyPair, BUFSIZ, file);
if(ferror(file) != 0) {
fclose (file);
printf("Error reading keypair from file '%s'.\n", fileName);
return -1;
}
fclose (file);
printf("Read keypair '%s' from file '%s'.\n", keyPair, fileName);
return 0;
}
/*
* Write server key to file
*/
int writeKeyPairToFile(const char *fileName, const char* keyPair) {
FILE *file;
file = fopen(fileName, "w");
if(file == NULL) {
printf("Could not open file '%s' for writing keypair\n", fileName);
return -1;
}
fputs(keyPair, file);
if(ferror(file) != 0) {
fclose (file);
printf("Error writing keypair to file '%s'.\n", fileName);
return -1;
}
fclose (file);
printf("Wrote keypair '%s' to file '%s'.\n", keyPair, fileName);
return 0;
}
int main(int argc, char **argv) {
char *version;
uint64 serverID;
unsigned int error;
char buffer[BUFSIZ] = { 0 };
char filename[BUFSIZ];
char port_str[20];
char *keyPair;
/* Create struct for callback function pointers */
struct ServerLibFunctions funcs;
/* Initialize all callbacks with NULL */
memset(&funcs, 0, sizeof(struct ServerLibFunctions));
/* Now assign the used callback function pointers */
//funcs.onClientConnected = onClientConnected;
//funcs.onClientDisconnected = onClientDisconnected;
//funcs.onClientMoved = onClientMoved;
//funcs.onChannelCreated = onChannelCreated;
//funcs.onChannelEdited = onChannelEdited;
//funcs.onChannelDeleted = onChannelDeleted;
//funcs.onUserLoggingMessageEvent = onUserLoggingMessageEvent;
//funcs.onClientStartTalkingEvent = onClientStartTalkingEvent;
//funcs.onClientStopTalkingEvent = onClientStopTalkingEvent;
//funcs.onAccountingErrorEvent = onAccountingErrorEvent;
funcs.permClientCanConnect = onPermClientCanConnect;
funcs.permClientCanGetChannelDescription = onPermClientCanGetChannelDescription;
funcs.permClientUpdate = onPermClientUpdate;
funcs.permClientKickFromChannel = onPermClientKickFromChannel;
funcs.permClientKickFromServer = onPermClientKickFromServer;
funcs.permClientMove = onPermClientMove;
funcs.permChannelMove = onPermChannelMove;
funcs.permSendTextMessage = onPermSendTextMessage;
funcs.permSendConnectionInfo = onPermSendConnectionInfo;
funcs.permServerRequestConnectionInfo = onPermServerRequestConnectionInfo;
funcs.permChannelCreate = onPermChannelCreate;
funcs.permChannelEdit = onPermChannelEdit;
funcs.permChannelDelete = onPermChannelDelete;
funcs.permChannelSubscribe = onPermChannelSubscribe;
/* Initialize server lib with callbacks */
if((error = ts3server_initServerLib(&funcs, LogType_FILE | LogType_CONSOLE | LogType_USERLOGGING, NULL, argc, (const char* const *)argv)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error initialzing serverlib: %s\n", errormsg);
ts3server_freeMemory(errormsg);
}
return 1;
}
/* Query and print server lib version */
if((error = ts3server_getServerLibVersion(&version)) != ERROR_ok) {
printf("Error querying server lib version: %d\n", error);
return 1;
}
printf("Server lib version: %s\n", version);
ts3server_freeMemory(version); /* Release dynamically allocated memory */
/* Attempt to load keypair from file */
/* Assemble filename: keypair_<port>.txt */
strcpy(filename, "keypair_");
sprintf(port_str, "%d", 9987); // Default port
strcat(filename, port_str);
strcat(filename, ".txt");
/* Try reading keyPair from file */
if(readKeyPairFromFile(filename, buffer) == 0) {
keyPair = buffer; /* Id read from file */
} else {
keyPair = ""; /* No Id saved, start virtual server with empty keyPair string */
}
/* Create virtual server using default port 9987 with max 10 slots */
/* Create the virtual server with specified port, name, keyPair and max clients */
printf("Create virtual server using keypair '%s'\n", keyPair);
//listen on any address on ipv4 and ipv6 (it is also possible to enter multiple ipv4 and ipv6 addresses here)
if((error = ts3server_createVirtualServer(9987, "0.0.0.0, ::", "TeamSpeak SDK Testserver", keyPair, 8, &serverID)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error creating virtual server: %s (%d)\n", errormsg, error);
ts3server_freeMemory(errormsg);
}
return 1;
}
/* If we didn't load the keyPair before, query it from virtual server and save to file */
if(!*buffer) {
if((error = ts3server_getVirtualServerKeyPair(serverID, &keyPair)) != ERROR_ok) {
char* errormsg;
if(ts3server_getGlobalErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error querying keyPair: %s\n\n", errormsg);
ts3server_freeMemory(errormsg);
}
return 0;
}
/* Save keyPair to file "keypair_<port>.txt"*/
if(writeKeyPairToFile(filename, keyPair) != 0) {
ts3server_freeMemory(keyPair);
return 0;
}
ts3server_freeMemory(keyPair);
}
/* Set welcome message */
if((error = ts3server_setVirtualServerVariableAsString(serverID, VIRTUALSERVER_WELCOMEMESSAGE, "Hello TeamSpeak")) != ERROR_ok) {
printf("Error setting server welcomemessage: %d\n", error);
return 1;
}
/* Set server password */
if((error = ts3server_setVirtualServerVariableAsString(serverID, VIRTUALSERVER_PASSWORD, "secret")) != ERROR_ok) {
printf("Error setting server password: %d\n", error);
return 1;
}
/* Flush above two changes to server */
if((error = ts3server_flushVirtualServerVariable(serverID)) != ERROR_ok) {
printf("Error flushing server variables: %d\n", error);
return 1;
}
/* Set codec quality of channel(s) */
uint64* channelList = NULL;
if ((error = ts3server_getChannelList(serverID, &channelList)) != ERROR_ok) {
printf("Couldn't get channel list: %d\n", error);
return 1;
}
uint64* channelIDPtr = NULL;
for (channelIDPtr = channelList; *channelIDPtr != (uint64)NULL; ++channelIDPtr) {
if ((error = ts3server_setChannelVariableAsInt(serverID, *channelIDPtr, CHANNEL_CODEC_QUALITY, 10)) != ERROR_ok) {
printf("Couldn't set channel codec quality: %d\n", error);
ts3server_freeMemory(channelList);
return 1;
}
if ((error = ts3server_flushChannelVariable(serverID, *channelIDPtr)) != ERROR_ok) {
if (error != ERROR_ok_no_update) {
printf("Error flushing channel variables: %d\n", error);
ts3server_freeMemory(channelList);
return 1;
}
}
}
ts3server_freeMemory(channelList);
/* Wait for user input */
printf("\n--- Press Return to shutdown server and exit ---\n");
getchar();
/* Stop virtual server */
if((error = ts3server_stopVirtualServer(serverID)) != ERROR_ok) {
printf("Error stopping virtual server: %d\n", error);
return 1;
}
/* Shutdown server lib */
if((error = ts3server_destroyServerLib()) != ERROR_ok) {
printf("Error destroying server lib: %d\n", error);
return 1;
}
return 0;
}
@@ -0,0 +1,5 @@
message("generating TeamSpeak SDK sample ${TS_SDK_SAMPLE}")
set (TS_SAMPLE_SRC
"${CMAKE_CURRENT_LIST_DIR}/main.c"
)