1496 lines
No EOL
61 KiB
TypeScript
1496 lines
No EOL
61 KiB
TypeScript
/// <reference path="channel.ts" />
|
|
/// <reference path="modal/ModalChangeVolume.ts" />
|
|
/// <reference path="client_move.ts" />
|
|
|
|
enum ClientType {
|
|
CLIENT_VOICE,
|
|
CLIENT_QUERY,
|
|
CLIENT_INTERNAL,
|
|
CLIENT_WEB,
|
|
CLIENT_MUSIC,
|
|
CLIENT_UNDEFINED
|
|
}
|
|
|
|
class ClientProperties {
|
|
client_type: ClientType = ClientType.CLIENT_VOICE; //TeamSpeaks type
|
|
client_type_exact: ClientType = ClientType.CLIENT_VOICE;
|
|
|
|
client_database_id: number = 0;
|
|
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;
|
|
client_created: number = 0;
|
|
client_totalconnections: number = 0;
|
|
|
|
client_flag_avatar: string = "";
|
|
client_icon_id: number = 0;
|
|
|
|
client_away_message: string = "";
|
|
client_away: boolean = false;
|
|
|
|
client_country: string = "";
|
|
|
|
client_input_hardware: boolean = false;
|
|
client_output_hardware: boolean = false;
|
|
client_input_muted: boolean = false;
|
|
client_output_muted: boolean = false;
|
|
client_is_channel_commander: boolean = false;
|
|
|
|
client_teaforo_id: number = 0;
|
|
client_teaforo_name: string = "";
|
|
client_teaforo_flags: number = 0; /* 0x01 := Banned | 0x02 := Stuff | 0x04 := Premium */
|
|
|
|
|
|
/* 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;
|
|
}
|
|
|
|
class ClientConnectionInfo {
|
|
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;
|
|
}
|
|
|
|
class ClientEntry {
|
|
protected _clientId: number;
|
|
protected _channel: ChannelEntry;
|
|
protected _tag: JQuery<HTMLElement>;
|
|
|
|
protected _properties: ClientProperties;
|
|
protected lastVariableUpdate: number = 0;
|
|
protected _speaking: boolean;
|
|
protected _listener_initialized: boolean;
|
|
|
|
protected _audio_handle: connection.voice.VoiceClient;
|
|
protected _audio_volume: number;
|
|
protected _audio_muted: boolean;
|
|
|
|
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;
|
|
|
|
channelTree: ChannelTree;
|
|
|
|
constructor(clientId: number, clientName, properties: ClientProperties = new ClientProperties()) {
|
|
this._properties = properties;
|
|
this._properties.client_nickname = clientName;
|
|
this._clientId = clientId;
|
|
this.channelTree = null;
|
|
this._channel = null;
|
|
}
|
|
|
|
destroy() {
|
|
if(this._tag) {
|
|
this._tag.remove();
|
|
this._tag = undefined;
|
|
}
|
|
if(this._audio_handle) {
|
|
log.warn(LogCategory.AUDIO, tr("Destroying client with an active audio handle. This could cause memory leaks!"));
|
|
try {
|
|
this._audio_handle.abort_replay();
|
|
} catch(error) {
|
|
log.warn(LogCategory.AUDIO, tr("Failed to abort replay: %o"), error);
|
|
}
|
|
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) {
|
|
try {
|
|
this._audio_handle.abort_replay();
|
|
} catch(error) {
|
|
log.warn(LogCategory.AUDIO, tr("Failed to abort replay: %o"), error);
|
|
}
|
|
this._audio_handle.callback_playback = undefined;
|
|
this._audio_handle.callback_stopped = undefined;
|
|
this._audio_handle = undefined;
|
|
}
|
|
|
|
this._channel = undefined;
|
|
}
|
|
|
|
set_audio_handle(handle: connection.voice.VoiceClient) {
|
|
if(this._audio_handle === handle)
|
|
return;
|
|
|
|
if(this._audio_handle) {
|
|
this._audio_handle.callback_playback = undefined;
|
|
this._audio_handle.callback_stopped = undefined;
|
|
}
|
|
//TODO may ensure that the id is the same?
|
|
this._audio_handle = handle;
|
|
if(!handle) {
|
|
this.speaking = false;
|
|
return;
|
|
}
|
|
|
|
handle.callback_playback = () => this.speaking = true;
|
|
handle.callback_stopped = () => this.speaking = false;
|
|
}
|
|
|
|
get_audio_handle() : connection.voice.VoiceClient {
|
|
return this._audio_handle;
|
|
}
|
|
|
|
get properties() : ClientProperties {
|
|
return this._properties;
|
|
}
|
|
|
|
currentChannel() : ChannelEntry { return this._channel; }
|
|
clientNickName(){ return this.properties.client_nickname; }
|
|
clientUid(){ return this.properties.client_unique_identifier; }
|
|
clientId(){ return this._clientId; }
|
|
|
|
is_muted() { return !!this._audio_muted; }
|
|
set_muted(flag: boolean, update_icon: boolean, force?: boolean) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
if(update_icon)
|
|
this.updateClientSpeakIcon();
|
|
|
|
for(const client of this.channelTree.clients) {
|
|
if(client === this || client.properties.client_unique_identifier != this.properties.client_unique_identifier)
|
|
continue;
|
|
client.set_muted(flag, true);
|
|
}
|
|
}
|
|
|
|
protected initializeListener() {
|
|
if(this._listener_initialized) return;
|
|
this._listener_initialized = true;
|
|
|
|
this.tag.on('mouseup', event => {
|
|
if(!this.channelTree.client_mover.is_active()) {
|
|
this.channelTree.onSelect(this);
|
|
}
|
|
});
|
|
|
|
if(!(this instanceof LocalClientEntry) && !(this instanceof MusicClientEntry))
|
|
this.tag.dblclick(event => {
|
|
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
|
|
return;
|
|
}
|
|
this.open_text_chat();
|
|
});
|
|
|
|
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
|
this.tag.on("contextmenu", (event) => {
|
|
event.preventDefault();
|
|
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
|
|
(this.channelTree.currently_selected_context_callback || ((_) => null))(event);
|
|
return;
|
|
}
|
|
|
|
this.channelTree.onSelect(this, true);
|
|
this.showContextMenu(event.pageX, event.pageY, () => {});
|
|
return false;
|
|
});
|
|
}
|
|
|
|
this.tag.on('mousedown', event => {
|
|
if(event.which != 1) return; //Only the left button
|
|
|
|
let clients = this.channelTree.currently_selected as (ClientEntry | ClientEntry[]);
|
|
|
|
if(ppt.key_pressed(ppt.SpecialKey.SHIFT)) {
|
|
if(clients != this && !($.isArray(clients) && clients.indexOf(this) != -1))
|
|
clients = $.isArray(clients) ? [...clients, this] : [clients, this];
|
|
} else {
|
|
clients = this;
|
|
}
|
|
|
|
this.channelTree.client_mover.activate(clients, target => {
|
|
if(!target) return;
|
|
|
|
for(const client of $.isArray(clients) ? clients : [clients]) {
|
|
if(target == client._channel) continue;
|
|
|
|
const source = client._channel;
|
|
const self = this.channelTree.client.getClient();
|
|
this.channelTree.client.serverConnection.send_command("clientmove", {
|
|
clid: client.clientId(),
|
|
cid: target.getChannelId()
|
|
}).then(event => {
|
|
if(client.clientId() == this.channelTree.client.clientId)
|
|
this.channelTree.client.sound.play(Sound.CHANNEL_JOINED);
|
|
else if(target !== source && target != self.currentChannel())
|
|
this.channelTree.client.sound.play(Sound.USER_MOVED);
|
|
});
|
|
}
|
|
|
|
this.channelTree.onSelect();
|
|
}, event);
|
|
});
|
|
}
|
|
|
|
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[] = [];
|
|
for(let group of this.channelTree.client.groups.serverGroups.sort(GroupManager.sorter())) {
|
|
if(group.type != GroupType.NORMAL) continue;
|
|
|
|
let entry: contextmenu.MenuEntry = {} as any;
|
|
|
|
//TODO: May add the server group icon?
|
|
entry.checkbox_checked = this.groupAssigned(group);
|
|
entry.name = group.name + " [" + (group.properties.savedb ? "perm" : "tmp") + "]";
|
|
if(this.groupAssigned(group)) {
|
|
entry.callback = () => {
|
|
this.channelTree.client.serverConnection.send_command("servergroupdelclient", {
|
|
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 = () => {
|
|
this.channelTree.client.serverConnection.send_command("servergroupaddclient", {
|
|
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);
|
|
}
|
|
entry.type = contextmenu.MenuEntryType.CHECKBOX;
|
|
|
|
server_groups.push(entry);
|
|
}
|
|
|
|
let channel_groups: contextmenu.MenuEntry[] = [];
|
|
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;
|
|
entry.name = group.name + " [" + (group.properties.savedb ? "perm" : "tmp") + "]";
|
|
entry.callback = () => {
|
|
this.channelTree.client.serverConnection.send_command("setclientchannelgroup", {
|
|
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);
|
|
entry.type = contextmenu.MenuEntryType.CHECKBOX;
|
|
channel_groups.push(entry);
|
|
}
|
|
|
|
return [{
|
|
type: contextmenu.MenuEntryType.SUB_MENU,
|
|
icon_class: "client-permission_server_groups",
|
|
name: tr("Set server group"),
|
|
sub_menu: [
|
|
{
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-permission_server_groups",
|
|
name: "Server groups dialog",
|
|
callback: () => this.open_assignment_modal()
|
|
},
|
|
contextmenu.Entry.HR(),
|
|
...server_groups
|
|
]
|
|
},{
|
|
type: contextmenu.MenuEntryType.SUB_MENU,
|
|
icon_class: "client-permission_channel",
|
|
name: tr("Set channel group"),
|
|
sub_menu: [
|
|
...channel_groups
|
|
]
|
|
},{
|
|
type: contextmenu.MenuEntryType.SUB_MENU,
|
|
icon_class: "client-permission_client",
|
|
name: tr("Permissions"),
|
|
sub_menu: [
|
|
{
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-permission_client",
|
|
name: tr("Client permissions"),
|
|
callback: () => Modals.spawnPermissionEdit(this.channelTree.client, "clp", {unique_id: this.clientUid()}).open()
|
|
},
|
|
{
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-permission_client",
|
|
name: tr("Client channel permissions"),
|
|
callback: () => Modals.spawnPermissionEdit(this.channelTree.client, "clchp", {unique_id: this.clientUid(), channel_id: this._channel ? this._channel.channelId : undefined }).open()
|
|
}
|
|
]
|
|
}];
|
|
}
|
|
|
|
open_assignment_modal() {
|
|
Modals.createServerGroupAssignmentModal(this, (groups, flag) => {
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
showContextMenu(x: number, y: number, on_close: () => void = undefined) {
|
|
let trigger_close = true;
|
|
contextmenu.spawn_context_menu(x, y,
|
|
...this.contextmenu_info(), {
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-change_nickname",
|
|
name: (contextmenu.get_provider().html_format_enabled() ? "<b>" : "") +
|
|
tr("Open text chat") +
|
|
(contextmenu.get_provider().html_format_enabled() ? "</b>" : ""),
|
|
callback: () => {
|
|
this.open_text_chat();
|
|
}
|
|
},
|
|
contextmenu.Entry.HR(),
|
|
{
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-about",
|
|
name: tr("Show client info"),
|
|
callback: () => Modals.openClientInfo(this)
|
|
},
|
|
contextmenu.Entry.HR(),
|
|
{
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-poke",
|
|
name: tr("Poke client"),
|
|
callback: () => {
|
|
createInputModal(tr("Poke client"), tr("Poke message:<br>"), text => true, result => {
|
|
if(typeof(result) === "string") {
|
|
//TODO tr
|
|
console.log("Poking client " + this.clientNickName() + " with message " + result);
|
|
this.channelTree.client.serverConnection.send_command("clientpoke", {
|
|
clid: this.clientId(),
|
|
msg: result
|
|
});
|
|
|
|
}
|
|
}, { width: 400, maxLength: 512 }).open();
|
|
}
|
|
}, {
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-edit",
|
|
name: tr("Change description"),
|
|
callback: () => {
|
|
createInputModal(tr("Change client description"), tr("New description:<br>"), text => true, result => {
|
|
if(typeof(result) === "string") {
|
|
//TODO tr
|
|
console.log("Changing " + this.clientNickName() + "'s description to " + result);
|
|
this.channelTree.client.serverConnection.send_command("clientedit", {
|
|
clid: this.clientId(),
|
|
client_description: result
|
|
});
|
|
|
|
}
|
|
}, { width: 400, maxLength: 1024 }).open();
|
|
}
|
|
},
|
|
contextmenu.Entry.HR(),
|
|
...this.assignment_context(),
|
|
contextmenu.Entry.HR(), {
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-move_client_to_own_channel",
|
|
name: tr("Move client to your channel"),
|
|
callback: () => {
|
|
this.channelTree.client.serverConnection.send_command("clientmove", {
|
|
clid: this.clientId(),
|
|
cid: this.channelTree.client.getClient().currentChannel().getChannelId()
|
|
});
|
|
}
|
|
}, {
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-kick_channel",
|
|
name: tr("Kick client from channel"),
|
|
callback: () => {
|
|
createInputModal(tr("Kick client from channel"), tr("Kick reason:<br>"), text => true, result => {
|
|
if(typeof(result) !== 'boolean' || result) {
|
|
//TODO tr
|
|
console.log("Kicking client " + this.clientNickName() + " from channel with reason " + result);
|
|
this.channelTree.client.serverConnection.send_command("clientkick", {
|
|
clid: this.clientId(),
|
|
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
|
|
reasonmsg: result
|
|
});
|
|
|
|
}
|
|
}, { width: 400, maxLength: 255 }).open();
|
|
}
|
|
}, {
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-kick_server",
|
|
name: tr("Kick client fom server"),
|
|
callback: () => {
|
|
createInputModal(tr("Kick client from server"), tr("Kick reason:<br>"), text => true, result => {
|
|
if(typeof(result) !== 'boolean' || result) {
|
|
//TODO tr
|
|
console.log("Kicking client " + this.clientNickName() + " from server with reason " + result);
|
|
this.channelTree.client.serverConnection.send_command("clientkick", {
|
|
clid: this.clientId(),
|
|
reasonid: ViewReasonId.VREASON_SERVER_KICK,
|
|
reasonmsg: result
|
|
});
|
|
|
|
}
|
|
}, { width: 400, maxLength: 255 }).open();
|
|
}
|
|
}, {
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-ban_client",
|
|
name: tr("Ban client"),
|
|
invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
|
|
callback: () => {
|
|
Modals.spawnBanClient(this.channelTree.client, [{
|
|
name: this.properties.client_nickname,
|
|
unique_id: this.properties.client_unique_identifier
|
|
}], (data) => {
|
|
this.channelTree.client.serverConnection.send_command("banclient", {
|
|
uid: this.properties.client_unique_identifier,
|
|
banreason: data.reason,
|
|
time: data.length
|
|
}, {
|
|
flagset: [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]
|
|
}).then(() => {
|
|
this.channelTree.client.sound.play(Sound.USER_BANNED);
|
|
});
|
|
});
|
|
}
|
|
},
|
|
contextmenu.Entry.HR(),
|
|
/*
|
|
{
|
|
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) => {
|
|
this.channelTree.client.serverConnection.send_command("banclient", {
|
|
uid: this.properties.client_unique_identifier,
|
|
banreason: reason,
|
|
time: duration
|
|
});
|
|
});
|
|
}
|
|
},
|
|
MenuEntry.HR(),
|
|
*/
|
|
{
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-volume",
|
|
name: tr("Change Volume"),
|
|
callback: () => {
|
|
Modals.spawnChangeVolume(this, true, this._audio_volume, undefined, volume => {
|
|
this._audio_volume = volume;
|
|
this.channelTree.client.settings.changeServer("volume_client_" + this.clientUid(), volume);
|
|
if(this._audio_handle)
|
|
this._audio_handle.set_volume(volume);
|
|
//TODO: Update in info
|
|
});
|
|
}
|
|
},
|
|
{
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Change playback latency"),
|
|
callback: () => {
|
|
Modals.spawnChangeLatency(this, this._audio_handle.latency_settings(), () => {
|
|
this._audio_handle.reset_latency_settings();
|
|
return this._audio_handle.latency_settings();
|
|
}, settings => this._audio_handle.latency_settings(settings), this._audio_handle.support_flush ? () => {
|
|
this._audio_handle.flush();
|
|
} : undefined);
|
|
},
|
|
visible: this._audio_handle && this._audio_handle.support_latency_settings()
|
|
}, {
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-input_muted_local",
|
|
name: tr("Mute client"),
|
|
visible: !this._audio_muted,
|
|
callback: () => this.set_muted(true, true)
|
|
}, {
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-input_muted_local",
|
|
name: tr("Unmute client"),
|
|
visible: this._audio_muted,
|
|
callback: () => this.set_muted(false, true)
|
|
},
|
|
contextmenu.Entry.CLOSE(() => trigger_close ? on_close() : {})
|
|
);
|
|
}
|
|
|
|
get tag() : JQuery<HTMLElement> {
|
|
if(this._tag) return this._tag;
|
|
|
|
let container_client = $.spawn("div")
|
|
.addClass("tree-entry client")
|
|
.attr("client-id", this.clientId());
|
|
|
|
|
|
/* unread marker */
|
|
{
|
|
container_client.append(
|
|
$.spawn("div")
|
|
.addClass("marker-text-unread hidden")
|
|
.attr("private-conversation", this._clientId)
|
|
);
|
|
}
|
|
container_client.append(
|
|
$.spawn("div")
|
|
.addClass("icon_client_state")
|
|
.attr("title", "Client state")
|
|
);
|
|
|
|
container_client.append(
|
|
$.spawn("div")
|
|
.addClass("group-prefix")
|
|
.attr("title", "Server groups prefixes")
|
|
.hide()
|
|
);
|
|
container_client.append(
|
|
$.spawn("div")
|
|
.addClass("client-name")
|
|
.text(this.clientNickName())
|
|
);
|
|
container_client.append(
|
|
$.spawn("div")
|
|
.addClass("group-suffix")
|
|
.attr("title", "Server groups suffix")
|
|
.hide()
|
|
);
|
|
container_client.append(
|
|
$.spawn("div")
|
|
.addClass("client-away-message")
|
|
.text(this.clientNickName())
|
|
);
|
|
|
|
let container_icons = $.spawn("div").addClass("container-icons");
|
|
|
|
container_icons.append(
|
|
$.spawn("div")
|
|
.addClass("icon icon_talk_power client-input_muted")
|
|
.hide()
|
|
);
|
|
container_icons.append(
|
|
$.spawn("div")
|
|
.addClass("container-icons-group")
|
|
);
|
|
container_icons.append(
|
|
$.spawn("div")
|
|
.addClass("container-icon-client")
|
|
);
|
|
container_client.append(container_icons);
|
|
|
|
this._tag = container_client;
|
|
this.initializeListener();
|
|
return this._tag;
|
|
}
|
|
|
|
static bbcodeTag(id: number, name: string, uid: string) : string {
|
|
return "[url=client://" + id + "/" + uid + "~" + encodeURIComponent(name) + "]" + name + "[/url]";
|
|
}
|
|
|
|
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
|
|
}));
|
|
}
|
|
|
|
create_bbcode() : string {
|
|
return ClientEntry.bbcodeTag(this.clientId(), this.clientNickName(), this.clientUid());
|
|
}
|
|
|
|
createChatTag(braces: boolean = false) : JQuery {
|
|
return ClientEntry.chatTag(this.clientId(), this.clientNickName(), this.clientUid(), braces);
|
|
}
|
|
|
|
set speaking(flag) {
|
|
if(flag === this._speaking) return;
|
|
this._speaking = flag;
|
|
this.updateClientSpeakIcon();
|
|
}
|
|
|
|
updateClientStatusIcons() {
|
|
let talk_power = this.properties.client_talk_power >= this._channel.properties.channel_needed_talk_power;
|
|
if(talk_power)
|
|
this.tag.find(".icon_talk_power").hide();
|
|
else
|
|
this.tag.find(".icon_talk_power").show();
|
|
}
|
|
|
|
updateClientSpeakIcon() {
|
|
let icon: string = "";
|
|
let clicon: string = "";
|
|
|
|
if(this.properties.client_type_exact == ClientType.CLIENT_QUERY) {
|
|
icon = "client-server_query";
|
|
console.log("Server query!");
|
|
} else {
|
|
if (this.properties.client_away) {
|
|
icon = "client-away";
|
|
} else if (this._audio_muted && !(this instanceof LocalClientEntry)) {
|
|
icon = "client-input_muted_local";
|
|
} else if(!this.properties.client_output_hardware) {
|
|
icon = "client-hardware_output_muted";
|
|
} else if(this.properties.client_output_muted) {
|
|
icon = "client-output_muted";
|
|
} else if(!this.properties.client_input_hardware) {
|
|
icon = "client-hardware_input_muted";
|
|
} else if(this.properties.client_input_muted) {
|
|
icon = "client-input_muted";
|
|
} else {
|
|
if(this._speaking) {
|
|
if(this.properties.client_is_channel_commander)
|
|
clicon = "client_cc_talk";
|
|
else
|
|
clicon = "client_talk";
|
|
} else {
|
|
if(this.properties.client_is_channel_commander)
|
|
clicon = "client_cc_idle";
|
|
else
|
|
clicon = "client_idle";
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if(clicon.length > 0)
|
|
this.tag.find(".icon_client_state").attr('class', 'icon_client_state clicon ' + clicon);
|
|
else if(icon.length > 0)
|
|
this.tag.find(".icon_client_state").attr('class', 'icon_client_state icon ' + icon);
|
|
else
|
|
this.tag.find(".icon_client_state").attr('class', 'icon_client_state icon_empty');
|
|
}
|
|
|
|
updateAwayMessage() {
|
|
let tag = this.tag.find(".client-away-message");
|
|
if(this.properties.client_away == true && this.properties.client_away_message){
|
|
tag.text("[" + this.properties.client_away_message + "]");
|
|
tag.show();
|
|
} else {
|
|
tag.hide();
|
|
}
|
|
}
|
|
|
|
updateVariables(...variables: {key: string, value: string}[]) {
|
|
let group = log.group(log.LogType.DEBUG, LogCategory.CLIENT, tr("Update properties (%i) of %s (%i)"), variables.length, this.clientNickName(), this.clientId());
|
|
|
|
let update_icon_status = false;
|
|
let update_icon_speech = false;
|
|
let update_away = false;
|
|
let reorder_channel = false;
|
|
let update_avatar = false;
|
|
|
|
{
|
|
const entries = [];
|
|
for(const variable of variables)
|
|
entries.push({
|
|
key: variable.key,
|
|
value: variable.value,
|
|
type: typeof (this.properties[variable.key])
|
|
});
|
|
log.table(LogType.DEBUG, LogCategory.PERMISSIONS, "Client update properties", entries);
|
|
}
|
|
|
|
for(const variable of variables) {
|
|
const old_value = this._properties[variable.key];
|
|
JSON.map_field_to(this._properties, variable.value, variable.key);
|
|
|
|
if(variable.key == "client_nickname") {
|
|
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(log.server.Type.CLIENT_NICKNAME_CHANGED, {
|
|
own_client: false,
|
|
client: this.log_data(),
|
|
new_name: variable.value,
|
|
old_name: old_value
|
|
});
|
|
}
|
|
}
|
|
|
|
this.tag.find(".client-name").text(variable.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);
|
|
reorder_channel = true;
|
|
}
|
|
if(
|
|
variable.key == "client_away" ||
|
|
variable.key == "client_input_hardware" ||
|
|
variable.key == "client_output_hardware" ||
|
|
variable.key == "client_output_muted" ||
|
|
variable.key == "client_input_muted" ||
|
|
variable.key == "client_is_channel_commander"){
|
|
update_icon_speech = true;
|
|
}
|
|
if(variable.key == "client_away_message" || variable.key == "client_away") {
|
|
update_away = true;
|
|
}
|
|
if(variable.key == "client_unique_identifier") {
|
|
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);
|
|
this.set_muted(mute_status, false, mute_status); /* force only needed when we want to mute the client */
|
|
|
|
if(this._audio_handle)
|
|
this._audio_handle.set_volume(this._audio_muted ? 0 : this._audio_volume);
|
|
|
|
update_icon_speech = true;
|
|
log.debug(LogCategory.CLIENT, tr("Loaded client (%s) server specific properties. Volume: %o Muted: %o."), this.clientUid(), this._audio_volume, this._audio_muted);
|
|
}
|
|
if(variable.key == "client_talk_power") {
|
|
reorder_channel = true;
|
|
update_icon_status = true;
|
|
}
|
|
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;
|
|
this.updateClientIcon();
|
|
}
|
|
if(variable.key =="client_channel_group_id" || variable.key == "client_servergroups")
|
|
this.update_displayed_client_groups();
|
|
else if(variable.key == "client_flag_avatar")
|
|
update_avatar = true;
|
|
}
|
|
|
|
/* process updates after variables have been set */
|
|
if(this._channel && reorder_channel)
|
|
this._channel.reorderClients();
|
|
if(update_icon_speech)
|
|
this.updateClientSpeakIcon();
|
|
if(update_icon_status)
|
|
this.updateClientStatusIcons();
|
|
if(update_away)
|
|
this.updateAwayMessage();
|
|
|
|
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();
|
|
}
|
|
|
|
group.end();
|
|
}
|
|
|
|
update_displayed_client_groups() {
|
|
this.tag.find(".container-icons-group").children().remove();
|
|
|
|
for(let id of this.assignedServerGroupIds())
|
|
this.updateGroupIcon(this.channelTree.client.groups.serverGroup(id));
|
|
this.update_group_icon_order();
|
|
this.updateGroupIcon(this.channelTree.client.groups.channelGroup(this.properties.client_channel_group_id));
|
|
|
|
let prefix_groups: string[] = [];
|
|
let suffix_groups: string[] = [];
|
|
for(const group_id of this.assignedServerGroupIds()) {
|
|
const group = this.channelTree.client.groups.serverGroup(group_id);
|
|
if(!group) continue;
|
|
|
|
if(group.properties.namemode == 1)
|
|
prefix_groups.push(group.name);
|
|
else if(group.properties.namemode == 2)
|
|
suffix_groups.push(group.name);
|
|
}
|
|
|
|
const tag_group_prefix = this.tag.find(".group-prefix");
|
|
const tag_group_suffix = this.tag.find(".group-suffix");
|
|
if(prefix_groups.length > 0) {
|
|
tag_group_prefix.text("[" + prefix_groups.join("][") + "]").show();
|
|
} else {
|
|
tag_group_prefix.hide()
|
|
}
|
|
|
|
if(suffix_groups.length > 0) {
|
|
tag_group_suffix.text("[" + suffix_groups.join("][") + "]").show();
|
|
} else {
|
|
tag_group_suffix.hide()
|
|
}
|
|
}
|
|
|
|
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);
|
|
});
|
|
}));
|
|
}
|
|
|
|
updateClientIcon() {
|
|
this.tag.find(".container-icon-client").children().remove();
|
|
if(this.properties.client_icon_id > 0) {
|
|
this.channelTree.client.fileManager.icons.generateTag(this.properties.client_icon_id).attr("title", "Client icon")
|
|
.appendTo(this.tag.find(".container-icon-client"));
|
|
}
|
|
}
|
|
|
|
updateGroupIcon(group: Group) {
|
|
if(!group) return;
|
|
|
|
const container = this.tag.find(".container-icons-group");
|
|
container.find(".icon_group_" + group.id).remove();
|
|
|
|
if (group.properties.iconid > 0) {
|
|
container.append(
|
|
$.spawn("div").attr('group-power', group.properties.sortid)
|
|
.addClass("container-group-icon icon_group_" + group.id)
|
|
.append(this.channelTree.client.fileManager.icons.generateTag(group.properties.iconid)).attr("title", group.name)
|
|
);
|
|
}
|
|
}
|
|
|
|
update_group_icon_order() {
|
|
const container = this.tag.find(".container-icons-group");
|
|
|
|
container.append(...[...container.children()].sort((a, b) => parseInt(a.getAttribute("group-power")) - parseInt(b.getAttribute("group-power"))));
|
|
}
|
|
|
|
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 {
|
|
return this.properties.client_channel_group_id;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
onDelete() { }
|
|
|
|
calculateOnlineTime() : number {
|
|
return Date.now() / 1000 - this.properties.client_lastconnected;
|
|
}
|
|
|
|
avatarId?() : string {
|
|
function str2ab(str) {
|
|
let buf = new ArrayBuffer(str.length); // 2 bytes for each char
|
|
let bufView = new Uint8Array(buf);
|
|
for (let i=0, strLen = str.length; i<strLen; i++) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
update_family_index() {
|
|
if(!this._channel) return;
|
|
const index = this._channel.calculate_family_index();
|
|
|
|
this.tag.css('padding-left', (5 + (index + 2) * 16) + "px");
|
|
}
|
|
|
|
log_data() : log.server.base.Client {
|
|
return {
|
|
client_unique_id: this.properties.client_unique_identifier,
|
|
client_name: this.clientNickName(),
|
|
client_id: this._clientId
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
set flag_text_unread(flag: boolean) {
|
|
this._tag.find(".marker-text-unread").toggleClass("hidden", !flag);
|
|
}
|
|
}
|
|
|
|
class LocalClientEntry extends ClientEntry {
|
|
handle: ConnectionHandler;
|
|
|
|
private renaming: boolean;
|
|
|
|
constructor(handle: ConnectionHandler) {
|
|
super(0, "local client");
|
|
this.handle = handle;
|
|
}
|
|
|
|
showContextMenu(x: number, y: number, on_close: () => void = undefined): void {
|
|
const _self = this;
|
|
|
|
contextmenu.spawn_context_menu(x, y,
|
|
...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",
|
|
callback: () =>_self.openRename(),
|
|
type: contextmenu.MenuEntryType.ENTRY
|
|
}, {
|
|
name: tr("Change description"),
|
|
icon_class: "client-edit",
|
|
callback: () => {
|
|
createInputModal(tr("Change own description"), tr("New description:<br>"), text => true, result => {
|
|
if(result) {
|
|
console.log(tr("Changing own description to %s"), result);
|
|
_self.channelTree.client.serverConnection.send_command("clientedit", {
|
|
clid: _self.clientId(),
|
|
client_description: result
|
|
});
|
|
|
|
}
|
|
}, { width: 400, maxLength: 1024 }).open();
|
|
},
|
|
type: contextmenu.MenuEntryType.ENTRY
|
|
},
|
|
contextmenu.Entry.HR(),
|
|
...this.assignment_context(),
|
|
contextmenu.Entry.CLOSE(on_close)
|
|
);
|
|
}
|
|
|
|
initializeListener(): void {
|
|
if(this._listener_initialized)
|
|
this.tag.off();
|
|
this._listener_initialized = false; /* could there be a better system */
|
|
super.initializeListener();
|
|
this.tag.find(".client-name").addClass("client-name-own");
|
|
|
|
this.tag.on('dblclick', () => {
|
|
if(Array.isArray(this.channelTree.currently_selected)) { //Multiselect
|
|
return;
|
|
}
|
|
this.openRename();
|
|
});
|
|
}
|
|
|
|
openRename() : void {
|
|
this.channelTree.client_mover.enabled = false;
|
|
|
|
const elm = this.tag.find(".client-name");
|
|
elm.attr("contenteditable", "true");
|
|
elm.removeClass("client-name-own");
|
|
elm.css("background-color", "white");
|
|
elm.focus();
|
|
this.renaming = true;
|
|
|
|
elm.on('keypress', event => {
|
|
if(event.keyCode == KeyCode.KEY_RETURN) {
|
|
$(event.target).trigger("focusout");
|
|
return false;
|
|
}
|
|
});
|
|
|
|
elm.on('focusout', event => {
|
|
this.channelTree.client_mover.enabled = true;
|
|
|
|
if(!this.renaming) return;
|
|
this.renaming = false;
|
|
|
|
elm.css("background-color", "");
|
|
elm.removeAttr("contenteditable");
|
|
elm.addClass("client-name-own");
|
|
let text = elm.text().toString();
|
|
if(this.clientNickName() == text) return;
|
|
|
|
elm.text(this.clientNickName());
|
|
const old_name = this.clientNickName();
|
|
this.handle.serverConnection.command_helper.updateClient("client_nickname", text).then((e) => {
|
|
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, text);
|
|
this.channelTree.client.log.log(log.server.Type.CLIENT_NICKNAME_CHANGED, {
|
|
client: this.log_data(),
|
|
old_name: old_name,
|
|
new_name: text,
|
|
own_client: true
|
|
});
|
|
}).catch((e: CommandResult) => {
|
|
this.channelTree.client.log.log(log.server.Type.CLIENT_NICKNAME_CHANGE_FAILED, {
|
|
reason: e.extra_message
|
|
});
|
|
this.openRename();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
class MusicClientProperties extends ClientProperties {
|
|
player_state: number = 0;
|
|
player_volume: number = 0;
|
|
|
|
client_playlist_id: number = 0;
|
|
client_disabled: boolean = false;
|
|
}
|
|
|
|
class MusicClientPlayerInfo {
|
|
bot_id: number = 0;
|
|
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 = "";
|
|
|
|
song_id: number = 0;
|
|
song_url: string = "";
|
|
song_invoker: number = 0;
|
|
song_loaded: boolean = false;
|
|
song_title: string = "";
|
|
song_thumbnail: string = "";
|
|
song_length: number = 0;
|
|
}
|
|
|
|
class MusicClientEntry extends ClientEntry {
|
|
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());
|
|
}
|
|
|
|
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 {
|
|
let trigger_close = true;
|
|
contextmenu.spawn_context_menu(x, y,
|
|
...this.contextmenu_info(), {
|
|
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) {
|
|
this.channelTree.client.serverConnection.send_command("clientedit", {
|
|
clid: this.clientId(),
|
|
client_nickname: result
|
|
});
|
|
|
|
}
|
|
}, { width: "40em", min_width: "10em", maxLength: 255 }).open();
|
|
},
|
|
type: contextmenu.MenuEntryType.ENTRY
|
|
}, {
|
|
name: tr("Change bot description"),
|
|
icon_class: "client-edit",
|
|
disabled: false,
|
|
callback: () => {
|
|
createInputModal(tr("Change music bots description"), tr("New description:<br>"), text => true, result => {
|
|
if(typeof(result) === 'string') {
|
|
this.channelTree.client.serverConnection.send_command("clientedit", {
|
|
clid: this.clientId(),
|
|
client_description: result
|
|
});
|
|
|
|
}
|
|
}, { width: "60em", min_width: "10em", maxLength: 255 }).open();
|
|
},
|
|
type: contextmenu.MenuEntryType.ENTRY
|
|
},
|
|
/*
|
|
{
|
|
name: tr("Open music panel"),
|
|
icon: "client-edit",
|
|
disabled: true,
|
|
callback: () => {},
|
|
type: MenuEntryType.ENTRY
|
|
},
|
|
*/
|
|
{
|
|
name: tr("Open bot's playlist"),
|
|
icon_class: "client-edit",
|
|
disabled: false,
|
|
callback: () => {
|
|
this.channelTree.client.serverConnection.command_helper.request_playlist_list().then(lists => {
|
|
for(const entry of lists) {
|
|
if(entry.playlist_id == this.properties.client_playlist_id) {
|
|
Modals.spawnPlaylistEdit(this.channelTree.client, entry);
|
|
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();
|
|
});
|
|
},
|
|
type: contextmenu.MenuEntryType.ENTRY
|
|
},
|
|
{
|
|
name: tr("Quick url replay"),
|
|
icon_class: "client-edit",
|
|
disabled: false,
|
|
callback: () => {
|
|
createInputModal(tr("Please enter the URL"), tr("URL:"), text => true, result => {
|
|
if(result) {
|
|
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(),
|
|
...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"),
|
|
callback: () => {
|
|
this.channelTree.client.serverConnection.send_command("clientmove", {
|
|
clid: this.clientId(),
|
|
cid: this.channelTree.client.getClient().currentChannel().getChannelId()
|
|
});
|
|
}
|
|
}, {
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-kick_channel",
|
|
name: tr("Kick client from channel"),
|
|
callback: () => {
|
|
createInputModal(tr("Kick client from channel"), tr("Kick reason:<br>"), text => true, result => {
|
|
if(typeof(result) !== 'boolean' || result) {
|
|
console.log(tr("Kicking client %o from channel with reason %o"), this.clientNickName(), result);
|
|
this.channelTree.client.serverConnection.send_command("clientkick", {
|
|
clid: this.clientId(),
|
|
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
|
|
reasonmsg: result
|
|
});
|
|
}
|
|
}, { width: 400, maxLength: 255 }).open();
|
|
}
|
|
},
|
|
contextmenu.Entry.HR(),
|
|
{
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-volume",
|
|
name: tr("Change local volume"),
|
|
callback: () => {
|
|
Modals.spawnChangeVolume(this, true, this._audio_handle.get_volume(), undefined, volume => {
|
|
this.channelTree.client.settings.changeServer("volume_client_" + this.clientUid(), volume);
|
|
this._audio_handle.set_volume(volume);
|
|
});
|
|
}
|
|
},
|
|
{
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
icon_class: "client-volume",
|
|
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;
|
|
|
|
Modals.spawnChangeVolume(this, false, this.properties.player_volume, max_volume / 100, value => {
|
|
if(typeof(value) !== "number")
|
|
return;
|
|
|
|
this.channelTree.client.serverConnection.send_command("clientedit", {
|
|
clid: this.clientId(),
|
|
player_volume: value,
|
|
}).then(() => {
|
|
//TODO: Update in info
|
|
});
|
|
});
|
|
}
|
|
},
|
|
{
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Change playback latency"),
|
|
callback: () => {
|
|
Modals.spawnChangeLatency(this, this._audio_handle.latency_settings(), () => {
|
|
this._audio_handle.reset_latency_settings();
|
|
return this._audio_handle.latency_settings();
|
|
}, settings => this._audio_handle.latency_settings(settings), this._audio_handle.support_flush ? () => {
|
|
this._audio_handle.flush();
|
|
} : undefined);
|
|
},
|
|
visible: this._audio_handle && this._audio_handle.support_latency_settings()
|
|
},
|
|
contextmenu.Entry.HR(),
|
|
{
|
|
name: tr("Delete bot"),
|
|
icon_class: "client-delete",
|
|
disabled: false,
|
|
callback: () => {
|
|
const tag = $.spawn("div").append(MessageHelper.formatMessage(tr("Do you really want to delete {0}"), this.createChatTag(false)));
|
|
Modals.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
|
|
});
|
|
}
|
|
});
|
|
},
|
|
type: contextmenu.MenuEntryType.ENTRY
|
|
},
|
|
contextmenu.Entry.CLOSE(() => trigger_close && on_close())
|
|
);
|
|
}
|
|
|
|
initializeListener(): void {
|
|
super.initializeListener();
|
|
}
|
|
|
|
handlePlayerInfo(json) {
|
|
if(json) {
|
|
const info = new MusicClientPlayerInfo();
|
|
JSON.map_to(info, json);
|
|
if(this._info_promise_resolve)
|
|
this._info_promise_resolve(info);
|
|
this._info_promise_reject = undefined;
|
|
}
|
|
if(this._info_promise) {
|
|
if(this._info_promise_reject)
|
|
this._info_promise_reject("timeout");
|
|
this._info_promise = undefined;
|
|
this._info_promise_age = undefined;
|
|
this._info_promise_reject = undefined;
|
|
this._info_promise_resolve = undefined;
|
|
}
|
|
}
|
|
|
|
requestPlayerInfo(max_age: number = 1000) : Promise<MusicClientPlayerInfo> {
|
|
if(this._info_promise && this._info_promise_age && Date.now() - max_age <= this._info_promise_age) return this._info_promise;
|
|
this._info_promise_age = Date.now();
|
|
this._info_promise = new Promise<MusicClientPlayerInfo>((resolve, reject) => {
|
|
this._info_promise_reject = reject;
|
|
this._info_promise_resolve = resolve;
|
|
});
|
|
|
|
this.channelTree.client.serverConnection.send_command("musicbotplayerinfo", {bot_id: this.properties.client_database_id });
|
|
return this._info_promise;
|
|
}
|
|
} |