phantombot-plugins/web/js/socketWrapper.js
2022-02-09 13:02:51 +01:00

625 lines
20 KiB
JavaScript

/*
* 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 <http://www.gnu.org/licenses/>.
*/
//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://') + 'twitchbot.helcel.net' + '/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: local_auth || 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 = [];
});