#include #include #include #include #include #include "../InstanceHandler.h" #include "../manager/ConversationManager.h" #include "../music/MusicBotManager.h" #include "../client/music/MusicClient.h" #include "../client/voice/VoiceClient.h" #include "./ConnectedClient.h" using namespace ts; using namespace ts::server; using namespace std; using namespace std::chrono; extern InstanceHandler *serverInstance; std::deque split(std::string str, std::string sep) { char *cstr = const_cast(str.c_str()); char *current; std::deque arr; current = strtok(cstr, sep.c_str()); while (current != NULL) { arr.push_back(current); current = strtok(NULL, sep.c_str()); } return arr; } #define ERR(sender, msg) \ do { \ send_message(sender, msg); \ return true; \ } while(false) #define TLEN(index) if(arguments.size() < index) ERR(serverInstance->musicRoot(), "Invalid argument count"); #define TARG(index, type) (arguments.size() > index && arguments[index] == type) #define TMUSIC(bot) if(!bot->current_player()) ERR(serverInstance->musicRoot(), "Im not playing a song!"); #define GBOT(var, ignore_disabled) \ auto var = this->selectedBot.lock(); \ if(!var) var = dynamic_pointer_cast(target); \ if(!var) { \ send_message(serverInstance->musicRoot(), "Please select a music bot! (" + ts::config::music::command_prefix + "mbot select )"); \ return true; \ } \ if(!ignore_disabled && var->properties()[property::CLIENT_DISABLED].as()) { \ send_message(serverInstance->musicRoot(), strobf("This bot has been disabled. Upgrade your TeaSpeak license to use more bots.").string()); \ return true; \ } inline string filterUrl(string in){ size_t index = 0; while ((index = in.find("[URL")) != std::string::npos && index < in.length()) { auto end = in.find(']', index); in.replace(index, end - index + 1, "", 0); } while ((index = in.find("[/URL")) != std::string::npos && index < in.length()) { auto end = in.find(']', index); in.replace(index, end - index + 1, "", 0); } return in; } inline void permissionableCommand(ConnectedClient* client, stringstream& ss, const std::string& cmd, permission::PermissionType perm, permission::PermissionType sec = permission::unknown) { auto permA = perm == permission::unknown || permission::v2::permission_granted(1, client->calculate_permission(perm, client->getChannelId())); auto permB = permA || (sec != permission::unknown && permission::v2::permission_granted(1, client->calculate_permission(sec, client->getChannelId()))); if(!(permA || permB)) { ss << "[color=red]" << cmd << "[/color]" << endl; } else { ss << "[color=green]" << cmd << "[/color]" << endl; } } inline std::string bot_volume(float vol) { auto volume = to_string(vol * 100); auto idx = volume.find('.'); return volume.substr(0, idx + 2); } //FIXME add chat command for channel commander (within bot settings) //Return true if blocked bool ConnectedClient::handleTextMessage(ChatMessageMode mode, std::string text, const std::shared_ptr& target) { if (text.length() < ts::config::music::command_prefix.length()) return false; if (text.find(ts::config::music::command_prefix) != 0) return false; if(!this->currentChannel) return false; std::string command = text.substr(ts::config::music::command_prefix.length()); auto arguments = command.find(' ') != -1 ? split(command.substr(command.find(' ') + 1), " ") : deque{}; command = command.substr(0, command.find(' ')); debugMessage(this->getServerId(), "Having command \"" + command + "\"."); for (const auto &arg : arguments) debugMessage(this->getServerId(), " Argument: '" + arg + "'"); #define PERM_CHECK_BOT(perm, reqperm, err) \ if(bot->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId() && \ !permission::v2::permission_granted(bot->calculate_permission(permission::reqperm, bot->getChannelId()), this->calculate_permission(permission::perm, bot->getChannelId()))) { \ send_message(serverInstance->musicRoot(), err); \ return true; \ } //TODO: Correct error message print! #define HANDLE_CMD_ERROR(_message) \ send_message(serverInstance->musicRoot(), string(_message) + ": action failed"); /* if(result.extraProperties.count("extra_msg") > 0) \ send_message(serverInstance->musicRoot(), string(_message) + ": " + result.extraProperties["extra_msg"]); \ else if(result.extraProperties.count("failed_permid") > 0) \ send_message(serverInstance->musicRoot(), string(_message) + ". (Missing permission " + permission::resolvePermissionData((permission::PermissionType) stoull(result.extraProperties["failed_permid"]))->name + ")"); \ else \ send_message(serverInstance->musicRoot(), string(_message) + ": " + result.error.message); */ #define JOIN_ARGS(variable, index) \ string variable; \ { \ stringstream ss; \ for (auto it = arguments.begin() + index; it != arguments.end(); it++) \ ss << *it << (it + 1 == arguments.end() ? "" : " "); \ variable = ss.str(); \ } handle_text_command_fn_t function; if(mode == ChatMessageMode::TEXTMODE_SERVER) { function = [&](const shared_ptr& sender, const string& message) { this->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, sender, 0, 0, system_clock::now(), message); }; } else if(mode == ChatMessageMode::TEXTMODE_CHANNEL) { function = [&](const shared_ptr& sender, const string& message) { this->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, sender, 0, 0, system_clock::now(), message); }; } else if(mode == ChatMessageMode::TEXTMODE_PRIVATE) { function = [&, target](const shared_ptr& sender, const string& message) { this->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, target, this->getClientId(), 0, system_clock::now(), message); }; } return handle_text_command(mode, command, arguments, function, target); } bool ConnectedClient::handle_text_command( ChatMessageMode mode, const string &command, const deque &arguments, const function &, const string &)> &send_message, const shared_ptr& target) { if (command == "mbot") { if(!config::music::enabled) { send_message(serverInstance->musicRoot(), "Music bots are not enabled! Enable them via the server config!"); return true; } if (TARG(0, "create")) { Command cmd(""); auto result = this->handleCommandMusicBotCreate(cmd); if(result.error_code()) { result.release_details(); HANDLE_CMD_ERROR("Failed to create music bot"); return true; } send_message(serverInstance->musicRoot(), "Bot created"); return true; } else if (TARG(0, "list")) { bool server = false; if(TARG(1, "server")) server = true; string locationStr = server ? "on this server" : "in this channel"; if(!permission::v2::permission_granted(1, this->calculate_permission(server ? permission::b_client_music_server_list : permission::b_client_music_channel_list, this->getChannelId()))) { send_message(serverInstance->musicRoot(), "You don't have the permission to list all music bots " + locationStr); return true; } auto mbots = this->server->getClientsByChannel(server ? nullptr : this->currentChannel); if (mbots.empty()) send_message(serverInstance->musicRoot(), "There are no music bots " + locationStr); else { send_message(serverInstance->musicRoot(), "There are " + to_string(mbots.size()) + " music bots " + locationStr + ":"); for (const auto &mbot : mbots) { if(mbot->properties()[property::CLIENT_DISABLED].as()) { send_message(serverInstance->musicRoot(), " - [color=red]" + to_string(mbot->getClientDatabaseId()) + " | " + mbot->getDisplayName() + " [DISABLED][/color]"); } else { send_message(serverInstance->musicRoot(), " - [color=green]" + to_string(mbot->getClientDatabaseId()) + " | " + mbot->getDisplayName() + "[/color]"); } } } return true; } else if (TARG(0, "select")) { TLEN(2); if(arguments[1].find_first_not_of("0123456789") != std::string::npos) { send_message(serverInstance->musicRoot(), "Invalid bot id"); return true; } auto botId = static_cast(stoll(arguments[1])); auto bot = this->server->musicManager->findBotById(botId); if (!bot) ERR(serverInstance->musicRoot(), "Could not find target bot"); if(bot->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId() && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_music_channel_list, this->getChannelId())) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_music_server_list, this->getChannelId()))) { //No perms for listing send_message(serverInstance->musicRoot(), "You don't have the permission to select a music bot"); return true; } this->selectedBot = bot; send_message(serverInstance->musicRoot(), "You successfully select the bot " + to_string(botId) + " | " + bot->getDisplayName()); return true; } else if (TARG(0, "rename")) { TLEN(2); GBOT(bot, true); stringstream ss; for (auto it = arguments.begin() + 1; it != arguments.end(); it++) ss << *it << (it + 1 == arguments.end() ? "" : " "); string name = ss.str(); Command cmd(""); cmd["client_nickname"] = ss.str(); auto result = this->handleCommandClientEdit(cmd, bot); if(result.error_code()) { HANDLE_CMD_ERROR("Failed to rename bot"); result.release_details(); return true; } send_message(serverInstance->musicRoot(), "Name successfully changed!"); return true; } else if (TARG(0, "delete")) { GBOT(bot, true); PERM_CHECK_BOT(i_client_music_delete_power, i_client_music_needed_delete_power, "You don't have the permission to rename this music bot"); this->server->musicManager->deleteBot(bot); send_message(bot, "You successfully deleted this music bot!"); return true; } else if(TARG(0, "yt") || TARG(0, "soundcloud") || TARG(0, "sc")){ TLEN(2); GBOT(bot, false); PERM_CHECK_BOT(i_client_music_play_power, i_client_music_needed_play_power, "You don't have the permission to play something this music bot"); auto playlist = bot->playlist(); if(!playlist) { send_message(bot, "bot hasnt a playlist!"); return true; } JOIN_ARGS(url, 1); send_message(bot, "Queueing video " + url); playlist->add_song(this->ref(), filterUrl(url), "YouTube"); return true; } else if(TARG(0, "stream")){ TLEN(2); GBOT(bot, false); PERM_CHECK_BOT(i_client_music_play_power, i_client_music_needed_play_power, "You don't have the permission to play something this music bot"); auto playlist = bot->playlist(); if(!playlist) { send_message(bot, "bot hasnt a playlist!"); return true; } JOIN_ARGS(url, 1); send_message(bot, "Queueing video " + url); playlist->add_song(this->ref(), filterUrl(url), "FFMpeg"); return true; } else if(TARG(0, "player")) { TLEN(2); GBOT(bot, false); PERM_CHECK_BOT(i_client_music_play_power, i_client_music_needed_play_power, "You don't have the permission to play something this music bot"); auto playlist = bot->playlist(); if(!playlist) { send_message(bot, "bot hasnt a playlist!"); return true; } JOIN_ARGS(url, 2); send_message(bot, "Queueing video " + url); playlist->add_song(this->ref(), filterUrl(url), arguments[1]); return true; } else if(TARG(0, "forward")){ TLEN(2); GBOT(bot, false); PERM_CHECK_BOT(i_client_music_play_power, i_client_music_needed_play_power, "You don't have the permission to play something this music bot"); TMUSIC(bot); if(arguments[1].find_first_not_of("0123456789") != std::string::npos) { send_message(bot, "Invalid number of seconds!"); return true; } threads::Thread([bot, arguments]() { bot->current_player()->forward(seconds(stoll(arguments[1]))); }).detach(); send_message(bot, "Skipped " + to_string(stoll(arguments[1])) + " seconds!"); return true; } else if(TARG(0, "rewind")){ TLEN(2); GBOT(bot, false); PERM_CHECK_BOT(i_client_music_play_power, i_client_music_needed_play_power, "You don't have the permission to play something this music bot"); TMUSIC(bot); if(arguments[1].find_first_not_of("0123456789") != std::string::npos) { send_message(bot, "Invalid number of seconds!"); return true; } threads::Thread([bot, arguments]() { bot->current_player()->rewind(seconds(stoll(arguments[1]))); }).detach(); send_message(bot, "Rewind " + to_string(stoll(arguments[1])) + " seconds!"); return true; } else if(TARG(0, "stop")){ GBOT(bot, true); PERM_CHECK_BOT(i_client_music_play_power, i_client_music_needed_play_power, "You don't have the permission to play something this music bot"); TMUSIC(bot); bot->current_player()->stop(); send_message(bot, "Music stopped!"); return true; } else if(TARG(0, "pause")){ GBOT(bot, false); PERM_CHECK_BOT(i_client_music_play_power, i_client_music_needed_play_power, "You don't have the permission to play something this music bot"); TMUSIC(bot); bot->current_player()->pause(); send_message(bot, "Music paused!"); return true; } else if(TARG(0, "play")) { if(arguments.size() >= 2) { GBOT(bot, false); PERM_CHECK_BOT(i_client_music_play_power, i_client_music_needed_play_power, "You don't have the permission to play something this music bot"); auto playlist = bot->playlist(); if(!playlist) { send_message(bot, "bot hasnt a playlist!"); return true; } send_message(bot, "Queueing video " + arguments[1]); playlist->add_song(this->ref(), filterUrl(arguments[1]), ""); return true; } else { GBOT(bot, false); PERM_CHECK_BOT(i_client_music_play_power, i_client_music_needed_play_power, "You don't have the permission to play something this music bot"); TMUSIC(bot); bot->current_player()->play(); send_message(bot, "Music started!"); return true; } } else if(TARG(0, "info")){ GBOT(bot, true); PERM_CHECK_BOT(i_client_music_info, i_client_music_needed_info, "You don't have the permission to display the music bot information"); send_message(bot, "Music bot info:"); send_message(bot, " Bot id : " + to_string(bot->getClientDatabaseId())); send_message(bot, " Bot name: " + bot->getDisplayName()); send_message(bot, " Bot volume: " + bot_volume(bot->volumeModifier())); send_message(bot, " State: " + to_string(bot->player_state())); if(bot->current_player()){ auto player = bot->current_player(); send_message(bot, " Play state: player open"); send_message(bot, " State : " + string(::music::stateNames[player->state()])); send_message(bot, " Title : " + player->songTitle()); send_message(bot, " Description : " + player->songDescription()); send_message(bot, " Timeline : " + to_string(duration_cast(player->currentIndex()).count()) + "/" + to_string(duration_cast(player->length()).count())); send_message(bot, " Buffered : " + to_string(duration_cast(player->bufferedUntil() - player->currentIndex()).count()) + " seconds"); } else { send_message(bot, " Play state: not playing"); } auto bot_playlist = bot->playlist(); if(bot_playlist) { send_message(bot, " Playlist ID : " + to_string(bot_playlist->playlist_id())); send_message(bot, " Playlist size : " + to_string(bot_playlist->list_songs().size())); } else { send_message(bot, " Playlist ID : No playlist assigned"); } return true; } else if(TARG(0, "queue") || TARG(0, "playlist") || TARG(0, "pl")) { GBOT(bot, false); PERM_CHECK_BOT(i_client_music_info, i_client_music_needed_info, "You don't have the permission to display the music bot information"); auto bot_playlist = bot->playlist(); if(bot_playlist) { send_message(bot, "Playlist ID : " + to_string(bot_playlist->playlist_id())); send_message(bot, "Playlist entries (" + to_string(bot_playlist->list_songs().size()) + "): [color=orange]orange[/color] = currently index"); set dbids; for(const auto& song : bot_playlist->list_songs()) dbids.insert(song->invoker); auto dbinfo = serverInstance->databaseHelper()->queryDatabaseInfo(this->getServer(), deque(dbids.begin(), dbids.end())); auto current_song = bot_playlist->currently_playing(); for(const auto& song : bot_playlist->list_songs()) { string invoker = "unknown"; for(const auto& e : dbinfo) { if(e->cldbid == song->invoker) { invoker = "[URL=client://0/" + e->uniqueId + "~" + e->lastName + "]" + e->lastName + "[/URL]"; break; } } string data = "\"" + song->url + "\" added by " + invoker; if(song->id == current_song) data = "[color=orange]" + data + "[/color]"; send_message(bot, " - " + data); } } else { send_message(bot, "The bot hasn't a playlist"); } return true; } else if(TARG(0, "next")) { GBOT(bot, false); PERM_CHECK_BOT(i_client_music_play_power, i_client_music_needed_play_power, "You don't have the permission to play something this music bot"); bot->forwardSong(); auto song = bot->current_song(); if(song) send_message(bot, "Replaying next song (" + song->getUrl() + ")"); else send_message(bot, "Queue is empty! Could not forward!"); return true; } else if(TARG(0, "volume")) { GBOT(bot, false); PERM_CHECK_BOT(i_client_music_play_power, i_client_music_needed_play_power, "You don't have the permission to play something this music bot"); if(arguments.size() < 2) { send_message(bot, "Current volume: " + bot_volume(bot->volumeModifier())); return true; } if(arguments[1].find_first_not_of(".-0123456789") != std::string::npos) { send_message(bot, "Invalid volume!"); return true; } auto volume = stof(arguments[1]); if(volume < 0 || volume > 100) { send_message(bot, "Invalid volume! Volume must be greater or equal to zero and less or equal then one!"); return true; } auto max_volume = this->calculate_permission(permission::i_client_music_create_modify_max_volume, 0); if(max_volume.has_value && !permission::v2::permission_granted(volume, max_volume)) { send_message(bot, "You don't have the permission to use higher volumes that " + to_string(max_volume.value) + "%"); return true; } bot->volume_modifier(volume / 100); send_message(bot, "Volume successfully changed to " + bot_volume(bot->volumeModifier())); return true; } else if(TARG(0, "formats")) { auto providers = ::music::manager::registeredTypes(); stringstream ss; ss << "Available providers:" << endl; for(const auto& prov : providers) { ss << " " << prov->providerName << ":" << endl; ss << " Description: " << prov->providerDescription << endl; auto fmts = prov->availableFormats(); ss << " Supported formats: (" << fmts.size() << ")" << endl; for(const auto& fmt : fmts) ss << " - " << fmt << endl; auto prots = prov->availableProtocols(); ss << " Supported protocols: (" << prots.size() << ")" << endl; for(const auto& fmt : prots) ss << " - " << fmt << endl; } send_message(serverInstance->musicRoot(), ss.str()); return true; } else if(TARG(0, "settings")) { GBOT(bot, false); PERM_CHECK_BOT(i_client_music_play_power, i_client_music_needed_play_power, "You don't have the permission to change anything on the bot"); //TODO FIXME! if(TARG(1, "bot")) { const static vector editable_properties = { "client_nickname", "client_player_volume", "client_is_channel_commander", "client_version", "client_country", "client_platform", "client_bot_type", "client_uptime_mode", "client_is_priority_speaker", "client_flag_notify_song_change" }; if(arguments.size() < 3) { send_message(bot, "Bot properties:"); for(const auto& property : bot->properties()->list_properties(~0)) { if(find(editable_properties.begin(), editable_properties.end(), property.type().name) == editable_properties.end()) continue; send_message(bot, " - " + property.type().name + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : "")); } } else if(arguments.size() < 4) { if(find(editable_properties.begin(), editable_properties.end(), arguments[2]) == editable_properties.end()) { send_message(bot, "Unknown property or property is not editable."); return true; } const std::shared_ptr &property_info = property::info(arguments[2]); if(!property_info || property_info->type_property == property::PROP_TYPE_UNKNOWN || property_info->property_index == property::CLIENT_UNDEFINED) { send_message(bot, "Unknown property " + arguments[2] + "."); return true; } auto prop = bot->properties()[(property::ClientProperties) property_info->property_index]; send_message(bot, "Bot property " + property_info->name + " = " + prop.value() + " " + (property_info->default_value == prop.value() ? "(default)" : "")); return true; } else { Command cmd(""); JOIN_ARGS(value, 3); cmd[arguments[2]] = value; auto result = this->handleCommandClientEdit(cmd, bot); if(result.error_code()) { HANDLE_CMD_ERROR("Failed to change bot property"); result.release_details(); return true; } send_message(serverInstance->musicRoot(), "Property successfully changed!"); return true; } return true; } else if(TARG(1, "playlist")) { auto playlist = bot->playlist(); if(!playlist) { send_message(bot, "Bot hasn't a playlist!"); return true; } if(arguments.size() < 3) { send_message(bot, "Playlist properties:"); for(const auto& property : playlist->properties().list_properties(property::FLAG_PLAYLIST_VARIABLE)) { send_message(bot, " - " + property.type().name + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : "")); } } else if(arguments.size() < 4) { const std::shared_ptr &property_info = property::info(arguments[2]); if(!property_info || property_info->type_property == property::PROP_TYPE_UNKNOWN || property_info->property_index == property::PLAYLIST_UNDEFINED) { send_message(bot, "Unknown property " + arguments[2] + "."); return true; } auto prop = playlist->properties()[(property::PlaylistProperties) property_info->property_index]; send_message(bot, "Bot property " + property_info->name + " = " + prop.value() + " " + (property_info->default_value == prop.value() ? "(default)" : "")); } else { const std::shared_ptr &property_info = property::info(arguments[2]); if(!property_info || property_info->type_property == property::PROP_TYPE_UNKNOWN || property_info->property_index == property::PLAYLIST_UNDEFINED) { send_message(bot, "Unknown property " + arguments[2] + "."); return true; } JOIN_ARGS(value, 3); if(!property_info->validate_input(value)) { send_message(bot, "Please enter a valid value!"); return true; } if((property_info->flags & property::FLAG_USER_EDITABLE) == 0) { send_message(bot, "This property isnt changeable!"); return true; } playlist->properties()[(property::PlaylistProperties) property_info->property_index] = value; send_message(bot, "Property successfully changed"); return true; } } else { send_message(bot, "Please enter a valid setting mode"); } return true; } } else if (command == "help") { //send_message(serverInstance->musicRoot(), " ̶.̶̶m̶̶b̶̶o̶̶t̶̶ ̶̶f̶̶o̶̶r̶̶m̶̶a̶̶t̶̶s (Not supported yet)"); stringstream ss; ss << "Available music bot commands: ([color=green]green[/color] = permission granted | [color=red]red[/color] = insufficient permissions)" << endl; bool has_list_server = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_music_server_list, this->getChannelId())); bool has_list_channel = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_music_channel_list, this->getChannelId())); permissionableCommand(this, ss, string() + " .mbot list [<[color=" + (has_list_server ? "green" : "red") + "]server[/color]|[color=" + (has_list_channel ? "green" : "red") + "]channel[/color]>]", permission::b_client_music_channel_list, permission::b_client_music_server_list); permissionableCommand(this, ss, " " + ts::config::music::command_prefix + "mbot select ", permission::b_client_music_channel_list, permission::b_client_music_server_list); permissionableCommand(this, ss, " " + ts::config::music::command_prefix + "mbot formats", permission::unknown); permissionableCommand(this, ss, " " + ts::config::music::command_prefix + "mbot create", permission::b_client_music_create_temporary); // [] permissionableCommand(this, ss, " " + ts::config::music::command_prefix + "mbot info", permission::i_client_music_info); permissionableCommand(this, ss, " " + ts::config::music::command_prefix + "mbot rename", permission::i_client_music_rename_power); permissionableCommand(this, ss, " " + ts::config::music::command_prefix + "mbot delete", permission::i_client_music_delete_power); permissionableCommand(this, ss, " " + ts::config::music::command_prefix + "mbot yt