Teaspeak-Server/server/src/client/command_handler/music.cpp

1200 lines
49 KiB
C++

//
// Created by wolverindev on 26.01.20.
//
#include <memory>
#include <spdlog/sinks/rotating_file_sink.h>
#include <iostream>
#include <bitset>
#include <algorithm>
#include <openssl/sha.h>
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../music/MusicClient.h"
#include "../query/QueryClient.h"
#include "../../weblist/WebListManager.h"
#include "../../manager/ConversationManager.h"
#include "../../manager/PermissionNameMapper.h"
#include <experimental/filesystem>
#include <cstdint>
#include <StringVariable.h>
#include "helpers.h"
#include "./bulk_parsers.h"
#include <Properties.h>
#include <log/LogUtils.h>
#include <misc/sassert.h>
#include <misc/base64.h>
#include <misc/hex.h>
#include <misc/digest.h>
#include <misc/rnd.h>
#include <misc/timer.h>
#include <misc/strobf.h>
#include <misc/scope_guard.h>
#include <bbcode/bbcodes.h>
#include <src/music/MusicPlaylist.h>
namespace fs = std::experimental::filesystem;
using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
command_result ConnectedClient::handleCommandMusicBotCreate(Command& cmd) {
if(!config::music::enabled) return command_result{error::music_disabled};
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
if(this->server->music_manager_->max_bots() != -1 && this->server->music_manager_->max_bots() <= this->server->music_manager_->current_bot_count()){
if(config::license->isPremium())
return command_result{error::music_limit_reached};
else
return command_result{error::music_limit_reached, strobf("You reached the server music bot limit. You could increase this limit by extend your server with a premium license.").string()};
}
auto permissions_list = this->calculate_permissions({
permission::i_client_music_limit,
permission::b_client_music_create_permanent,
permission::b_client_music_create_semi_permanent,
permission::b_client_music_create_temporary,
permission::i_channel_join_power,
permission::i_client_music_delete_power,
permission::i_client_music_create_modify_max_volume
}, this->getChannelId());
auto permissions = std::map<permission::PermissionType, permission::v2::PermissionFlaggedValue>(permissions_list.begin(), permissions_list.end());
auto max_bots = permissions[permission::i_client_music_limit];
if(max_bots.has_value) {
auto ownBots = this->server->music_manager_->listBots(this->getClientDatabaseId());
if(!permission::v2::permission_granted(ownBots.size() + 1, max_bots))
return command_result{error::music_client_limit_reached};
}
MusicClient::Type::value create_type;
if(cmd[0].has("type")) {
create_type = cmd["type"].as<MusicClient::Type::value>();
switch(create_type) {
case MusicClient::Type::PERMANENT:
if(!permission::v2::permission_granted(1, permissions[permission::b_client_music_create_permanent]))
return command_result{permission::b_client_music_create_permanent};
break;
case MusicClient::Type::SEMI_PERMANENT:
if(!permission::v2::permission_granted(1, permissions[permission::b_client_music_create_semi_permanent]))
return command_result{permission::b_client_music_create_semi_permanent};
break;
case MusicClient::Type::TEMPORARY:
if(!permission::v2::permission_granted(1, permissions[permission::b_client_music_create_temporary]))
return command_result{permission::b_client_music_create_temporary};
break;
default:
return command_result{error::vs_critical};
}
} else {
if(permission::v2::permission_granted(1, permissions[permission::b_client_music_create_permanent]))
create_type = MusicClient::Type::PERMANENT;
else if(permission::v2::permission_granted(1, permissions[permission::b_client_music_create_semi_permanent]))
create_type = MusicClient::Type::SEMI_PERMANENT;
else if(permission::v2::permission_granted(1, permissions[permission::b_client_music_create_temporary]))
create_type = MusicClient::Type::TEMPORARY;
else
return command_result{permission::b_client_music_create_temporary};
}
shared_lock server_channel_lock(this->server->channel_tree_lock);
auto channel = cmd[0].has("cid") ? this->server->channelTree->findChannel(cmd["cid"]) : this->currentChannel;
if(!channel) {
if(cmd[0].has("cid")) return command_result{error::channel_invalid_id};
} else {
if(this->calculate_and_get_join_state(channel) != permission::ok)
channel = nullptr;
}
if(!channel)
channel = this->server->channelTree->getDefaultChannel();
auto bot = this->server->music_manager_->createBot(this->getClientDatabaseId());
if(!bot) return command_result{error::vs_critical};
bot->set_bot_type(create_type);
if(permissions[permission::i_client_music_create_modify_max_volume].has_value) {
auto max_volume = min(100, max(0, permissions[permission::i_client_music_create_modify_max_volume].value));
if(max_volume >= 0)
bot->volume_modifier(max_volume / 100.f);
}
this->selectedBot = bot;
{
server_channel_lock.unlock();
unique_lock server_channel_w_lock(this->server->channel_tree_lock);
this->server->client_move(
bot,
channel,
nullptr,
"music bot created",
ViewReasonId::VREASON_USER_ACTION,
false,
server_channel_w_lock
);
}
bot->properties()[property::CLIENT_LAST_CHANNEL] = channel ? channel->channelId() : 0;
bot->properties()[property::CLIENT_COUNTRY] = config::geo::countryFlag;
if(permissions[permission::i_client_music_delete_power].has_value && permissions[permission::i_client_music_delete_power].value >= 0) {
bot->clientPermissions->set_permission(permission::i_client_music_needed_delete_power, {permissions[permission::i_client_music_delete_power].value,0}, permission::v2::set_value, permission::v2::do_nothing);
}
Command notify(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifymusiccreated" : "");
notify["bot_id"] = bot->getClientDatabaseId();
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandMusicBotDelete(Command& cmd) {
if(!config::music::enabled) return command_result{error::music_disabled};
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = this->server->music_manager_->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
if(bot->getOwner() != this->getClientDatabaseId()) {
ACTION_REQUIRES_PERMISSION(permission::i_client_music_delete_power, bot->calculate_permission(permission::i_client_music_needed_delete_power, bot->getChannelId()), this->getChannelId());
}
this->server->music_manager_->deleteBot(bot);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandMusicBotSetSubscription(ts::Command &cmd) {
if(!config::music::enabled) return command_result{error::music_disabled};
auto bot = this->server->music_manager_->findBotById(cmd["bot_id"]);
if(!bot && cmd["bot_id"].as<ClientDbId>() != 0) return command_result{error::music_invalid_id};
{
auto old_bot = this->subscribed_bot.lock();
if(old_bot)
old_bot->remove_subscriber(_this.lock());
}
if(bot) {
bot->add_subscriber(_this.lock());
this->subscribed_bot = bot;
}
return command_result{error::ok};
}
void apply_song(Command& command, const std::shared_ptr<ts::music::SongInfo>& element, int index = 0) {
if(!element) return;
command[index]["song_id"] = element ? element->getSongId() : 0;
command[index]["song_url"] = element ? element->getUrl() : "";
command[index]["song_invoker"] = element ? element->getInvoker() : 0;
command[index]["song_loaded"] = false;
auto entry = dynamic_pointer_cast<ts::music::PlayableSong>(element);
if(entry) {
auto data = entry->song_loaded_data();
command[index]["song_loaded"] = entry->song_loaded() && data;
if(entry->song_loaded() && data) {
command[index]["song_title"] = data->title;
command[index]["song_description"] = data->description;
command[index]["song_thumbnail"] = data->thumbnail;
command[index]["song_length"] = data->length.count();
}
}
}
command_result ConnectedClient::handleCommandMusicBotPlayerInfo(Command& cmd) {
if(!config::music::enabled) return command_result{error::music_disabled};
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto bot = this->server->music_manager_->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifymusicplayerinfo" : "");
result["bot_id"] = bot->getClientDatabaseId();
result["player_state"] =(int) bot->player_state();
auto player = bot->current_player();
if(player) {
result["player_buffered_index"] = player->bufferedUntil().count();
result["player_replay_index"] = player->currentIndex().count();
result["player_max_index"] = player->length().count();
result["player_seekable"] = player->seek_supported();
result["player_title"] = player->songTitle();
result["player_description"] = player->songDescription();
} else {
result["player_buffered_index"] = 0;
result["player_replay_index"] = 0;
result["player_max_index"] = 0;
result["player_seekable"] = 0;
result["player_title"] = "";
result["player_description"] = "";
}
apply_song(result, bot->current_song());
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandMusicBotPlayerAction(Command& cmd) {
if(!config::music::enabled) return command_result{error::music_disabled};
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = this->server->music_manager_->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
ACTION_REQUIRES_PERMISSION(permission::i_client_music_play_power, bot->calculate_permission(permission::i_client_music_needed_play_power, bot->getChannelId()), this->getChannelId());
auto player = bot->current_player();
if(cmd["action"] == 0) {
bot->stopMusic();
} else if(cmd["action"] == 1) {
bot->playMusic();
} else if(cmd["action"] == 2) {
bot->player_pause();
} else if(cmd["action"] == 3) {
bot->forwardSong();
} else if(cmd["action"] == 4) {
bot->rewindSong();
} else if(cmd["action"] == 5) {
if(!player) return command_result{error::music_no_player};
player->forward(::music::PlayerUnits(cmd["units"].as<int64_t>()));
} else if(cmd["action"] == 6) {
if(!player) return command_result{error::music_no_player};
player->rewind(::music::PlayerUnits(cmd["units"].as<int64_t>()));
} else return command_result{error::music_invalid_action};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistList(ts::Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto self_ref = this->ref();
auto playlists = this->server->music_manager_->playlists();
playlists.erase(find_if(playlists.begin(), playlists.end(), [&](const shared_ptr<music::PlayablePlaylist>& playlist) {
return playlist->client_has_permissions(self_ref, permission::i_playlist_needed_view_power, permission::i_playlist_view_power, music::PlaylistPermissions::do_no_require_granted) != permission::ok;
}), playlists.end());
if(playlists.empty())
return command_result{error::database_empty_result};
Command notify(this->notify_response_command("notifyplaylistlist"));
size_t index = 0;
for(const auto& entry : playlists) {
notify[index]["playlist_id"] = entry->playlist_id();
auto bot = entry->current_bot();
notify[index]["playlist_bot_id"] = bot ? bot->getClientDatabaseId() : 0;
notify[index]["playlist_title"] = entry->properties()[property::PLAYLIST_TITLE].value();
notify[index]["playlist_type"] = entry->properties()[property::PLAYLIST_TYPE].value();
notify[index]["playlist_owner_dbid"] = entry->properties()[property::PLAYLIST_OWNER_DBID].value();
notify[index]["playlist_owner_name"] = entry->properties()[property::PLAYLIST_OWNER_NAME].value();
auto permissions = entry->permission_manager();
auto permission = permissions->permission_value_flagged(permission::i_playlist_needed_modify_power);
notify[index]["needed_power_modify"] = permission.has_value ? permission.value : 0;
permission = permissions->permission_value_flagged(permission::i_playlist_needed_permission_modify_power);
notify[index]["needed_power_permission_modify"] = permission.has_value ? permission.value : 0;
permission = permissions->permission_value_flagged(permission::i_playlist_needed_delete_power);
notify[index]["needed_power_delete"] = permission.has_value ? permission.value : 0;
permission = permissions->permission_value_flagged(permission::i_playlist_song_needed_add_power);
notify[index]["needed_power_song_add"] = permission.has_value ? permission.value : 0;
permission = permissions->permission_value_flagged(permission::i_playlist_song_needed_move_power);
notify[index]["needed_power_song_move"] = permission.has_value ? permission.value : 0;
permission = permissions->permission_value_flagged(permission::i_playlist_song_needed_remove_power);
notify[index]["needed_power_song_remove"] = permission.has_value ? permission.value : 0;
index++;
}
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistCreate(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_playlist_create, 1);
{
auto max_playlists = this->calculate_permission(permission::i_max_playlists, 0);
if(max_playlists.has_value) {
auto playlists = ref_server->music_manager_->find_playlists_by_client(this->getClientDatabaseId());
if(!permission::v2::permission_granted(playlists.size(), max_playlists))
return command_result{permission::i_max_playlists};
}
}
auto playlist = ref_server->music_manager_->create_playlist(this->getClientDatabaseId(), this->getDisplayName());
if(!playlist) return command_result{error::vs_critical};
playlist->properties()[property::PLAYLIST_TYPE] = music::Playlist::Type::GENERAL;
{
auto max_songs = this->calculate_permission(permission::i_max_playlist_size, 0);
if(max_songs.has_value && max_songs.value >= 0)
playlist->properties()[property::PLAYLIST_MAX_SONGS] = max_songs.value;
}
auto power = this->calculate_permission(permission::i_playlist_song_remove_power, 0);
if(power.has_value && power.value >= 0)
playlist->permission_manager()->set_permission(permission::i_playlist_song_needed_remove_power, {power.value, 0}, permission::v2::set_value, permission::v2::do_nothing);
power = this->calculate_permission(permission::i_playlist_delete_power, 0);
if(power.has_value && power.value >= 0)
playlist->permission_manager()->set_permission(permission::i_playlist_needed_delete_power, {power.value, 0}, permission::v2::set_value, permission::v2::do_nothing);
power = this->calculate_permission(permission::i_playlist_modify_power, 0);
if(power.has_value && power.value >= 0)
playlist->permission_manager()->set_permission(permission::i_playlist_needed_modify_power, {power.value, 0}, permission::v2::set_value, permission::v2::do_nothing);
power = this->calculate_permission(permission::i_playlist_permission_modify_power, 0);
if(power.has_value && power.value >= 0)
playlist->permission_manager()->set_permission(permission::i_playlist_needed_permission_modify_power, {power.value, 0}, permission::v2::set_value, permission::v2::do_nothing);
Command notify(this->notify_response_command("notifyplaylistcreated"));
notify["playlist_id"] = playlist->playlist_id();
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistDelete(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_delete_power, permission::i_playlist_delete_power); perr)
return command_result{perr};
string error;
if(!ref_server->music_manager_->delete_playlist(playlist->playlist_id(), error)) {
logError(this->getServerId(), "Failed to delete playlist with id {}. Error: {}", playlist->playlist_id(), error);
return command_result{error::vs_critical};
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistInfo(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_view_power, permission::i_playlist_view_power, music::PlaylistPermissions::do_no_require_granted); perr)
return command_result{perr};
Command notify(this->notify_response_command("notifyplaylistinfo"));
for(const auto& property : playlist->properties().list_properties(property::FLAG_PLAYLIST_VARIABLE)) {
notify[property.type().name] = property.value();
}
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistEdit(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_modify_power, permission::i_playlist_modify_power); perr)
return command_result{perr};
deque<pair<const property::PropertyDescription*, string>> properties;
for(const auto& key : cmd[0].keys()) {
if(key == "playlist_id") continue;
if(key == "return_code") continue;
const auto& property = property::find<property::PlaylistProperties>(key);
if(property == property::PLAYLIST_UNDEFINED) {
logError(this->getServerId(), R"([{}] Tried to edit a not existing playlist property "{}" to "{}")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if((property.flags & property::FLAG_USER_EDITABLE) == 0) {
logError(this->getServerId(), "[{}] Tried to change a playlist property which is not changeable. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if(!property.validate_input(cmd[key].as<string>())) {
logError(this->getServerId(), "[{}] Tried to change a playlist property to an invalid value. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if(property == property::PLAYLIST_CURRENT_SONG_ID) {
auto song_id = cmd[key].as<SongId>();
auto song = song_id > 0 ? playlist->find_song(song_id) : nullptr;
if(song_id != 0 && !song)
return command_result{error::playlist_invalid_song_id};
} else if(property == property::PLAYLIST_MAX_SONGS) {
auto value = cmd[key].as<int32_t>();
auto max_value = this->calculate_permission(permission::i_max_playlist_size, this->getChannelId());
if(max_value.has_value && !permission::v2::permission_granted(value, max_value))
return command_result{permission::i_max_playlist_size};
}
properties.emplace_back(&property, key);
}
for(const auto& property : properties) {
if(*property.first == property::PLAYLIST_CURRENT_SONG_ID) {
playlist->set_current_song(cmd[property.second]);
continue;
}
playlist->properties()[property.first] = cmd[property.second].string();
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistPermList(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
{
auto value = playlist->calculate_client_specific_permissions(permission::b_virtualserver_playlist_permission_list, this->ref());
if(!permission::v2::permission_granted(1, value))
return command_result{permission::b_virtualserver_playlist_permission_list};
}
auto permissions = playlist->permission_manager()->permissions();
if(permissions.empty())
return command_result{error::database_empty_result};
Command result(this->notify_response_command("notifyplaylistpermlist"));
auto permission_mapper = serverInstance->getPermissionMapper();
auto perm_sid = cmd.hasParm("permsid") || cmd.hasParm("names");
int index = 0;
result["playlist_id"] = playlist->playlist_id();
for (const auto &[perm, value] : permissions) {
if(value.flags.value_set) {
if (perm_sid)
result[index]["permsid"] = permission_mapper->permission_name(this->getType(), perm);
else
result[index]["permid"] = perm;
result[index]["permvalue"] = value.values.value;
result[index]["permnegated"] = value.flags.negate;
result[index]["permskip"] = value.flags.skip;
index++;
}
if(value.flags.grant_set) {
if (perm_sid)
result[index]["permsid"] = permission_mapper->permission_name_grant(this->getType(), perm);
else
result[index]["permid"] = perm | PERM_ID_GRANT;
result[index]["permvalue"] = value.values.grant;
result[index]["permnegated"] = 0;
result[index]["permskip"] = 0;
index++;
}
}
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
return command_result{perr};
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::PLAYLIST,
permission::v2::PermissionUpdateType::set_value,
playlist->playlist_id(), "",
0, ""
);
}
return pparser.build_command_result();
}
command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
return command_result{perr};
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::PLAYLIST,
permission::v2::PermissionUpdateType::delete_value,
playlist->playlist_id(), "",
0, ""
);
}
return pparser.build_command_result();
}
command_result ConnectedClient::handleCommandPlaylistClientList(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
{
auto value = playlist->calculate_client_specific_permissions(permission::b_virtualserver_playlist_permission_list, this->ref());
if(!permission::v2::permission_granted(1, value))
return command_result{permission::b_virtualserver_playlist_permission_list};
}
auto permissions = playlist->permission_manager()->channel_permissions();
if(permissions.empty())
return command_result{error::database_empty_result};
Command result(this->notify_response_command("notifyplaylistclientlist"));
auto permission_mapper = serverInstance->getPermissionMapper();
int index = 0;
ClientDbId last_client_id{0};
result["playlist_id"] = playlist->playlist_id();
for (const auto &[perm, client, value] : permissions) {
if(last_client_id != client)
result[index++]["cldbid"] = client;
}
if(index == 0)
return command_result{error::database_empty_result};
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistClientPermList(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
{
auto value = playlist->calculate_client_specific_permissions(permission::b_virtualserver_playlist_permission_list, this->ref());
if(!permission::v2::permission_granted(1, value))
return command_result{permission::b_virtualserver_playlist_permission_list};
}
auto permissions = playlist->permission_manager()->channel_permissions();
if(permissions.empty())
return command_result{error::database_empty_result};
Command result(this->notify_response_command("notifyplaylistclientpermlist"));
auto client_id = cmd.hasParm("cldbid") ? cmd[0]["cldbid"].as<ClientDbId>() : 0;
auto permission_mapper = serverInstance->getPermissionMapper();
auto perm_sid = cmd.hasParm("permsid") || cmd.hasParm("names");
int index = 0;
ClientDbId last_client_id{0};
result["playlist_id"] = playlist->playlist_id();
result["cldbid"] = client_id;
for (const auto &[perm, client, value] : permissions) {
if(client_id > 0 && client != client_id) continue;
if(last_client_id != client)
result[index]["cldbid"] = client;
if(value.flags.value_set) {
if (perm_sid)
result[index]["permsid"] = permission_mapper->permission_name(this->getType(), perm);
else
result[index]["permid"] = perm;
result[index]["permvalue"] = value.values.value;
result[index]["permnegated"] = value.flags.negate;
result[index]["permskip"] = value.flags.skip;
index++;
}
if(value.flags.grant_set) {
if (perm_sid)
result[index]["permsid"] = permission_mapper->permission_name_grant(this->getType(), perm);
else
result[index]["permid"] = perm | PERM_ID_GRANT;
result[index]["permvalue"] = value.values.grant;
result[index]["permnegated"] = 0;
result[index]["permskip"] = 0;
index++;
}
}
if(index == 0)
return command_result{error::database_empty_result};
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistClientAddPerm(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
auto client_id = cmd[0]["cldbid"].as<ClientDbId>();
if(client_id == 0) return command_result{error::client_invalid_id};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
return command_result{perr};
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
return pparser.build_command_result();
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value, client_id);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::PLAYLIST_CLIENT,
permission::v2::PermissionUpdateType::set_value,
playlist->playlist_id(), "",
client_id, ""
);
}
return pparser.build_command_result();
}
command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
auto client_id = cmd[0]["cldbid"].as<ClientDbId>();
if(client_id == 0) return command_result{error::client_invalid_id};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
return command_result{perr};
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
return pparser.build_command_result();
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value, client_id);
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::PLAYLIST_CLIENT,
permission::v2::PermissionUpdateType::delete_value,
playlist->playlist_id(), "",
client_id, ""
);
}
return pparser.build_command_result();
}
constexpr auto max_song_meta_info = 1024 * 512;
inline size_t estimated_song_info_size(const std::shared_ptr<ts::music::PlaylistEntryInfo>& song, bool extract_metadata) {
return 128 + std::min(song->metadata.json_string.length(), (size_t) max_song_meta_info) + extract_metadata * 256;
}
inline void fill_song_info(ts::command_builder_bulk bulk, const std::shared_ptr<ts::music::PlaylistEntryInfo>& song, bool extract_metadata) {
bulk.reserve(estimated_song_info_size(song, extract_metadata));
bulk.put("song_id", song->song_id);
bulk.put("song_invoker", song->invoker);
bulk.put("song_previous_song_id", song->previous_song_id);
bulk.put("song_url", song->original_url);
bulk.put("song_url_loader", song->url_loader);
bulk.put("song_loaded", song->metadata.is_loaded());
if(song->metadata.json_string.length() > 1024 * 1024 * 512) {
logWarning(LOG_GENERAL, "Dropping song metadata because its way to big. ({}bytes)", song->metadata.json_string.size());
} else {
bulk.put("song_metadata", song->metadata.json_string);
}
if(extract_metadata) {
bulk.reserve(256, true);
auto metadata = song->metadata.loaded_data;
if(extract_metadata && song->metadata.is_loaded() && metadata) {
bulk.put("song_metadata_title", metadata->title);
bulk.put("song_metadata_description", metadata->description);
bulk.put("song_metadata_url", metadata->url);
bulk.put("song_metadata_length", metadata->length.count());
if(auto thumbnail = static_pointer_cast<::music::ThumbnailUrl>(metadata->thumbnail); thumbnail && thumbnail->type() == ::music::THUMBNAIL_URL) {
bulk.put("song_metadata_thumbnail_url", thumbnail->url());
} else {
bulk.put("song_metadata_thumbnail_url", "none");
}
}
}
}
command_result ConnectedClient::handleCommandPlaylistSongList(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_view_power, permission::i_playlist_view_power); perr)
return command_result{perr};
auto songs = playlist->list_songs();
if(songs.empty())
return command_result{error::database_empty_result};
ts::command_builder result{this->notify_response_command("notifyplaylistsonglist")};
result.put(0, "version", 2); /* to signalize that we're sending the response bulked */
auto extract_metadata = cmd.hasParm("extract-metadata");
size_t index{0};
for(const auto& song : songs) {
if(index == 0) {
result.put(0, "playlist_id", playlist->playlist_id());
}
fill_song_info(result.bulk(index), song, extract_metadata);
if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK && result.current_size() + estimated_song_info_size(song, extract_metadata) > 128 * 1024) {
this->sendCommand(result);
result.reset();
index = 0;
} else {
index++;
}
}
if(index > 0)
this->sendCommand(result);
if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK) {
ts::command_builder finish{"notifyplaylistsonglistfinished"};
finish.put(0, "playlist_id", playlist->playlist_id());
this->sendCommand(finish);
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistSongSetCurrent(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_song_needed_move_power, permission::i_playlist_song_move_power); perr)
return command_result{perr};
if(!playlist->set_current_song(cmd["song_id"]))
return command_result{error::playlist_invalid_song_id};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistSongAdd(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_song_needed_add_power, permission::i_playlist_song_add_power); perr)
return command_result{perr};
if(cmd[0].has("invoker"))
cmd["type"] = "";
else if(!cmd[0].has("type"))
cmd["type"] = "";
if(!cmd[0].has("previous")) {
auto songs = playlist->list_songs();
if(songs.empty())
cmd["previous"] = "0";
else
cmd["previous"] = songs.back()->song_id;
}
auto& type = cmd[0]["type"];
std::string loader_string{""};
if((type.castable<int>() && type.as<int>() == 0) || type.as<string>() == "yt") {
loader_string = "YouTube";
} else if((type.castable<int>() && type.as<int>() == 1) || type.as<string>() == "ffmpeg") {
loader_string = "FFMpeg";
} else if((type.castable<int>() && type.as<int>() == 2) || type.as<string>() == "channel") {
loader_string = "ChannelProvider";
}
auto song = playlist->add_song(_this.lock(), cmd["url"], loader_string, cmd["previous"]);
if(!song) return command_result{error::vs_critical};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistSongReorder(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_song_needed_move_power, permission::i_playlist_song_move_power); perr)
return command_result{perr};
SongId song_id = cmd["song_id"];
SongId previous_id = cmd["song_previous_song_id"];
auto song = playlist->find_song(song_id);
if(!song) return command_result{error::playlist_invalid_song_id};
if(!playlist->reorder_song(song_id, previous_id))
return command_result{error::vs_critical};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistSongRemove(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_song_needed_remove_power, permission::i_playlist_song_remove_power); perr)
return command_result{perr};
SongId song_id = cmd["song_id"];
auto song = playlist->find_song(song_id);
if(!song) return command_result{error::playlist_invalid_song_id};
if(!playlist->delete_song(song_id))
return command_result{error::vs_critical};
return command_result{error::ok};
}
/****** legacy ******/
command_result ConnectedClient::handleCommandMusicBotQueueList(Command& cmd) {
#if false
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = this->server->musicManager->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
auto playlist = dynamic_pointer_cast<music::PlayablePlaylist>(bot->playlist());
if(!playlist) return command_result{error::vs_critical};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_view_power, permission::i_playlist_view_power); perr)
return command_result{perr};
bool bulked = cmd.hasParm("bulk") || cmd.hasParm("balk") || cmd.hasParm("pipe") || cmd.hasParm("bar") || cmd.hasParm("paypal");
int command_index = 0;
Command notify(this->notify_response_command("notifymusicqueueentry"));
auto songs = playlist->list_songs();
int begin_index{0};
for(auto it = songs.begin(); it != songs.end(); it++)
if((*it)->song_id == playlist->currently_playing())
break;
else
begin_index--;
for(auto it = songs.begin(); it != songs.end(); it++) {
if(!bulked)
notify = Command(this->notify_response_command("notifymusicqueueentry"));
auto song = *it;
notify[command_index]["song_id"] = song->song_id;
notify[command_index]["song_url"] = song->original_url;
notify[command_index]["song_invoker"] = song->invoker;
notify[command_index]["song_loaded"] = song->metadata.is_loaded();
auto entry = song->load_future && song->load_future->succeeded() ? song->load_future->getValue({}) : nullptr;
if(entry) {
notify[command_index]["song_loaded"] = true;
if(entry->type != ::music::TYPE_STREAM && entry->type != ::music::TYPE_VIDEO)
continue;
auto info = reinterpret_pointer_cast<::music::UrlSongInfo>(entry);
if(info) {
notify[command_index]["song_title"] = info->title;
notify[command_index]["song_description"] = info->description;
if(auto thumbnail = reinterpret_pointer_cast<::music::ThumbnailUrl>(info->thumbnail); thumbnail && thumbnail->type() == ::music::THUMBNAIL_URL)
notify[command_index]["song_thumbnail"] = thumbnail->url();
else
notify[command_index]["song_thumbnail"] = "";
notify[command_index]["song_length"] = info->length.count();
}
}
notify[command_index]["queue_index"] = begin_index++;
if(!bulked)
this->sendCommand(notify);
else
command_index++;
}
if(bulked) {
if(command_index > 0) {
this->sendCommand(notify);
} else
return command_result{error::database_empty_result};
}
if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK) {
Command notify("notifymusicqueuefinish");
notify["bot_id"] = bot->getClientDatabaseId();
this->sendCommand(notify);
}
return command_result{error::ok};
#endif
return command_result{error::not_implemented}; //FIXME
}
command_result ConnectedClient::handleCommandMusicBotQueueAdd(Command& cmd) {
return command_result{error::not_implemented}; //FIXME
/*
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = this->server->musicManager->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true);
MusicClient::loader_t loader;
auto& type = cmd[0]["type"];
if((type.castable<int>() && type.as<int>() == 0) || type.as<string>() == "yt") {
loader = bot->ytLoader(this->getServer());
} else if((type.castable<int>() && type.as<int>() == 1) || type.as<string>() == "ffmpeg") {
loader = bot->ffmpegLoader(this->getServer());
} else if((type.castable<int>() && type.as<int>() == 2) || type.as<string>() == "channel") {
loader = bot->channelLoader(this->getServer());
} else if((type.castable<int>() && type.as<int>() == -1) || type.as<string>() == "any") {
loader = bot->providerLoader(this->getServer(), "");
}
if(!loader) return command_result{error::music_invalid_action};
auto entry = bot->queue()->insertEntry(cmd["url"], _this.lock(), loader);
if(!entry) return command_result{error::vs_critical};
this->server->forEachClient([&](shared_ptr<ConnectedClient> client) {
client->notifyMusicQueueAdd(bot, entry, bot->queue()->queueEntries().size() - 1, _this.lock());
});
return command_result{error::ok};
*/
}
command_result ConnectedClient::handleCommandMusicBotQueueRemove(Command& cmd) {
return command_result{error::not_implemented}; //FIXME
/*
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = this->server->musicManager->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true);
std::deque<std::shared_ptr<music::SongInfo>> songs;
for(int index = 0; index < cmd.bulkCount(); index++) {
auto entry = bot->queue()->find_queue(cmd["song_id"]);
if(!entry) {
if(cmd.hasParm("skip_error")) continue;
return command_result{error::database_empty_result};
}
songs.push_back(move(entry));
}
for(const auto& entry : songs)
bot->queue()->deleteEntry(dynamic_pointer_cast<music::PlayableSong>(entry));
this->server->forEachClient([&](shared_ptr<ConnectedClient> client) {
client->notifyMusicQueueRemove(bot, songs, _this.lock());
});
return command_result{error::ok};
*/
}
command_result ConnectedClient::handleCommandMusicBotQueueReorder(Command& cmd) {
return command_result{error::not_implemented}; //FIXME
/*
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = this->server->musicManager->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true);
auto entry = bot->queue()->find_queue(cmd["song_id"]);
if(!entry) return command_result{error::database_empty_result};
auto order = bot->queue()->changeOrder(entry, cmd["index"]);
if(order < 0) return command_result{error::vs_critical};
this->server->forEachClient([&](shared_ptr<ConnectedClient> client) {
client->notifyMusicQueueOrderChange(bot, entry, order, _this.lock());
});
return command_result{error::ok};
*/
}
command_result ConnectedClient::handleCommandMusicBotPlaylistAssign(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = ref_server->music_manager_->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
if(bot->getOwner() != this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_music_play_power, bot->calculate_permission(permission::i_client_music_needed_play_power, 0));
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist && cmd["playlist_id"] != 0) return command_result{error::playlist_invalid_id};
if(ref_server->music_manager_->find_bot_by_playlist(playlist))
return command_result{error::playlist_already_in_use};
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_view_power, permission::i_playlist_view_power); perr)
return command_result{perr};
if(!ref_server->music_manager_->assign_playlist(bot, playlist))
return command_result{error::vs_critical};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistSetSubscription(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
if(!config::music::enabled) return command_result{error::music_disabled};
auto playlist = ref_server->music_manager_->find_playlist(cmd["playlist_id"]);
if(!playlist && cmd["playlist_id"] != 0) return command_result{error::playlist_invalid_id};
{
auto old_playlist = this->_subscribed_playlist.lock();
if(old_playlist)
old_playlist->remove_subscriber(_this.lock());
}
if(playlist) {
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_view_power, permission::i_playlist_view_power); perr)
return command_result{perr};
playlist->add_subscriber(_this.lock());
this->_subscribed_playlist = playlist;
}
return command_result{error::ok};
}