2020-01-26 12:04:38 -05:00
# include <src/VirtualServer.h>
2019-07-17 13:37:18 -04:00
# include <misc/rnd.h>
# include <misc/digest.h>
# include <misc/base64.h>
# include <src/client/music/MusicClient.h>
# include <src/InstanceHandler.h>
# include <algorithm>
# include <log/LogUtils.h>
using namespace std ;
using namespace std : : chrono ;
using namespace ts ;
using namespace ts : : server ;
using namespace ts : : music ;
extern InstanceHandler * serverInstance ;
threads : : ThreadPool MusicBotManager : : tick_music { config : : threads : : music : : execute_per_bot , " music tick " } ;
threads : : ThreadPool MusicBotManager : : load_music { 4 , " music loader " } ;
void MusicBotManager : : adjustTickPool ( ) {
size_t bots = 0 ;
2020-11-07 07:17:51 -05:00
for ( const auto & server : serverInstance - > getVoiceServerManager ( ) - > serverInstances ( ) ) bots + = server - > music_manager_ - > current_bot_count ( ) ;
2019-07-17 13:37:18 -04:00
if ( bots = = 0 )
tick_music . setThreads ( 1 ) ;
else
tick_music . setThreads ( min ( config : : threads : : music : : execute_limit , bots * config : : threads : : music : : execute_per_bot ) ) ;
}
2019-11-22 14:51:00 -05:00
void MusicBotManager : : shutdown ( ) {
2020-01-23 20:57:58 -05:00
tick_music . shutdown ( ) ;
load_music . shutdown ( ) ;
2019-11-22 14:51:00 -05:00
}
2020-01-26 12:04:38 -05:00
MusicBotManager : : MusicBotManager ( const shared_ptr < server : : VirtualServer > & server ) : handle ( server ) { }
2019-07-17 13:37:18 -04:00
MusicBotManager : : ~ MusicBotManager ( ) { }
void MusicBotManager : : cleanup_semi_bots ( ) {
for ( const auto & bot : this - > available_bots ( ) )
if ( bot - > get_bot_type ( ) = = MusicClient : : Type : : SEMI_PERMANENT | | bot - > get_bot_type ( ) = = MusicClient : : Type : : TEMPORARY )
this - > deleteBot ( bot ) ;
}
void MusicBotManager : : cleanup_client_bots ( ts : : ClientDbId clientid ) {
for ( const auto & bot : this - > available_bots ( ) )
if ( bot - > get_bot_type ( ) = = MusicClient : : Type : : TEMPORARY & & bot - > getOwner ( ) = = clientid )
this - > deleteBot ( bot ) ;
}
2020-08-03 07:51:47 -04:00
std : : deque < std : : shared_ptr < ts : : server : : MusicClient > > MusicBotManager : : available_bots ( ) {
2019-07-17 13:37:18 -04:00
lock_guard lock ( music_bots_lock ) ;
return this - > music_bots ;
}
2020-08-03 07:51:47 -04:00
std : : shared_ptr < ts : : server : : MusicClient > MusicBotManager : : find_bot_by_playlist ( const std : : shared_ptr < ts : : music : : PlayablePlaylist > & playlist ) {
2019-07-17 13:37:18 -04:00
for ( const auto & bot : this - > available_bots ( ) )
if ( bot - > playlist ( ) = = playlist )
return bot ;
return nullptr ;
}
2020-08-03 07:51:47 -04:00
std : : deque < std : : shared_ptr < ts : : server : : MusicClient > > MusicBotManager : : listBots ( ClientDbId clid ) {
2019-07-17 13:37:18 -04:00
lock_guard lock ( music_bots_lock ) ;
std : : deque < std : : shared_ptr < server : : MusicClient > > res ;
for ( const auto & bot : this - > music_bots )
if ( bot - > properties ( ) [ property : : CLIENT_OWNER ] = = clid ) res . push_back ( bot ) ;
return res ;
}
std : : shared_ptr < server : : MusicClient > MusicBotManager : : createBot ( ClientDbId owner ) {
if ( ! config : : license - > isPremium ( ) ) {
if ( this - > current_bot_count ( ) > = this - > max_bots ( ) ) return nullptr ; //Test the license
}
auto handle = this - > handle . lock ( ) ;
assert ( handle ) ;
auto uid = base64 : : encode ( digest : : sha1 ( " music# " + rnd_string ( 15 ) ) ) ;
auto musicBot = make_shared < MusicClient > ( this - > handle . lock ( ) , uid ) ;
musicBot - > _this = musicBot ;
musicBot - > manager = this ;
musicBot - > server = handle ;
DatabaseHelper : : assignDatabaseId ( handle - > getSql ( ) , handle - > getServerId ( ) , musicBot ) ;
2020-03-19 20:05:39 -04:00
if ( config : : music : : enabled ) {
2019-07-17 13:37:18 -04:00
lock_guard lock ( this - > music_bots_lock ) ;
this - > music_bots . push_back ( musicBot ) ;
}
( LOG_SQL_CMD ) ( sql : : command ( handle - > getSql ( ) , " INSERT INTO `musicbots` (`serverId`, `botId`, `uniqueId`, `owner`) VALUES (:sid, :botId, :uid, :owner) " ,
variable { " :sid " , handle - > getServerId ( ) } , variable { " :botId " , musicBot - > getClientDatabaseId ( ) } , variable { " :uid " , musicBot - > getUid ( ) } , variable { " :owner " , owner } ) . execute ( ) ) ;
musicBot - > properties ( ) [ property : : CLIENT_OWNER ] = owner ;
musicBot - > setDisplayName ( " Im a music bot! " ) ;
musicBot - > properties ( ) [ property : : CLIENT_LASTCONNECTED ] = duration_cast < seconds > ( system_clock : : now ( ) . time_since_epoch ( ) ) . count ( ) ;
2020-01-23 20:57:58 -05:00
musicBot - > properties ( ) [ property : : CLIENT_CREATED ] = duration_cast < seconds > ( system_clock : : now ( ) . time_since_epoch ( ) ) . count ( ) ;
musicBot - > properties ( ) [ property : : CLIENT_VERSION ] = " TeaMusic " ;
musicBot - > properties ( ) [ property : : CLIENT_PLATFORM ] = " internal " ;
2020-03-19 20:05:39 -04:00
if ( ! config : : music : : enabled ) return nullptr ;
handle - > groups - > enableCache ( musicBot - > getClientDatabaseId ( ) ) ;
2019-07-17 13:37:18 -04:00
handle - > registerClient ( musicBot ) ;
{
auto playlist = this - > create_playlist ( owner , " owned via bot " ) ;
playlist - > properties ( ) [ property : : PLAYLIST_TYPE ] = Playlist : : Type : : BOT_BOUND ;
musicBot - > set_playlist ( playlist ) ;
}
MusicBotManager : : adjustTickPool ( ) ;
return musicBot ;
}
bool MusicBotManager : : assign_playlist ( const std : : shared_ptr < ts : : server : : MusicClient > & bot , const std : : shared_ptr < ts : : music : : PlayablePlaylist > & playlist ) {
lock_guard lock ( music_bots_lock ) ;
if ( playlist ) {
auto assigned_bot = this - > find_bot_by_playlist ( playlist ) ;
if ( assigned_bot )
return assigned_bot = = bot ;
}
2020-01-23 20:57:58 -05:00
auto old_playlist = bot - > playlist ( ) ;
bot - > set_playlist ( playlist ) ;
2019-07-17 13:37:18 -04:00
if ( old_playlist & & old_playlist - > playlist_type ( ) = = Playlist : : Type : : BOT_BOUND ) {
string error ;
if ( ! this - > delete_playlist ( old_playlist - > playlist_id ( ) , error ) ) {
logError ( this - > ref_server ( ) - > getServerId ( ) , " Failed to delete music bot bound playlist. Error: {} " , error ) ;
}
}
return true ;
}
void MusicBotManager : : deleteBot ( std : : shared_ptr < server : : MusicClient > musicBot ) {
{
lock_guard lock ( this - > music_bots_lock ) ;
auto found = find ( this - > music_bots . begin ( ) , this - > music_bots . end ( ) , musicBot ) ;
if ( found = = this - > music_bots . end ( ) ) return ;
this - > music_bots . erase ( found ) ;
}
auto handle = this - > handle . lock ( ) ;
assert ( handle ) ;
MusicBotManager : : adjustTickPool ( ) ;
{
unique_lock server_channel_lock ( handle - > channel_tree_lock ) ;
handle - > client_move ( musicBot , nullptr , nullptr , " Music bot deleted " , ViewReasonId : : VREASON_SERVER_LEFT , true , server_channel_lock ) ;
handle - > unregisterClient ( musicBot , " bot deleted " , server_channel_lock ) ;
}
2019-09-14 08:22:16 -04:00
handle - > groups - > disableCache ( musicBot - > getClientDatabaseId ( ) ) ;
2019-07-17 13:37:18 -04:00
serverInstance - > databaseHelper ( ) - > deleteClient ( handle , musicBot - > getClientDatabaseId ( ) ) ;
serverInstance - > databaseHelper ( ) - > deleteClient ( nullptr , musicBot - > getClientDatabaseId ( ) ) ;
this - > assign_playlist ( musicBot , nullptr ) ; /* remove any playlists */
/*
auto playlist = musicBot - > playlist ( ) ;
if ( playlist & & playlist - > playlist_type ( ) = = Playlist : : Type : : BOT_BOUND ) {
string error ;
if ( ! this - > delete_playlist ( playlist - > playlist_id ( ) , error ) ) {
logError ( this - > ref_server ( ) - > getServerId ( ) , " Failed to delete music bot bound playlist. Error: {} " , error ) ;
}
}
*/
sql : : command ( handle - > getSql ( ) , " DELETE FROM `musicbots` WHERE `serverId` = :sid AND `botId` = :bid " , variable { " :sid " , handle - > getServerId ( ) } , variable { " :bid " , musicBot - > getClientDatabaseId ( ) } ) . executeLater ( ) . waitAndGetLater ( LOG_SQL_CMD , { - 1 , " future failed " } ) ;
std : : thread ( [ musicBot ] {
musicBot - > player_reset ( false ) ;
} ) . detach ( ) ;
}
int MusicBotManager : : max_bots ( ) {
int bots = this - > handle . lock ( ) - > properties ( ) [ property : : VIRTUALSERVER_MUSIC_BOT_LIMIT ] ;
if ( ! config : : license - > isPremium ( ) )
return bots = = 0 ? 0 : 1 ;
return bots ;
}
int MusicBotManager : : current_bot_count ( ) {
return this - > music_bots . size ( ) ;
}
std : : shared_ptr < server : : MusicClient > MusicBotManager : : findBotById ( ClientDbId id ) {
lock_guard lock ( music_bots_lock ) ;
for ( const auto & bot : this - > music_bots )
if ( bot - > getClientDatabaseId ( ) = = id ) return bot ;
return nullptr ;
}
// CREATE_TABLE("musicbots", "`serverId` INT, `botId` INT, `uniqueId` TEXT, `owner` INT");
void MusicBotManager : : load_bots ( ) {
if ( ! config : : music : : enabled ) return ;
2020-01-23 20:57:58 -05:00
auto handle = this - > handle . lock ( ) ;
assert ( handle ) ;
2019-07-17 13:37:18 -04:00
auto res = sql : : command ( handle - > getSql ( ) , " SELECT `botId`, `uniqueId`, `owner` FROM `musicbots` WHERE `serverId` = :sid " , variable { " :sid " , handle - > getServerId ( ) } ) . query ( & MusicBotManager : : sqlCreateMusicBot , this ) ;
( LOG_SQL_CMD ) ( res ) ;
MusicBotManager : : adjustTickPool ( ) ;
auto bot_limit = this - > max_bots ( ) ;
if ( bot_limit > = 0 ) {
size_t disabled_bots = 0 ;
for ( const auto & bot : this - > music_bots ) {
if ( bot_limit > = 1 ) {
if ( ! bot - > properties ( ) [ property : : CLIENT_DISABLED ] . as < bool > ( ) )
bot_limit - - ;
} else {
//TODO log message
bot - > properties ( ) [ property : : CLIENT_DISABLED ] = true ;
disabled_bots + + ;
}
}
if ( disabled_bots > 0 ) {
logMessage ( handle - > getServerId ( ) , " [Music] Disabled {} music bots, because they exceed tha max music bot limit " , disabled_bots ) ;
}
}
}
int MusicBotManager : : sqlCreateMusicBot ( int length , std : : string * values , std : : string * names ) {
2020-01-23 20:57:58 -05:00
auto handle = this - > handle . lock ( ) ;
assert ( handle ) ;
2019-07-17 13:37:18 -04:00
ClientDbId botId = 0 , owner = 0 ;
std : : string uid ;
for ( int index = 0 ; index < length ; index + + )
if ( names [ index ] = = " botId " )
botId = stoll ( values [ index ] ) ;
else if ( names [ index ] = = " uniqueId " )
uid = values [ index ] ;
else if ( names [ index ] = = " owner " )
owner = stoll ( values [ index ] ) ;
if ( botId = = 0 | | uid . empty ( ) ) return 0 ;
auto musicBot = make_shared < MusicClient > ( handle , uid ) ;
musicBot - > _this = musicBot ;
musicBot - > manager = this ;
DatabaseHelper : : assignDatabaseId ( handle - > getSql ( ) , handle - > getServerId ( ) , musicBot ) ;
musicBot - > properties ( ) [ property : : CLIENT_OWNER ] = owner ;
if ( musicBot - > properties ( ) [ property : : CLIENT_UPTIME_MODE ] = = MusicClient : : UptimeMode : : TIME_SINCE_SERVER_START ) {
musicBot - > properties ( ) [ property : : CLIENT_LASTCONNECTED ] = duration_cast < seconds > ( system_clock : : now ( ) . time_since_epoch ( ) ) . count ( ) ;
}
2020-01-23 20:57:58 -05:00
handle - > groups - > enableCache ( musicBot - > getClientDatabaseId ( ) ) ;
2019-11-23 15:16:55 -05:00
if ( musicBot - > getClientDatabaseId ( ) ! = botId ) logCritical ( handle - > getServerId ( ) , " Invalid music bot id mapping! " ) ;
2019-07-17 13:37:18 -04:00
{
auto playlist = this - > find_playlist ( musicBot - > properties ( ) [ property : : CLIENT_PLAYLIST_ID ] ) ;
if ( ! playlist ) {
debugMessage ( this - > ref_server ( ) - > getServerId ( ) , " Bot {} hasn't a valid playlist ({}). Creating a new one " , musicBot - > getClientDatabaseId ( ) , musicBot - > properties ( ) [ property : : CLIENT_PLAYLIST_ID ] . value ( ) ) ;
playlist = this - > create_playlist ( 0 , " Music Manager " ) ;
playlist - > properties ( ) [ property : : PLAYLIST_TYPE ] = Playlist : : Type : : BOT_BOUND ;
}
2020-01-23 20:57:58 -05:00
playlist - > load_songs ( ) ;
2019-07-17 13:37:18 -04:00
musicBot - > set_playlist ( playlist ) ;
}
{
lock_guard lock ( this - > music_bots_lock ) ;
this - > music_bots . push_back ( musicBot ) ;
}
return 0 ;
}
void MusicBotManager : : connectBots ( ) {
for ( const auto & bot : this - > music_bots ) {
2020-01-23 20:57:58 -05:00
bot - > server = this - > handle . lock ( ) ;
if ( ! bot - > currentChannel | | bot - > getClientId ( ) = = 0 )
bot - > initialize_bot ( ) ;
2019-07-17 13:37:18 -04:00
}
}
void MusicBotManager : : disconnectBots ( ) {
auto handle = this - > handle . lock ( ) ;
assert ( handle ) ;
for ( const auto & bot : this - > music_bots ) {
if ( bot - > currentChannel ) {
unique_lock server_channel_lock ( handle - > channel_tree_lock ) ;
handle - > client_move ( bot , nullptr , nullptr , " Music bot deleted " , ViewReasonId : : VREASON_SERVER_LEFT , true , server_channel_lock ) ;
}
2020-01-23 20:57:58 -05:00
bot - > server = nullptr ;
2019-07-17 13:37:18 -04:00
}
}
void MusicBotManager : : load_playlists ( ) {
if ( ! config : : music : : enabled ) return ;
2020-03-19 20:05:39 -04:00
2019-07-17 13:37:18 -04:00
lock_guard playlist_lock ( this - > playlists_lock ) ;
auto sql_result = sql : : command ( this - > ref_server ( ) - > getSql ( ) , " SELECT `playlist_id` FROM `playlists` WHERE `serverId` = :server_id " , variable { " :server_id " , this - > ref_server ( ) - > getServerId ( ) } ) . query ( [ & ] ( int length , string * values , string * names ) {
if ( length ! = 1 ) return ;
PlaylistId playlist_id = 0 ;
try {
playlist_id = stoll ( values [ 0 ] ) ;
} catch ( const std : : exception & ex ) {
logError ( this - > ref_server ( ) - > getServerId ( ) , " Failed to parse playlist id from database. ID: {} " , values [ 0 ] ) ;
return ;
}
for ( const auto & playlist : this - > playlists_list ) {
if ( playlist - > playlist_id ( ) = = playlist_id ) {
logError ( this - > ref_server ( ) - > getServerId ( ) , " Duplicated playlist it's. ({}) " , playlist_id ) ;
return ;
}
}
auto properties = serverInstance - > databaseHelper ( ) - > loadPlaylistProperties ( this - > ref_server ( ) , playlist_id ) ;
auto permissions = serverInstance - > databaseHelper ( ) - > loadPlaylistPermissions ( this - > ref_server ( ) , playlist_id ) ;
auto playlist = make_shared < PlayablePlaylist > ( this - > ref ( ) , properties , permissions ) ;
playlist - > _self = playlist ;
playlist - > load_songs ( ) ;
this - > playlists_list . push_back ( playlist ) ;
if ( playlist - > playlist_id ( ) > this - > playlists_index )
this - > playlists_index = playlist - > playlist_id ( ) ;
} ) ;
LOG_SQL_CMD ( sql_result ) ;
}
std : : shared_ptr < PlayablePlaylist > MusicBotManager : : find_playlist ( ts : : PlaylistId id ) {
lock_guard lock ( this - > playlists_lock ) ;
for ( const auto & playlist : this - > playlists_list )
if ( playlist - > playlist_id ( ) = = id )
return playlist ;
return nullptr ;
}
std : : deque < std : : shared_ptr < PlayablePlaylist > > MusicBotManager : : find_playlists_by_client ( ClientDbId client_dbid ) {
std : : deque < std : : shared_ptr < PlayablePlaylist > > result ;
{
lock_guard lock ( this - > playlists_lock ) ;
for ( const auto & playlist : this - > playlists_list )
if ( playlist - > properties ( ) [ property : : PLAYLIST_OWNER_DBID ] = = client_dbid )
result . push_back ( playlist ) ;
}
return result ;
}
//playlists => "`serverId` INT NOT NULL, `playlist_id` INT"
std : : shared_ptr < PlayablePlaylist > MusicBotManager : : create_playlist ( ts : : ClientDbId owner_id , const std : : string & owner_name ) {
auto playlist_id = + + this - > playlists_index ;
auto sql_result = sql : : command ( this - > ref_server ( ) - > getSql ( ) , " INSERT INTO `playlists` (`serverId`, `playlist_id`) VALUES (:server_id, :playlist_id) " ,
variable { " :server_id " , this - > ref_server ( ) - > getServerId ( ) } ,
variable { " :playlist_id " , playlist_id }
) . execute ( ) ;
LOG_SQL_CMD ( sql_result ) ;
if ( ! sql_result )
return nullptr ;
auto properties = serverInstance - > databaseHelper ( ) - > loadPlaylistProperties ( this - > ref_server ( ) , playlist_id ) ;
auto permissions = serverInstance - > databaseHelper ( ) - > loadPlaylistPermissions ( this - > ref_server ( ) , playlist_id ) ;
auto playlist = make_shared < PlayablePlaylist > ( this - > ref ( ) , properties , permissions ) ;
playlist - > _self = playlist ;
playlist - > load_songs ( ) ;
{
lock_guard playlist_lock ( this - > playlists_lock ) ;
this - > playlists_list . push_back ( playlist ) ;
}
playlist - > properties ( ) [ property : : PLAYLIST_OWNER_DBID ] = owner_id ;
playlist - > properties ( ) [ property : : PLAYLIST_OWNER_NAME ] = owner_name ;
return playlist ;
}
bool MusicBotManager : : delete_playlist ( ts : : PlaylistId id , std : : string & error ) {
unique_lock playlist_lock ( this - > playlists_lock ) ;
auto playlist_entry = this - > find_playlist ( id ) ;
for ( const auto & bot : this - > available_bots ( ) ) {
if ( bot - > playlist ( ) = = playlist_entry ) {
error = " bot " + to_string ( bot - > getClientDatabaseId ( ) ) + " is using this playlist " ;
return false ;
}
}
auto it = find ( this - > playlists_list . begin ( ) , this - > playlists_list . end ( ) , playlist_entry ) ;
if ( it ! = this - > playlists_list . end ( ) )
this - > playlists_list . erase ( it ) ;
playlist_lock . unlock ( ) ;
if ( ! serverInstance - > databaseHelper ( ) - > deletePlaylist ( this - > ref_server ( ) , id ) ) {
error = " database deletion failed " ;
return false ;
}
return true ;
2020-02-01 08:32:16 -05:00
}
void MusicBotManager : : execute_tick ( ) {
auto vs = this - > handle . lock ( ) ;
if ( ! vs ) return ;
unique_lock playlist_lock ( this - > playlists_lock ) ;
auto playlists = this - > playlists_list ;
playlist_lock . unlock ( ) ;
auto db_helper = serverInstance - > databaseHelper ( ) ;
for ( auto & playlist : playlists )
db_helper - > savePlaylistPermissions ( vs , playlist - > playlist_id ( ) , playlist - > permission_manager ( ) ) ;
2019-07-17 13:37:18 -04:00
}