2020-03-30 13:44:18 +02:00
import * as contextmenu from "tc-shared/ui/elements/ContextMenu" ;
2020-04-18 19:37:30 +02:00
import { Registry } from "tc-shared/events" ;
2020-03-30 13:44:18 +02:00
import { ChannelTree } from "tc-shared/ui/view" ;
import * as log from "tc-shared/log" ;
import { LogCategory , LogType } from "tc-shared/log" ;
import { Settings , settings } from "tc-shared/settings" ;
import { Sound } from "tc-shared/sound/Sounds" ;
import { Group , GroupManager , GroupTarget , GroupType } from "tc-shared/permission/GroupManager" ;
import PermissionType from "tc-shared/permission/PermissionType" ;
import { createErrorModal , createInputModal } from "tc-shared/ui/elements/Modal" ;
import * as htmltags from "tc-shared/ui/htmltags" ;
import * as server_log from "tc-shared/ui/frames/server_log" ;
2020-04-18 19:37:30 +02:00
import { CommandResult , PlaylistSong } from "tc-shared/connection/ServerConnectionDeclaration" ;
2020-03-30 13:44:18 +02:00
import { ChannelEntry } from "tc-shared/ui/channel" ;
import { ConnectionHandler , ViewReasonId } from "tc-shared/ConnectionHandler" ;
import { voice } from "tc-shared/connection/ConnectionBase" ;
import VoiceClient = voice . VoiceClient ;
import { createServerGroupAssignmentModal } from "tc-shared/ui/modal/ModalGroupAssignment" ;
import { openClientInfo } from "tc-shared/ui/modal/ModalClientInfo" ;
import { spawnBanClient } from "tc-shared/ui/modal/ModalBanClient" ;
import { spawnChangeLatency } from "tc-shared/ui/modal/ModalChangeLatency" ;
import { spawnPlaylistEdit } from "tc-shared/ui/modal/ModalPlaylistEdit" ;
import { formatMessage } from "tc-shared/ui/frames/chat" ;
import { spawnYesNo } from "tc-shared/ui/modal/ModalYesNo" ;
import * as hex from "tc-shared/crypto/hex" ;
2020-04-18 19:37:30 +02:00
import { ClientEntry as ClientEntryView } from "./tree/Client" ;
import * as React from "react" ;
import { ChannelTreeEntry , ChannelTreeEntryEvents } from "tc-shared/ui/TreeEntry" ;
2020-05-20 20:47:48 +02:00
import { spawnClientVolumeChange , spawnMusicBotVolumeChange } from "tc-shared/ui/modal/ModalChangeVolumeNew" ;
2020-06-15 16:56:05 +02:00
import { spawnPermissionEditorModal } from "tc-shared/ui/modal/permissionv2/ModalPermissionEditor" ;
2020-03-30 13:44:18 +02:00
export enum ClientType {
2018-04-30 23:57:21 +02:00
CLIENT_VOICE ,
CLIENT_QUERY ,
CLIENT_INTERNAL ,
CLIENT_WEB ,
CLIENT_MUSIC ,
CLIENT_UNDEFINED
}
2020-03-30 13:44:18 +02:00
export class ClientProperties {
2018-04-30 23:57:21 +02:00
client_type : ClientType = ClientType . CLIENT_VOICE ; //TeamSpeaks type
client_type_exact : ClientType = ClientType . CLIENT_VOICE ;
2018-08-10 21:30:58 +02:00
client_database_id : number = 0 ;
2018-04-16 20:38:35 +02:00
client_version : string = "" ;
client_platform : string = "" ;
client_nickname : string = "unknown" ;
client_unique_identifier : string = "unknown" ;
client_description : string = "" ;
client_servergroups : string = "" ;
client_channel_group_id : number = 0 ;
client_lastconnected : number = 0 ;
2019-08-30 23:06:39 +02:00
client_created : number = 0 ;
client_totalconnections : number = 0 ;
2018-04-16 20:38:35 +02:00
client_flag_avatar : string = "" ;
2018-08-12 14:14:50 +02:00
client_icon_id : number = 0 ;
2018-04-16 20:38:35 +02:00
client_away_message : string = "" ;
client_away : boolean = false ;
2019-08-21 10:00:01 +02:00
client_country : string = "" ;
2018-04-16 20:38:35 +02:00
client_input_hardware : boolean = false ;
2018-04-30 23:57:21 +02:00
client_output_hardware : boolean = false ;
2018-04-16 20:38:35 +02:00
client_input_muted : boolean = false ;
2018-04-30 23:57:21 +02:00
client_output_muted : boolean = false ;
2018-04-16 20:38:35 +02:00
client_is_channel_commander : boolean = false ;
2018-04-30 23:57:21 +02:00
2019-08-21 10:00:01 +02:00
client_teaforo_id : number = 0 ;
client_teaforo_name : string = "" ;
client_teaforo_flags : number = 0 ; /* 0x01 := Banned | 0x02 := Stuff | 0x04 := Premium */
2018-08-12 13:26:56 +02:00
2019-08-30 23:06:39 +02:00
/* not updated in view! */
client_month_bytes_uploaded : number = 0 ;
client_month_bytes_downloaded : number = 0 ;
client_total_bytes_uploaded : number = 0 ;
client_total_bytes_downloaded : number = 0 ;
2018-08-12 13:26:56 +02:00
client_talk_power : number = 0 ;
2020-02-22 14:30:17 +01:00
client_is_priority_speaker : boolean = false ;
2018-04-16 20:38:35 +02:00
}
2020-03-30 13:44:18 +02:00
export class ClientConnectionInfo {
2019-08-30 23:06:39 +02:00
connection_bandwidth_received_last_minute_control : number = - 1 ;
connection_bandwidth_received_last_minute_keepalive : number = - 1 ;
connection_bandwidth_received_last_minute_speech : number = - 1 ;
connection_bandwidth_received_last_second_control : number = - 1 ;
connection_bandwidth_received_last_second_keepalive : number = - 1 ;
connection_bandwidth_received_last_second_speech : number = - 1 ;
connection_bandwidth_sent_last_minute_control : number = - 1 ;
connection_bandwidth_sent_last_minute_keepalive : number = - 1 ;
connection_bandwidth_sent_last_minute_speech : number = - 1 ;
connection_bandwidth_sent_last_second_control : number = - 1 ;
connection_bandwidth_sent_last_second_keepalive : number = - 1 ;
connection_bandwidth_sent_last_second_speech : number = - 1 ;
connection_bytes_received_control : number = - 1 ;
connection_bytes_received_keepalive : number = - 1 ;
connection_bytes_received_speech : number = - 1 ;
connection_bytes_sent_control : number = - 1 ;
connection_bytes_sent_keepalive : number = - 1 ;
connection_bytes_sent_speech : number = - 1 ;
connection_packets_received_control : number = - 1 ;
connection_packets_received_keepalive : number = - 1 ;
connection_packets_received_speech : number = - 1 ;
connection_packets_sent_control : number = - 1 ;
connection_packets_sent_keepalive : number = - 1 ;
connection_packets_sent_speech : number = - 1 ;
connection_ping : number = - 1 ;
connection_ping_deviation : number = - 1 ;
connection_server2client_packetloss_control : number = - 1 ;
connection_server2client_packetloss_keepalive : number = - 1 ;
connection_server2client_packetloss_speech : number = - 1 ;
connection_server2client_packetloss_total : number = - 1 ;
connection_client2server_packetloss_speech : number = - 1 ;
connection_client2server_packetloss_keepalive : number = - 1 ;
connection_client2server_packetloss_control : number = - 1 ;
connection_client2server_packetloss_total : number = - 1 ;
connection_filetransfer_bandwidth_sent : number = - 1 ;
connection_filetransfer_bandwidth_received : number = - 1 ;
connection_connected_time : number = - 1 ;
connection_idle_time : number = - 1 ;
connection_client_ip : string | undefined ;
connection_client_port : number = - 1 ;
}
2020-04-18 19:37:30 +02:00
export interface ClientEvents extends ChannelTreeEntryEvents {
"notify_enter_view" : { } ,
"notify_left_view" : { } ,
notify_properties_updated : {
updated_properties : { [ Key in keyof ClientProperties ] : ClientProperties [ Key ] } ;
client_properties : ClientProperties
} ,
notify_mute_state_change : { muted : boolean }
2020-05-20 20:47:48 +02:00
notify_speak_state_change : { speaking : boolean } ,
"notify_audio_level_changed" : { newValue : number } ,
2020-04-18 19:37:30 +02:00
"music_status_update" : {
player_buffered_index : number ,
player_replay_index : number
} ,
"music_song_change" : {
"song" : SongInfo
} ,
/* TODO: Move this out of the music bots interface? */
"playlist_song_add" : { song : PlaylistSong } ,
"playlist_song_remove" : { song_id : number } ,
"playlist_song_reorder" : { song_id : number , previous_song_id : number } ,
"playlist_song_loaded" : { song_id : number , success : boolean , error_msg? : string , metadata? : string } ,
}
export class ClientEntry extends ChannelTreeEntry < ClientEvents > {
readonly events : Registry < ClientEvents > ;
readonly view : React.RefObject < ClientEntryView > = React . createRef < ClientEntryView > ( ) ;
2020-02-02 15:05:36 +01:00
2018-04-30 23:57:21 +02:00
protected _clientId : number ;
protected _channel : ChannelEntry ;
2018-04-16 20:38:35 +02:00
2018-04-30 23:57:21 +02:00
protected _properties : ClientProperties ;
protected lastVariableUpdate : number = 0 ;
2019-08-21 10:00:01 +02:00
protected _speaking : boolean ;
2019-04-04 21:47:52 +02:00
protected _listener_initialized : boolean ;
2019-08-21 10:00:01 +02:00
2020-03-30 13:44:18 +02:00
protected _audio_handle : VoiceClient ;
2019-08-21 10:00:01 +02:00
protected _audio_volume : number ;
protected _audio_muted : boolean ;
2018-02-27 17:20:49 +01:00
2019-08-30 23:06:39 +02:00
private _info_variables_promise : Promise < void > ;
private _info_variables_promise_timestamp : number ;
private _info_connection_promise : Promise < ClientConnectionInfo > ;
private _info_connection_promise_timestamp : number ;
private _info_connection_promise_resolve : any ;
private _info_connection_promise_reject : any ;
2018-02-27 17:20:49 +01:00
channelTree : ChannelTree ;
2019-04-04 21:47:52 +02:00
constructor ( clientId : number , clientName , properties : ClientProperties = new ClientProperties ( ) ) {
2020-04-18 19:37:30 +02:00
super ( ) ;
this . events = new Registry < ClientEvents > ( ) ;
2020-02-02 15:05:36 +01:00
2018-04-30 23:57:21 +02:00
this . _properties = properties ;
this . _properties . client_nickname = clientName ;
2018-02-27 17:20:49 +01:00
this . _clientId = clientId ;
this . channelTree = null ;
this . _channel = null ;
2019-04-04 21:47:52 +02:00
}
2019-08-21 10:00:01 +02:00
destroy() {
if ( this . _audio_handle ) {
2019-10-13 21:33:07 +02:00
log . warn ( LogCategory . AUDIO , tr ( "Destroying client with an active audio handle. This could cause memory leaks!" ) ) ;
2019-10-19 17:13:40 +02:00
try {
this . _audio_handle . abort_replay ( ) ;
} catch ( error ) {
log . warn ( LogCategory . AUDIO , tr ( "Failed to abort replay: %o" ) , error ) ;
}
2019-08-21 10:00:01 +02:00
this . _audio_handle . callback_playback = undefined ;
this . _audio_handle . callback_stopped = undefined ;
this . _audio_handle = undefined ;
}
this . _channel = undefined ;
}
tree_unregistered() {
this . channelTree = undefined ;
if ( this . _audio_handle ) {
2019-10-19 17:13:40 +02:00
try {
this . _audio_handle . abort_replay ( ) ;
} catch ( error ) {
log . warn ( LogCategory . AUDIO , tr ( "Failed to abort replay: %o" ) , error ) ;
}
2019-08-21 10:00:01 +02:00
this . _audio_handle . callback_playback = undefined ;
this . _audio_handle . callback_stopped = undefined ;
this . _audio_handle = undefined ;
}
this . _channel = undefined ;
}
2020-03-30 13:44:18 +02:00
set_audio_handle ( handle : VoiceClient ) {
2019-04-04 21:47:52 +02:00
if ( this . _audio_handle === handle )
return ;
2019-08-21 10:00:01 +02:00
if ( this . _audio_handle ) {
this . _audio_handle . callback_playback = undefined ;
this . _audio_handle . callback_stopped = undefined ;
}
2019-04-04 21:47:52 +02:00
//TODO may ensure that the id is the same?
this . _audio_handle = handle ;
if ( ! handle ) {
this . speaking = false ;
return ;
}
2018-02-27 17:20:49 +01:00
2019-04-04 21:47:52 +02:00
handle . callback_playback = ( ) = > this . speaking = true ;
handle . callback_stopped = ( ) = > this . speaking = false ;
}
2018-02-27 17:20:49 +01:00
2020-03-30 13:44:18 +02:00
get_audio_handle ( ) : VoiceClient {
2019-04-04 21:47:52 +02:00
return this . _audio_handle ;
2018-02-27 17:20:49 +01:00
}
2018-04-30 23:57:21 +02:00
get properties ( ) : ClientProperties {
return this . _properties ;
}
2018-11-04 00:39:29 +01:00
currentChannel ( ) : ChannelEntry { return this . _channel ; }
2018-02-27 17:20:49 +01:00
clientNickName ( ) { return this . properties . client_nickname ; }
clientUid ( ) { return this . properties . client_unique_identifier ; }
clientId ( ) { return this . _clientId ; }
2019-08-21 10:00:01 +02:00
is_muted() { return ! ! this . _audio_muted ; }
2020-04-18 19:37:30 +02:00
set_muted ( flag : boolean , force : boolean ) {
2019-08-21 10:00:01 +02:00
if ( this . _audio_muted === flag && ! force )
return ;
if ( flag ) {
this . channelTree . client . serverConnection . send_command ( 'clientmute' , {
clid : this.clientId ( )
} ) ;
} else if ( this . _audio_muted ) {
this . channelTree . client . serverConnection . send_command ( 'clientunmute' , {
clid : this.clientId ( )
} ) ;
}
this . _audio_muted = flag ;
this . channelTree . client . settings . changeServer ( "mute_client_" + this . clientUid ( ) , flag ) ;
if ( this . _audio_handle ) {
if ( flag ) {
this . _audio_handle . set_volume ( 0 ) ;
} else {
this . _audio_handle . set_volume ( this . _audio_volume ) ;
}
}
2020-04-18 19:37:30 +02:00
this . events . fire ( "notify_mute_state_change" , { muted : flag } ) ;
2019-08-21 10:00:01 +02:00
for ( const client of this . channelTree . clients ) {
2020-04-18 19:37:30 +02:00
if ( client === this || client . properties . client_unique_identifier !== this . properties . client_unique_identifier )
2019-08-21 10:00:01 +02:00
continue ;
2020-04-18 19:37:30 +02:00
client . set_muted ( flag , false ) ;
2019-08-21 10:00:01 +02:00
}
}
2019-10-13 21:33:07 +02:00
protected initializeListener() {
2018-11-04 13:54:18 +01:00
if ( this . _listener_initialized ) return ;
this . _listener_initialized = true ;
2020-04-18 19:37:30 +02:00
//FIXME: TODO!
/ *
2019-04-04 21:47:52 +02:00
this . tag . on ( 'mousedown' , event = > {
2018-11-04 13:54:18 +01:00
if ( event . which != 1 ) return ; //Only the left button
2019-01-19 15:21:37 +01:00
let clients = this . channelTree . currently_selected as ( ClientEntry | ClientEntry [ ] ) ;
2019-01-20 18:43:14 +01:00
2020-03-30 13:44:18 +02:00
if ( ppt . key_pressed ( SpecialKey . SHIFT ) ) {
2019-01-20 18:43:14 +01:00
if ( clients != this && ! ( $ . isArray ( clients ) && clients . indexOf ( this ) != - 1 ) )
clients = $ . isArray ( clients ) ? [ . . . clients , this ] : [ clients , this ] ;
} else {
clients = this ;
}
2019-01-19 15:21:37 +01:00
this . channelTree . client_mover . activate ( clients , target = > {
2018-11-04 00:39:29 +01:00
if ( ! target ) return ;
2019-01-19 15:21:37 +01:00
for ( const client of $ . isArray ( clients ) ? clients : [ clients ] ) {
if ( target == client . _channel ) continue ;
const source = client . _channel ;
const self = this . channelTree . client . getClient ( ) ;
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . send_command ( "clientmove" , {
2019-01-19 15:21:37 +01:00
clid : client.clientId ( ) ,
cid : target.getChannelId ( )
} ) . then ( event = > {
if ( client . clientId ( ) == this . channelTree . client . clientId )
2019-04-04 21:47:52 +02:00
this . channelTree . client . sound . play ( Sound . CHANNEL_JOINED ) ;
2019-01-19 15:21:37 +01:00
else if ( target !== source && target != self . currentChannel ( ) )
2019-04-04 21:47:52 +02:00
this . channelTree . client . sound . play ( Sound . USER_MOVED ) ;
2019-01-19 15:21:37 +01:00
} ) ;
}
this . channelTree . onSelect ( ) ;
2018-11-04 00:39:29 +01:00
} , event ) ;
} ) ;
2020-04-18 19:37:30 +02:00
* /
}
protected onSelect ( singleSelect : boolean ) {
super . onSelect ( singleSelect ) ;
if ( ! singleSelect ) return ;
if ( settings . static_global ( Settings . KEY_SWITCH_INSTANT_CLIENT ) ) {
if ( this instanceof MusicClientEntry )
this . channelTree . client . side_bar . show_music_player ( this ) ;
else
this . channelTree . client . side_bar . show_client_info ( this ) ;
}
2018-02-27 17:20:49 +01:00
}
2019-08-21 10:00:01 +02:00
protected contextmenu_info ( ) : contextmenu . MenuEntry [ ] {
return [
{
type : contextmenu . MenuEntryType . ENTRY ,
name : this.properties.client_type_exact === ClientType . CLIENT_MUSIC ? tr ( "Show bot info" ) : tr ( "Show client info" ) ,
callback : ( ) = > {
this . channelTree . client . side_bar . show_client_info ( this ) ;
} ,
icon_class : "client-about" ,
visible : ! settings . static_global ( Settings . KEY_SWITCH_INSTANT_CLIENT )
} , {
callback : ( ) = > { } ,
type : contextmenu . MenuEntryType . HR ,
name : "" ,
visible : ! settings . static_global ( Settings . KEY_SWITCH_INSTANT_CLIENT )
}
]
}
2019-06-30 16:03:28 +02:00
protected assignment_context ( ) : contextmenu . MenuEntry [ ] {
let server_groups : contextmenu.MenuEntry [ ] = [ ] ;
2018-09-30 21:50:59 +02:00
for ( let group of this . channelTree . client . groups . serverGroups . sort ( GroupManager . sorter ( ) ) ) {
2018-09-30 22:47:41 +02:00
if ( group . type != GroupType . NORMAL ) continue ;
2018-09-30 21:50:59 +02:00
2019-06-30 16:03:28 +02:00
let entry : contextmenu.MenuEntry = { } as any ;
2018-09-30 21:50:59 +02:00
2019-06-30 16:03:28 +02:00
//TODO: May add the server group icon?
entry . checkbox_checked = this . groupAssigned ( group ) ;
2018-09-30 21:50:59 +02:00
entry . name = group . name + " [" + ( group . properties . savedb ? "perm" : "tmp" ) + "]" ;
if ( this . groupAssigned ( group ) ) {
entry . callback = ( ) = > {
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . send_command ( "servergroupdelclient" , {
2018-09-30 21:50:59 +02:00
sgid : group.id ,
cldbid : this.properties.client_database_id
} ) ;
} ;
entry . disabled = ! this . channelTree . client . permissions . neededPermission ( PermissionType . I_GROUP_MEMBER_ADD_POWER ) . granted ( group . requiredMemberRemovePower ) ;
} else {
entry . callback = ( ) = > {
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . send_command ( "servergroupaddclient" , {
2018-09-30 21:50:59 +02:00
sgid : group.id ,
cldbid : this.properties.client_database_id
} ) ;
} ;
entry . disabled = ! this . channelTree . client . permissions . neededPermission ( PermissionType . I_GROUP_MEMBER_REMOVE_POWER ) . granted ( group . requiredMemberAddPower ) ;
}
2019-06-30 16:03:28 +02:00
entry . type = contextmenu . MenuEntryType . CHECKBOX ;
2018-09-30 21:50:59 +02:00
server_groups . push ( entry ) ;
}
2019-06-30 16:03:28 +02:00
let channel_groups : contextmenu.MenuEntry [ ] = [ ] ;
2018-09-30 21:50:59 +02:00
for ( let group of this . channelTree . client . groups . channelGroups . sort ( GroupManager . sorter ( ) ) ) {
if ( group . type != GroupType . NORMAL ) continue ;
2019-06-30 16:03:28 +02:00
let entry : contextmenu.MenuEntry = { } as any ;
//TODO: May add the channel group icon?
entry . checkbox_checked = this . assignedChannelGroup ( ) == group . id ;
2018-09-30 21:50:59 +02:00
entry . name = group . name + " [" + ( group . properties . savedb ? "perm" : "tmp" ) + "]" ;
entry . callback = ( ) = > {
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . send_command ( "setclientchannelgroup" , {
2018-09-30 21:50:59 +02:00
cldbid : this.properties.client_database_id ,
cgid : group.id ,
cid : this.currentChannel ( ) . channelId
} ) ;
} ;
entry . disabled = ! this . channelTree . client . permissions . neededPermission ( PermissionType . I_GROUP_MEMBER_ADD_POWER ) . granted ( group . requiredMemberRemovePower ) ;
2019-06-30 16:03:28 +02:00
entry . type = contextmenu . MenuEntryType . CHECKBOX ;
2018-09-30 21:50:59 +02:00
channel_groups . push ( entry ) ;
}
return [ {
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . SUB_MENU ,
icon_class : "client-permission_server_groups" ,
2018-12-05 20:46:33 +01:00
name : tr ( "Set server group" ) ,
2018-09-30 21:50:59 +02:00
sub_menu : [
{
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY ,
2019-08-21 10:00:01 +02:00
icon_class : "client-permission_server_groups" ,
2018-09-30 21:50:59 +02:00
name : "Server groups dialog" ,
2019-09-19 01:25:57 +02:00
callback : ( ) = > this . open_assignment_modal ( )
2018-09-30 21:50:59 +02:00
} ,
2019-06-30 16:03:28 +02:00
contextmenu . Entry . HR ( ) ,
2018-09-30 21:50:59 +02:00
. . . server_groups
]
} , {
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . SUB_MENU ,
icon_class : "client-permission_channel" ,
2018-12-05 20:46:33 +01:00
name : tr ( "Set channel group" ) ,
2018-09-30 21:50:59 +02:00
sub_menu : [
. . . channel_groups
]
} , {
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . SUB_MENU ,
icon_class : "client-permission_client" ,
2018-12-05 20:46:33 +01:00
name : tr ( "Permissions" ) ,
2019-08-21 10:00:01 +02:00
sub_menu : [
{
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-permission_client" ,
name : tr ( "Client permissions" ) ,
2020-06-15 16:56:05 +02:00
callback : ( ) = > spawnPermissionEditorModal ( this . channelTree . client , "client" , { clientDatabaseId : this.properties.client_database_id } )
2019-08-21 10:00:01 +02:00
} ,
{
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-permission_client" ,
name : tr ( "Client channel permissions" ) ,
2020-06-15 16:56:05 +02:00
callback : ( ) = > spawnPermissionEditorModal ( this . channelTree . client , "client-channel" , { clientDatabaseId : this.properties.client_database_id } )
2019-08-21 10:00:01 +02:00
}
]
2018-09-30 21:50:59 +02:00
} ] ;
}
2019-09-19 01:25:57 +02:00
open_assignment_modal() {
2020-03-30 13:44:18 +02:00
createServerGroupAssignmentModal ( this , ( groups , flag ) = > {
2019-09-19 01:25:57 +02:00
if ( groups . length == 0 ) return Promise . resolve ( true ) ;
if ( groups . length == 1 ) {
if ( flag ) {
return this . channelTree . client . serverConnection . send_command ( "servergroupaddclient" , {
sgid : groups [ 0 ] ,
cldbid : this.properties.client_database_id
} ) . then ( result = > true ) ;
} else
return this . channelTree . client . serverConnection . send_command ( "servergroupdelclient" , {
sgid : groups [ 0 ] ,
cldbid : this.properties.client_database_id
} ) . then ( result = > true ) ;
} else {
const data = groups . map ( e = > { return { sgid : e } ; } ) ;
data [ 0 ] [ "cldbid" ] = this . properties . client_database_id ;
if ( flag ) {
return this . channelTree . client . serverConnection . send_command ( "clientaddservergroup" , data , { flagset : [ "continueonerror" ] } ) . then ( result = > true ) ;
} else
return this . channelTree . client . serverConnection . send_command ( "clientdelservergroup" , data , { flagset : [ "continueonerror" ] } ) . then ( result = > true ) ;
}
} ) ;
}
2019-08-21 10:00:01 +02:00
open_text_chat() {
const chat = this . channelTree . client . side_bar ;
const conversation = chat . private_conversations ( ) . find_conversation ( {
name : this.clientNickName ( ) ,
client_id : this.clientId ( ) ,
unique_id : this.clientUid ( )
} , {
attach : true ,
create : true
} ) ;
chat . private_conversations ( ) . set_selected_conversation ( conversation ) ;
chat . show_private_conversations ( ) ;
chat . private_conversations ( ) . try_input_focus ( ) ;
}
2018-02-27 17:20:49 +01:00
showContextMenu ( x : number , y : number , on_close : ( ) = > void = undefined ) {
2019-03-07 15:30:53 +01:00
let trigger_close = true ;
2019-06-30 16:03:28 +02:00
contextmenu . spawn_context_menu ( x , y ,
2019-08-21 10:00:01 +02:00
. . . this . contextmenu_info ( ) , {
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-change_nickname" ,
name : ( contextmenu . get_provider ( ) . html_format_enabled ( ) ? "<b>" : "" ) +
2020-05-20 20:47:48 +02:00
tr ( "Open text chat" ) +
( contextmenu . get_provider ( ) . html_format_enabled ( ) ? "</b>" : "" ) ,
2019-03-07 15:30:53 +01:00
callback : ( ) = > {
2019-08-21 10:00:01 +02:00
this . open_text_chat ( ) ;
2018-02-27 17:20:49 +01:00
}
2019-09-19 01:25:57 +02:00
} ,
contextmenu . Entry . HR ( ) ,
{
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-about" ,
name : tr ( "Show client info" ) ,
2020-03-30 13:44:18 +02:00
callback : ( ) = > openClientInfo ( this )
2019-09-19 01:25:57 +02:00
} ,
contextmenu . Entry . HR ( ) ,
{
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-poke" ,
2018-12-05 20:46:33 +01:00
name : tr ( "Poke client" ) ,
2019-03-07 15:30:53 +01:00
callback : ( ) = > {
2018-12-05 20:46:33 +01:00
createInputModal ( tr ( "Poke client" ) , tr ( "Poke message:<br>" ) , text = > true , result = > {
2018-09-30 22:36:17 +02:00
if ( typeof ( result ) === "string" ) {
2018-12-05 20:46:33 +01:00
//TODO tr
2019-03-07 15:30:53 +01:00
console . log ( "Poking client " + this . clientNickName ( ) + " with message " + result ) ;
this . channelTree . client . serverConnection . send_command ( "clientpoke" , {
clid : this.clientId ( ) ,
2018-02-27 17:20:49 +01:00
msg : result
} ) ;
}
} , { width : 400 , maxLength : 512 } ) . open ( ) ;
}
} , {
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-edit" ,
2018-12-05 20:46:33 +01:00
name : tr ( "Change description" ) ,
2019-03-07 15:30:53 +01:00
callback : ( ) = > {
2018-12-05 20:46:33 +01:00
createInputModal ( tr ( "Change client description" ) , tr ( "New description:<br>" ) , text = > true , result = > {
2018-09-30 22:36:17 +02:00
if ( typeof ( result ) === "string" ) {
2018-12-05 20:46:33 +01:00
//TODO tr
2019-03-07 15:30:53 +01:00
console . log ( "Changing " + this . clientNickName ( ) + "'s description to " + result ) ;
this . channelTree . client . serverConnection . send_command ( "clientedit" , {
clid : this.clientId ( ) ,
2018-02-27 17:20:49 +01:00
client_description : result
} ) ;
}
} , { width : 400 , maxLength : 1024 } ) . open ( ) ;
}
} ,
2019-06-30 16:03:28 +02:00
contextmenu . Entry . HR ( ) ,
2018-09-30 21:50:59 +02:00
. . . this . assignment_context ( ) ,
2019-06-30 16:03:28 +02:00
contextmenu . Entry . HR ( ) , {
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-move_client_to_own_channel" ,
2018-12-05 20:46:33 +01:00
name : tr ( "Move client to your channel" ) ,
2018-04-11 17:56:09 +02:00
callback : ( ) = > {
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . send_command ( "clientmove" , {
2018-04-11 17:56:09 +02:00
clid : this.clientId ( ) ,
cid : this.channelTree.client.getClient ( ) . currentChannel ( ) . getChannelId ( )
} ) ;
}
} , {
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-kick_channel" ,
2018-12-05 20:46:33 +01:00
name : tr ( "Kick client from channel" ) ,
2018-11-04 13:54:18 +01:00
callback : ( ) = > {
2018-12-05 20:46:33 +01:00
createInputModal ( tr ( "Kick client from channel" ) , tr ( "Kick reason:<br>" ) , text = > true , result = > {
2019-03-17 12:39:21 +01:00
if ( typeof ( result ) !== 'boolean' || result ) {
2018-12-05 20:46:33 +01:00
//TODO tr
2019-03-07 15:30:53 +01:00
console . log ( "Kicking client " + this . clientNickName ( ) + " from channel with reason " + result ) ;
this . channelTree . client . serverConnection . send_command ( "clientkick" , {
clid : this.clientId ( ) ,
2018-02-27 17:20:49 +01:00
reasonid : ViewReasonId.VREASON_CHANNEL_KICK ,
reasonmsg : result
} ) ;
}
} , { width : 400 , maxLength : 255 } ) . open ( ) ;
}
} , {
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-kick_server" ,
2018-12-05 20:46:33 +01:00
name : tr ( "Kick client fom server" ) ,
2018-11-04 13:54:18 +01:00
callback : ( ) = > {
2018-12-05 20:46:33 +01:00
createInputModal ( tr ( "Kick client from server" ) , tr ( "Kick reason:<br>" ) , text = > true , result = > {
2019-03-17 12:39:21 +01:00
if ( typeof ( result ) !== 'boolean' || result ) {
2018-12-05 20:46:33 +01:00
//TODO tr
2019-03-07 15:30:53 +01:00
console . log ( "Kicking client " + this . clientNickName ( ) + " from server with reason " + result ) ;
this . channelTree . client . serverConnection . send_command ( "clientkick" , {
clid : this.clientId ( ) ,
2018-02-27 17:20:49 +01:00
reasonid : ViewReasonId.VREASON_SERVER_KICK ,
reasonmsg : result
} ) ;
}
} , { width : 400 , maxLength : 255 } ) . open ( ) ;
}
2018-04-11 17:56:09 +02:00
} , {
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-ban_client" ,
2018-12-05 20:46:33 +01:00
name : tr ( "Ban client" ) ,
2018-04-30 23:57:21 +02:00
invalidPermission : ! this . channelTree . client . permissions . neededPermission ( PermissionType . I_CLIENT_BAN_MAX_BANTIME ) . granted ( 1 ) ,
callback : ( ) = > {
2020-03-30 13:44:18 +02:00
spawnBanClient ( this . channelTree . client , [ {
2019-09-19 01:25:57 +02:00
name : this.properties.client_nickname ,
unique_id : this.properties.client_unique_identifier
} ] , ( data ) = > {
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . send_command ( "banclient" , {
2018-04-30 23:57:21 +02:00
uid : this.properties.client_unique_identifier ,
2018-10-20 19:58:06 +02:00
banreason : data.reason ,
time : data.length
2019-02-23 14:15:22 +01:00
} , {
flagset : [ data . no_ip ? "no-ip" : "" , data . no_hwid ? "no-hardware-id" : "" , data . no_name ? "no-nickname" : "" ]
} ) . then ( ( ) = > {
2019-04-04 21:47:52 +02:00
this . channelTree . client . sound . play ( Sound . USER_BANNED ) ;
2018-11-04 00:39:29 +01:00
} ) ;
2018-04-30 23:57:21 +02:00
} ) ;
}
2018-04-11 17:56:09 +02:00
} ,
2019-06-30 16:03:28 +02:00
contextmenu . Entry . HR ( ) ,
2018-09-26 15:04:56 +02:00
/ *
{
type : MenuEntryType . ENTRY ,
icon : "client-kick_server" ,
name : "Add group to client" ,
invalidPermission : true , //!this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
callback : ( ) = > {
Modals . spawnBanClient ( this . properties . client_nickname , ( duration , reason ) = > {
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . send_command ( "banclient" , {
2018-09-26 15:04:56 +02:00
uid : this.properties.client_unique_identifier ,
banreason : reason ,
time : duration
} ) ;
} ) ;
}
} ,
MenuEntry . HR ( ) ,
* /
2018-04-11 17:56:09 +02:00
{
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-volume" ,
2018-12-05 20:46:33 +01:00
name : tr ( "Change Volume" ) ,
2020-05-20 20:47:48 +02:00
callback : ( ) = > spawnClientVolumeChange ( this )
2019-11-09 15:56:01 +01:00
} ,
{
type : contextmenu . MenuEntryType . ENTRY ,
name : tr ( "Change playback latency" ) ,
callback : ( ) = > {
2020-03-30 13:44:18 +02:00
spawnChangeLatency ( this , this . _audio_handle . latency_settings ( ) , ( ) = > {
2019-11-09 15:56:01 +01:00
this . _audio_handle . reset_latency_settings ( ) ;
return this . _audio_handle . latency_settings ( ) ;
2019-11-09 16:17:48 +01:00
} , settings = > this . _audio_handle . latency_settings ( settings ) , this . _audio_handle . support_flush ? ( ) = > {
this . _audio_handle . flush ( ) ;
} : undefined ) ;
2019-11-09 15:56:01 +01:00
} ,
visible : this._audio_handle && this . _audio_handle . support_latency_settings ( )
2019-08-21 10:00:01 +02:00
} , {
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-input_muted_local" ,
name : tr ( "Mute client" ) ,
visible : ! this . _audio_muted ,
2020-04-18 19:37:30 +02:00
callback : ( ) = > this . set_muted ( true , false )
2019-08-21 10:00:01 +02:00
} , {
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-input_muted_local" ,
name : tr ( "Unmute client" ) ,
visible : this._audio_muted ,
2020-04-18 19:37:30 +02:00
callback : ( ) = > this . set_muted ( false , false )
2018-02-27 17:20:49 +01:00
} ,
2020-02-02 15:05:36 +01:00
contextmenu . Entry . CLOSE ( ( ) = > trigger_close && on_close ? on_close ( ) : { } )
2018-02-27 17:20:49 +01:00
) ;
}
2019-01-20 18:43:14 +01:00
static bbcodeTag ( id : number , name : string , uid : string ) : string {
return "[url=client://" + id + "/" + uid + "~" + encodeURIComponent ( name ) + "]" + name + "[/url]" ;
}
2018-04-30 23:57:21 +02:00
2019-01-20 18:43:14 +01:00
static chatTag ( id : number , name : string , uid : string , braces : boolean = false ) : JQuery {
return $ ( htmltags . generate_client ( {
client_name : name ,
client_id : id ,
client_unique_id : uid ,
add_braces : braces
} ) ) ;
}
2018-04-30 23:57:21 +02:00
2019-01-20 18:43:14 +01:00
create_bbcode ( ) : string {
return ClientEntry . bbcodeTag ( this . clientId ( ) , this . clientNickName ( ) , this . clientUid ( ) ) ;
2018-02-27 17:20:49 +01:00
}
2018-04-11 17:56:09 +02:00
createChatTag ( braces : boolean = false ) : JQuery {
2018-02-27 17:20:49 +01:00
return ClientEntry . chatTag ( this . clientId ( ) , this . clientNickName ( ) , this . clientUid ( ) , braces ) ;
}
set speaking ( flag ) {
2019-08-21 10:00:01 +02:00
if ( flag === this . _speaking ) return ;
2018-02-27 17:20:49 +01:00
this . _speaking = flag ;
2020-04-18 19:37:30 +02:00
this . events . fire ( "notify_speak_state_change" , { speaking : flag } ) ;
2018-02-27 17:20:49 +01:00
}
2020-04-18 19:37:30 +02:00
isSpeaking() { return this . _speaking ; }
2018-02-27 17:20:49 +01:00
2018-04-16 20:38:35 +02:00
updateVariables ( . . . variables : { key : string , value : string } [ ] ) {
2018-11-20 15:06:18 +01:00
let reorder_channel = false ;
2019-08-21 10:00:01 +02:00
let update_avatar = false ;
2018-11-20 15:06:18 +01:00
2020-04-18 19:37:30 +02:00
/* devel-block(log-client-property-updates) */
let group = log . group ( log . LogType . DEBUG , LogCategory . CLIENT , tr ( "Update properties (%i) of %s (%i)" ) , variables . length , this . clientNickName ( ) , this . clientId ( ) ) ;
2019-03-25 20:04:04 +01:00
{
const entries = [ ] ;
for ( const variable of variables )
entries . push ( {
key : variable.key ,
value : variable.value ,
type : typeof ( this . properties [ variable . key ] )
} ) ;
2019-08-30 23:06:39 +02:00
log . table ( LogType . DEBUG , LogCategory . PERMISSIONS , "Client update properties" , entries ) ;
2019-03-25 20:04:04 +01:00
}
2020-04-18 19:37:30 +02:00
/* devel-block-end */
2019-03-25 20:04:04 +01:00
2019-04-04 21:47:52 +02:00
for ( const variable of variables ) {
2019-08-21 10:00:01 +02:00
const old_value = this . _properties [ variable . key ] ;
2018-08-10 21:30:58 +02:00
JSON . map_field_to ( this . _properties , variable . value , variable . key ) ;
2018-04-16 20:38:35 +02:00
if ( variable . key == "client_nickname" ) {
2019-08-21 10:00:01 +02:00
if ( variable . value !== old_value && typeof ( old_value ) === "string" ) {
if ( ! ( this instanceof LocalClientEntry ) ) { /* own changes will be logged somewhere else */
2020-03-30 13:44:18 +02:00
this . channelTree . client . log . log ( server_log . Type . CLIENT_NICKNAME_CHANGED , {
2019-08-21 10:00:01 +02:00
own_client : false ,
client : this.log_data ( ) ,
new_name : variable.value ,
old_name : old_value
} ) ;
}
}
const chat = this . channelTree . client . side_bar ;
const conversation = chat . private_conversations ( ) . find_conversation ( {
name : this.clientNickName ( ) ,
client_id : this.clientId ( ) ,
unique_id : this.clientUid ( )
} , {
attach : false ,
create : false
} ) ;
if ( conversation )
conversation . set_client_name ( variable . value ) ;
2018-11-20 15:06:18 +01:00
reorder_channel = true ;
2018-04-16 20:38:35 +02:00
}
if ( variable . key == "client_unique_identifier" ) {
2019-08-21 10:00:01 +02:00
this . _audio_volume = parseFloat ( this . channelTree . client . settings . server ( "volume_client_" + this . clientUid ( ) , "1" ) ) ;
const mute_status = this . channelTree . client . settings . server ( "mute_client_" + this . clientUid ( ) , false ) ;
2020-04-18 19:37:30 +02:00
this . set_muted ( mute_status , mute_status ) ; /* force only needed when we want to mute the client */
2019-08-21 10:00:01 +02:00
if ( this . _audio_handle )
this . _audio_handle . set_volume ( this . _audio_muted ? 0 : this._audio_volume ) ;
log . debug ( LogCategory . CLIENT , tr ( "Loaded client (%s) server specific properties. Volume: %o Muted: %o." ) , this . clientUid ( ) , this . _audio_volume , this . _audio_muted ) ;
2018-04-16 20:38:35 +02:00
}
2018-08-12 13:26:56 +02:00
if ( variable . key == "client_talk_power" ) {
2018-11-20 15:06:18 +01:00
reorder_channel = true ;
2020-04-18 19:37:30 +02:00
//update_icon_status = true; DONE
2018-08-12 13:26:56 +02:00
}
2019-04-25 20:21:50 +02:00
if ( variable . key == "client_icon_id" ) {
/ * y e a h w e l i k e j a v a s c r i p t . D u e t o J S w i e r e d i n t e g e r b e h a v i o u r p a r s i n g f o r e x a m p l e f a i l s f o r 1 8 4 4 6 7 4 4 0 7 3 4 0 9 8 2 9 8 6 3 .
* parseInt ( "18446744073409829863" ) evaluates to 18446744073409829000 .
* In opposite "18446744073409829863" >>> 0 evaluates to 3995244544 , which is the icon id : )
* /
this . properties . client_icon_id = variable . value as any >>> 0 ;
}
2019-08-21 10:00:01 +02:00
else if ( variable . key == "client_flag_avatar" )
update_avatar = true ;
2018-04-11 17:56:09 +02:00
}
2018-04-16 20:38:35 +02:00
2018-11-20 15:06:18 +01:00
/* process updates after variables have been set */
2019-08-21 10:00:01 +02:00
const side_bar = this . channelTree . client . side_bar ;
{
const client_info = side_bar . client_info ( ) ;
if ( client_info . current_client ( ) === this )
client_info . set_current_client ( this , true ) ; /* force an update */
}
if ( update_avatar ) {
this . channelTree . client . fileManager . avatars . update_cache ( this . avatarId ( ) , this . properties . client_flag_avatar ) ;
const conversations = side_bar . private_conversations ( ) ;
const conversation = conversations . find_conversation ( { name : this.clientNickName ( ) , unique_id : this.clientUid ( ) , client_id : this.clientId ( ) } , { create : false , attach : false } ) ;
if ( conversation )
conversation . update_avatar ( ) ;
}
2020-04-18 19:37:30 +02:00
/* devel-block(log-client-property-updates) */
2018-04-16 20:38:35 +02:00
group . end ( ) ;
2020-04-18 19:37:30 +02:00
/* devel-block-end */
2018-12-17 21:21:52 +01:00
2020-04-18 19:37:30 +02:00
{
let properties = { } ;
for ( const property of variables )
properties [ property . key ] = this . properties [ property . key ] ;
this . events . fire ( "notify_properties_updated" , { updated_properties : properties as any , client_properties : this.properties } ) ;
2018-12-17 21:21:52 +01:00
}
2018-09-30 22:36:17 +02:00
}
2019-08-30 23:06:39 +02:00
updateClientVariables ( force_update? : boolean ) : Promise < void > {
if ( Date . now ( ) - 10 * 60 * 1000 < this . _info_variables_promise_timestamp && this . _info_variables_promise && ( typeof ( force_update ) !== "boolean" || force_update ) )
return this . _info_variables_promise ;
this . _info_variables_promise_timestamp = Date . now ( ) ;
return ( this . _info_variables_promise = new Promise < void > ( ( resolve , reject ) = > {
this . channelTree . client . serverConnection . send_command ( "clientgetvariables" , { clid : this.clientId ( ) } ) . then ( ( ) = > resolve ( ) ) . catch ( error = > {
this . _info_connection_promise_timestamp = 0 ; /* not succeeded */
reject ( error ) ;
} ) ;
} ) ) ;
2018-02-27 17:20:49 +01:00
}
assignedServerGroupIds ( ) : number [ ] {
let result = [ ] ;
for ( let id of this . properties . client_servergroups . split ( "," ) ) {
if ( id . length == 0 ) continue ;
result . push ( Number . parseInt ( id ) ) ;
}
return result ;
}
assignedChannelGroup ( ) : number {
2018-04-16 20:38:35 +02:00
return this . properties . client_channel_group_id ;
2018-02-27 17:20:49 +01:00
}
groupAssigned ( group : Group ) : boolean {
if ( group . target == GroupTarget . SERVER ) {
for ( let id of this . assignedServerGroupIds ( ) )
if ( id == group . id ) return true ;
return false ;
} else return group . id == this . assignedChannelGroup ( ) ;
}
2019-04-04 21:47:52 +02:00
onDelete() { }
2018-02-27 17:20:49 +01:00
calculateOnlineTime ( ) : number {
2018-04-30 23:57:21 +02:00
return Date . now ( ) / 1000 - this . properties . client_lastconnected ;
2018-04-16 20:38:35 +02:00
}
avatarId ? ( ) : string {
function str2ab ( str ) {
let buf = new ArrayBuffer ( str . length ) ; // 2 bytes for each char
let bufView = new Uint8Array ( buf ) ;
2018-08-12 16:38:38 +02:00
for ( let i = 0 , strLen = str . length ; i < strLen ; i + + ) {
2018-04-16 20:38:35 +02:00
bufView [ i ] = str . charCodeAt ( i ) ;
}
return buf ;
}
try {
let raw = atob ( this . properties . client_unique_identifier ) ;
let input = hex . encode ( str2ab ( raw ) ) ;
let result : string = "" ;
for ( let index = 0 ; index < input . length ; index ++ ) {
let c = input . charAt ( index ) ;
let offset : number = 0 ;
if ( c >= '0' && c <= '9' )
offset = c . charCodeAt ( 0 ) - '0' . charCodeAt ( 0 ) ;
else if ( c >= 'A' && c <= 'F' )
offset = c . charCodeAt ( 0 ) - 'A' . charCodeAt ( 0 ) + 0x0A ;
else if ( c >= 'a' && c <= 'f' )
offset = c . charCodeAt ( 0 ) - 'a' . charCodeAt ( 0 ) + 0x0A ;
result += String . fromCharCode ( 'a' . charCodeAt ( 0 ) + offset ) ;
}
return result ;
} catch ( e ) { //invalid base 64 (like music bot etc)
return undefined ;
}
2018-02-27 17:20:49 +01:00
}
2019-02-17 16:08:10 +01:00
2020-03-30 13:44:18 +02:00
log_data ( ) : server_log . base . Client {
2019-07-10 00:52:08 +02:00
return {
client_unique_id : this.properties.client_unique_identifier ,
client_name : this.clientNickName ( ) ,
client_id : this._clientId
}
2019-02-17 16:08:10 +01:00
}
2019-08-30 23:06:39 +02:00
/* max 1s ago, so we could update every second */
request_connection_info ( ) : Promise < ClientConnectionInfo > {
if ( Date . now ( ) - 900 < this . _info_connection_promise_timestamp && this . _info_connection_promise )
return this . _info_connection_promise ;
if ( this . _info_connection_promise_reject )
this . _info_connection_promise_resolve ( "timeout" ) ;
let _local_reject ; /* to ensure we're using the right resolve! */
this . _info_connection_promise = new Promise < ClientConnectionInfo > ( ( resolve , reject ) = > {
this . _info_connection_promise_resolve = resolve ;
this . _info_connection_promise_reject = reject ;
_local_reject = reject ;
} ) ;
this . _info_connection_promise_timestamp = Date . now ( ) ;
this . channelTree . client . serverConnection . send_command ( "getconnectioninfo" , { clid : this._clientId } ) . catch ( error = > _local_reject ( error ) ) ;
return this . _info_connection_promise ;
}
set_connection_info ( info : ClientConnectionInfo ) {
if ( ! this . _info_connection_promise_resolve )
return ;
this . _info_connection_promise_resolve ( info ) ;
this . _info_connection_promise_resolve = undefined ;
this . _info_connection_promise_reject = undefined ;
}
2020-05-20 20:47:48 +02:00
setAudioVolume ( value : number ) {
if ( this . _audio_volume == value )
return ;
this . _audio_volume = value ;
this . get_audio_handle ( ) ? . set_volume ( value ) ;
this . channelTree . client . settings . changeServer ( "volume_client_" + this . clientUid ( ) , value ) ;
this . events . fire ( "notify_audio_level_changed" , { newValue : value } ) ;
}
getAudioVolume() {
return this . _audio_volume ;
}
2018-02-27 17:20:49 +01:00
}
2020-03-30 13:44:18 +02:00
export class LocalClientEntry extends ClientEntry {
2019-04-04 21:47:52 +02:00
handle : ConnectionHandler ;
2018-02-27 17:20:49 +01:00
private renaming : boolean ;
2019-04-04 21:47:52 +02:00
constructor ( handle : ConnectionHandler ) {
2018-05-09 11:50:05 +02:00
super ( 0 , "local client" ) ;
2018-02-27 17:20:49 +01:00
this . handle = handle ;
}
showContextMenu ( x : number , y : number , on_close : ( ) = > void = undefined ) : void {
const _self = this ;
2019-06-30 16:03:28 +02:00
contextmenu . spawn_context_menu ( x , y ,
2019-08-21 10:00:01 +02:00
. . . this . contextmenu_info ( ) , {
2019-06-30 16:03:28 +02:00
name : ( contextmenu . get_provider ( ) . html_format_enabled ( ) ? "<b>" : "" ) +
2020-05-20 20:47:48 +02:00
tr ( "Change name" ) +
( contextmenu . get_provider ( ) . html_format_enabled ( ) ? "</b>" : "" ) ,
2019-06-30 16:03:28 +02:00
icon_class : "client-change_nickname" ,
2018-02-27 17:20:49 +01:00
callback : ( ) = > _self . openRename ( ) ,
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY
2018-02-27 17:20:49 +01:00
} , {
2018-12-05 20:46:33 +01:00
name : tr ( "Change description" ) ,
2019-06-30 16:03:28 +02:00
icon_class : "client-edit" ,
2018-02-27 17:20:49 +01:00
callback : ( ) = > {
2018-12-05 20:46:33 +01:00
createInputModal ( tr ( "Change own description" ) , tr ( "New description:<br>" ) , text = > true , result = > {
2018-02-27 17:20:49 +01:00
if ( result ) {
2018-12-05 20:46:33 +01:00
console . log ( tr ( "Changing own description to %s" ) , result ) ;
2019-02-23 14:15:22 +01:00
_self . channelTree . client . serverConnection . send_command ( "clientedit" , {
2018-02-27 17:20:49 +01:00
clid : _self.clientId ( ) ,
client_description : result
} ) ;
}
} , { width : 400 , maxLength : 1024 } ) . open ( ) ;
} ,
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY
2018-02-27 17:20:49 +01:00
} ,
2019-06-30 16:03:28 +02:00
contextmenu . Entry . HR ( ) ,
2018-09-30 22:58:53 +02:00
. . . this . assignment_context ( ) ,
2019-06-30 16:03:28 +02:00
contextmenu . Entry . CLOSE ( on_close )
2018-02-27 17:20:49 +01:00
) ;
}
initializeListener ( ) : void {
super . initializeListener ( ) ;
2020-04-18 19:37:30 +02:00
}
2018-02-27 17:20:49 +01:00
2020-04-18 19:37:30 +02:00
renameSelf ( new_name : string ) : Promise < boolean > {
const old_name = this . properties . client_nickname ;
this . updateVariables ( { key : "client_nickname" , value : new_name } ) ; /* change it locally */
return this . handle . serverConnection . command_helper . updateClient ( "client_nickname" , new_name ) . then ( ( e ) = > {
settings . changeGlobal ( Settings . KEY_CONNECT_USERNAME , new_name ) ;
this . channelTree . client . log . log ( server_log . Type . CLIENT_NICKNAME_CHANGED , {
client : this.log_data ( ) ,
old_name : old_name ,
new_name : new_name ,
own_client : true
} ) ;
return true ;
} ) . catch ( ( e : CommandResult ) = > {
this . updateVariables ( { key : "client_nickname" , value : old_name } ) ; /* change it back */
this . channelTree . client . log . log ( server_log . Type . CLIENT_NICKNAME_CHANGE_FAILED , {
reason : e.extra_message
} ) ;
return false ;
2018-02-27 17:20:49 +01:00
} ) ;
}
openRename ( ) : void {
2020-04-18 19:37:30 +02:00
const view = this . channelTree . view . current ;
if ( ! view ) return ; //TODO: Fallback input modal
view . scrollEntryInView ( this , ( ) = > {
const own_view = this . view . current ;
if ( ! own_view ) {
return ; //TODO: Fallback input modal
2018-02-27 17:20:49 +01:00
}
2020-04-18 19:37:30 +02:00
own_view . setState ( {
rename : true ,
renameInitialName : this.properties.client_nickname
2018-02-27 17:20:49 +01:00
} ) ;
} ) ;
}
2018-06-20 19:06:55 +02:00
}
2020-03-30 13:44:18 +02:00
export class MusicClientProperties extends ClientProperties {
2018-08-10 21:30:58 +02:00
player_state : number = 0 ;
player_volume : number = 0 ;
2019-01-20 18:43:14 +01:00
client_playlist_id : number = 0 ;
client_disabled : boolean = false ;
2020-02-22 14:30:17 +01:00
client_flag_notify_song_change : boolean = false ;
client_bot_type : number = 0 ;
client_uptime_mode : number = 0 ;
2018-08-10 21:30:58 +02:00
}
2020-02-02 15:05:36 +01:00
/ *
* 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 ( ) ;
}
}
* /
2020-03-30 13:44:18 +02:00
export class SongInfo {
2020-02-02 15:05:36 +01:00
song_id : number = 0 ;
song_url : string = "" ;
song_invoker : number = 0 ;
song_loaded : boolean = false ;
/* only if song_loaded = true */
song_title : string = "" ;
song_description : string = "" ;
song_thumbnail : string = "" ;
song_length : number = 0 ;
}
2020-03-30 13:44:18 +02:00
export class MusicClientPlayerInfo extends SongInfo {
2018-11-26 20:52:56 +01:00
bot_id : number = 0 ;
2018-08-10 21:30:58 +02:00
player_state : number = 0 ;
player_buffered_index : number = 0 ;
player_replay_index : number = 0 ;
player_max_index : number = 0 ;
player_seekable : boolean = false ;
player_title : string = "" ;
player_description : string = "" ;
2018-06-20 19:06:55 +02:00
}
2020-03-30 13:44:18 +02:00
export class MusicClientEntry extends ClientEntry {
2018-08-10 21:30:58 +02:00
private _info_promise : Promise < MusicClientPlayerInfo > ;
private _info_promise_age : number = 0 ;
private _info_promise_resolve : any ;
private _info_promise_reject : any ;
2018-06-20 19:06:55 +02:00
constructor ( clientId , clientName ) {
super ( clientId , clientName , new MusicClientProperties ( ) ) ;
}
2019-08-21 10:00:01 +02:00
destroy() {
super . destroy ( ) ;
this . _info_promise = undefined ;
this . _info_promise_reject = undefined ;
this . _info_promise_resolve = undefined ;
}
2018-06-20 19:06:55 +02:00
get properties ( ) : MusicClientProperties {
return this . _properties as MusicClientProperties ;
}
showContextMenu ( x : number , y : number , on_close : ( ) = > void = undefined ) : void {
2019-03-07 15:30:53 +01:00
let trigger_close = true ;
2019-06-30 16:03:28 +02:00
contextmenu . spawn_context_menu ( x , y ,
2019-08-21 10:00:01 +02:00
. . . this . contextmenu_info ( ) , {
2019-10-13 21:33:07 +02:00
name : ( contextmenu . get_provider ( ) . html_format_enabled ( ) ? "<b>" : "" ) +
2020-05-20 20:47:48 +02:00
tr ( "Change bot name" ) +
( contextmenu . get_provider ( ) . html_format_enabled ( ) ? "</b>" : "" ) ,
2019-06-30 16:03:28 +02:00
icon_class : "client-change_nickname" ,
2018-11-04 13:54:18 +01:00
disabled : false ,
callback : ( ) = > {
2018-12-05 20:46:33 +01:00
createInputModal ( tr ( "Change music bots nickname" ) , tr ( "New nickname:<br>" ) , text = > text . length >= 3 && text . length <= 31 , result = > {
2018-11-04 13:54:18 +01:00
if ( result ) {
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . send_command ( "clientedit" , {
2018-11-04 13:54:18 +01:00
clid : this.clientId ( ) ,
client_nickname : result
} ) ;
}
2019-10-13 21:33:07 +02:00
} , { width : "40em" , min_width : "10em" , maxLength : 255 } ) . open ( ) ;
2018-11-04 13:54:18 +01:00
} ,
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY
2018-06-20 19:06:55 +02:00
} , {
2018-12-05 20:46:33 +01:00
name : tr ( "Change bot description" ) ,
2019-06-30 16:03:28 +02:00
icon_class : "client-edit" ,
2018-11-04 13:54:18 +01:00
disabled : false ,
callback : ( ) = > {
2018-12-05 20:46:33 +01:00
createInputModal ( tr ( "Change music bots description" ) , tr ( "New description:<br>" ) , text = > true , result = > {
2018-11-04 13:54:18 +01:00
if ( typeof ( result ) === 'string' ) {
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . send_command ( "clientedit" , {
2018-11-04 13:54:18 +01:00
clid : this.clientId ( ) ,
client_description : result
} ) ;
}
2019-10-13 21:33:07 +02:00
} , { width : "60em" , min_width : "10em" , maxLength : 255 } ) . open ( ) ;
2018-11-04 13:54:18 +01:00
} ,
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY
2019-01-20 18:43:14 +01:00
} ,
/ *
{
2018-12-05 20:46:33 +01:00
name : tr ( "Open music panel" ) ,
2018-06-20 19:06:55 +02:00
icon : "client-edit" ,
disabled : true ,
callback : ( ) = > { } ,
type : MenuEntryType . ENTRY
2019-01-20 18:43:14 +01:00
} ,
* /
{
name : tr ( "Open bot's playlist" ) ,
2019-06-30 16:03:28 +02:00
icon_class : "client-edit" ,
2019-01-20 18:43:14 +01:00
disabled : false ,
callback : ( ) = > {
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . command_helper . request_playlist_list ( ) . then ( lists = > {
2019-01-20 18:43:14 +01:00
for ( const entry of lists ) {
if ( entry . playlist_id == this . properties . client_playlist_id ) {
2020-03-30 13:44:18 +02:00
spawnPlaylistEdit ( this . channelTree . client , entry ) ;
2019-01-20 18:43:14 +01:00
return ;
}
}
createErrorModal ( tr ( "Invalid permissions" ) , tr ( "You dont have to see the bots playlist." ) ) . open ( ) ;
} ) . catch ( error = > {
createErrorModal ( tr ( "Failed to query playlist." ) , tr ( "Failed to query playlist info." ) ) . open ( ) ;
} ) ;
} ,
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY
2019-01-20 18:43:14 +01:00
} ,
{
2018-12-05 20:46:33 +01:00
name : tr ( "Quick url replay" ) ,
2019-06-30 16:03:28 +02:00
icon_class : "client-edit" ,
2018-11-04 13:54:18 +01:00
disabled : false ,
callback : ( ) = > {
2018-12-05 20:46:33 +01:00
createInputModal ( tr ( "Please enter the URL" ) , tr ( "URL:" ) , text = > true , result = > {
2018-11-04 13:54:18 +01:00
if ( result ) {
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . send_command ( "musicbotqueueadd" , {
2018-11-26 20:52:56 +01:00
bot_id : this.properties.client_database_id ,
2018-11-04 13:54:18 +01:00
type : "yt" , //Its a hint not a force!
url : result
} ) . catch ( error = > {
if ( error instanceof CommandResult ) {
error = error . extra_message || error . message ;
}
2018-12-05 20:46:33 +01:00
//TODO tr
createErrorModal ( tr ( "Failed to replay url" ) , "Failed to enqueue url:<br>" + error ) . open ( ) ;
2018-11-04 13:54:18 +01:00
} ) ;
}
} , { width : 400 , maxLength : 255 } ) . open ( ) ;
} ,
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY
2018-06-20 19:06:55 +02:00
} ,
2019-06-30 16:03:28 +02:00
contextmenu . Entry . HR ( ) ,
2018-09-30 21:50:59 +02:00
. . . super . assignment_context ( ) ,
2019-06-30 16:03:28 +02:00
contextmenu . Entry . HR ( ) , {
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-move_client_to_own_channel" ,
2018-12-05 20:46:33 +01:00
name : tr ( "Move client to your channel" ) ,
2018-11-04 00:39:29 +01:00
callback : ( ) = > {
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . send_command ( "clientmove" , {
2018-11-04 00:39:29 +01:00
clid : this.clientId ( ) ,
cid : this.channelTree.client.getClient ( ) . currentChannel ( ) . getChannelId ( )
} ) ;
}
} , {
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-kick_channel" ,
2018-12-05 20:46:33 +01:00
name : tr ( "Kick client from channel" ) ,
2018-11-04 00:39:29 +01:00
callback : ( ) = > {
2018-12-05 20:46:33 +01:00
createInputModal ( tr ( "Kick client from channel" ) , tr ( "Kick reason:<br>" ) , text = > true , result = > {
2019-03-17 12:39:21 +01:00
if ( typeof ( result ) !== 'boolean' || result ) {
2018-12-05 20:46:33 +01:00
console . log ( tr ( "Kicking client %o from channel with reason %o" ) , this . clientNickName ( ) , result ) ;
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . send_command ( "clientkick" , {
2018-11-04 00:39:29 +01:00
clid : this.clientId ( ) ,
reasonid : ViewReasonId.VREASON_CHANNEL_KICK ,
reasonmsg : result
} ) ;
}
} , { width : 400 , maxLength : 255 } ) . open ( ) ;
}
} ,
2019-06-30 16:03:28 +02:00
contextmenu . Entry . HR ( ) ,
2018-11-04 00:39:29 +01:00
{
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-volume" ,
2019-01-20 18:43:14 +01:00
name : tr ( "Change local volume" ) ,
2020-05-20 20:47:48 +02:00
callback : ( ) = > spawnClientVolumeChange ( this )
2019-01-20 18:43:14 +01:00
} ,
{
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY ,
icon_class : "client-volume" ,
2019-01-20 18:43:14 +01:00
name : tr ( "Change remote volume" ) ,
callback : ( ) = > {
let max_volume = this . channelTree . client . permissions . neededPermission ( PermissionType . I_CLIENT_MUSIC_CREATE_MODIFY_MAX_VOLUME ) . value ;
if ( max_volume < 0 )
max_volume = 100 ;
2020-05-20 20:47:48 +02:00
spawnMusicBotVolumeChange ( this , max_volume / 100 ) ;
2018-11-04 00:39:29 +01:00
}
} ,
2019-11-09 15:56:01 +01:00
{
type : contextmenu . MenuEntryType . ENTRY ,
name : tr ( "Change playback latency" ) ,
callback : ( ) = > {
2020-03-30 13:44:18 +02:00
spawnChangeLatency ( this , this . _audio_handle . latency_settings ( ) , ( ) = > {
2019-11-09 15:56:01 +01:00
this . _audio_handle . reset_latency_settings ( ) ;
return this . _audio_handle . latency_settings ( ) ;
2019-11-09 16:17:48 +01:00
} , settings = > this . _audio_handle . latency_settings ( settings ) , this . _audio_handle . support_flush ? ( ) = > {
this . _audio_handle . flush ( ) ;
} : undefined ) ;
2019-11-09 15:56:01 +01:00
} ,
visible : this._audio_handle && this . _audio_handle . support_latency_settings ( )
} ,
2019-06-30 16:03:28 +02:00
contextmenu . Entry . HR ( ) ,
2018-06-20 19:06:55 +02:00
{
2018-12-05 20:46:33 +01:00
name : tr ( "Delete bot" ) ,
2019-06-30 16:03:28 +02:00
icon_class : "client-delete" ,
2018-11-04 13:54:18 +01:00
disabled : false ,
2018-06-20 19:06:55 +02:00
callback : ( ) = > {
2020-03-30 13:44:18 +02:00
const tag = $ . spawn ( "div" ) . append ( formatMessage ( tr ( "Do you really want to delete {0}" ) , this . createChatTag ( false ) ) ) ;
spawnYesNo ( tr ( "Are you sure?" ) , $ . spawn ( "div" ) . append ( tag ) , result = > {
2020-05-20 20:47:48 +02:00
if ( result ) {
this . channelTree . client . serverConnection . send_command ( "musicbotdelete" , {
bot_id : this.properties.client_database_id
} ) ;
}
2018-11-04 13:54:18 +01:00
} ) ;
2018-06-20 19:06:55 +02:00
} ,
2019-06-30 16:03:28 +02:00
type : contextmenu . MenuEntryType . ENTRY
2018-06-20 19:06:55 +02:00
} ,
2020-02-02 15:05:36 +01:00
contextmenu . Entry . CLOSE ( ( ) = > trigger_close && on_close ? on_close ( ) : { } )
2018-06-20 19:06:55 +02:00
) ;
}
initializeListener ( ) : void {
super . initializeListener ( ) ;
}
2018-08-10 21:30:58 +02:00
handlePlayerInfo ( json ) {
if ( json ) {
2019-09-12 23:59:35 +02:00
const info = new MusicClientPlayerInfo ( ) ;
2020-05-20 20:47:48 +02:00
JSON . map_to ( info , json ) ;
2018-08-10 21:30:58 +02:00
if ( this . _info_promise_resolve )
this . _info_promise_resolve ( info ) ;
this . _info_promise_reject = undefined ;
this . _info_promise_resolve = undefined ;
}
}
requestPlayerInfo ( max_age : number = 1000 ) : Promise < MusicClientPlayerInfo > {
2020-02-02 15:05:36 +01:00
if ( this . _info_promise !== undefined && this . _info_promise_age > 0 && Date . now ( ) - max_age <= this . _info_promise_age ) return this . _info_promise ;
2018-08-10 21:30:58 +02:00
this . _info_promise_age = Date . now ( ) ;
this . _info_promise = new Promise < MusicClientPlayerInfo > ( ( resolve , reject ) = > {
this . _info_promise_reject = reject ;
this . _info_promise_resolve = resolve ;
} ) ;
2019-02-23 14:15:22 +01:00
this . channelTree . client . serverConnection . send_command ( "musicbotplayerinfo" , { bot_id : this.properties.client_database_id } ) ;
2018-08-10 21:30:58 +02:00
return this . _info_promise ;
}
2018-02-27 17:20:49 +01:00
}