#include #include #include #include "ConnectedClient.h" #include "voice/VoiceClient.h" #include "../server/VoiceServer.h" #include "../InstanceHandler.h" #include "../server/QueryServer.h" #include "../manager/PermissionNameMapper.h" #include "music/MusicClient.h" #include #include #include #include "./web/WebClient.h" #include "query/command3.h" using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; using namespace ts::token; # define INVOKER(command, invoker) \ do { \ if(invoker) { \ command["invokerid"] = invoker->getClientId(); \ command["invokername"] = invoker->getDisplayName(); \ command["invokeruid"] = invoker->getUid(); \ } else { \ command["invokerid"] = 0; \ command["invokername"] = "undefined"; \ command["invokeruid"] = "undefined"; \ } \ } while(0) #define INVOKER_NEW(command, invoker) \ do { \ if(invoker) { \ command.put_unchecked(0, "invokerid", invoker->getClientId()); \ command.put_unchecked(0, "invokername", invoker->getDisplayName()); \ command.put_unchecked(0, "invokeruid", invoker->getUid()); \ } else { \ command.put_unchecked(0, "invokerid", "0"); \ command.put_unchecked(0, "invokername", "undefined"); \ command.put_unchecked(0, "invokeruid", "undefined"); \ } \ } while(0) bool ConnectedClient::notifyServerGroupList(bool as_notify) { Command cmd(as_notify ? "notifyservergrouplist" : ""); int index = 0; for (const auto& group : (this->server ? this->server->groups : serverInstance->getGroupManager().get())->availableServerGroups(true)) { if(group->target() == GroupTarget::GROUPTARGET_CHANNEL) { cmd[index]["cgid"] = group->groupId(); } else { cmd[index]["sgid"] = group->groupId(); } for (const auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) cmd[index][std::string{prop.type().name}] = prop.value(); auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power); auto add_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_add_power); auto remove_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_remove_power); cmd[index]["n_modifyp"] = modify_power.has_value ? modify_power.value : 0; cmd[index]["n_member_addp"] = add_power.has_value ? add_power.value : 0; cmd[index]["n_member_removep"] = remove_power.has_value ? remove_power.value : 0; index++; } this->sendCommand(cmd); return true; } bool ConnectedClient::notifyGroupPermList(const std::shared_ptr& group, bool as_sid) { ts::command_builder result{this->getExternalType() == CLIENT_TEAMSPEAK ? group->target() == GROUPTARGET_SERVER ? "notifyservergrouppermlist" : "notifychannelgrouppermlist" : ""}; if (group->target() == GROUPTARGET_SERVER) result.put_unchecked(0, "sgid", group->groupId()); else result.put_unchecked(0, "cgid", group->groupId()); int index = 0; auto permissions = group->permissions()->permissions(); auto permission_mapper = serverInstance->getPermissionMapper(); auto client_type = this->getType(); result.reserve_bulks(permissions.size() * 2); for (const auto &permission_data : permissions) { auto& permission = get<1>(permission_data); if(!permission.flags.value_set) continue; if(as_sid) { result.put_unchecked(index, "permsid", permission_mapper->permission_name(client_type, get<0>(permission_data))); } else { result.put_unchecked(index, "permid", (uint16_t) get<0>(permission_data)); } result.put_unchecked(index, "permvalue", permission.values.value); result.put_unchecked(index, "permnegated", permission.flags.negate); result.put_unchecked(index, "permskip", permission.flags.skip); index++; } for (const auto &permission_data : permissions) { auto& permission = get<1>(permission_data); if(!permission.flags.grant_set) continue; if(as_sid) { result.put_unchecked(index, "permsid", permission_mapper->permission_name_grant(client_type, get<0>(permission_data))); } else { result.put_unchecked(index, "permid", (uint16_t) (get<0>(permission_data) | PERM_ID_GRANT)); } result.put_unchecked(index, "permvalue", permission.values.grant); result.put_unchecked(index, "permnegated", 0); result.put_unchecked(index, "permskip", 0); index++; } if (index == 0) return false; this->sendCommand(result); //Need hack return true; } bool ConnectedClient::notifyClientPermList(ClientDbId cldbid, const std::shared_ptr& mgr, bool perm_sids) { Command res(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientpermlist" : ""); auto permissions = mgr->permissions(); if(permissions.empty()) return false; int index = 0; res[index]["cldbid"] = cldbid; auto permission_mapper = serverInstance->getPermissionMapper(); auto client_type = this->getType(); for (const auto &permission_data : permissions) { auto& permission = std::get<1>(permission_data); if(permission.flags.value_set) { if (perm_sids) res[index]["permsid"] = permission_mapper->permission_name(client_type, get<0>(permission_data)); else res[index]["permid"] = get<0>(permission_data); res[index]["permvalue"] = permission.values.value; res[index]["permnegated"] = permission.flags.negate; res[index]["permskip"] = permission.flags.skip; index++; } if(permission.flags.grant_set) { if (perm_sids) res[index]["permsid"] = permission_mapper->permission_name_grant(client_type, get<0>(permission_data)); else res[index]["permid"] = (get<0>(permission_data) | PERM_ID_GRANT); res[index]["permvalue"] = permission.values.grant; res[index]["permnegated"] = 0; res[index]["permskip"] = 0; index++; } } this->sendCommand(res); return true; } bool ConnectedClient::notifyChannelGroupList(bool as_notify) { Command cmd(as_notify ? "notifychannelgrouplist" : ""); int index = 0; for (const auto &group : (this->server ? this->server->groups : serverInstance->getGroupManager().get())->availableChannelGroups(true)) { if(group->target() == GroupTarget::GROUPTARGET_CHANNEL) { cmd[index]["cgid"] = group->groupId(); } else { cmd[index]["sgid"] = group->groupId(); } for (auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) cmd[index][std::string{prop.type().name}] = prop.value(); auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power); auto add_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_add_power); auto remove_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_remove_power); cmd[index]["n_modifyp"] = modify_power.has_value ? modify_power.value : 0; cmd[index]["n_member_addp"] = add_power.has_value ? add_power.value : 0; cmd[index]["n_member_removep"] = remove_power.has_value ? remove_power.value : 0; index++; } if(index == 0) return false; this->sendCommand(cmd); return true; } bool ConnectedClient::notifyTextMessage(ChatMessageMode mode, const shared_ptr &invoker, uint64_t targetId, ChannelId channel_id, const std::chrono::system_clock::time_point& timestamp, const string &textMessage) { //notifytextmessage targetmode=1 msg=asdasd target=2 invokerid=1 invokername=WolverinDEV invokeruid=xxjnc14LmvTk+Lyrm8OOeo4tOqw= Command cmd("notifytextmessage"); INVOKER(cmd, invoker); cmd["targetmode"] = mode; cmd["target"] = targetId; cmd["msg"] = textMessage; cmd["timestamp"] = floor(timestamp.time_since_epoch()).count(); if(this->getType() != ClientType::CLIENT_TEAMSPEAK) cmd["cid"] = channel_id; this->sendCommand(cmd); return true; } bool ConnectedClient::notifyServerGroupClientAdd(const shared_ptr &invoker, const shared_ptr &client, const shared_ptr &group) { Command cmd("notifyservergroupclientadded"); INVOKER(cmd, invoker); cmd["sgid"] = group->groupId(); cmd["clid"] = client->getClientId(); cmd["name"] = client->getDisplayName(); cmd["cluid"] = client->getUid(); this->sendCommand(cmd); return true; } bool ConnectedClient::notifyServerGroupClientRemove(std::shared_ptr invoker, std::shared_ptr client, std::shared_ptr group) { Command cmd("notifyservergroupclientdeleted"); INVOKER(cmd, invoker); cmd["sgid"] = group->groupId(); cmd["clid"] = client->getClientId(); cmd["name"] = client->getDisplayName(); cmd["cluid"] = client->getUid(); this->sendCommand(cmd); return true; } bool ConnectedClient::notifyClientChannelGroupChanged( const std::shared_ptr &invoker, const std::shared_ptr &client, const std::shared_ptr& channel, const std::shared_ptr& inherited, const std::shared_ptr& group, bool lock_channel_tree) { assert(!lock_channel_tree); /* not supported */ if(!this->isClientVisible(client, false) && client != this) return false; if(client->getChannel() != channel) return false; assert(client); assert(channel); assert(inherited); assert(group); Command cmd("notifyclientchannelgroupchanged"); INVOKER(cmd, invoker); cmd["cgid"] = group->groupId(); cmd["clid"] = client->getClientId(); cmd["cid"] = channel->channelId(); cmd["cgi"] = inherited ? inherited->channelId() : channel->channelId(); //The inherited channel this->sendCommand(cmd); return true; } bool ConnectedClient::notifyConnectionInfo(const shared_ptr &target, const shared_ptr &info) { command_builder notify{"notifyconnectioninfo"}; auto bulk = notify.bulk(0); bulk.put_unchecked("clid", target->getClientId()); auto not_set = this->getType() == CLIENT_TEAMSPEAK ? 0 : -1; /* we deliver data to the web client as well, because its a bit dump :D */ if(target->getClientId() != this->getClientId()) { auto file_stats = target->connectionStatistics->file_stats(); /* default values which normally sets the client */ bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_CONTROL, not_set); bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_KEEPALIVE, not_set); bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_SPEECH, not_set); bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_CONTROL, not_set); bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_KEEPALIVE, not_set); bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_SPEECH, not_set); bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_CONTROL, not_set); bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_KEEPALIVE, not_set); bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_SPEECH, not_set); bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_CONTROL, not_set); bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_KEEPALIVE, not_set); bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_SPEECH, not_set); /* its flipped here because the report is out of the clients view */ bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_CONTROL, not_set); bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_KEEPALIVE, not_set); bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_SPEECH, not_set); bulk.put_unchecked(property::CONNECTION_BYTES_SENT_CONTROL, not_set); bulk.put_unchecked(property::CONNECTION_BYTES_SENT_KEEPALIVE, not_set); bulk.put_unchecked(property::CONNECTION_BYTES_SENT_SPEECH, not_set); /* its flipped here because the report is out of the clients view */ bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_CONTROL, not_set); bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE, not_set); bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_SPEECH, not_set); bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_CONTROL, not_set); bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_KEEPALIVE, not_set); bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_SPEECH, not_set); bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_CONTROL, not_set); bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_KEEPALIVE, not_set); bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_SPEECH, not_set); bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_TOTAL, not_set); bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_SPEECH, not_set); bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_KEEPALIVE, not_set); bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_CONTROL, not_set); bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_TOTAL, not_set); bulk.put_unchecked(property::CONNECTION_PING, 0); bulk.put_unchecked(property::CONNECTION_PING_DEVIATION, 0); bulk.put_unchecked(property::CONNECTION_CONNECTED_TIME, 0); bulk.put_unchecked(property::CONNECTION_IDLE_TIME, 0); /* its flipped here because the report is out of the clients view */ bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_SENT, file_stats.bytes_received); bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, file_stats.bytes_sent); } if(info) { for(const auto& [key, value] : info->properties) bulk.put(key, value); } else { //Fill in what we can, else we trust the client if(target->getType() == ClientType::CLIENT_TEASPEAK || target->getType() == ClientType::CLIENT_TEAMSPEAK || target->getType() == ClientType::CLIENT_WEB) { auto& stats = target->connectionStatistics->total_stats(); /* its flipped here because the report is out of the clients view */ bulk.put(property::CONNECTION_BYTES_RECEIVED_CONTROL, stats.connection_bytes_received[stats::ConnectionStatistics::category::COMMAND]); bulk.put(property::CONNECTION_BYTES_RECEIVED_KEEPALIVE, stats.connection_bytes_received[stats::ConnectionStatistics::category::KEEP_ALIVE]); bulk.put(property::CONNECTION_BYTES_RECEIVED_SPEECH, stats.connection_bytes_received[stats::ConnectionStatistics::category::VOICE]); bulk.put(property::CONNECTION_BYTES_SENT_CONTROL, stats.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND]); bulk.put(property::CONNECTION_BYTES_SENT_KEEPALIVE, stats.connection_bytes_sent[stats::ConnectionStatistics::category::KEEP_ALIVE]); bulk.put(property::CONNECTION_BYTES_SENT_SPEECH, stats.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE]); /* its flipped here because the report is out of the clients view */ bulk.put(property::CONNECTION_PACKETS_RECEIVED_CONTROL, stats.connection_packets_received[stats::ConnectionStatistics::category::COMMAND]); bulk.put(property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE, stats.connection_packets_received[stats::ConnectionStatistics::category::KEEP_ALIVE]); bulk.put(property::CONNECTION_PACKETS_RECEIVED_SPEECH, stats.connection_packets_received[stats::ConnectionStatistics::category::VOICE]); bulk.put(property::CONNECTION_PACKETS_SENT_CONTROL, stats.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND]); bulk.put(property::CONNECTION_PACKETS_SENT_KEEPALIVE, stats.connection_packets_sent[stats::ConnectionStatistics::category::KEEP_ALIVE]); bulk.put(property::CONNECTION_PACKETS_SENT_SPEECH, stats.connection_packets_sent[stats::ConnectionStatistics::category::VOICE]); } } if(auto vc = dynamic_pointer_cast(target); vc) { bulk.put(property::CONNECTION_PING, floor(vc->current_ping()).count()); bulk.put(property::CONNECTION_PING_DEVIATION, vc->current_ping_deviation()); } #ifdef COMPILE_WEB_CLIENT else if(dynamic_pointer_cast(target)) bulk.put(property::CONNECTION_PING, floor(dynamic_pointer_cast(target)->client_ping()).count()); #endif if(auto vc = dynamic_pointer_cast(target); vc){ auto& calculator = vc->connection->packet_statistics(); auto report = calculator.loss_report(); bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_SPEECH, std::to_string(report.voice_loss())); bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_KEEPALIVE, std::to_string(report.keep_alive_loss())); bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_CONTROL, std::to_string(report.control_loss())); bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_TOTAL, std::to_string(report.total_loss())); } if(target->getClientId() == this->getClientId() || permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, this->getChannelId()))) { bulk.put(property::CONNECTION_CLIENT_IP, target->getLoggingPeerIp()); bulk.put(property::CONNECTION_CLIENT_PORT, target->getPeerPort()); } bulk.put(property::CONNECTION_CONNECTED_TIME, chrono::duration_cast(chrono::system_clock::now() - target->connectTimestamp).count()); bulk.put(property::CONNECTION_IDLE_TIME, chrono::duration_cast(chrono::system_clock::now() - target->idleTimestamp).count()); this->sendCommand(notify); return true; } bool ConnectedClient::notifyClientMoved(const shared_ptr &client, const std::shared_ptr &target_channel, ViewReasonId reason, std::string msg, std::shared_ptr invoker, bool lock_channel_tree) { assert(!lock_channel_tree); assert(client->getClientId() > 0); assert(client->currentChannel); assert(target_channel); assert(this->isClientVisible(client, false) || &*client == this); Command mv("notifyclientmoved"); mv["clid"] = client->getClientId(); mv["cfid"] = client->currentChannel->channelId(); mv["ctid"] = target_channel->channelId(); mv["reasonid"] = reason; if (invoker) INVOKER(mv, invoker); if (!msg.empty()) mv["reasonmsg"] = msg; else mv["reasonmsg"] = ""; this->sendCommand(mv); return true; } bool ConnectedClient::notifyClientUpdated(const std::shared_ptr &client, const deque &props, bool lock) { shared_lock channel_lock(this->channel_lock, defer_lock); if(lock) channel_lock.lock(); if(!this->isClientVisible(client, false) && client != this) return false; auto client_id = client->getClientId(); if(client_id == 0) { logError(this->getServerId(), "{} Attempted to send a clientupdate for client id 0. Updated client: {}", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client)); return false; } Command response("notifyclientupdated"); response["clid"] = client_id; for (const auto &prop : props) { if(lastOnlineTimestamp.time_since_epoch().count() > 0 && (*prop == property::CLIENT_TOTAL_ONLINE_TIME || *prop == property::CLIENT_MONTH_ONLINE_TIME)) response[prop->name] = client->properties()[prop].as() + duration_cast(system_clock::now() - client->lastOnlineTimestamp).count(); else response[prop->name] = client->properties()[prop].value(); } this->sendCommand(response); return true; } bool ConnectedClient::notifyPluginCmd(std::string name, std::string msg, std::shared_ptr sender) { Command notify("notifyplugincmd"); notify["name"] = name; notify["data"] = msg; INVOKER(notify, sender); this->sendCommand(notify); return true; } bool ConnectedClient::notifyClientChatComposing(const shared_ptr &client) { Command notify("notifyclientchatcomposing"); notify["clid"] = client->getClientId(); notify["cluid"] = client->getUid(); this->sendCommand(notify); return true; } bool ConnectedClient::notifyClientChatClosed(const shared_ptr &client) { Command notify("notifyclientchatclosed"); notify["clid"] = client->getClientId(); notify["cluid"] = client->getUid(); this->sendCommand(notify); return true; } bool ConnectedClient::notifyChannelMoved(const std::shared_ptr &channel, ChannelId order, const std::shared_ptr &invoker) { if(!channel || !this->channels->channel_visible(channel)) return false; Command notify("notifychannelmoved"); INVOKER(notify, invoker); notify["reasonid"] = ViewReasonId::VREASON_MOVED; notify["cid"] = channel->channelId(); notify["cpid"] = channel->parent() ? channel->parent()->channelId() : 0; notify["order"] = order; this->sendCommand(notify); return true; } bool ConnectedClient::notifyChannelCreate(const std::shared_ptr &channel, ChannelId orderId, const std::shared_ptr &invoker) { Command notify("notifychannelcreated"); for (auto &prop : channel->properties().list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { if(prop.type() == property::CHANNEL_ORDER) notify[prop.type().name] = orderId; else if(prop.type() == property::CHANNEL_DESCRIPTION) continue; else notify[prop.type().name] = prop.value(); } INVOKER(notify, invoker); this->sendCommand(notify); return true; } bool ConnectedClient::notifyChannelHide(const std::deque &channel_ids, bool lock_channel_tree) { if(channel_ids.empty()) return true; if(this->getType() == ClientType::CLIENT_TEAMSPEAK) { //Voice hasnt that event shared_lock server_channel_lock(this->server->channel_tree_lock, defer_lock); unique_lock client_channel_lock(this->channel_lock, defer_lock); if(lock_channel_tree) { server_channel_lock.lock(); client_channel_lock.lock(); } else { assert(mutex_locked(this->channel_lock)); } deque> clients_to_remove; { for(const auto& w_client : this->visibleClients) { auto client = w_client.lock(); if(!client) continue; if(client->currentChannel) { auto id = client->currentChannel->channelId(); for(const auto cid : channel_ids) { if(cid == id) { clients_to_remove.push_back(client); break; } } } } } //TODO: May send a unsubscribe and remove the clients like that? this->notifyClientLeftView(clients_to_remove, "Channel gone out of view", false, ViewReasonServerLeft); return this->notifyChannelDeleted(channel_ids, this->server->serverRoot); } else { Command notify("notifychannelhide"); int index = 0; for(const auto& channel : channel_ids) notify[index++]["cid"] = channel; this->sendCommand(notify); } return true; } bool ConnectedClient::notifyChannelShow(const std::shared_ptr &channel, ts::ChannelId orderId) { if(!channel) return false; auto result = false; if(this->getType() == ClientType::CLIENT_TEAMSPEAK) { //Voice hasn't that event result = this->notifyChannelCreate(channel, orderId, this->server->serverRoot); } else { Command notify("notifychannelshow"); for (auto &prop : channel->properties().list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { if(prop.type() == property::CHANNEL_ORDER) { notify[prop.type().name] = orderId; } else if(prop.type() == property::CHANNEL_DESCRIPTION) { continue; } else notify[prop.type().name] = prop.value(); } this->sendCommand(notify); } if(result && this->subscribeToAll) this->subscribeChannel({channel}, false, true); return true; } bool ConnectedClient::notifyChannelDescriptionChanged(std::shared_ptr channel) { if(!this->channels->channel_visible(channel)) return false; Command notifyDesChanges("notifychanneldescriptionchanged"); notifyDesChanges["cid"] = channel->channelId(); this->sendCommand(notifyDesChanges); return true; } bool ConnectedClient::notifyChannelPasswordChanged(std::shared_ptr channel) { if(!this->channels->channel_visible(channel)) return false; Command notifyDesChanges("notifychannelpasswordchanged"); notifyDesChanges["cid"] = channel->channelId(); this->sendCommand(notifyDesChanges); return true; } bool ConnectedClient::notifyClientEnterView(const std::shared_ptr &client, const std::shared_ptr &invoker, const std::string& reason, const std::shared_ptr &to, ViewReasonId reasonId, const std::shared_ptr &from, bool lock_channel_tree) { sassert(client && client->getClientId() > 0); sassert(to); sassert(!lock_channel_tree); /* we don't support locking */ sassert(!this->isClientVisible(client, false) || &*client == this); switch (reasonId) { case ViewReasonId::VREASON_MOVED: case ViewReasonId::VREASON_BAN: case ViewReasonId::VREASON_CHANNEL_KICK: case ViewReasonId::VREASON_SERVER_KICK: if(!invoker) { logCritical(this->getServerId(), "{} ConnectedClient::notifyClientEnterView() => missing invoker for reason id {}", CLIENT_STR_LOG_PREFIX, reasonId); if(this->server) ;//invoker = this->server->serverRoot.get(); } break; default: break; } ts::command_builder builder{"notifycliententerview", 1024, 1}; builder.put_unchecked(0, "cfid", from ? from->channelId() : 0); builder.put_unchecked(0, "ctid", to ? to->channelId() : 0); builder.put_unchecked(0, "reasonid", reasonId); INVOKER_NEW(builder, invoker); switch (reasonId) { case ViewReasonId::VREASON_MOVED: case ViewReasonId::VREASON_BAN: case ViewReasonId::VREASON_CHANNEL_KICK: case ViewReasonId::VREASON_SERVER_KICK: builder.put_unchecked(0, "reasonmsg", reason); break; default: break; } for (const auto &elm : client->properties()->list_properties(property::FLAG_CLIENT_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { builder.put_unchecked(0, elm.type().name, elm.value()); } visibleClients.emplace_back(client); this->sendCommand(builder); return true; } bool ConnectedClient::notifyClientEnterView(const std::deque> &clients, const ts::ViewReasonSystemT &_vrs) { if(clients.empty()) return true; assert(mutex_locked(this->channel_lock)); Command cmd("notifycliententerview"); cmd["cfid"] = 0; cmd["reasonid"] = ViewReasonId::VREASON_SYSTEM; ChannelId current_channel = 0; size_t index = 0; auto it = clients.begin(); while(it != clients.end()) { auto client = *(it++); if(this->isClientVisible(client, false)) continue; auto channel = client->getChannel(); sassert(!channel || channel->channelId() != 0); if(!channel) /* hmm suspecious */ continue; if(current_channel != channel->channelId()) { if(current_channel == 0) cmd[index]["ctid"] = (current_channel = channel->channelId()); else { it--; /* we still have to send him */ break; } } this->visibleClients.push_back(client); for (const auto &elm : client->properties()->list_properties(property::FLAG_CLIENT_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { cmd[index][std::string{elm.type().name}] = elm.value(); } index++; if(index > 16) /* max 16 clients per packet */ break; } if(index > 0) this->sendCommand(cmd); if(it != clients.end()) return this->notifyClientEnterView({it, clients.end()}, _vrs); return true; } bool ConnectedClient::notifyChannelEdited( const std::shared_ptr &channel, const std::vector &properties, const std::shared_ptr &invoker, bool) { auto v_channel = this->channels->find_channel(channel->channelId()); if(!v_channel) return false; //Not visible? Important do not remove! bool send_description_change{false}; Command notify("notifychanneledited"); for(auto prop : properties) { const auto& prop_info = property::describe(prop); if(prop == property::CHANNEL_ORDER) notify[prop_info.name] = v_channel->previous_channel; else if(prop == property::CHANNEL_DESCRIPTION) { send_description_change = true; } else { notify[prop_info.name] = channel->properties()[prop].as(); } } notify["cid"] = channel->channelId(); notify["reasonid"] = ViewReasonId::VREASON_EDITED; INVOKER(notify, invoker); this->sendCommand(notify); if(send_description_change) { Command notify_dchange{"notifychanneldescriptionchanged"}; notify_dchange["cid"] = channel->channelId(); this->sendCommand(notify_dchange); } return true; } bool ConnectedClient::notifyChannelDeleted(const deque& channel_ids, const std::shared_ptr& invoker) { if(channel_ids.empty()) return true; Command notify("notifychanneldeleted"); int index = 0; for (const auto& channel_id : channel_ids) notify[index++]["cid"] = channel_id; INVOKER(notify, invoker); notify["reasonid"] = ViewReasonId::VREASON_EDITED; this->sendCommand(notify); return true; } bool ConnectedClient::notifyServerUpdated(std::shared_ptr invoker) { Command response("notifyserverupdated"); for (const auto& elm : this->server->properties().list_properties(property::FLAG_SERVER_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { if(elm.type() == property::VIRTUALSERVER_MIN_WINPHONE_VERSION) continue; //if(elm->type() == property::VIRTUALSERVER_RESERVED_SLOTS) response[elm.type().name] = elm.value(); } if(getType() == CLIENT_QUERY) INVOKER(response, invoker); this->sendCommand(response); return true; } bool ConnectedClient::notifyClientPoke(std::shared_ptr invoker, std::string msg) { Command cmd("notifyclientpoke"); INVOKER(cmd, invoker); cmd["msg"] = msg; this->sendCommand(cmd); return true; } bool ConnectedClient::notifyChannelSubscribed(const deque> &channels) { Command notify("notifychannelsubscribed"); int index = 0; for (const auto& ch : channels) { notify[index]["es"] = this->server->getClientsByChannel(ch).empty() ? ch->emptySince() : 0; notify[index++]["cid"] = ch->channelId(); } this->sendCommand(notify); return true; } bool ConnectedClient::notifyChannelUnsubscribed(const deque> &channels) { Command notify("notifychannelunsubscribed"); int index = 0; for (const auto& ch : channels) { notify[index]["es"] = this->server->getClientsByChannel(ch).empty() ? ch->emptySince() : 0; notify[index++]["cid"] = ch->channelId(); } this->sendCommand(notify); return true; } bool ConnectedClient::notifyMusicQueueAdd(const shared_ptr& bot, const shared_ptr& entry, int index, const std::shared_ptr& invoker) { Command notify("notifymusicqueueadd"); notify["bot_id"] = bot->getClientDatabaseId(); notify["song_id"] = entry->getSongId(); notify["url"] = entry->getUrl(); notify["index"] = index; INVOKER(notify, invoker); this->sendCommand(notify); return true; } bool ConnectedClient::notifyMusicQueueRemove(const std::shared_ptr &bot, const std::deque> &entry, const std::shared_ptr& invoker) { Command notify("notifymusicqueueremove"); notify["bot_id"] = bot->getClientDatabaseId(); int index = 0; for(const auto& song : entry) notify[index++]["song_id"] = song->getSongId(); INVOKER(notify, invoker); this->sendCommand(notify); return true; } bool ConnectedClient::notifyMusicQueueOrderChange(const std::shared_ptr &bot, const std::shared_ptr &entry, int order, const std::shared_ptr& invoker) { Command notify("notifymusicqueueorderchange"); notify["bot_id"] = bot->getClientDatabaseId(); notify["song_id"] = entry->getSongId(); notify["index"] = order; INVOKER(notify, invoker); this->sendCommand(notify); return true; } bool ConnectedClient::notifyMusicPlayerStatusUpdate(const std::shared_ptr &bot) { Command notify("notifymusicstatusupdate"); notify["bot_id"] = bot->getClientDatabaseId(); auto player = bot->current_player(); if(player) { notify["player_buffered_index"] = player->bufferedUntil().count(); notify["player_replay_index"] = player->currentIndex().count(); } else { notify["player_buffered_index"] = 0; notify["player_replay_index"] = 0; } this->sendCommand(notify); return true; } extern void apply_song(Command& command, const std::shared_ptr& element, int index = 0); bool ConnectedClient::notifyMusicPlayerSongChange(const std::shared_ptr &bot, const shared_ptr &newEntry) { Command notify("notifymusicplayersongchange"); notify["bot_id"] = bot->getClientDatabaseId(); if(newEntry) { apply_song(notify, newEntry); } else { notify["song_id"] = 0; } this->sendCommand(notify); return true; } bool ConnectedClient::notifyConversationMessageDelete(const ts::ChannelId channel_id, const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end, ts::ClientDbId client_id, size_t size) { Command notify("notifyconversationmessagedelete"); notify["cid"] = channel_id; notify["timestamp_begin"] = floor(begin.time_since_epoch()).count(); notify["timestamp_end"] = floor(end.time_since_epoch()).count(); notify["cldbid"] = client_id; notify["limit"] = size; this->sendCommand(notify); return true; }