diff --git a/web/js/sockerWrapper.js b/web/js/sockerWrapper.js
new file mode 100644
index 0000000..fa6c78d
--- /dev/null
+++ b/web/js/sockerWrapper.js
@@ -0,0 +1,625 @@
+/*
+ * 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 = [];
+});
\ No newline at end of file
diff --git a/web/overlay/index.html b/web/overlay/index.html
new file mode 100644
index 0000000..c5a2a14
--- /dev/null
+++ b/web/overlay/index.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+ PlampBot Song Requests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/overlay/index.js b/web/overlay/index.js
new file mode 100644
index 0000000..22e2b57
--- /dev/null
+++ b/web/overlay/index.js
@@ -0,0 +1,158 @@
+$(function () {
+ var webSocket = getWebSocket(),
+ queryMap = getQueryMap(),
+ isDebug = localStorage.getItem('phantombot_follow_debug') === 'true' || false;
+ queue = [];
+
+ /*
+ * @function Gets a new instance of the websocket.
+ *
+ * @return {ReconnectingWebSocket}
+ */
+ function getWebSocket() {
+ let socketUri = ((window.location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.host + '/ws/followpolls'), // URI of the socket.
+ reconnectInterval = 5000; // How often in milliseconds we should try reconnecting.
+
+ return new ReconnectingWebSocket(socketUri, null, {
+ reconnectInterval: reconnectInterval
+ });
+ }
+
+ /*
+ * @function Parses the query params in the URL and puts them into a map.
+ *
+ * @return {Map}
+ */
+ function getQueryMap() {
+ let queryString = window.location.search, // Query string that starts with ?
+ queryParts = queryString.substr(1).split('&'), // Split at each &, which is a new query.
+ queryMap = new Map(); // Create a new map for save our keys and values.
+
+ for (let i = 0; i < queryParts.length; i++) {
+ let key = queryParts[i].substr(0, queryParts[i].indexOf('=')),
+ value = queryParts[i].substr(queryParts[i].indexOf('=') + 1, queryParts[i].length);
+
+ if (key.length > 0 && value.length > 0) {
+ queryMap.set(key, value);
+ }
+ }
+
+ return queryMap;
+ }
+
+ /*
+ * @function Prints debug logs.
+ *
+ * @param {String} message
+ */
+ function printDebug(message, force) {
+ if (isDebug || force) {
+ console.log('%c[PhantomBot Log]', 'color: #6441a5; font-weight: 900;', message);
+ }
+ }
+
+ /*
+ * @function Toggles the debug mode.
+ *
+ * @param {String} toggle
+ */
+ window.toggleDebug = function (toggle) {
+ localStorage.setItem('phantombot_follow_debug', toggle.toString());
+
+ // Refresh the page.
+ window.location.reload();
+ }
+
+ /*
+ * @function Checks if the query map has the option, if not, returns default.
+ *
+ * @param {String} option
+ * @param {String} def
+ * @return {String}
+ */
+ function getOptionSetting(option, def) {
+ if (queryMap.has(option)) {
+ return queryMap.get(option);
+ } else {
+ return def;
+ }
+ }
+
+ /*
+ * @function Sends a message to the socket
+ *
+ * @param {String} message
+ */
+ function sendToSocket(message) {
+ try {
+ webSocket.send(JSON.stringify(message));
+ } catch (ex) {
+ printDebug('Failed to send a message to the socket: ' + ex.stack);
+ }
+ }
+
+
+ function sleep(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+
+ /*
+ * @event Called once the socket opens.
+ */
+ webSocket.onopen = function () {
+ printDebug('Successfully connected to the socket.', true);
+ // Authenticate with the socket.
+ sendToSocket({
+ authenticate: getAuth()
+ });
+ };
+
+ /*
+ * @event Called when the socket closes.
+ */
+ webSocket.onclose = function () {
+ printDebug('Disconnected from the socket.', true);
+ };
+
+ /*
+ * @event Called when we get a message.
+ *
+ * @param {Object} e
+ */
+ webSocket.onmessage = function (e) {
+ try {
+ let rawMessage = e.data,
+ message = JSON.parse(rawMessage);
+
+ printDebug('[MESSAGE] ' + rawMessage);
+
+ if (message.query_id === undefined) {
+ // Check for our auth result.
+ if (message.authresult !== undefined) {
+ if (message.authresult === 'true') {
+ printDebug('Successfully authenticated with the socket.', true);
+ // Handle this.
+ handleBrowserInteraction()
+ } else {
+ printDebug('Failed to authenticate with the socket.', true);
+ }
+ } else
+
+ // Queue all events and process them one at-a-time.
+ if (message.alert_image !== undefined || message.audio_panel_hook !== undefined) {
+ queue.push(message);
+ }
+
+ // Message cannot be handled error.
+ else {
+ printDebug('Failed to process message from socket: ' + rawMessage);
+ }
+ }
+ } catch (ex) {
+ printDebug('Failed to parse socket message [' + e.data + ']: ' + e.stack);
+ }
+ };
+
+ // Handle processing the queue.
+ setInterval(handleQueue, 5e2);
+});
\ No newline at end of file
diff --git a/web/overlay/follow/index.html b/web/overlay/styles.css
similarity index 100%
rename from web/overlay/follow/index.html
rename to web/overlay/styles.css
diff --git a/web/overlay/subscribe/index.html b/web/overlay/subscribe/index.html
deleted file mode 100644
index e69de29..0000000