TeaWeb/shared/js/tree/Client.ts

1335 lines
55 KiB
TypeScript
Raw Normal View History

import * as contextmenu from "../ui/elements/ContextMenu";
import {Registry} from "../events";
2020-09-12 12:51:03 +00:00
import {ChannelTree} from "./ChannelTree";
import * as log from "../log";
import {LogCategory, logInfo, LogType} from "../log";
import {Settings, settings} from "../settings";
import {Sound} from "../sound/Sounds";
import {Group, GroupManager, GroupTarget, GroupType} from "../permission/GroupManager";
import PermissionType from "../permission/PermissionType";
import {createErrorModal, createInputModal} from "../ui/elements/Modal";
import * as htmltags from "../ui/htmltags";
import {CommandResult, PlaylistSong} from "../connection/ServerConnectionDeclaration";
2020-09-12 12:51:03 +00:00
import {ChannelEntry} from "./Channel";
import {ConnectionHandler, ViewReasonId} from "../ConnectionHandler";
import {createServerGroupAssignmentModal} from "../ui/modal/ModalGroupAssignment";
import {openClientInfo} from "../ui/modal/ModalClientInfo";
import {spawnBanClient} from "../ui/modal/ModalBanClient";
import {spawnChangeLatency} from "../ui/modal/ModalChangeLatency";
import {formatMessage} from "../ui/frames/chat";
import {spawnYesNo} from "../ui/modal/ModalYesNo";
import * as hex from "../crypto/hex";
2020-09-12 12:51:03 +00:00
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "./ChannelTreeEntry";
import {spawnClientVolumeChange, spawnMusicBotVolumeChange} from "../ui/modal/ModalChangeVolumeNew";
import {spawnPermissionEditorModal} from "../ui/modal/permission/ModalPermissionEditor";
import {EventClient, EventType} from "../ui/frames/log/Definitions";
import {W2GPluginCmdHandler} from "../video-viewer/W2GPlugin";
import {global_client_actions} from "../events/GlobalEvents";
2020-08-26 10:33:53 +00:00
import {ClientIcon} from "svg-sprites/client-icons";
import {VoiceClient} from "../voice/VoiceClient";
import {VoicePlayerEvents, VoicePlayerState} from "../voice/VoicePlayer";
2020-09-26 19:34:46 +00:00
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
import {VideoClient} from "tc-shared/connection/VideoConnection";
2020-03-30 11:44:18 +00:00
export enum ClientType {
2018-04-30 21:57:21 +00:00
CLIENT_VOICE,
CLIENT_QUERY,
CLIENT_INTERNAL,
CLIENT_WEB,
CLIENT_MUSIC,
CLIENT_UNDEFINED
}
2020-03-30 11:44:18 +00:00
export class ClientProperties {
2018-04-30 21:57:21 +00:00
client_type: ClientType = ClientType.CLIENT_VOICE; //TeamSpeaks type
client_type_exact: ClientType = ClientType.CLIENT_VOICE;
2018-08-10 19:30:58 +00:00
client_database_id: number = 0;
2018-04-16 18:38:35 +00: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 21:06:39 +00:00
client_created: number = 0;
client_totalconnections: number = 0;
2018-04-16 18:38:35 +00:00
client_flag_avatar: string = "";
2018-08-12 12:14:50 +00:00
client_icon_id: number = 0;
2018-04-16 18:38:35 +00:00
client_away_message: string = "";
client_away: boolean = false;
2019-08-21 08:00:01 +00:00
client_country: string = "";
2018-04-16 18:38:35 +00:00
client_input_hardware: boolean = false;
2018-04-30 21:57:21 +00:00
client_output_hardware: boolean = false;
2018-04-16 18:38:35 +00:00
client_input_muted: boolean = false;
2018-04-30 21:57:21 +00:00
client_output_muted: boolean = false;
2018-04-16 18:38:35 +00:00
client_is_channel_commander: boolean = false;
2018-04-30 21:57:21 +00:00
2019-08-21 08:00:01 +00:00
client_teaforo_id: number = 0;
client_teaforo_name: string = "";
client_teaforo_flags: number = 0; /* 0x01 := Banned | 0x02 := Stuff | 0x04 := Premium */
2019-08-30 21:06:39 +00: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;
client_talk_power: number = 0;
2020-09-26 19:34:46 +00:00
client_talk_request: number = 0;
client_talk_request_msg: string = "";
client_is_talker: boolean = false;
2020-02-22 13:30:17 +00:00
client_is_priority_speaker: boolean = false;
2018-04-16 18:38:35 +00:00
}
2020-03-30 11:44:18 +00:00
export class ClientConnectionInfo {
2019-08-30 21:06:39 +00: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;
}
export interface ClientEvents extends ChannelTreeEntryEvents {
notify_properties_updated: {
updated_properties: {[Key in keyof ClientProperties]: ClientProperties[Key]};
client_properties: ClientProperties
},
notify_mute_state_change: { muted: boolean }
notify_speak_state_change: { speaking: boolean },
2020-09-07 10:42:00 +00:00
notify_audio_level_changed: { newValue: number },
notify_status_icon_changed: { newIcon: ClientIcon },
notify_video_handle_changed: { oldHandle: VideoClient | undefined, newHandle: VideoClient | undefined },
2020-09-07 10:42:00 +00:00
music_status_update: {
player_buffered_index: number,
player_replay_index: number
},
2020-09-07 10:42:00 +00:00
music_song_change: {
"song": SongInfo
},
/* TODO: Move this out of the music bots interface? */
2020-09-07 10:42:00 +00:00
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 },
}
const StatusIconUpdateKeys: (keyof ClientProperties)[] = [
"client_away",
"client_input_hardware",
"client_output_hardware",
"client_output_muted",
"client_input_muted",
"client_is_channel_commander",
"client_talk_power"
];
export class ClientEntry extends ChannelTreeEntry<ClientEvents> {
readonly events: Registry<ClientEvents>;
2020-09-07 10:42:00 +00:00
channelTree: ChannelTree;
2020-02-02 14:05:36 +00:00
2018-04-30 21:57:21 +00:00
protected _clientId: number;
protected _channel: ChannelEntry;
2018-04-16 18:38:35 +00:00
2018-04-30 21:57:21 +00:00
protected _properties: ClientProperties;
protected lastVariableUpdate: number = 0;
2019-08-21 08:00:01 +00:00
protected _speaking: boolean;
2019-04-04 19:47:52 +00:00
protected _listener_initialized: boolean;
2019-08-21 08:00:01 +00:00
2020-09-07 10:42:00 +00:00
protected voiceHandle: VoiceClient;
protected voiceVolume: number;
protected voiceMuted: boolean;
private readonly voiceCallbackStateChanged;
2018-02-27 16:20:49 +00:00
protected videoHandle: VideoClient;
2020-09-07 10:42:00 +00:00
private promiseClientInfo: Promise<void>;
private promiseClientInfoTimestamp: number;
2019-08-30 21:06:39 +00:00
2020-09-07 10:42:00 +00:00
private promiseConnectionInfo: Promise<ClientConnectionInfo>;
private promiseConnectionInfoTimestamp: number;
private promiseConnectionInfoResolve: any;
private promiseConnectionInfoReject: any;
2018-02-27 16:20:49 +00:00
2019-04-04 19:47:52 +00:00
constructor(clientId: number, clientName, properties: ClientProperties = new ClientProperties()) {
super();
this.events = new Registry<ClientEvents>();
2020-02-02 14:05:36 +00:00
2018-04-30 21:57:21 +00:00
this._properties = properties;
this._properties.client_nickname = clientName;
2018-02-27 16:20:49 +00:00
this._clientId = clientId;
this.channelTree = null;
this._channel = null;
2020-09-07 10:42:00 +00:00
this.voiceCallbackStateChanged = this.handleVoiceStateChange.bind(this);
this.events.on(["notify_speak_state_change", "notify_mute_state_change"], () => this.events.fire_later("notify_status_icon_changed", { newIcon: this.getStatusIcon() }));
this.events.on("notify_properties_updated", event => {
for (const key of StatusIconUpdateKeys) {
if (key in event.updated_properties) {
this.events.fire_later("notify_status_icon_changed", { newIcon: this.getStatusIcon() })
return;
}
}
});
2019-04-04 19:47:52 +00:00
}
2019-08-21 08:00:01 +00:00
destroy() {
2020-09-07 10:42:00 +00:00
if(this.voiceHandle) {
log.error(LogCategory.AUDIO, tr("Destroying client with an active audio handle. This could cause memory leaks!"));
this.setVoiceClient(undefined);
2019-08-21 08:00:01 +00:00
}
if(this.videoHandle) {
log.error(LogCategory.AUDIO, tr("Destroying client with an active video handle. This could cause memory leaks!"));
this.setVideoClient(undefined);
}
2019-08-21 08:00:01 +00:00
this._channel = undefined;
this.events.destroy();
2019-08-21 08:00:01 +00:00
}
2020-09-07 10:42:00 +00:00
setVoiceClient(handle: VoiceClient) {
if(this.voiceHandle === handle)
return;
if(this.voiceHandle) {
this.voiceHandle.events.off(this.voiceCallbackStateChanged);
2019-08-21 08:00:01 +00:00
}
2020-09-07 10:42:00 +00:00
this.voiceHandle = handle;
if(handle) {
this.voiceHandle.events.on("notify_state_changed", this.voiceCallbackStateChanged);
this.handleVoiceStateChange({ oldState: VoicePlayerState.STOPPED, newState: handle.getState() });
}
2019-08-21 08:00:01 +00:00
}
setVideoClient(handle: VideoClient) {
if(this.videoHandle === handle) {
return;
}
const oldHandle = this.videoHandle;
this.videoHandle = handle;
this.events.fire("notify_video_handle_changed", { oldHandle: oldHandle, newHandle: handle });
}
2020-09-07 10:42:00 +00:00
private handleVoiceStateChange(event: VoicePlayerEvents["notify_state_changed"]) {
switch (event.newState) {
case VoicePlayerState.PLAYING:
case VoicePlayerState.STOPPING:
this.speaking = true;
break;
case VoicePlayerState.STOPPED:
case VoicePlayerState.INITIALIZING:
this.speaking = false;
break;
2019-04-04 19:47:52 +00:00
}
2020-09-07 10:42:00 +00:00
}
2018-02-27 16:20:49 +00:00
2020-09-07 10:42:00 +00:00
private updateVoiceVolume() {
let volume = this.voiceMuted ? 0 : this.voiceVolume;
/* TODO: If a whisper session has been set, update this as well */
this.voiceHandle?.setVolume(volume);
2019-04-04 19:47:52 +00:00
}
2018-02-27 16:20:49 +00:00
2020-09-07 10:42:00 +00:00
getVoiceClient() : VoiceClient {
return this.voiceHandle;
2018-02-27 16:20:49 +00:00
}
getVideoClient() : VideoClient {
return this.videoHandle;
}
2018-04-30 21:57:21 +00:00
get properties() : ClientProperties {
return this._properties;
}
2020-09-24 13:51:22 +00:00
getStatusIcon() : ClientIcon {
if (this.properties.client_type_exact == ClientType.CLIENT_QUERY) {
return ClientIcon.ServerQuery;
} else if (this.properties.client_away) {
return ClientIcon.Away;
} else if (!this.getVoiceClient() && !(this instanceof LocalClientEntry)) {
return ClientIcon.InputMutedLocal;
} else if (!this.properties.client_output_hardware) {
return ClientIcon.HardwareOutputMuted;
} else if (this.properties.client_output_muted) {
return ClientIcon.OutputMuted;
} else if (!this.properties.client_input_hardware) {
return ClientIcon.HardwareInputMuted;
} else if (this.properties.client_input_muted) {
return ClientIcon.InputMuted;
} else {
if (this.isSpeaking()) {
if (this.properties.client_is_channel_commander) {
return ClientIcon.PlayerCommanderOn;
} else {
return ClientIcon.PlayerOn;
}
} else {
if (this.properties.client_is_channel_commander) {
return ClientIcon.PlayerCommanderOff;
} else {
return ClientIcon.PlayerOff;
}
}
}
}
2018-11-03 23:39:29 +00:00
currentChannel() : ChannelEntry { return this._channel; }
2018-02-27 16:20:49 +00:00
clientNickName(){ return this.properties.client_nickname; }
clientUid(){ return this.properties.client_unique_identifier; }
clientId(){ return this._clientId; }
2020-09-07 10:42:00 +00:00
isMuted() { return !!this.voiceMuted; }
/* TODO: Move this method to the view (e.g. channel tree) and rename with to setClientMuted */
setMuted(flagMuted: boolean, force: boolean) {
if(this.voiceMuted === flagMuted && !force) {
2019-08-21 08:00:01 +00:00
return;
2020-09-07 10:42:00 +00:00
}
2019-08-21 08:00:01 +00:00
2020-09-07 10:42:00 +00:00
if(flagMuted) {
2019-08-21 08:00:01 +00:00
this.channelTree.client.serverConnection.send_command('clientmute', {
clid: this.clientId()
2020-08-26 10:33:53 +00:00
}).then(() => {});
2020-09-07 10:42:00 +00:00
} else if(this.voiceMuted) {
2019-08-21 08:00:01 +00:00
this.channelTree.client.serverConnection.send_command('clientunmute', {
clid: this.clientId()
2020-08-26 10:33:53 +00:00
}).then(() => {});
2019-08-21 08:00:01 +00:00
}
2020-09-07 10:42:00 +00:00
this.voiceMuted = flagMuted;
2019-08-21 08:00:01 +00:00
2020-09-07 10:42:00 +00:00
this.channelTree.client.settings.changeServer(Settings.FN_CLIENT_MUTED(this.clientUid()), flagMuted);
this.updateVoiceVolume();
2019-08-21 08:00:01 +00:00
2020-09-07 10:42:00 +00:00
this.events.fire("notify_mute_state_change", { muted: flagMuted });
2019-08-21 08:00:01 +00:00
for(const client of this.channelTree.clients) {
if(client === this || client.properties.client_unique_identifier !== this.properties.client_unique_identifier)
2019-08-21 08:00:01 +00:00
continue;
2020-09-07 10:42:00 +00:00
client.setMuted(flagMuted, false);
2019-08-21 08:00:01 +00:00
}
}
2019-10-13 19:33:07 +00:00
protected initializeListener() {
if(this._listener_initialized) return;
this._listener_initialized = true;
//FIXME: TODO!
/*
2019-04-04 19:47:52 +00:00
this.tag.on('mousedown', event => {
if(event.which != 1) return; //Only the left button
2019-01-19 14:21:37 +00:00
let clients = this.channelTree.currently_selected as (ClientEntry | ClientEntry[]);
2019-01-20 17:43:14 +00:00
2020-03-30 11:44:18 +00:00
if(ppt.key_pressed(SpecialKey.SHIFT)) {
2019-01-20 17:43:14 +00:00
if(clients != this && !($.isArray(clients) && clients.indexOf(this) != -1))
clients = $.isArray(clients) ? [...clients, this] : [clients, this];
} else {
clients = this;
}
2019-01-19 14:21:37 +00:00
this.channelTree.client_mover.activate(clients, target => {
2018-11-03 23:39:29 +00:00
if(!target) return;
2019-01-19 14:21:37 +00: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 13:15:22 +00:00
this.channelTree.client.serverConnection.send_command("clientmove", {
2019-01-19 14:21:37 +00:00
clid: client.clientId(),
cid: target.getChannelId()
}).then(event => {
if(client.clientId() == this.channelTree.client.clientId)
2019-04-04 19:47:52 +00:00
this.channelTree.client.sound.play(Sound.CHANNEL_JOINED);
2019-01-19 14:21:37 +00:00
else if(target !== source && target != self.currentChannel())
2019-04-04 19:47:52 +00:00
this.channelTree.client.sound.play(Sound.USER_MOVED);
2019-01-19 14:21:37 +00:00
});
}
this.channelTree.onSelect();
2018-11-03 23:39:29 +00:00
}, event);
});
*/
}
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 16:20:49 +00:00
}
2019-08-21 08:00:01 +00: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)
}
]
}
protected assignment_context() : contextmenu.MenuEntry[] {
let server_groups: contextmenu.MenuEntry[] = [];
2018-09-30 19:50:59 +00:00
for(let group of this.channelTree.client.groups.serverGroups.sort(GroupManager.sorter())) {
2018-09-30 20:47:41 +00:00
if(group.type != GroupType.NORMAL) continue;
2018-09-30 19:50:59 +00:00
let entry: contextmenu.MenuEntry = {} as any;
2018-09-30 19:50:59 +00:00
//TODO: May add the server group icon?
entry.checkbox_checked = this.groupAssigned(group);
2018-09-30 19:50:59 +00:00
entry.name = group.name + " [" + (group.properties.savedb ? "perm" : "tmp") + "]";
if(this.groupAssigned(group)) {
entry.callback = () => {
2019-02-23 13:15:22 +00:00
this.channelTree.client.serverConnection.send_command("servergroupdelclient", {
2018-09-30 19:50:59 +00:00
sgid: group.id,
cldbid: this.properties.client_database_id
2020-08-26 10:33:53 +00:00
}).then(() => {});
2018-09-30 19:50:59 +00:00
};
entry.disabled = !this.channelTree.client.permissions.neededPermission(PermissionType.I_GROUP_MEMBER_ADD_POWER).granted(group.requiredMemberRemovePower);
} else {
entry.callback = () => {
2019-02-23 13:15:22 +00:00
this.channelTree.client.serverConnection.send_command("servergroupaddclient", {
2018-09-30 19:50:59 +00:00
sgid: group.id,
cldbid: this.properties.client_database_id
2020-08-26 10:33:53 +00:00
}).then(() => {});
2018-09-30 19:50:59 +00:00
};
entry.disabled = !this.channelTree.client.permissions.neededPermission(PermissionType.I_GROUP_MEMBER_REMOVE_POWER).granted(group.requiredMemberAddPower);
}
entry.type = contextmenu.MenuEntryType.CHECKBOX;
2018-09-30 19:50:59 +00:00
server_groups.push(entry);
}
let channel_groups: contextmenu.MenuEntry[] = [];
2018-09-30 19:50:59 +00:00
for(let group of this.channelTree.client.groups.channelGroups.sort(GroupManager.sorter())) {
if(group.type != GroupType.NORMAL) continue;
let entry: contextmenu.MenuEntry = {} as any;
//TODO: May add the channel group icon?
entry.checkbox_checked = this.assignedChannelGroup() == group.id;
2018-09-30 19:50:59 +00:00
entry.name = group.name + " [" + (group.properties.savedb ? "perm" : "tmp") + "]";
entry.callback = () => {
2019-02-23 13:15:22 +00:00
this.channelTree.client.serverConnection.send_command("setclientchannelgroup", {
2018-09-30 19:50:59 +00:00
cldbid: this.properties.client_database_id,
cgid: group.id,
cid: this.currentChannel().channelId
2020-08-26 10:33:53 +00:00
}).then(() => {});
2018-09-30 19:50:59 +00:00
};
entry.disabled = !this.channelTree.client.permissions.neededPermission(PermissionType.I_GROUP_MEMBER_ADD_POWER).granted(group.requiredMemberRemovePower);
entry.type = contextmenu.MenuEntryType.CHECKBOX;
2018-09-30 19:50:59 +00:00
channel_groups.push(entry);
}
return [{
type: contextmenu.MenuEntryType.SUB_MENU,
icon_class: "client-permission_server_groups",
name: tr("Set server group"),
2018-09-30 19:50:59 +00:00
sub_menu: [
{
type: contextmenu.MenuEntryType.ENTRY,
2019-08-21 08:00:01 +00:00
icon_class: "client-permission_server_groups",
2018-09-30 19:50:59 +00:00
name: "Server groups dialog",
2019-09-18 23:25:57 +00:00
callback: () => this.open_assignment_modal()
2018-09-30 19:50:59 +00:00
},
contextmenu.Entry.HR(),
2018-09-30 19:50:59 +00:00
...server_groups
]
},{
type: contextmenu.MenuEntryType.SUB_MENU,
icon_class: "client-permission_channel",
name: tr("Set channel group"),
2018-09-30 19:50:59 +00:00
sub_menu: [
...channel_groups
]
},{
type: contextmenu.MenuEntryType.SUB_MENU,
icon_class: "client-permission_client",
name: tr("Permissions"),
2019-08-21 08:00:01 +00:00
sub_menu: [
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-permission_client",
name: tr("Client permissions"),
2020-06-15 14:56:05 +00:00
callback: () => spawnPermissionEditorModal(this.channelTree.client, "client", { clientDatabaseId: this.properties.client_database_id })
2019-08-21 08:00:01 +00:00
},
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-permission_client",
name: tr("Client channel permissions"),
2020-06-15 14:56:05 +00:00
callback: () => spawnPermissionEditorModal(this.channelTree.client, "client-channel", { clientDatabaseId: this.properties.client_database_id })
2019-08-21 08:00:01 +00:00
}
]
2018-09-30 19:50:59 +00:00
}];
}
2019-09-18 23:25:57 +00:00
open_assignment_modal() {
2020-03-30 11:44:18 +00:00
createServerGroupAssignmentModal(this, (groups, flag) => {
2019-09-18 23:25:57 +00: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
2020-08-26 10:33:53 +00:00
}).then(() => true);
2019-09-18 23:25:57 +00:00
} else
return this.channelTree.client.serverConnection.send_command("servergroupdelclient", {
sgid: groups[0],
cldbid: this.properties.client_database_id
2020-08-26 10:33:53 +00:00
}).then(() => true);
2019-09-18 23:25:57 +00:00
} else {
const data = groups.map(e => { return {sgid: e}; });
data[0]["cldbid"] = this.properties.client_database_id;
if(flag) {
2020-08-26 10:33:53 +00:00
return this.channelTree.client.serverConnection.send_command("clientaddservergroup", data, {flagset: ["continueonerror"]}).then(() => true);
2019-09-18 23:25:57 +00:00
} else
2020-08-26 10:33:53 +00:00
return this.channelTree.client.serverConnection.send_command("clientdelservergroup", data, {flagset: ["continueonerror"]}).then(() => true);
2019-09-18 23:25:57 +00:00
}
});
}
2019-08-21 08:00:01 +00:00
open_text_chat() {
const chat = this.channelTree.client.side_bar;
2020-07-17 21:56:20 +00:00
const conversation = chat.private_conversations().findOrCreateConversation(this);
conversation.setActiveClientEntry(this);
chat.private_conversations().setActiveConversation(conversation);
2019-08-21 08:00:01 +00:00
chat.show_private_conversations();
2020-07-17 21:56:20 +00:00
chat.private_conversations().focusInput();
2019-08-21 08:00:01 +00:00
}
2018-02-27 16:20:49 +00:00
showContextMenu(x: number, y: number, on_close: () => void = undefined) {
2020-08-08 13:20:32 +00:00
const w2gPlugin = this.channelTree.client.getPluginCmdRegistry().getPluginHandler<W2GPluginCmdHandler>(W2GPluginCmdHandler.kPluginChannel);
2019-03-07 14:30:53 +00:00
let trigger_close = true;
contextmenu.spawn_context_menu(x, y,
2019-08-21 08:00:01 +00:00
...this.contextmenu_info(), {
type: contextmenu.MenuEntryType.ENTRY,
icon_class: ClientIcon.ChangeNickname,
name: (contextmenu.get_provider().html_format_enabled() ? "<b>" : "") +
tr("Open text chat") +
(contextmenu.get_provider().html_format_enabled() ? "</b>" : ""),
2019-03-07 14:30:53 +00:00
callback: () => {
2019-08-21 08:00:01 +00:00
this.open_text_chat();
2018-02-27 16:20:49 +00:00
}
2020-08-08 13:20:32 +00:00
}, {
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Watch clients video"),
icon_class: ClientIcon.W2g,
2020-08-08 13:20:32 +00:00
visible: w2gPlugin?.getCurrentWatchers().findIndex(e => e.clientId === this.clientId()) !== -1,
callback: () => {
global_client_actions.fire("action_w2g", {
following: this.clientId(),
handlerId: this.channelTree.client.handlerId
});
}
2019-09-18 23:25:57 +00:00
},
contextmenu.Entry.HR(),
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: ClientIcon.About,
2019-09-18 23:25:57 +00:00
name: tr("Show client info"),
2020-03-30 11:44:18 +00:00
callback: () => openClientInfo(this)
2019-09-18 23:25:57 +00:00
},
contextmenu.Entry.HR(),
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: ClientIcon.Poke,
name: tr("Poke client"),
2019-03-07 14:30:53 +00:00
callback: () => {
2020-08-26 10:33:53 +00:00
createInputModal(tr("Poke client"), tr("Poke message:<br>"), () => true, result => {
2018-09-30 20:36:17 +00:00
if(typeof(result) === "string") {
2019-03-07 14:30:53 +00:00
this.channelTree.client.serverConnection.send_command("clientpoke", {
clid: this.clientId(),
2018-02-27 16:20:49 +00:00
msg: result
}).then(() => {
this.channelTree.client.log.log(EventType.CLIENT_POKE_SEND, {
target: this.log_data(),
message: result
});
2018-02-27 16:20:49 +00:00
});
}
}, { width: 400, maxLength: 512 }).open();
}
}, {
type: contextmenu.MenuEntryType.ENTRY,
icon_class: ClientIcon.Edit,
name: tr("Change description"),
2019-03-07 14:30:53 +00:00
callback: () => {
2020-08-26 10:33:53 +00:00
createInputModal(tr("Change client description"), tr("New description:<br>"), () => true, result => {
2018-09-30 20:36:17 +00:00
if(typeof(result) === "string") {
2019-03-07 14:30:53 +00:00
this.channelTree.client.serverConnection.send_command("clientedit", {
clid: this.clientId(),
2018-02-27 16:20:49 +00:00
client_description: result
2020-08-26 10:33:53 +00:00
}).then(() => {});
2018-02-27 16:20:49 +00:00
}
}, { width: 400, maxLength: 1024 }).open();
}
},
contextmenu.Entry.HR(),
2018-09-30 19:50:59 +00:00
...this.assignment_context(),
contextmenu.Entry.HR(), {
type: contextmenu.MenuEntryType.ENTRY,
icon_class: ClientIcon.MoveClientToOwnChannel,
name: tr("Move client to your channel"),
2018-04-11 15:56:09 +00:00
callback: () => {
2019-02-23 13:15:22 +00:00
this.channelTree.client.serverConnection.send_command("clientmove", {
2018-04-11 15:56:09 +00:00
clid: this.clientId(),
cid: this.channelTree.client.getClient().currentChannel().getChannelId()
2020-08-26 10:33:53 +00:00
}).then(() => {});
2018-04-11 15:56:09 +00:00
}
}, {
type: contextmenu.MenuEntryType.ENTRY,
icon_class: ClientIcon.KickChannel,
name: tr("Kick client from channel"),
callback: () => {
2020-08-26 10:33:53 +00:00
createInputModal(tr("Kick client from channel"), tr("Kick reason:<br>"), () => true, result => {
2019-03-17 11:39:21 +00:00
if(typeof(result) !== 'boolean' || result) {
2020-08-26 10:33:53 +00:00
logInfo(LogCategory.CLIENT, tr("Kicking client %s from channel with reason %s"), this.clientNickName(), result);
2019-03-07 14:30:53 +00:00
this.channelTree.client.serverConnection.send_command("clientkick", {
clid: this.clientId(),
2018-02-27 16:20:49 +00:00
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
reasonmsg: result
2020-08-26 10:33:53 +00:00
}).then(() => {});
2018-02-27 16:20:49 +00:00
}
}, { width: 400, maxLength: 255 }).open();
}
}, {
type: contextmenu.MenuEntryType.ENTRY,
icon_class: ClientIcon.KickServer,
name: tr("Kick client fom server"),
callback: () => {
2020-08-26 10:33:53 +00:00
createInputModal(tr("Kick client from server"), tr("Kick reason:<br>"), () => true, result => {
2019-03-17 11:39:21 +00:00
if(typeof(result) !== 'boolean' || result) {
2020-08-26 10:33:53 +00:00
logInfo(LogCategory.CLIENT, tr("Kicking client %s from server with reason %s"), this.clientNickName(), result);
2019-03-07 14:30:53 +00:00
this.channelTree.client.serverConnection.send_command("clientkick", {
clid: this.clientId(),
2018-02-27 16:20:49 +00:00
reasonid: ViewReasonId.VREASON_SERVER_KICK,
reasonmsg: result
2020-08-26 10:33:53 +00:00
}).then(() => {});
2018-02-27 16:20:49 +00:00
}
}, { width: 400, maxLength: 255 }).open();
}
2018-04-11 15:56:09 +00:00
}, {
type: contextmenu.MenuEntryType.ENTRY,
icon_class: ClientIcon.BanClient,
name: tr("Ban client"),
2018-04-30 21:57:21 +00:00
invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
callback: () => {
2020-03-30 11:44:18 +00:00
spawnBanClient(this.channelTree.client, [{
2019-09-18 23:25:57 +00:00
name: this.properties.client_nickname,
unique_id: this.properties.client_unique_identifier
}], (data) => {
2019-02-23 13:15:22 +00:00
this.channelTree.client.serverConnection.send_command("banclient", {
2018-04-30 21:57:21 +00:00
uid: this.properties.client_unique_identifier,
banreason: data.reason,
time: data.length
2019-02-23 13:15:22 +00:00
}, {
flagset: [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]
}).then(() => {
2019-04-04 19:47:52 +00:00
this.channelTree.client.sound.play(Sound.USER_BANNED);
2018-11-03 23:39:29 +00:00
});
2018-04-30 21:57:21 +00:00
});
}
2018-04-11 15:56:09 +00:00
},
contextmenu.Entry.HR(),
2018-09-26 13:04:56 +00: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 13:15:22 +00:00
this.channelTree.client.serverConnection.send_command("banclient", {
2018-09-26 13:04:56 +00:00
uid: this.properties.client_unique_identifier,
banreason: reason,
time: duration
});
});
}
},
MenuEntry.HR(),
*/
2018-04-11 15:56:09 +00:00
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: ClientIcon.Volume,
name: tr("Change Volume"),
callback: () => spawnClientVolumeChange(this)
},
{
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Change playback latency"),
callback: () => {
2020-09-07 10:42:00 +00:00
spawnChangeLatency(this, this.voiceHandle.getLatencySettings(), () => {
this.voiceHandle.resetLatencySettings();
return this.voiceHandle.getLatencySettings();
}, settings => this.voiceHandle.setLatencySettings(settings), () => this.voiceHandle.flushBuffer());
},
2020-09-07 10:42:00 +00:00
visible: !!this.voiceHandle
2019-08-21 08:00:01 +00:00
}, {
type: contextmenu.MenuEntryType.ENTRY,
icon_class: ClientIcon.InputMutedLocal,
2019-08-21 08:00:01 +00:00
name: tr("Mute client"),
2020-09-07 10:42:00 +00:00
visible: !this.voiceMuted,
callback: () => this.setMuted(true, false)
2019-08-21 08:00:01 +00:00
}, {
type: contextmenu.MenuEntryType.ENTRY,
icon_class: ClientIcon.InputMutedLocal,
2019-08-21 08:00:01 +00:00
name: tr("Unmute client"),
2020-09-07 10:42:00 +00:00
visible: this.voiceMuted,
callback: () => this.setMuted(false, false)
2018-02-27 16:20:49 +00:00
},
2020-02-02 14:05:36 +00:00
contextmenu.Entry.CLOSE(() => trigger_close && on_close ? on_close() : {})
2018-02-27 16:20:49 +00:00
);
}
2019-01-20 17:43:14 +00:00
static bbcodeTag(id: number, name: string, uid: string) : string {
return "[url=client://" + id + "/" + uid + "~" + encodeURIComponent(name) + "]" + name + "[/url]";
}
2018-04-30 21:57:21 +00:00
2019-01-20 17:43:14 +00: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 21:57:21 +00:00
2019-01-20 17:43:14 +00:00
create_bbcode() : string {
return ClientEntry.bbcodeTag(this.clientId(), this.clientNickName(), this.clientUid());
2018-02-27 16:20:49 +00:00
}
2018-04-11 15:56:09 +00:00
createChatTag(braces: boolean = false) : JQuery {
2018-02-27 16:20:49 +00:00
return ClientEntry.chatTag(this.clientId(), this.clientNickName(), this.clientUid(), braces);
}
set speaking(flag) {
2019-08-21 08:00:01 +00:00
if(flag === this._speaking) return;
2018-02-27 16:20:49 +00:00
this._speaking = flag;
this.events.fire("notify_speak_state_change", { speaking: flag });
2018-02-27 16:20:49 +00:00
}
isSpeaking() { return this._speaking; }
2018-02-27 16:20:49 +00:00
2018-04-16 18:38:35 +00:00
updateVariables(...variables: {key: string, value: string}[]) {
let reorder_channel = false;
2019-08-21 08:00:01 +00:00
let update_avatar = false;
/* 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 19:04:04 +00: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 21:06:39 +00:00
log.table(LogType.DEBUG, LogCategory.PERMISSIONS, "Client update properties", entries);
2019-03-25 19:04:04 +00:00
}
/* devel-block-end */
2019-03-25 19:04:04 +00:00
2019-04-04 19:47:52 +00:00
for(const variable of variables) {
2019-08-21 08:00:01 +00:00
const old_value = this._properties[variable.key];
2018-08-10 19:30:58 +00:00
JSON.map_field_to(this._properties, variable.value, variable.key);
2018-04-16 18:38:35 +00:00
if(variable.key == "client_nickname") {
2019-08-21 08:00:01 +00:00
if(variable.value !== old_value && typeof(old_value) === "string") {
if(!(this instanceof LocalClientEntry)) { /* own changes will be logged somewhere else */
this.channelTree.client.log.log(EventType.CLIENT_NICKNAME_CHANGED, {
2019-08-21 08:00:01 +00:00
client: this.log_data(),
new_name: variable.value,
old_name: old_value
});
}
}
reorder_channel = true;
2018-04-16 18:38:35 +00:00
}
if(variable.key == "client_unique_identifier") {
2020-09-07 10:42:00 +00:00
this.voiceVolume = this.channelTree.client.settings.server(Settings.FN_CLIENT_VOLUME(this.clientUid()), 1);
2020-07-19 16:49:00 +00:00
const mute_status = this.channelTree.client.settings.server(Settings.FN_CLIENT_MUTED(this.clientUid()), false);
2020-09-07 10:42:00 +00:00
this.setMuted(mute_status, mute_status); /* force only needed when we want to mute the client */
this.updateVoiceVolume();
log.debug(LogCategory.CLIENT, tr("Loaded client (%s) server specific properties. Volume: %o Muted: %o."), this.clientUid(), this.voiceVolume, this.voiceMuted);
2018-04-16 18:38:35 +00:00
}
if(variable.key == "client_talk_power") {
reorder_channel = true;
//update_icon_status = true; DONE
}
2019-04-25 18:21:50 +00:00
if(variable.key == "client_icon_id") {
/* yeah we like javascript. Due to JS wiered integer behaviour parsing for example fails for 18446744073409829863.
* 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 08:00:01 +00:00
else if(variable.key == "client_flag_avatar")
update_avatar = true;
2018-04-11 15:56:09 +00:00
}
2018-04-16 18:38:35 +00:00
/* process updates after variables have been set */
2020-08-22 15:50:38 +00:00
const side_bar = this.channelTree?.client?.side_bar;
if(side_bar) {
2019-08-21 08:00:01 +00:00
const client_info = side_bar.client_info();
if(client_info.current_client() === this)
client_info.set_current_client(this, true); /* force an update */
}
2020-07-17 21:56:20 +00:00
if(update_avatar)
2020-08-22 15:50:38 +00:00
this.channelTree.client?.fileManager?.avatars.updateCache(this.avatarId(), this.properties.client_flag_avatar);
2019-08-21 08:00:01 +00:00
/* devel-block(log-client-property-updates) */
2018-04-16 18:38:35 +00:00
group.end();
/* devel-block-end */
2018-12-17 20:21:52 +00: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 20:21:52 +00:00
}
2018-09-30 20:36:17 +00:00
}
2019-08-30 21:06:39 +00:00
updateClientVariables(force_update?: boolean) : Promise<void> {
2020-09-07 10:42:00 +00:00
if(Date.now() - 10 * 60 * 1000 < this.promiseClientInfoTimestamp && this.promiseClientInfo && (typeof(force_update) !== "boolean" || force_update))
return this.promiseClientInfo;
2019-08-30 21:06:39 +00:00
2020-09-07 10:42:00 +00:00
this.promiseClientInfoTimestamp = Date.now();
return (this.promiseClientInfo = new Promise<void>((resolve, reject) => {
2019-08-30 21:06:39 +00:00
this.channelTree.client.serverConnection.send_command("clientgetvariables", {clid: this.clientId()}).then(() => resolve()).catch(error => {
2020-09-07 10:42:00 +00:00
this.promiseConnectionInfoTimestamp = 0; /* not succeeded */
2019-08-30 21:06:39 +00:00
reject(error);
});
}));
2018-02-27 16:20:49 +00: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 18:38:35 +00:00
return this.properties.client_channel_group_id;
2018-02-27 16:20:49 +00: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 19:47:52 +00:00
onDelete() { }
2018-02-27 16:20:49 +00:00
calculateOnlineTime() : number {
2018-04-30 21:57:21 +00:00
return Date.now() / 1000 - this.properties.client_lastconnected;
2018-04-16 18:38:35 +00: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 14:38:38 +00:00
for (let i=0, strLen = str.length; i<strLen; i++) {
2018-04-16 18:38:35 +00: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 16:20:49 +00:00
}
Implemented the Material Design and fixed some bugs (#33) * cleaned up some files * Fundamental style update * Redesigned some style * fixed hostbanner popup * Removed old identity stuff * fixed close listener * Fixed changelog date * fixed release chat icons * fixed url * Fixed hostbanner * Uploaded missing images * Improved update handling * Improved script files * Fixed loading error and icon error * fixed Yes/No modal * Fixed loader issues with MS Edge * fixed modal style bug * Fixed control bar overflow for small devices * Improved error handling on identity creation * Logging generate error to terminal * fixed possible php error * fixed some possible loading errors when other files have'nt been already loaded. * removed debug message * Changed emsrcypten flags * Improved codec error handling * removed webassembly as required dependency * Improved and fixed channel tree issues * Improved the sliders * Removed unneeded files * fixed loader versions cache * second slight performance improved (dont animate elements anymore if they are not shown) * Fixed query visibility setting * not showing useless client infos for query clients * Added an auto reconnect system * Added a canceled message and increased reconnect interval * removed implemented todo * fixed repetitive channel names * Reworked the channel tree selected lines * Fixed channel tree names * Fixed name alignment * fixed the native client * added min width to the server select groups to avoid a disappearing effect on shrink * fixed bugged downloaded icons
2019-02-17 15:08:10 +00:00
log_data() : EventClient {
return {
client_unique_id: this.properties.client_unique_identifier,
client_name: this.clientNickName(),
client_id: this._clientId
}
Implemented the Material Design and fixed some bugs (#33) * cleaned up some files * Fundamental style update * Redesigned some style * fixed hostbanner popup * Removed old identity stuff * fixed close listener * Fixed changelog date * fixed release chat icons * fixed url * Fixed hostbanner * Uploaded missing images * Improved update handling * Improved script files * Fixed loading error and icon error * fixed Yes/No modal * Fixed loader issues with MS Edge * fixed modal style bug * Fixed control bar overflow for small devices * Improved error handling on identity creation * Logging generate error to terminal * fixed possible php error * fixed some possible loading errors when other files have'nt been already loaded. * removed debug message * Changed emsrcypten flags * Improved codec error handling * removed webassembly as required dependency * Improved and fixed channel tree issues * Improved the sliders * Removed unneeded files * fixed loader versions cache * second slight performance improved (dont animate elements anymore if they are not shown) * Fixed query visibility setting * not showing useless client infos for query clients * Added an auto reconnect system * Added a canceled message and increased reconnect interval * removed implemented todo * fixed repetitive channel names * Reworked the channel tree selected lines * Fixed channel tree names * Fixed name alignment * fixed the native client * added min width to the server select groups to avoid a disappearing effect on shrink * fixed bugged downloaded icons
2019-02-17 15:08:10 +00:00
}
2019-08-30 21:06:39 +00:00
/* max 1s ago, so we could update every second */
request_connection_info() : Promise<ClientConnectionInfo> {
2020-09-07 10:42:00 +00:00
if(Date.now() - 900 < this.promiseConnectionInfoTimestamp && this.promiseConnectionInfo)
return this.promiseConnectionInfo;
2019-08-30 21:06:39 +00:00
2020-09-07 10:42:00 +00:00
if(this.promiseConnectionInfoReject)
this.promiseConnectionInfoResolve("timeout");
2019-08-30 21:06:39 +00:00
let _local_reject; /* to ensure we're using the right resolve! */
2020-09-07 10:42:00 +00:00
this.promiseConnectionInfo = new Promise<ClientConnectionInfo>((resolve, reject) => {
this.promiseConnectionInfoResolve = resolve;
this.promiseConnectionInfoReject = reject;
2019-08-30 21:06:39 +00:00
_local_reject = reject;
});
2020-09-07 10:42:00 +00:00
this.promiseConnectionInfoTimestamp = Date.now();
2019-08-30 21:06:39 +00:00
this.channelTree.client.serverConnection.send_command("getconnectioninfo", {clid: this._clientId}).catch(error => _local_reject(error));
2020-09-07 10:42:00 +00:00
return this.promiseConnectionInfo;
2019-08-30 21:06:39 +00:00
}
set_connection_info(info: ClientConnectionInfo) {
2020-09-07 10:42:00 +00:00
if(!this.promiseConnectionInfoResolve)
2019-08-30 21:06:39 +00:00
return;
2020-09-07 10:42:00 +00:00
this.promiseConnectionInfoResolve(info);
this.promiseConnectionInfoResolve = undefined;
this.promiseConnectionInfoReject = undefined;
2019-08-30 21:06:39 +00:00
}
setAudioVolume(value: number) {
2020-09-07 10:42:00 +00:00
if(this.voiceVolume == value)
return;
2020-09-07 10:42:00 +00:00
this.voiceVolume = value;
2020-09-07 10:42:00 +00:00
this.updateVoiceVolume();
2020-07-19 16:49:00 +00:00
this.channelTree.client.settings.changeServer(Settings.FN_CLIENT_VOLUME(this.clientUid()), value);
this.events.fire("notify_audio_level_changed", { newValue: value });
}
getAudioVolume() {
2020-09-07 10:42:00 +00:00
return this.voiceVolume;
}
2018-02-27 16:20:49 +00:00
}
2020-03-30 11:44:18 +00:00
export class LocalClientEntry extends ClientEntry {
2019-04-04 19:47:52 +00:00
handle: ConnectionHandler;
2018-02-27 16:20:49 +00:00
2019-04-04 19:47:52 +00:00
constructor(handle: ConnectionHandler) {
2018-05-09 09:50:05 +00:00
super(0, "local client");
2018-02-27 16:20:49 +00:00
this.handle = handle;
}
showContextMenu(x: number, y: number, on_close: () => void = undefined): void {
contextmenu.spawn_context_menu(x, y,
2019-08-21 08:00:01 +00:00
...this.contextmenu_info(), {
name: (contextmenu.get_provider().html_format_enabled() ? "<b>" : "") +
tr("Change name") +
(contextmenu.get_provider().html_format_enabled() ? "</b>" : ""),
icon_class: "client-change_nickname",
2020-09-26 19:34:46 +00:00
callback: () => this.openRenameModal(), /* FIXME: Pass the UI event registry */
type: contextmenu.MenuEntryType.ENTRY
2018-02-27 16:20:49 +00:00
}, {
name: tr("Change description"),
icon_class: "client-edit",
2018-02-27 16:20:49 +00:00
callback: () => {
2020-08-26 10:33:53 +00:00
createInputModal(tr("Change own description"), tr("New description:<br>"), () => true, result => {
2018-02-27 16:20:49 +00:00
if(result) {
2020-08-26 10:33:53 +00:00
logInfo(LogCategory.CLIENT, tr("Changing own description to %s"), result);
this.channelTree.client.serverConnection.send_command("clientedit", {
clid: this.clientId(),
2018-02-27 16:20:49 +00:00
client_description: result
2020-08-26 10:33:53 +00:00
}).then(() => {});
2018-02-27 16:20:49 +00:00
}
}, { width: 400, maxLength: 1024 }).open();
},
type: contextmenu.MenuEntryType.ENTRY
2018-02-27 16:20:49 +00:00
},
contextmenu.Entry.HR(),
2018-09-30 20:58:53 +00:00
...this.assignment_context(),
contextmenu.Entry.CLOSE(on_close)
2018-02-27 16:20:49 +00:00
);
}
initializeListener(): void {
super.initializeListener();
}
2018-02-27 16:20:49 +00: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 */
2020-08-26 10:33:53 +00:00
return this.handle.serverConnection.send_command("clientupdate", { client_nickname: new_name }).then(() => {
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, new_name);
this.channelTree.client.log.log(EventType.CLIENT_NICKNAME_CHANGED_OWN, {
client: this.log_data(),
old_name: old_name,
new_name: new_name,
});
return true;
}).catch((e: CommandResult) => {
this.updateVariables({ key: "client_nickname", value: old_name }); /* change it back */
this.channelTree.client.log.log(EventType.CLIENT_NICKNAME_CHANGE_FAILED, {
reason: e.extra_message
});
return false;
2018-02-27 16:20:49 +00:00
});
}
2020-09-26 19:34:46 +00:00
openRenameModal() {
createInputModal(tr("Enter your new name"), tr("Enter your new client name"), text => text.length >= 3 && text.length <= 30, value => {
if(value) {
this.renameSelf(value as string).then(result => {
if(!result) {
createErrorModal(tr("Failed change nickname"), tr("Failed to change your client nickname")).open();
}
});
2018-02-27 16:20:49 +00:00
}
2020-09-26 19:34:46 +00:00
}).open();
}
2018-02-27 16:20:49 +00:00
2020-09-26 19:34:46 +00:00
openRename(events: Registry<ChannelTreeUIEvents>) : void {
events.fire("notify_client_name_edit", { initialValue: this.clientNickName(), treeEntryId: this.uniqueEntryId });
2018-02-27 16:20:49 +00:00
}
}
2020-09-07 10:42:00 +00:00
export enum MusicClientPlayerState {
SLEEPING,
LOADING,
PLAYING,
PAUSED,
STOPPED
}
2020-03-30 11:44:18 +00:00
export class MusicClientProperties extends ClientProperties {
2020-09-07 10:42:00 +00:00
player_state: number = 0; /* MusicClientPlayerState */
2018-08-10 19:30:58 +00:00
player_volume: number = 0;
2019-01-20 17:43:14 +00:00
client_playlist_id: number = 0;
client_disabled: boolean = false;
2020-02-22 13:30:17 +00:00
client_flag_notify_song_change: boolean = false;
client_bot_type: number = 0;
client_uptime_mode: number = 0;
2018-08-10 19:30:58 +00:00
}
2020-03-30 11:44:18 +00:00
export class SongInfo {
2020-02-02 14:05:36 +00: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 11:44:18 +00:00
export class MusicClientPlayerInfo extends SongInfo {
bot_id: number = 0;
2018-08-10 19:30:58 +00: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 = "";
}
2020-03-30 11:44:18 +00:00
export class MusicClientEntry extends ClientEntry {
2018-08-10 19:30:58 +00:00
private _info_promise: Promise<MusicClientPlayerInfo>;
private _info_promise_age: number = 0;
private _info_promise_resolve: any;
private _info_promise_reject: any;
constructor(clientId, clientName) {
super(clientId, clientName, new MusicClientProperties());
}
2019-08-21 08:00:01 +00:00
destroy() {
super.destroy();
this._info_promise = undefined;
this._info_promise_reject = undefined;
this._info_promise_resolve = undefined;
}
get properties() : MusicClientProperties {
return this._properties as MusicClientProperties;
}
showContextMenu(x: number, y: number, on_close: () => void = undefined): void {
2019-03-07 14:30:53 +00:00
let trigger_close = true;
contextmenu.spawn_context_menu(x, y,
2019-08-21 08:00:01 +00:00
...this.contextmenu_info(), {
2019-10-13 19:33:07 +00:00
name: (contextmenu.get_provider().html_format_enabled() ? "<b>" : "") +
tr("Change bot name") +
(contextmenu.get_provider().html_format_enabled() ? "</b>" : ""),
icon_class: "client-change_nickname",
disabled: false,
callback: () => {
createInputModal(tr("Change music bots nickname"), tr("New nickname:<br>"), text => text.length >= 3 && text.length <= 31, result => {
if(result) {
2019-02-23 13:15:22 +00:00
this.channelTree.client.serverConnection.send_command("clientedit", {
clid: this.clientId(),
client_nickname: result
2020-08-26 10:33:53 +00:00
}).then(() => {});
}
2019-10-13 19:33:07 +00:00
}, { width: "40em", min_width: "10em", maxLength: 255 }).open();
},
type: contextmenu.MenuEntryType.ENTRY
}, {
name: tr("Change bot description"),
icon_class: "client-edit",
disabled: false,
callback: () => {
2020-08-26 10:33:53 +00:00
createInputModal(tr("Change music bots description"), tr("New description:<br>"), () => true, result => {
if(typeof(result) === 'string') {
2019-02-23 13:15:22 +00:00
this.channelTree.client.serverConnection.send_command("clientedit", {
clid: this.clientId(),
client_description: result
2020-08-26 10:33:53 +00:00
}).then(() => {});
}
2019-10-13 19:33:07 +00:00
}, { width: "60em", min_width: "10em", maxLength: 255 }).open();
},
type: contextmenu.MenuEntryType.ENTRY
2019-01-20 17:43:14 +00:00
},
/*
{
name: tr("Open music panel"),
icon: "client-edit",
disabled: true,
callback: () => {},
type: MenuEntryType.ENTRY
2019-01-20 17:43:14 +00:00
},
*/
{
name: tr("Quick url replay"),
icon_class: "client-edit",
disabled: false,
callback: () => {
2020-08-26 10:33:53 +00:00
createInputModal(tr("Please enter the URL"), tr("URL:"), () => true, result => {
if(result) {
2019-02-23 13:15:22 +00:00
this.channelTree.client.serverConnection.send_command("musicbotqueueadd", {
bot_id: this.properties.client_database_id,
type: "yt", //Its a hint not a force!
url: result
}).catch(error => {
if(error instanceof CommandResult) {
error = error.extra_message || error.message;
}
//TODO tr
createErrorModal(tr("Failed to replay url"), "Failed to enqueue url:<br>" + error).open();
});
}
}, { width: 400, maxLength: 255 }).open();
},
type: contextmenu.MenuEntryType.ENTRY
},
contextmenu.Entry.HR(),
2018-09-30 19:50:59 +00:00
...super.assignment_context(),
contextmenu.Entry.HR(),{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-move_client_to_own_channel",
name: tr("Move client to your channel"),
2018-11-03 23:39:29 +00:00
callback: () => {
2019-02-23 13:15:22 +00:00
this.channelTree.client.serverConnection.send_command("clientmove", {
2018-11-03 23:39:29 +00:00
clid: this.clientId(),
cid: this.channelTree.client.getClient().currentChannel().getChannelId()
2020-08-26 10:33:53 +00:00
}).then(() => {});
2018-11-03 23:39:29 +00:00
}
}, {
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-kick_channel",
name: tr("Kick client from channel"),
2018-11-03 23:39:29 +00:00
callback: () => {
2020-08-26 10:33:53 +00:00
createInputModal(tr("Kick client from channel"), tr("Kick reason:<br>"), () => true, result => {
2019-03-17 11:39:21 +00:00
if(typeof(result) !== 'boolean' || result) {
2020-08-26 10:33:53 +00:00
logInfo(LogCategory.CLIENT, tr("Kicking client %o from channel with reason %o"), this.clientNickName(), result);
2019-02-23 13:15:22 +00:00
this.channelTree.client.serverConnection.send_command("clientkick", {
2018-11-03 23:39:29 +00:00
clid: this.clientId(),
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
reasonmsg: result
2020-08-26 10:33:53 +00:00
}).then(() => {});
2018-11-03 23:39:29 +00:00
}
}, { width: 400, maxLength: 255 }).open();
}
},
contextmenu.Entry.HR(),
2018-11-03 23:39:29 +00:00
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-volume",
2019-01-20 17:43:14 +00:00
name: tr("Change local volume"),
callback: () => spawnClientVolumeChange(this)
2019-01-20 17:43:14 +00:00
},
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-volume",
2019-01-20 17:43:14 +00: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) {
2019-01-20 17:43:14 +00:00
max_volume = 100;
}
2019-01-20 17:43:14 +00:00
spawnMusicBotVolumeChange(this, max_volume / 100);
2018-11-03 23:39:29 +00:00
}
},
{
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Change playback latency"),
callback: () => {
2020-09-07 10:42:00 +00:00
spawnChangeLatency(this, this.voiceHandle.getLatencySettings(), () => {
this.voiceHandle.resetLatencySettings();
return this.voiceHandle.getLatencySettings();
}, settings => this.voiceHandle.setLatencySettings(settings), () => this.voiceHandle.flushBuffer());
},
2020-09-07 10:42:00 +00:00
visible: !!this.voiceHandle
},
contextmenu.Entry.HR(),
{
name: tr("Delete bot"),
icon_class: "client-delete",
disabled: false,
callback: () => {
2020-03-30 11:44:18 +00: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 => {
if(result) {
this.channelTree.client.serverConnection.send_command("musicbotdelete", {
bot_id: this.properties.client_database_id
2020-08-26 10:33:53 +00:00
}).then(() => {});
}
});
},
type: contextmenu.MenuEntryType.ENTRY
},
2020-02-02 14:05:36 +00:00
contextmenu.Entry.CLOSE(() => trigger_close && on_close ? on_close() : {})
);
}
initializeListener(): void {
super.initializeListener();
}
2018-08-10 19:30:58 +00:00
handlePlayerInfo(json) {
if(json) {
2019-09-12 21:59:35 +00:00
const info = new MusicClientPlayerInfo();
JSON.map_to(info, json);
2018-08-10 19:30:58 +00: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 14:05:36 +00: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 19:30:58 +00: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;
});
2020-08-26 10:33:53 +00:00
this.channelTree.client.serverConnection.send_command("musicbotplayerinfo", {bot_id: this.properties.client_database_id }).then(() => {});
2018-08-10 19:30:58 +00:00
return this._info_promise;
}
2020-09-07 10:42:00 +00:00
isCurrentlyPlaying() {
switch (this.properties.player_state) {
case MusicClientPlayerState.PLAYING:
case MusicClientPlayerState.LOADING:
return true;
default:
return false;
}
}
2018-02-27 16:20:49 +00:00
}