// // Created by wolverindev on 26.01.20. // #include #include #include #include #include #include #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 #include #include #include "helpers.h" #include "./bulk_parsers.h" #include #include #include #include #include #include #include #include #include #include #include #include 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->musicManager->max_bots() != -1 && this->server->musicManager->max_bots() <= this->server->musicManager->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 = map(permissions_list.begin(), permissions_list.end()); auto max_bots = permissions[permission::i_client_music_limit]; if(max_bots.has_value) { auto ownBots = this->server->musicManager->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(); 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->musicManager->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->musicManager->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->musicManager->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->musicManager->findBotById(cmd["bot_id"]); if(!bot && cmd["bot_id"].as() != 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& 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(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->musicManager->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->musicManager->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())); } else if(cmd["action"] == 6) { if(!player) return command_result{error::music_no_player}; player->rewind(::music::PlayerUnits(cmd["units"].as())); } 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->musicManager->playlists(); playlists.erase(find_if(playlists.begin(), playlists.end(), [&](const shared_ptr& 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->musicManager->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->musicManager->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->musicManager->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->musicManager->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->musicManager->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->musicManager->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> properties; for(const auto& key : cmd[0].keys()) { if(key == "playlist_id") continue; if(key == "return_code") continue; const auto& property = property::find(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())) { 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(); 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(); 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->musicManager->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->musicManager->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 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->musicManager->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 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->musicManager->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->musicManager->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() : 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->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return command_result{error::playlist_invalid_id}; auto client_id = cmd[0]["cldbid"].as(); 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 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->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return command_result{error::playlist_invalid_id}; auto client_id = cmd[0]["cldbid"].as(); 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 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& 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& 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->musicManager->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->musicManager->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->musicManager->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() && type.as() == 0) || type.as() == "yt") { loader_string = "YouTube"; } else if((type.castable() && type.as() == 1) || type.as() == "ffmpeg") { loader_string = "FFMpeg"; } else if((type.castable() && type.as() == 2) || type.as() == "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->musicManager->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->musicManager->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(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() && type.as() == 0) || type.as() == "yt") { loader = bot->ytLoader(this->getServer()); } else if((type.castable() && type.as() == 1) || type.as() == "ffmpeg") { loader = bot->ffmpegLoader(this->getServer()); } else if((type.castable() && type.as() == 2) || type.as() == "channel") { loader = bot->channelLoader(this->getServer()); } else if((type.castable() && type.as() == -1) || type.as() == "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 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> 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(entry)); this->server->forEachClient([&](shared_ptr 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 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->musicManager->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->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist && cmd["playlist_id"] != 0) return command_result{error::playlist_invalid_id}; if(ref_server->musicManager->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->musicManager->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->musicManager->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}; }