import * as log from "tc-shared/log"; import {LogCategory} from "tc-shared/log"; import * as server_log from "tc-shared/ui/frames/server_log"; import {AbstractServerConnection, CommandOptions, ServerCommand} from "tc-shared/connection/ConnectionBase"; import {Sound} from "tc-shared/sound/Sounds"; import {CommandResult, ErrorID} from "tc-shared/connection/ServerConnectionDeclaration"; import {createErrorModal, createInfoModal, createInputModal, createModal} from "tc-shared/ui/elements/Modal"; import { ClientConnectionInfo, ClientEntry, ClientType, LocalClientEntry, MusicClientEntry, SongInfo } from "tc-shared/ui/client"; import {ChannelEntry} from "tc-shared/ui/channel"; import {ConnectionHandler, ConnectionState, DisconnectReason, ViewReasonId} from "tc-shared/ConnectionHandler"; import {bbcode_chat, formatMessage} from "tc-shared/ui/frames/chat"; import {server_connections} from "tc-shared/ui/frames/connection_handlers"; import {spawnPoke} from "tc-shared/ui/modal/ModalPoke"; import {PrivateConversationState} from "tc-shared/ui/frames/side/private_conversations"; import {Conversation} from "tc-shared/ui/frames/side/conversations"; import {AbstractCommandHandler, AbstractCommandHandlerBoss} from "tc-shared/connection/AbstractCommandHandler"; export class ServerConnectionCommandBoss extends AbstractCommandHandlerBoss { constructor(connection: AbstractServerConnection) { super(connection); } } export class ConnectionCommandHandler extends AbstractCommandHandler { readonly connection: AbstractServerConnection; readonly connection_handler: ConnectionHandler; constructor(connection: AbstractServerConnection) { super(connection); this.connection_handler = connection.client; this["error"] = this.handleCommandResult; this["channellist"] = this.handleCommandChannelList; this["channellistfinished"] = this.handleCommandChannelListFinished; this["notifychannelcreated"] = this.handleCommandChannelCreate; this["notifychanneldeleted"] = this.handleCommandChannelDelete; this["notifychannelhide"] = this.handleCommandChannelHide; this["notifychannelshow"] = this.handleCommandChannelShow; this["notifyserverconnectioninfo"] = this.handleNotifyServerConnectionInfo; this["notifyconnectioninfo"] = this.handleNotifyConnectionInfo; this["notifycliententerview"] = this.handleCommandClientEnterView; this["notifyclientleftview"] = this.handleCommandClientLeftView; this["notifyclientmoved"] = this.handleNotifyClientMoved; this["initserver"] = this.handleCommandServerInit; this["notifychannelmoved"] = this.handleNotifyChannelMoved; this["notifychanneledited"] = this.handleNotifyChannelEdited; this["notifytextmessage"] = this.handleNotifyTextMessage; this["notifyclientchatcomposing"] = this.notifyClientChatComposing; this["notifyclientchatclosed"] = this.handleNotifyClientChatClosed; this["notifyclientupdated"] = this.handleNotifyClientUpdated; this["notifyserveredited"] = this.handleNotifyServerEdited; this["notifyserverupdated"] = this.handleNotifyServerUpdated; this["notifyclientpoke"] = this.handleNotifyClientPoke; this["notifymusicplayerinfo"] = this.handleNotifyMusicPlayerInfo; this["notifyservergroupclientadded"] = this.handleNotifyServerGroupClientAdd; this["notifyservergroupclientdeleted"] = this.handleNotifyServerGroupClientRemove; this["notifyclientchannelgroupchanged"] = this.handleNotifyClientChannelGroupChanged; this["notifychannelsubscribed"] = this.handleNotifyChannelSubscribed; this["notifychannelunsubscribed"] = this.handleNotifyChannelUnsubscribed; this["notifyconversationhistory"] = this.handleNotifyConversationHistory; this["notifyconversationmessagedelete"] = this.handleNotifyConversationMessageDelete; this["notifymusicstatusupdate"] = this.handleNotifyMusicStatusUpdate; this["notifymusicplayersongchange"] = this.handleMusicPlayerSongChange; this["notifyplaylistsongadd"] = this.handleNotifyPlaylistSongAdd; this["notifyplaylistsongremove"] = this.handleNotifyPlaylistSongRemove; this["notifyplaylistsongreorder"] = this.handleNotifyPlaylistSongReorder; this["notifyplaylistsongloaded"] = this.handleNotifyPlaylistSongLoaded; } private loggable_invoker(unique_id, client_id, name) : server_log.base.Client | undefined { const id = parseInt(client_id); if(typeof(client_id) === "undefined" || Number.isNaN(id)) return undefined; if(id == 0) return { client_id: 0, client_unique_id: this.connection_handler.channelTree.server.properties.virtualserver_unique_identifier, client_name: this.connection_handler.channelTree.server.properties.virtualserver_name, }; return { client_unique_id: unique_id, client_name: name, client_id: client_id }; } proxy_command_promise(promise: Promise, options: CommandOptions) { if(!options.process_result) return promise; return promise.catch(ex => { if(options.process_result) { if(ex instanceof CommandResult) { let res = ex; if(!res.success) { if(res.id == ErrorID.PERMISSION_ERROR) { //Permission error const permission = this.connection_handler.permissions.resolveInfo(res.json["failed_permid"] as number); res.message = tr("Insufficient client permissions. Failed on permission ") + (permission ? permission.name : "unknown"); this.connection_handler.log.log(server_log.Type.ERROR_PERMISSION, { permission: this.connection_handler.permissions.resolveInfo(res.json["failed_permid"] as number) }); this.connection_handler.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS); } else if(res.id != ErrorID.EMPTY_RESULT) { this.connection_handler.log.log(server_log.Type.ERROR_CUSTOM, { message: res.extra_message.length == 0 ? res.message : res.extra_message }); } } } else if(typeof(ex) === "string") { this.connection_handler.log.log(server_log.Type.CONNECTION_COMMAND_ERROR, {error: ex}); } else { log.error(LogCategory.NETWORKING, tr("Invalid promise result type: %s. Result: %o"), typeof (ex), ex); } } return Promise.reject(ex); }); } handle_command(command: ServerCommand) : boolean { if(this[command.command]) { this[command.command](command.arguments); return true; } return false; } set_handler(command: string, handler: any) { this[command] = handler; } unset_handler(command: string, handler?: any) { if(handler && this[command] != handler) return; this[command] = undefined; } handleCommandResult(json) { json = json[0]; //Only one bulk let code : string = json["return_code"]; if(!code || code.length == 0) { log.warn(LogCategory.NETWORKING, tr("Invalid return code! (%o)"), json); return; } let retListeners = this.connection["_retListener"]; for(let e of retListeners) { if(e.code != code) continue; retListeners.remove(e); let result = new CommandResult(json); if(result.success) e.resolve(result); else e.reject(result); break; } } handleCommandServerInit(json){ //We could setup the voice channel if(this.connection.support_voice()) { log.debug(LogCategory.NETWORKING, tr("Setting up voice")); } else { log.debug(LogCategory.NETWORKING, tr("Skipping voice setup (No voice bridge available)")); } json = json[0]; //Only one bulk this.connection_handler.channelTree.registerClient(this.connection_handler.getClient()); this.connection.client.side_bar.channel_conversations().reset(); this.connection.client.clientId = parseInt(json["aclid"]); this.connection.client.getClient().updateVariables( {key: "client_nickname", value: json["acn"]}); let updates: { key: string, value: string }[] = []; for(let key in json) { if(key === "aclid") continue; if(key === "acn") continue; updates.push({key: key, value: json[key]}); } this.connection.client.channelTree.server.updateVariables(false, ...updates); const properties = this.connection.client.channelTree.server.properties; /* host message */ if(properties.virtualserver_hostmessage_mode > 0) { if(properties.virtualserver_hostmessage_mode == 1) { /* show in log */ if(properties.virtualserver_hostmessage) this.connection_handler.log.log(server_log.Type.SERVER_HOST_MESSAGE, { message: properties.virtualserver_hostmessage }); } else { /* create modal/create modal and quit */ if(properties.virtualserver_hostmessage || properties.virtualserver_hostmessage_mode == 3) createModal({ header: tr("Host message"), body: bbcode_chat(properties.virtualserver_hostmessage), footer: undefined }).open(); if(properties.virtualserver_hostmessage_mode == 3) { /* first let the client initialize his stuff */ setTimeout(() => { this.connection_handler.log.log(server_log.Type.SERVER_HOST_MESSAGE_DISCONNECT, { message: properties.virtualserver_welcomemessage }); this.connection.disconnect("host message disconnect"); this.connection_handler.handleDisconnect(DisconnectReason.SERVER_HOSTMESSAGE); this.connection_handler.sound.play(Sound.CONNECTION_DISCONNECTED); }, 100); } } } /* welcome message */ if(properties.virtualserver_welcomemessage) { this.connection_handler.log.log(server_log.Type.SERVER_WELCOME_MESSAGE, { message: properties.virtualserver_welcomemessage }); } /* priviledge key */ if(properties.virtualserver_ask_for_privilegekey) { createInputModal(tr("Use a privilege key"), tr("This is a newly created server for which administrator privileges have not yet been claimed.
Please enter the \"privilege key\" that was automatically generated when this server was created to gain administrator permissions."), message => message.length > 0, result => { if(!result) return; const scon = server_connections.active_connection(); if(scon.serverConnection.connected) scon.serverConnection.send_command("tokenuse", { token: result }).then(() => { createInfoModal(tr("Use privilege key"), tr("Privilege key successfully used!")).open(); }).catch(error => { createErrorModal(tr("Use privilege key"), formatMessage(tr("Failed to use privilege key: {}"), error instanceof CommandResult ? error.message : error)).open(); }); }, { field_placeholder: tr("Enter Privilege Key") }).open(); } this.connection.updateConnectionState(ConnectionState.CONNECTED); } handleNotifyServerConnectionInfo(json) { json = json[0]; /* everything is a number, so lets parse it */ for(const key of Object.keys(json)) json[key] = parseFloat(json[key]); this.connection_handler.channelTree.server.set_connection_info(json); } handleNotifyConnectionInfo(json) { json = json[0]; const object = new ClientConnectionInfo(); /* everything is a number (except ip), so lets parse it */ for(const key of Object.keys(json)) { if(key === "connection_client_ip") object[key] = json[key]; else object[key] = parseFloat(json[key]); } const client = this.connection_handler.channelTree.findClient(parseInt(json["clid"])); if(!client) { log.warn(LogCategory.NETWORKING, tr("Received client connection info for unknown client (%o)"), json["clid"]); return; } client.set_connection_info(object); } private createChannelFromJson(json, ignoreOrder: boolean = false) { let tree = this.connection.client.channelTree; let channel = new ChannelEntry(parseInt(json["cid"]), json["channel_name"], tree.findChannel(json["cpid"])); tree.insertChannel(channel); if(json["channel_order"] !== "0") { let prev = tree.findChannel(json["channel_order"]); if(!prev && json["channel_order"] != 0) { if(!ignoreOrder) { log.error(LogCategory.NETWORKING, tr("Invalid channel order id!")); return; } } let parent = tree.findChannel(json["cpid"]); if(!parent && json["cpid"] != 0) { log.error(LogCategory.NETWORKING, tr("Invalid channel parent")); return; } tree.moveChannel(channel, prev, parent); //TODO test if channel exists! } if(ignoreOrder) { for(let ch of tree.channels) { if(ch.properties.channel_order == channel.channelId) { tree.moveChannel(ch, channel, channel.parent); //Corrent the order :) } } } let updates: { key: string, value: string }[] = []; for(let key in json) { if(key === "cid") continue; if(key === "cpid") continue; if(key === "invokerid") continue; if(key === "invokername") continue; if(key === "invokeruid") continue; if(key === "reasonid") continue; updates.push({key: key, value: json[key]}); } channel.updateVariables(...updates); } handleCommandChannelList(json) { this.connection.client.channelTree.hide_channel_tree(); /* dont perform channel inserts on the dom to prevent style recalculations */ log.debug(LogCategory.NETWORKING, tr("Got %d new channels"), json.length); for(let index = 0; index < json.length; index++) this.createChannelFromJson(json[index], true); } handleCommandChannelListFinished(json) { this.connection.client.channelTree.show_channel_tree(); } handleCommandChannelCreate(json) { this.createChannelFromJson(json[0]); } handleCommandChannelShow(json) { this.createChannelFromJson(json[0]); //TODO may chat? } handleCommandChannelDelete(json) { let tree = this.connection.client.channelTree; const conversations = this.connection.client.side_bar.channel_conversations(); log.info(LogCategory.NETWORKING, tr("Got %d channel deletions"), json.length); for(let index = 0; index < json.length; index++) { conversations.delete_conversation(parseInt(json[index]["cid"])); let channel = tree.findChannel(json[index]["cid"]); if(!channel) { log.error(LogCategory.NETWORKING, tr("Invalid channel onDelete (Unknown channel)")); continue; } tree.deleteChannel(channel); } } handleCommandChannelHide(json) { let tree = this.connection.client.channelTree; const conversations = this.connection.client.side_bar.channel_conversations(); log.info(LogCategory.NETWORKING, tr("Got %d channel hides"), json.length); for(let index = 0; index < json.length; index++) { conversations.delete_conversation(parseInt(json[index]["cid"])); let channel = tree.findChannel(json[index]["cid"]); if(!channel) { log.error(LogCategory.NETWORKING, tr("Invalid channel on hide (Unknown channel)")); continue; } tree.deleteChannel(channel); } } handleCommandClientEnterView(json) { let tree = this.connection.client.channelTree; let client: ClientEntry; let channel = undefined; let old_channel = undefined; let reason_id, reason_msg; let invokerid, invokername, invokeruid; for(const entry of json) { /* attempt to update properties if given */ channel = typeof(entry["ctid"]) !== "undefined" ? tree.findChannel(parseInt(entry["ctid"])) : channel; old_channel = typeof(entry["cfid"]) !== "undefined" ? tree.findChannel(parseInt(entry["cfid"])) : old_channel; reason_id = typeof(entry["reasonid"]) !== "undefined" ? entry["reasonid"] : reason_id; reason_msg = typeof(entry["reason_msg"]) !== "undefined" ? entry["reason_msg"] : reason_msg; invokerid = typeof(entry["invokerid"]) !== "undefined" ? parseInt(entry["invokerid"]) : invokerid; invokername = typeof(entry["invokername"]) !== "undefined" ? entry["invokername"] : invokername; invokeruid = typeof(entry["invokeruid"]) !== "undefined" ? entry["invokeruid"] : invokeruid; client = tree.findClient(parseInt(entry["clid"])); if(!client) { if(parseInt(entry["client_type_exact"]) == ClientType.CLIENT_MUSIC) { client = new MusicClientEntry(parseInt(entry["clid"]), entry["client_nickname"]); } else { client = new ClientEntry(parseInt(entry["clid"]), entry["client_nickname"]); } client.properties.client_type = parseInt(entry["client_type"]); client = tree.insertClient(client, channel); } else { tree.moveClient(client, channel); } if(this.connection_handler.client_status.queries_visible || client.properties.client_type != ClientType.CLIENT_QUERY) { const own_channel = this.connection.client.getClient().currentChannel(); this.connection_handler.log.log(server_log.Type.CLIENT_VIEW_ENTER, { channel_from: old_channel ? old_channel.log_data() : undefined, channel_to: channel ? channel.log_data() : undefined, client: client.log_data(), invoker: this.loggable_invoker(invokeruid, invokerid, invokername), message:reason_msg, reason: parseInt(reason_id), own_channel: channel == own_channel }); if(reason_id == ViewReasonId.VREASON_USER_ACTION) { if(own_channel == channel) if(old_channel) this.connection_handler.sound.play(Sound.USER_ENTERED); else this.connection_handler.sound.play(Sound.USER_ENTERED_CONNECT); } else if(reason_id == ViewReasonId.VREASON_MOVED) { if(own_channel == channel) this.connection_handler.sound.play(Sound.USER_ENTERED_MOVED); } else if(reason_id == ViewReasonId.VREASON_CHANNEL_KICK) { if(own_channel == channel) this.connection_handler.sound.play(Sound.USER_ENTERED_KICKED); } else if(reason_id == ViewReasonId.VREASON_SYSTEM) { } else { console.warn(tr("Unknown reasonid for %o"), reason_id); } } let updates: { key: string, value: string }[] = []; for(let key in entry) { if(key == "cfid") continue; if(key == "ctid") continue; if(key === "invokerid") continue; if(key === "invokername") continue; if(key === "invokeruid") continue; if(key === "reasonid") continue; updates.push({key: key, value: entry[key]}); } client.updateVariables(...updates); /* if its a new client join, or a system reason (like we joined) */ if(!old_channel || reason_id == 2) { /* client new join */ const conversation_manager = this.connection_handler.side_bar.private_conversations(); const conversation = conversation_manager.find_conversation({ unique_id: client.properties.client_unique_identifier, client_id: client.clientId(), name: client.clientNickName() }, { create: false, attach: true }); if(conversation) client.flag_text_unread = conversation.is_unread(); } if(client instanceof LocalClientEntry) { client.initializeListener(); this.connection_handler.update_voice_status(); this.connection_handler.side_bar.info_frame().update_channel_talk(); const conversations = this.connection.client.side_bar.channel_conversations(); conversations.set_current_channel(client.currentChannel().channelId); } } } handleCommandClientLeftView(json) { let reason_id = -1; for(const entry of json) { reason_id = entry["reasonid"] || reason_id; let tree = this.connection.client.channelTree; let client = tree.findClient(entry["clid"]); if(!client) { log.error(LogCategory.NETWORKING, tr("Unknown client left!")); return 0; } if(client == this.connection.client.getClient()) { if(reason_id == ViewReasonId.VREASON_BAN) { this.connection.client.handleDisconnect(DisconnectReason.CLIENT_BANNED, entry); } else if(reason_id == ViewReasonId.VREASON_SERVER_KICK) { this.connection.client.handleDisconnect(DisconnectReason.CLIENT_KICKED, entry); } else if(reason_id == ViewReasonId.VREASON_SERVER_SHUTDOWN) { this.connection.client.handleDisconnect(DisconnectReason.SERVER_CLOSED, entry); } else if(reason_id == ViewReasonId.VREASON_SERVER_STOPPED) { this.connection.client.handleDisconnect(DisconnectReason.SERVER_CLOSED, entry); } else { this.connection.client.handleDisconnect(DisconnectReason.UNKNOWN, entry); } this.connection_handler.side_bar.info_frame().update_channel_talk(); return; } if(this.connection_handler.client_status.queries_visible || client.properties.client_type != ClientType.CLIENT_QUERY) { const own_channel = this.connection.client.getClient().currentChannel(); let channel_from = tree.findChannel(entry["cfid"]); let channel_to = tree.findChannel(entry["ctid"]); const is_own_channel = channel_from == own_channel; this.connection_handler.log.log(server_log.Type.CLIENT_VIEW_LEAVE, { channel_from: channel_from ? channel_from.log_data() : undefined, channel_to: channel_to ? channel_to.log_data() : undefined, client: client.log_data(), invoker: this.loggable_invoker(entry["invokeruid"], entry["invokerid"], entry["invokername"]), message: entry["reasonmsg"], reason: parseInt(entry["reasonid"]), ban_time: parseInt(entry["bantime"]), own_channel: is_own_channel }); if(is_own_channel) { if(reason_id == ViewReasonId.VREASON_USER_ACTION) { this.connection_handler.sound.play(Sound.USER_LEFT); } else if(reason_id == ViewReasonId.VREASON_SERVER_LEFT) { this.connection_handler.sound.play(Sound.USER_LEFT_DISCONNECT); } else if(reason_id == ViewReasonId.VREASON_SERVER_KICK) { this.connection_handler.sound.play(Sound.USER_LEFT_KICKED_SERVER); } else if(reason_id == ViewReasonId.VREASON_CHANNEL_KICK) { this.connection_handler.sound.play(Sound.USER_LEFT_KICKED_CHANNEL); } else if(reason_id == ViewReasonId.VREASON_BAN) { this.connection_handler.sound.play(Sound.USER_LEFT_BANNED); } else if(reason_id == ViewReasonId.VREASON_TIMEOUT) { this.connection_handler.sound.play(Sound.USER_LEFT_TIMEOUT); } else if(reason_id == ViewReasonId.VREASON_MOVED) { this.connection_handler.sound.play(Sound.USER_LEFT_MOVED); } else { log.error(LogCategory.NETWORKING, tr("Unknown client left reason %d!"), reason_id); } } if(!channel_to) { /* client left the server */ const conversation_manager = this.connection_handler.side_bar.private_conversations(); const conversation = conversation_manager.find_conversation({ unique_id: client.properties.client_unique_identifier, client_id: client.clientId(), name: client.clientNickName() }, { create: false, attach: false }); if(conversation) { conversation.set_state(PrivateConversationState.DISCONNECTED); } } } tree.deleteClient(client); } } handleNotifyClientMoved(json) { json = json[0]; //Only one bulk let tree = this.connection.client.channelTree; let client = tree.findClient(json["clid"]); let self = client instanceof LocalClientEntry; let channel_to = tree.findChannel(parseInt(json["ctid"])); let channel_from = tree.findChannel(parseInt(json["cfid"])); if(!client) { log.error(LogCategory.NETWORKING, tr("Unknown client move (Client)!")); return 0; } if(!channel_to) { log.error(LogCategory.NETWORKING, tr("Unknown client move (Channel to)!")); return 0; } if(!self) { if(!channel_from) { log.error(LogCategory.NETWORKING, tr("Unknown client move (Channel from)!")); channel_from = client.currentChannel(); } else if(channel_from != client.currentChannel()) { log.error(LogCategory.NETWORKING, tr("Client move from invalid source channel! Local client registered in channel %d but server send %d."), client.currentChannel().channelId, channel_from.channelId ); } } else { channel_from = client.currentChannel(); } tree.moveClient(client, channel_to); if(self) { this.connection_handler.update_voice_status(channel_to); for(const entry of client.channelTree.clientsByChannel(channel_from)) { if(entry !== client && entry.get_audio_handle()) { entry.get_audio_handle().abort_replay(); entry.speaking = false; } } const side_bar = this.connection_handler.side_bar; side_bar.info_frame().update_channel_talk(); const conversation_to = side_bar.channel_conversations().conversation(channel_to.channelId, false); if(conversation_to) conversation_to.update_private_state(); if(channel_from) { const conversation_from = side_bar.channel_conversations().conversation(channel_from.channelId, false); if(conversation_from) conversation_from.update_private_state(); } side_bar.channel_conversations().update_chat_box(); } const own_channel = this.connection.client.getClient().currentChannel(); this.connection_handler.log.log(server_log.Type.CLIENT_VIEW_MOVE, { channel_from: channel_from ? { channel_id: channel_from.channelId, channel_name: channel_from.channelName() } : undefined, channel_from_own: channel_from == own_channel, channel_to: channel_to ? { channel_id: channel_to.channelId, channel_name: channel_to.channelName() } : undefined, channel_to_own: channel_to == own_channel, client: { client_id: client.clientId(), client_name: client.clientNickName(), client_unique_id: client.properties.client_unique_identifier }, client_own: self, invoker: this.loggable_invoker(json["invokeruid"], json["invokerid"], json["invokername"]), message: json["reasonmsg"], reason: parseInt(json["reasonid"]), }); if(json["reasonid"] == ViewReasonId.VREASON_MOVED) { if(self) this.connection_handler.sound.play(Sound.USER_MOVED_SELF); else if(own_channel == channel_to) this.connection_handler.sound.play(Sound.USER_ENTERED_MOVED); else if(own_channel == channel_from) this.connection_handler.sound.play(Sound.USER_LEFT_MOVED); } else if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) { if(self) {} //If we do an action we wait for the error response else if(own_channel == channel_to) this.connection_handler.sound.play(Sound.USER_ENTERED); else if(own_channel == channel_from) this.connection_handler.sound.play(Sound.USER_LEFT); } else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) { if(self) { this.connection_handler.sound.play(Sound.CHANNEL_KICKED); } else if(own_channel == channel_to) this.connection_handler.sound.play(Sound.USER_ENTERED_KICKED); else if(own_channel == channel_from) this.connection_handler.sound.play(Sound.USER_LEFT_KICKED_CHANNEL); } else { console.warn(tr("Unknown reason id %o"), json["reasonid"]); } } handleNotifyChannelMoved(json) { json = json[0]; //Only one bulk let tree = this.connection.client.channelTree; let channel = tree.findChannel(json["cid"]); if(!channel) { log.error(LogCategory.NETWORKING, tr("Unknown channel move (Channel)!")); return 0; } let prev = tree.findChannel(json["order"]); if(!prev && json["order"] != 0) { log.error(LogCategory.NETWORKING, tr("Unknown channel move (prev)!")); return 0; } let parent = tree.findChannel(json["cpid"]); if(!parent && json["cpid"] != 0) { log.error(LogCategory.NETWORKING, tr("Unknown channel move (parent)!")); return 0; } tree.moveChannel(channel, prev, parent); } handleNotifyChannelEdited(json) { json = json[0]; //Only one bulk let tree = this.connection.client.channelTree; let channel = tree.findChannel(json["cid"]); if(!channel) { log.error(LogCategory.NETWORKING, tr("Unknown channel edit (Channel)!")); return 0; } let updates: { key: string, value: string }[] = []; for(let key in json) { if(key === "cid") continue; if(key === "invokerid") continue; if(key === "invokername") continue; if(key === "invokeruid") continue; if(key === "reasonid") continue; updates.push({key: key, value: json[key]}); } channel.updateVariables(...updates); if(this.connection_handler.getClient().currentChannel() === channel) { //TODO: Playback sound that your channel has been edited this.connection_handler.update_voice_status(); } } handleNotifyTextMessage(json) { json = json[0]; //Only one bulk let mode = json["targetmode"]; if(mode == 1){ //json["invokerid"], json["invokername"], json["invokeruid"] const target_client_id = parseInt(json["target"]); const target_own = target_client_id === this.connection.client.getClientId(); if(target_own && target_client_id === json["invokerid"]) { log.error(LogCategory.NETWORKING, tr("Received conversation message from invalid client id. Data: %o"), json); return; } const conversation_manager = this.connection_handler.side_bar.private_conversations(); const conversation = conversation_manager.find_conversation({ client_id: target_own ? parseInt(json["invokerid"]) : target_client_id, unique_id: target_own ? json["invokeruid"] : undefined, name: target_own ? json["invokername"] : undefined }, { create: target_own, attach: target_own }); if(!conversation) { log.error(LogCategory.NETWORKING, tr("Received conversation message for unknown conversation! (%s)"), target_own ? tr("Remote message") : tr("Own message")); return; } conversation.append_message(json["msg"], { type: target_own ? "partner" : "self", name: json["invokername"], unique_id: json["invokeruid"], client_id: parseInt(json["invokerid"]) }); if(target_own) { this.connection_handler.sound.play(Sound.MESSAGE_RECEIVED, {default_volume: .5}); const client = this.connection_handler.channelTree.findClient(parseInt(json["invokerid"])); if(client) /* the client itself might be invisible */ client.flag_text_unread = conversation.is_unread(); } else { this.connection_handler.sound.play(Sound.MESSAGE_SEND, {default_volume: .5}); } } else if(mode == 2) { const invoker = this.connection_handler.channelTree.findClient(parseInt(json["invokerid"])); const own_channel_id = this.connection.client.getClient().currentChannel().channelId; const channel_id = typeof(json["cid"]) !== "undefined" ? parseInt(json["cid"]) : own_channel_id; const channel = this.connection_handler.channelTree.findChannel(channel_id) || this.connection_handler.getClient().currentChannel(); if(json["invokerid"] == this.connection.client.clientId) this.connection_handler.sound.play(Sound.MESSAGE_SEND, {default_volume: .5}); else if(channel_id == own_channel_id) { this.connection_handler.sound.play(Sound.MESSAGE_RECEIVED, {default_volume: .5}); } const conversations = this.connection_handler.side_bar.channel_conversations(); const conversation = conversations.conversation(channel_id); conversation.register_new_message({ sender_database_id: invoker ? invoker.properties.client_database_id : 0, sender_name: json["invokername"], sender_unique_id: json["invokeruid"], timestamp: typeof(json["timestamp"]) === "undefined" ? Date.now() : parseInt(json["timestamp"]), message: json["msg"] }); if(conversation.is_unread() && channel) channel.flag_text_unread = true; } else if(mode == 3) { this.connection_handler.log.log(server_log.Type.GLOBAL_MESSAGE, { message: json["msg"], sender: { client_unique_id: json["invokeruid"], client_name: json["invokername"], client_id: parseInt(json["invokerid"]) } }); const invoker = this.connection_handler.channelTree.findClient(parseInt(json["invokerid"])); const conversations = this.connection_handler.side_bar.channel_conversations(); const conversation = conversations.conversation(0); conversation.register_new_message({ sender_database_id: invoker ? invoker.properties.client_database_id : 0, sender_name: json["invokername"], sender_unique_id: json["invokeruid"], timestamp: typeof(json["timestamp"]) === "undefined" ? Date.now() : parseInt(json["timestamp"]), message: json["msg"] }); this.connection_handler.channelTree.server.flag_text_unread = conversation.is_unread(); } } notifyClientChatComposing(json) { json = json[0]; const conversation_manager = this.connection_handler.side_bar.private_conversations(); const conversation = conversation_manager.find_conversation({ client_id: parseInt(json["clid"]), unique_id: json["cluid"], name: undefined }, { create: false, attach: false }); if(!conversation) return; conversation.trigger_typing(); } handleNotifyClientChatClosed(json) { json = json[0]; //Only one bulk //Chat partner has closed the conversation //clid: "6" //cluid: "YoWmG+dRGKD+Rxb7SPLAM5+B9tY=" const conversation_manager = this.connection_handler.side_bar.private_conversations(); const conversation = conversation_manager.find_conversation({ client_id: parseInt(json["clid"]), unique_id: json["cluid"], name: undefined }, { create: false, attach: false }); if(!conversation) { log.warn(LogCategory.GENERAL, tr("Received chat close for client, but we haven't a chat open.")); return; } conversation.set_state(PrivateConversationState.CLOSED); } handleNotifyClientUpdated(json) { json = json[0]; //Only one bulk let client = this.connection.client.channelTree.findClient(json["clid"]); if(!client) { log.error(LogCategory.NETWORKING, tr("Tried to update an non existing client")); return; } let updates: { key: string, value: string }[] = []; for(let key in json) { if(key == "clid") continue; updates.push({key: key, value: json[key]}); } client.updateVariables(...updates); } handleNotifyServerEdited(json) { json = json[0]; let updates: { key: string, value: string }[] = []; for(let key in json) { if(key === "invokerid") continue; if(key === "invokername") continue; if(key === "invokeruid") continue; if(key === "reasonid") continue; updates.push({key: key, value: json[key]}); } this.connection.client.channelTree.server.updateVariables(false, ...updates); } handleNotifyServerUpdated(json) { json = json[0]; let updates: { key: string, value: string }[] = []; for(let key in json) { if(key === "invokerid") continue; if(key === "invokername") continue; if(key === "invokeruid") continue; if(key === "reasonid") continue; updates.push({key: key, value: json[key]}); } this.connection.client.channelTree.server.updateVariables(true, ...updates); } handleNotifyMusicPlayerInfo(json) { json = json[0]; let bot = this.connection.client.channelTree.find_client_by_dbid(json["bot_id"]); if(!bot || !(bot instanceof MusicClientEntry)) { log.warn(LogCategory.CLIENT, tr("Got music player info for unknown or invalid bot! (ID: %i, Entry: %o)"), json["bot_id"], bot); return; } bot.handlePlayerInfo(json); } handleNotifyClientPoke(json) { json = json[0]; spawnPoke(this.connection_handler, { id: parseInt(json["invokerid"]), name: json["invokername"], unique_id: json["invokeruid"] }, json["msg"]); this.connection_handler.sound.play(Sound.USER_POKED_SELF); } //TODO server chat message handleNotifyServerGroupClientAdd(json) { json = json[0]; const self = this.connection.client.getClient(); if(json["clid"] == self.clientId()) this.connection_handler.sound.play(Sound.GROUP_SERVER_ASSIGNED_SELF); } //TODO server chat message handleNotifyServerGroupClientRemove(json) { json = json[0]; const self = this.connection.client.getClient(); if(json["clid"] == self.clientId()) { this.connection_handler.sound.play(Sound.GROUP_SERVER_REVOKED_SELF); } else { } } //TODO server chat message handleNotifyClientChannelGroupChanged(json) { json = json[0]; const self = this.connection.client.getClient(); if(json["clid"] == self.clientId()) { this.connection_handler.sound.play(Sound.GROUP_CHANNEL_CHANGED_SELF); } } handleNotifyChannelSubscribed(json) { for(const entry of json) { const channel = this.connection.client.channelTree.findChannel(entry["cid"]); if(!channel) { console.warn(tr("Received channel subscribed for not visible channel (cid: %d)"), entry['cid']); continue; } channel.flag_subscribed = true; } } handleNotifyChannelUnsubscribed(json) { for(const entry of json) { const channel = this.connection.client.channelTree.findChannel(entry["cid"]); if(!channel) { console.warn(tr("Received channel unsubscribed for not visible channel (cid: %d)"), entry['cid']); continue; } channel.flag_subscribed = false; for(const client of channel.clients(false)) this.connection.client.channelTree.deleteClient(client); } } handleNotifyConversationHistory(json: any[]) { const conversations = this.connection.client.side_bar.channel_conversations(); const conversation = conversations.conversation(parseInt(json[0]["cid"])); if(!conversation) { log.warn(LogCategory.NETWORKING, tr("Received conversation history for invalid or unknown conversation (%o)"), json[0]["cid"]); return; } for(const entry of json) { conversation.register_new_message({ message: entry["msg"], sender_unique_id: entry["sender_unique_id"], sender_name: entry["sender_name"], timestamp: parseInt(entry["timestamp"]), sender_database_id: parseInt(entry["sender_database_id"]) }, false); } /* now update the boxes */ /* No update needed because the command which triggers this notify should update the chat box on success conversation.fix_scroll(true); conversation.handle.update_chat_box(); */ } handleNotifyConversationMessageDelete(json: any[]) { let conversation: Conversation; const conversations = this.connection.client.side_bar.channel_conversations(); for(const entry of json) { if(typeof(entry["cid"]) !== "undefined") conversation = conversations.conversation(parseInt(entry["cid"]), false); if(!conversation) continue; conversation.delete_messages(parseInt(entry["timestamp_begin"]), parseInt(entry["timestamp_end"]), parseInt(entry["cldbid"]), parseInt(entry["limit"])); } } handleNotifyMusicStatusUpdate(json: any[]) { json = json[0]; const bot_id = parseInt(json["bot_id"]); const client = this.connection.client.channelTree.find_client_by_dbid(bot_id); if(!client) { log.warn(LogCategory.CLIENT, tr("Received music bot status update for unknown bot (%d)"), bot_id); return; } client.events.fire("music_status_update", { player_replay_index: parseInt(json["player_replay_index"]), player_buffered_index: parseInt(json["player_buffered_index"]) }); } handleMusicPlayerSongChange(json: any[]) { json = json[0]; const bot_id = parseInt(json["bot_id"]); const client = this.connection.client.channelTree.find_client_by_dbid(bot_id); if(!client) { log.warn(LogCategory.CLIENT, tr("Received music bot status update for unknown bot (%d)"), bot_id); return; } const song_id = parseInt(json["song_id"]); let song: SongInfo; if(song_id) { song = new SongInfo(); JSON.map_to(song, json); } client.events.fire("music_song_change", { song: song }); } handleNotifyPlaylistSongAdd(json: any[]) { json = json[0]; const playlist_id = parseInt(json["playlist_id"]); const client = this.connection.client.channelTree.clients.find(e => e instanceof MusicClientEntry && e.properties.client_playlist_id === playlist_id); if(!client) { log.warn(LogCategory.CLIENT, tr("Received playlist song add event, but we've no music bot for the playlist (%d)"), playlist_id); return; } client.events.fire("playlist_song_add", { song: { song_id: parseInt(json["song_id"]), song_invoker: json["song_invoker"], song_previous_song_id: parseInt(json["song_previous_song_id"]), song_url: json["song_url"], song_url_loader: json["song_url_loader"], song_loaded: json["song_loaded"] == true || json["song_loaded"] == "1", song_metadata: json["song_metadata"] } }); } handleNotifyPlaylistSongRemove(json: any[]) { json = json[0]; const playlist_id = parseInt(json["playlist_id"]); const client = this.connection.client.channelTree.clients.find(e => e instanceof MusicClientEntry && e.properties.client_playlist_id === playlist_id); if(!client) { log.warn(LogCategory.CLIENT, tr("Received playlist song remove event, but we've no music bot for the playlist (%d)"), playlist_id); return; } const song_id = parseInt(json["song_id"]); client.events.fire("playlist_song_remove", { song_id: song_id }); } handleNotifyPlaylistSongReorder(json: any[]) { json = json[0]; const playlist_id = parseInt(json["playlist_id"]); const client = this.connection.client.channelTree.clients.find(e => e instanceof MusicClientEntry && e.properties.client_playlist_id === playlist_id); if(!client) { log.warn(LogCategory.CLIENT, tr("Received playlist song reorder event, but we've no music bot for the playlist (%d)"), playlist_id); return; } const song_id = parseInt(json["song_id"]); const previous_song_id = parseInt(json["song_previous_song_id"]); client.events.fire("playlist_song_reorder", { song_id: song_id, previous_song_id: previous_song_id }); } handleNotifyPlaylistSongLoaded(json: any[]) { json = json[0]; const playlist_id = parseInt(json["playlist_id"]); const client = this.connection.client.channelTree.clients.find(e => e instanceof MusicClientEntry && e.properties.client_playlist_id === playlist_id); if(!client) { log.warn(LogCategory.CLIENT, tr("Received playlist song loaded event, but we've no music bot for the playlist (%d)"), playlist_id); return; } const song_id = parseInt(json["song_id"]); client.events.fire("playlist_song_loaded", { song_id: song_id, success: json["success"] == 1, error_msg: json["load_error_msg"], metadata: json["song_metadata"] }); } }