/* * Copyright (C) 2016-2019 phantombot.tv * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ //NOTE: this is straight copied from panel's index.js // Main socket and functions. $(function() { var helpers = {}; helpers.DEBUG_STATES = { NONE: 0, DEBUG: 1, INFO: 2, FORCE: 3 }; // Debug status. 0 = off | 1 = on. helpers.DEBUG_STATE = (localStorage.getItem('phantombot_debug_state') !== null ? parseInt(localStorage.getItem('phantombot_debug_state')) : helpers.DEBUG_STATES.NONE); // Debug types. helpers.LOG_TYPE = helpers.DEBUG_STATES; /* * @function Used to print debug messages in the console. * * @param {String} message * @param {Number} type */ helpers.log = function(message, type) { if (helpers.DEBUG_STATE === helpers.DEBUG_STATES.DEBUG || type === helpers.DEBUG_STATE || type === helpers.LOG_TYPE.FORCE) { console.log('%c[PhantomBot Log]', 'color: #6441a5; font-weight: 900;', message); } }; /* * @function Used to print error messages in the console. * * @param {String} message * @param {Number} type */ helpers.logError = function(message, type) { console.log('%c[PhantomBot Error]', 'color: red; font-weight: 900;', message); }; var webSocket = new ReconnectingWebSocket((getProtocol() === 'https://' || window.location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.host + '/ws/panel', null, { reconnectInterval: 500 }), callbacks = [], listeners = [], familyHandlers = {}, socket = {}; /* * @function Used to send messages to the socket. This should be private to this script. * * @param {Object} message */ var sendToSocket = function(message) { try { let json = JSON.stringify(message); webSocket.send(json); // Make sure to not show the user's token. if (json.indexOf('authenticate') !== -1) { helpers.log('sendToSocket:: ' + json.substring(0, json.length - 20) + '.."}', helpers.LOG_TYPE.DEBUG); } else { helpers.log('sendToSocket:: ' + json, helpers.LOG_TYPE.DEBUG); } } catch (e) { helpers.logError('Failed to send message to socket: ' + e.message, helpers.LOG_TYPE.DEBUG); } }; /* * @function Generates a callback * * @param {String} id * @param {Array} tables * @param {Boolean} isUpdate * @param {Function} callback * @param {Boolean} storeKey */ var generateCallBack = function(id, tables, isUpdate, isArray, callback, storeKey) { if (callbacks[id] !== undefined) { helpers.logError('Callback with id "' + id + '" exists already. Aborting update.', helpers.LOG_TYPE.FORCE); } else { helpers.log('Created callback with id ' + id, helpers.LOG_TYPE.DEBUG); callbacks[id] = { await: (tables.length === 0 ? 1 : tables.length), isUpdate: isUpdate, isArray: isArray, func: function(e) { try { callback(e); } catch (ex) { // Line number won't be accurate, function will by anonymous, but we get the stack so it should be fine. helpers.logError('Failed to run callback: ' + ex.stack, helpers.LOG_TYPE.FORCE); } }, storeKey: storeKey, queryData: [] }; } }; /* * @function Adds a listener for the socket. * * @param {String} listener_id * @param {Function} callback */ socket.addListener = function(listener_id, callback) { if (listeners[listener_id] === undefined) { helpers.log('Adding listener with id ' + listener_id); listeners[listener_id] = function(e) { try { callback(e); } catch (ex) { // Line number won't be accurate, function will by anonymous, but we get the stack so it should be fine. helpers.logError('Failed to run listener: ' + ex.stack, helpers.LOG_TYPE.FORCE); } }; } }; /* * @function Removes a listener from the socket. * * @param {String} listener_id */ socket.removeListener = function(listener_id) { if (listeners[listener_id] !== undefined) { delete listeners[listener_id]; } }; socket.addFamilyHandler = function(familyName, callback) { familyHandlers[familyName] = function(e) { try { callback(e); } catch (ex) { // Line number won't be accurate, function will by anonymous, but we get the stack so it should be fine. helpers.logError('Failed to run family handler: ' + ex.stack, helpers.LOG_TYPE.FORCE); } }; } socket.removeFamilyHandler = function(familyName) { if (familyHandlers[familyName] !== undefined) { delete listeners[familyName]; } }; /* * @function Runs a bot commands as the bot in async, thus returning right away. * * @param {String} callback_id * @param {String} command * @param {Function} callback */ socket.sendCommand = function(callback_id, command, callback) { // Genetate a callback. generateCallBack(callback_id, [], true, false, callback); // Send the command. sendToSocket({ command: String(command), query_id: callback_id }); }; /* * @function Sends a raw request to the socket. * * @param {String} callback_id * @param {Function} callback */ socket.getBotVersion = function(callback_id, callback) { // Genetate a callback. generateCallBack(callback_id, [], true, false, callback); // Send the request. sendToSocket({ version: callback_id }); }; /* * @function Runs a bot commands as the bot. * * @param {String} callback_id * @param {String} command * @param {Function} callback */ socket.sendCommandSync = function(callback_id, command, callback) { // Genetate a callback. generateCallBack(callback_id, [], true, false, callback); // Send the command. sendToSocket({ command_sync: String(command), query_id: callback_id }); }; /* * @function Sends the websocket event. * * @param {String} callback_id * @param {String} script * @param {String} argsString * @param {Array} args * @param {Function} callback */ socket.wsEvent = function(callback_id, script, argsString, args, callback) { // Genetate a callback. generateCallBack(callback_id, [], true, false, callback); // Send event. sendToSocket({ socket_event: callback_id, script: script, args: { arguments: String(argsString), args: args } }); }; /* * @function Updates a value in the database of the bot. * * @param {String} callback_id * @param {String} table * @param {String} key * @param {String} value * @param {Function} callback */ socket.updateDBValue = function(callback_id, table, key, value, callback) { // Genetate a callback. generateCallBack(callback_id, [], true, false, callback); // Update the value. sendToSocket({ dbupdate: callback_id, update: { table: String(table), key: String(key), value: String(value) } }); }; /* * @function Updates values in the database of the bot. * * @param {String} callback_id * @param {Object} dataObj {tables: [], keys: [], values: } * @param {Function} callback */ socket.updateDBValues = function(callback_id, dataObj, callback) { // Genetate a callback. generateCallBack(callback_id, dataObj.tables, true, false, callback); // Start sending the updates to the socket. for (let i = 0; i < dataObj.tables.length; i++) { sendToSocket({ dbupdate: callback_id, update: { table: String(dataObj.tables[i]), key: String(dataObj.keys[i]), value: String(dataObj.values[i]) } }); } }; /* * @function Increases a value in the database. * * @param {String} callback_id * @param {String} table * @param {String} key * @param {String} value * @param {Function} callback */ socket.incrDBValue = function(callback_id, table, key, value, callback) { // Genetate a callback. generateCallBack(callback_id, [], true, false, callback); // Update the value. sendToSocket({ dbincr: callback_id, incr: { table: table, key: key, value: value } }); }; /* * @function Decreases a value in the database. * * @param {String} callback_id * @param {String} table * @param {String} key * @param {String} value * @param {Function} callback */ socket.decrDBValue = function(callback_id, table, key, value, callback) { // Genetate a callback. generateCallBack(callback_id, [], true, false, callback); // Update the value. sendToSocket({ dbdecr: callback_id, decr: { table: table, key: key, value: value } }); }; /* * @function Gets a value from the database * * @param {String} callback_id * @param {String} table * @param {String} key * @param {Function} callback */ socket.getDBValue = function(callback_id, table, key, callback) { // Genetate a callback. generateCallBack(callback_id, [], false, false, callback); // Query database. sendToSocket({ dbquery: callback_id, query: { table: String(table), key: String(key) } }); }; /* * @function Gets values from the database * * @param {String} callback_id * @param {Object} dataObj {tables: [], keys: []} * @param {Function} callback * @param {Boolean} storeKey - Store the value with the key name from the DB. Default stores it as the table, thus making it only possible to query the table once. */ socket.getDBValues = function(callback_id, dataObj, storeKey, callback) { callback = (callback === undefined ? storeKey : callback); // Genetate a callback. generateCallBack(callback_id, dataObj.tables, false, false, callback, (typeof storeKey === 'function' ? false : true)); // Start sending the updates to the socket. for (let i = 0; i < dataObj.tables.length; i++) { sendToSocket({ dbquery: callback_id, query: { table: String(dataObj.tables[i]), key: String(dataObj.keys[i]) } }); } }; /* * @function Gets values from the database by an order. * * @param {String} callback_id * @param {String} table * @param {Number} limit * @param {Number} offset * @param {String} order * @param {Function} callback */ socket.getDBTableValuesByOrder = function(callback_id, table, limit, offset, order, isNumber, callback) { // Genetate a callback. generateCallBack(callback_id, [], false, true, callback); // Query database. sendToSocket({ dbvaluesbyorder: callback_id, query: { table: table, limit: String(limit), offset: String(offset), order: order, number: String(isNumber) } }); }; /* * @function Gets all keys and values from a database table. * * @param {String} callback_id * @param {String} table * @param {Function} callback */ socket.getDBTableValues = function(callback_id, table, callback) { // Genetate a callback. generateCallBack(callback_id, [], false, true, callback); // Query database. sendToSocket({ dbkeys: callback_id, query: { table: String(table) } }); }; /* * @function Gets all keys and values from multiple database table. * * @param {String} callback_id * @param {Array Object} tables [{table: 'a'}, {table: 'b'}] * @param {Function} callback */ socket.getDBTablesValues = function(callback_id, tables, callback) { // Genetate a callback. generateCallBack(callback_id, [], false, true, callback); // Query database. sendToSocket({ dbkeyslist: callback_id, query: tables }); }; /* * @function Removes the data from the database. * * @param {String} callback_id * @param {String} table * @param {String} key * @param {Function} callback */ socket.removeDBValue = function(callback_id, table, key, callback) { // Genetate a callback. generateCallBack(callback_id, [], false, true, callback); // Send the event. sendToSocket({ dbdelkey: callback_id, delkey: { table: String(table), key: String(key) } }); }; /* * @function Removes the data from the database. * * @param {String} callback_id * @param {Object} dataObj {tables: [], keys: []} * @param {Function} callback */ socket.removeDBValues = function(callback_id, dataObj, callback) { // Genetate a callback. generateCallBack(callback_id, dataObj.tables, false, true, callback); for (let i = 0; i < dataObj.tables.length; i++) { // Send the event. sendToSocket({ dbdelkey: callback_id, delkey: { table: String(dataObj.tables[i]), key: String(dataObj.keys[i]) } }); } }; // WebSocket events. socket.getReadyState = function() {return webSocket.readyState;} /* * @function Called when the socket opens. */ webSocket.onopen = function() { helpers.log('Connection established with the websocket.', helpers.LOG_TYPE.FORCE); // Auth with the socket. sendToSocket({ authenticate: getAuth() }); }; /* * @function Socket calls when it closes */ webSocket.onclose = function() { helpers.logError('Connection lost with the websocket.', helpers.LOG_TYPE.FORCE); }; /* * @function Socket calls when it gets message. */ webSocket.onmessage = function(e) { try { helpers.log('Message from socket: ' + e.data, helpers.LOG_TYPE.DEBUG); if (e.data === 'PING') { webSocket.send('PONG'); return; } let message = JSON.parse(e.data); // Check this message here before doing anything else. if (message.authresult !== undefined) { if (message.authresult === 'false') { helpers.logError('Failed to auth with the socket.', helpers.LOG_TYPE.FORCE); } else { // This is to stop a reconnect loading the main page. if (helpers.isAuth === true) { return; } else { helpers.isAuth = true; } // XXX: revent loading main page //$.loadPage('dashboard', 'dashboard.html'); } return; } // Make sure this isn't a version request. if (message.versionresult !== undefined) { // Call the callback. callbacks[message.versionresult].func(message); // Delete the callback. delete callbacks[message.versionresult]; } else if(message.query_id !== undefined) { // console.log("base got websocket with data"); // console.log(e); // Handle callbacks. let callback = callbacks[message.query_id], listener = listeners[message.query_id]; if (callback !== undefined) { // Add our data to the callback array. if (!callback.isUpdate) { if (callback.isArray) { callback.queryData = message.results; } else if (callback.storeKey === true) { callback.queryData[Object.keys(message.results)[1]] = message.results.value; } else { callback.queryData[message.results.table] = message.results.value; } } // If we got all the data, run the callback. if (--callback.await === 0) { // Run the function and send the query data with it. callback.func(callback.queryData); // Log this. helpers.log('Called callback with id: ' + message.query_id, helpers.LOG_TYPE.DEBUG); // Remove the callback from the array. delete callbacks[message.query_id]; // Remove any active spinners. if (message.query_id !== 'get_bot_updates' && message.query_id.indexOf('get') !== -1) { // Remove any active spinners. $('.load-ajax').remove(); } if (message.query_id.indexOf('module_toggle') !== -1 || message.query_id.indexOf('module_status') !== -1 || message.query_id.endsWith('module')) { if (message.results.value == 'false') { $('.load-ajax').remove(); } } } else { helpers.log('Awaiting for data (' + callback.await + ' left) before calling callback with id: ' + message.query_id, helpers.LOG_TYPE.DEBUG); } } else if (listener !== undefined) { // Call the listener. listener(message.results); } } else if(message.eventFamily !== undefined) { let handler = familyHandlers[message.eventFamily]; if(handler !== undefined) { handler(e) } } } catch (ex) { // Line number won't be accurate, function will by anonymous, but we get the stack so it should be fine. helpers.logError('Failed to parse message from socket: ' + ex.stack + '\n\n' + e.data, helpers.LOG_TYPE.FORCE); } }; // Make this a global object. window.socket = socket; // Store all timers in here so we can destroy them. window.timers = []; });