/**
* Copyright (C) 2023 "SilenusTA https://www.twitch.tv/olddepressedgamer"
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import * as logger from "../../../backend/data_center/modules/logger.js";
import sr_api from "../../../backend/data_center/public/streamroller-message-api.cjs";
import { dirname } from "path";
import { fileURLToPath } from "url";
const __dirname = dirname(fileURLToPath(import.meta.url));
import fs from "fs";
import { default_serverData } from "./default_data.js"
const localConfig = {
host: "http://localhost",
port: "3000",
heartBeatHandle: null,
heartBeatTimeout: "5000",
DataCenterSocket: null,
}
// defaults for the serverConfig (our saved persistant data)
const default_serverConfig = {
__version__: "0.1.1",
extensionname: "autopilot",
channel: "AUTOPILOT_BE",
autopilotenabled: "on",
autopilotresetdefaults: "off"
}
let serverConfig = structuredClone(default_serverConfig);
let serverData = structuredClone(default_serverData);
const triggersandactions =
{
extensionname: serverConfig.extensionname,
description: "Autopilot handles triggers/actions that allow the user to perform interesting interactions betewen extensions",
// these are messages we can send out that other extensions might want to use to trigger an action
version: "0.1.1",
channel: serverConfig.channel,
triggers:
[
{
name: "Macro Triggered",
displaytitle: "Macro Triggered",
description: "A Macro was triggered",
messagetype: "trigger_MacroTriggered",
parameters: {}
},
{
name: "AllTriggers",
displaytitle: "AllTriggers",
description: "Catches all triggers (for debugging)",
messagetype: "trigger_AllTriggers",
parameters: {}
}
],
// these are messages we can receive to perform an action
actions:
[
{
name: "Activate Macro",
displaytitle: "Activate Macro",
description: "Activate a macro function",
messagetype: "action_ActivateMacro",
parameters: { name: "" }
},
{
name: "Set Group Pause State",
displaytitle: "Set Group Pause State",
description: "Pause/Unpause groups",
messagetype: "action_SetGroupPauseState",
parameters: {
group: "",
state: "unpaused"
}
},
{
name: "LogToConsole",
displaytitle: "LogToConsole",
description: "Log triggers to console",
messagetype: "action_LogToConsole",
parameters: {}
},
],
}
// ============================================================================
// FUNCTION: startClient
// ============================================================================
/**
* Starts the extension using the given data.
* @param {String} host
* @param {Number} port
* @param {Number} heartbeat
*/
async function startServer (host, port, heartbeat)
{
// setup the express app that will handle client side page requests
//app.use("/images/", express.static(__dirname + '/public/images'));
localConfig.host = host;
localConfig.port = port;
try
{
ConnectToDataCenter(localConfig.host, localConfig.port);
}
catch (err)
{
logger.err(serverConfig.extensionname + "autopilot.startServer", "initialise failed:", err);
}
}
// ============================================================================
// FUNCTION: ConnectToDataCenter
// ============================================================================
/**
* Connect to the StreamRoller websocket
* @param {string} host
* @param {number} port
*/
function ConnectToDataCenter (host, port)
{
try
{
localConfig.DataCenterSocket = sr_api.setupConnection(onDataCenterMessage, onDataCenterConnect, onDataCenterDisconnect,
host, port);
} catch (err)
{
logger.err(serverConfig.extensionname + "datahandler.initialise", "DataCenterSocket connection failed:", err);
}
}
/**
* Called when the StreamRoller websocket disconnects
* @param {string} reason
*/
function onDataCenterDisconnect (reason)
{
}
// ============================================================================
// FUNCTION: onDataCenterConnect
// ============================================================================
/**
* Called when the StreaRoller websocket connection starts
* @param {object} socket
*/
function onDataCenterConnect (socket)
{
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket(
"RequestConfig",
serverConfig.extensionname
));
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket(
"RequestData",
serverConfig.extensionname
));
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket("CreateChannel", serverConfig.extensionname, serverConfig.channel)
);
RequestExtList();
localConfig.heartBeatHandle = setTimeout(heartBeatCallback, localConfig.heartBeatTimeout)
}
// ============================================================================
// FUNCTION: onDataCenterMessage
// ============================================================================
/**
* Handles all streamroller inbound messages
* @param {object} server_packet
*/
function onDataCenterMessage (server_packet)
{
// -------------------------------------------------------------------------------------------------
// RECEIVED CONFIG
// -------------------------------------------------------------------------------------------------
if (server_packet.type === "ConfigFile")
{
// check it is our config
if (server_packet.data != "" && server_packet.to === serverConfig.extensionname)
{
if (server_packet.data.__version__ != default_serverConfig.__version__)
{
serverConfig = structuredClone(default_serverConfig);
console.log("\x1b[31m" + serverConfig.extensionname
+ " ConfigFile Updated", "The config file has been Updated to the latest version v"
+ default_serverConfig.__version__ + ". Your settings may have changed " + "\x1b[0m");
}
else
{
// update our config
if (server_packet.data != "")
serverConfig = structuredClone(server_packet.data)
}
// update server log, mainly here if we have added new default options when a user
// updates their version of StreamRoller
SaveConfigToServer();
}
}
// -------------------------------------------------------------------------------------------------
// RECEIVED DATA File
// -------------------------------------------------------------------------------------------------
else if (server_packet.type === "DataFile")
{
if (server_packet.to === serverConfig.extensionname)
{
if (server_packet.data.__version__ != default_serverData.__version__)
{
serverData = structuredClone(default_serverData);
console.log("\x1b[31m" + serverConfig.extensionname + " Datafile Updated", "The Data file has been Updated to the latest version v" + default_serverData.__version__ + ". Your settings may have changed" + "\x1b[0m");
SaveDataToServer()
SendUserPairings("");
SendMacros()
}
else
{
if (server_packet.data != "")
{
serverData = structuredClone(server_packet.data);
SendUserPairings("");
SendMacros()
}
}
}
}
// -------------------------------------------------------------------------------------------------
// RECEIVED EXTENSION LIST
// -------------------------------------------------------------------------------------------------
else if (server_packet.type === "ExtensionList")
{
if (server_packet.to === serverConfig.extensionname)
{
localConfig.extensions = server_packet.data
RequestChList();
}
}
// -------------------------------------------------------------------------------------------------
// RECEIVED CHANNEL LIST
// -------------------------------------------------------------------------------------------------
else if (server_packet.type === "ChannelList")
{
if (server_packet.to === serverConfig.extensionname)
{
localConfig.channels = server_packet.data
localConfig.channels.forEach(element =>
{
if (element != serverConfig.channel)
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket(
"JoinChannel",
serverConfig.extensionname,
element
));
});
}
}
// -------------------------------------------------------------------------------------------------
// ### EXTENSION MESSAGE ###
// -------------------------------------------------------------------------------------------------
else if (server_packet.type === "ExtensionMessage")
{
let extension_packet = server_packet.data;
// -------------------------------------------------------------------------------------------------
// REQUEST FOR SETTINGS DIALOG
// -------------------------------------------------------------------------------------------------
if (extension_packet.type === "RequestSettingsWidgetSmallCode")
{
SendSettingsWidgetSmall(extension_packet.from);
}
else if (extension_packet.type.startsWith("action_LogToConsole"))
{
console.log("--------- action_LogToConsole -------------")
console.log(JSON.stringify(extension_packet.data, null, 2))
console.log("-------------------------------------------")
}
else if (extension_packet.type.startsWith("action_ActivateMacro"))
{
if (extension_packet.to == serverConfig.extensionname)
triggerMacroButton(extension_packet.data.name)
}
else if (extension_packet.type.startsWith("action_SetGroupPauseState"))
{
if (extension_packet.to == serverConfig.extensionname)
actionAction_SetGroupPauseState(extension_packet.data.group, extension_packet.data.state)
}
// -------------------------------------------------------------------------------------------------
// SETTINGS DIALOG DATA
// -------------------------------------------------------------------------------------------------
else if (extension_packet.type === "SettingsWidgetSmallData")
{
if (extension_packet.to === serverConfig.extensionname)
{
if (extension_packet.data.autopilotresetdefaults == "on")
{
serverConfig = structuredClone(default_serverConfig);
serverData = structuredClone(default_serverData);
console.log("\x1b[31m" + serverConfig.extensionname + " Defaults restored", "The config files have been reset. Your settings may have changed" + "\x1b[0m");
SaveConfigToServer();
SaveDataToServer();
}
else
{
handleSettingsWidgetSmallData(extension_packet.data);
SendUserPairings("");
SendMacros();
SaveConfigToServer();
}
}
}
// -------------------------------------------------------------------------------------------------
// REQUEST FOR USER TRIGGERS
// -------------------------------------------------------------------------------------------------
else if (extension_packet.type === "SendTriggerAndActions")
{
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket("ExtensionMessage",
serverConfig.extensionname,
sr_api.ExtensionPacket(
"TriggerAndActions",
serverConfig.extensionname,
triggersandactions,
"",
server_packet.from
),
"",
server_packet.from
)
)
}
// -------------------------------------------------------------------------------------------------
// REQUEST FOR USER TRIGGERS
// -------------------------------------------------------------------------------------------------
else if (extension_packet.type === "RequestUserTriggers")
{
SendUserPairings(extension_packet.from)
}
// -------------------------------------------------------------------------------------------------
// REQUEST FOR USER TRIGGERS
// -------------------------------------------------------------------------------------------------
else if (extension_packet.type === "RequestMacroImages")
{
SendMacroImages(extension_packet.from)
}
// -------------------------------------------------------------------------------------------------
// UPDATED USER PAIRINGS RECEIVED
// -------------------------------------------------------------------------------------------------
else if (extension_packet.type === "UpdateUserPairings")
{
if (server_packet.to === serverConfig.extensionname)
{
ProcessUserPairings(extension_packet.data)
SaveDataToServer()
SendUserPairings("");
SendMacros()
}
}
// -------------------------------------------------------------------------------------------------
// REQUEST MACROS
// -------------------------------------------------------------------------------------------------
else if (extension_packet.type === "RequestMacros")
{
if (server_packet.to === serverConfig.extensionname)
SendMacros()
}
// -------------------------------------------------------------------------------------------------
// REQUEST SERVER DATA FILE
// -------------------------------------------------------------------------------------------------
else if (extension_packet.type === "RequestServerDataFile")
{
//This is used on the front end so users can save off a full data file of the server prior to updating
if (server_packet.to === serverConfig.extensionname && server_packet.from === "autopilot_frontend")
{
sr_api.sendMessage(
localConfig.DataCenterSocket,
sr_api.ServerPacket(
"ExtensionMessage",
serverConfig.extensionname,
sr_api.ExtensionPacket(
"AutopilotServerData",
serverConfig.extensionname,
serverData,
"",
"autopilot_frontend"
),
"",
"autopilot_frontend"
));
}
}
// -------------------------------------------------------------------------------------------------
// REQUEST SERVER DATA FILE
// -------------------------------------------------------------------------------------------------
else if (extension_packet.type === "userRequestSaveDataFile")
{
//This is used on the front end so users can save off a full data file of the server prior to updating
if (server_packet.to === serverConfig.extensionname && server_packet.from === "autopilot_frontend")
{
parseUserRequestSaveDataFile(extension_packet.data)
}
}
// -------------------------------------------------------------------------------------------------
// RECEIVED Unhandled extension message
// -------------------------------------------------------------------------------------------------
else
{
//console.log("ExtensionMessage not handled ", extension_packet)
// logger.log(serverConfig.extensionname + ".onDataCenterMessage", "ExtensionMessage not handled ", extension_packet.type, " from ", extension_packet.from);
}
}
// -------------------------------------------------------------------------------------------------
// RECEIVED CHANNEL DATA
// -------------------------------------------------------------------------------------------------
else if (server_packet.type === "ChannelData")
{
let extension_packet = server_packet.data;
// -------------------------------------------------------------------------------------------------
// CheckForTrigger
// These are triggers in other extensions
// -------------------------------------------------------------------------------------------------
if (extension_packet.type.startsWith("trigger_"))
{
CheckTriggers(extension_packet)
}
}
// -------------------------------------------------------------------------------------------------
// UNKNOWN CHANNEL MESSAGE RECEIVED
// -------------------------------------------------------------------------------------------------
else if (server_packet.type === "UnknownChannel")
{
// channel might not exist yet, extension might still be starting up so lets rescehuled the join attempt
// need to add some sort of flood control here so we are only attempting to join one at a time
console.log("UnknownChannel", server_packet)
if (server_packet.data != "" && server_packet.channel != undefined)
{
setTimeout(() =>
{
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket(
"JoinChannel",
serverConfig.extensionname,
server_packet.data
));
}, 10000);
}
}
else if (server_packet.type === "ChannelJoined"
|| server_packet.type === "ChannelCreated"
|| server_packet.type === "ChannelLeft"
|| server_packet.type === "HeartBeat"
|| server_packet.type === "UnknownExtension"
|| server_packet.type === "ChannelJoined"
|| server_packet.type === "LoggingLevel"
)
{
// just a blank handler for items we are not using to avoid message from the catchall
}
// ------------------------------------------------ unknown message type received -----------------------------------------------
else
logger.err(serverConfig.extensionname + ".onDataCenterMessage", "Unhandled message type:", server_packet);
}
// ============================================================================
// FUNCTION: SaveConfigToServer
// ============================================================================
/**
* Saves our config on the server
*/
function SaveConfigToServer ()
{
// saves our serverConfig to the server so we can load it again next time we startup
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket(
"SaveConfig",
serverConfig.extensionname,
serverConfig,
));
}
// ===========================================================================
// FUNCTION: SendSettingsWidgetSmall
// ===========================================================================
/**
* Sends our small settins widget to the given channel
*
* @param {string} tochannel
*/
function SendSettingsWidgetSmall (tochannel)
{
fs.readFile(__dirname + "/autopilotsettingswidgetsmall.html", function (err, filedata)
{
if (err)
{
logger.err(localConfig.SYSTEM_LOGGING_TAG + localConfig.EXTENSION_NAME +
".SendSettingsWidgetSmall", "failed to load modal", err);
//throw err;
}
else
{
//get the file as a string
let modalstring = filedata.toString();
// mormal replaces
for (const [key, value] of Object.entries(serverConfig))
{
// checkboxes
if (value === "on")
modalstring = modalstring.replace(key + "checked", "checked");
else if (typeof (value) === "string" || typeof (value) === "number")
modalstring = modalstring.replaceAll(key + "text", value);
}
// send the modified modal data to the server
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket(
"ExtensionMessage", // this type of message is just forwarded on to the extension
serverConfig.extensionname,
sr_api.ExtensionPacket(
"SettingsWidgetSmallCode", // message type
serverConfig.extensionname, //our name
modalstring,// data
"",
tochannel,
serverConfig.channel
),
"",
tochannel // in this case we only need the "to" channel as we will send only to the requester
))
}
});
}
// ===========================================================================
// FUNCTION: handleSettingsWidgetSmallData
// ===========================================================================
/**
* Handles data sent when a user submits our small setting dialog box
* @param {object} modalcode
*/
function handleSettingsWidgetSmallData (modalcode)
{
serverConfig.autopilotenabled = "off";
serverConfig.autopilotresetdefaults = "off";
for (const [key, value] of Object.entries(modalcode))
serverConfig[key] = value;
}
// ============================================================================
// FUNCTION: CheckTriggers
// ============================================================================
/**
* Handles received triggers checking if we have any matching trigger/action pairs
* @param {object} data
*/
function CheckTriggers (data)
{
if (Object.keys(serverData.userPairings).length != 0 && serverData.userPairings.pairings != undefined)
{
for (const [key, value] of Object.entries(serverData.userPairings.pairings))
{
//console.log("value.trigger.messagetype", value.trigger.messagetype)
if (value.trigger.messagetype == data.data.messagetype || value.trigger.messagetype == "trigger_AllTriggers")
ProcessReceivedTrigger(value, data)
}
}
}
// ============================================================================
// FUNCTION: ProcessReceivedTrigger
// ============================================================================
/**
* Processes a triger action pairing that has been triggered.
* @param {object} pairing
* @param {object} receivedTrigger
*/
function ProcessReceivedTrigger (pairing, receivedTrigger)
{
//check if the event fields match the trigger fields we have set for this entry
// if parameters are entered we need to check that they all match
// before we trigger the action (ie if one fails to match then we must ignore this trigger)
// IE ALL CHECKS BELOW SHOULD BE FOR FAILURES TO MATCH
let match = true
// we have the correct extension, channel and message type
// lets check the variables to see if those are a match
pairing.trigger.data.forEach((param) =>
{
for (var i in param)
{
try
{
// don't check the MATCHER variables as these are used to determine how to perform the match (Start of line etc)
if (i.indexOf("MATCHER_") != 0 && i != "cooldown" && i != "lastrun")
{
// get the relevant matcher for this value
let searchtype = param["MATCHER_" + i]
if (typeof (receivedTrigger.data.parameters[i]) == "string")// && typeof param[i] === "string")
{
switch (searchtype)
{
case "2"://match anywhere
if (param[i] != "" && receivedTrigger.data.parameters[i].toLowerCase().indexOf(param[i].toLowerCase()) == -1)
match = false;
break;
case "3"://match start of line only
if (param[i] != "" && receivedTrigger.data.parameters[i].toLowerCase().indexOf(param[i].toLowerCase()) != 0)
match = false;
break;
case "4"://doesn't match
if (param[i] != "" && receivedTrigger.data.parameters[i].toLowerCase().indexOf(param[i].toLowerCase()) == 0)
match = false;
break;
case "5"://match complete word only
if (param[i] != "" && receivedTrigger.data.parameters[i].toLowerCase().indexOf(param[i].toLowerCase()) == -1)
{
match = false;
}
else
{
let wordArray = receivedTrigger.data.parameters[i].split(" ")
match = false;
if (wordArray.includes(param[i]))
match = true;
}
break;
default:
// check for exact match
if (param[i] != "" && receivedTrigger.data.parameters[i].toLowerCase() != param[i].toLowerCase())
match = false;
}
}
//check non string types for not matching
else if (param[i] != "" && receivedTrigger.data.parameters[i] != param[i])
match = false;
}
}
catch (err)
{
console.log("ProcessReceivedTrigger ERROR", pairing.trigger.data)
console.log("ProcessReceivedTrigger error", err)
match = false;
}
if (!match)
break;
}
})
if (match)
{
// if we have a cooldown see if we have matched it
if (pairing.trigger.cooldown > 0)
{
const d = new Date();
let now = d.getTime()
// are we still in the the cooldown period
if (pairing.trigger.lastrun + (pairing.trigger.cooldown * 1000) > now)
return
else
pairing.trigger.lastrun = now
}
TriggerAction(pairing.action, receivedTrigger)
}
}
// ============================================================================
// FUNCTION: TriggerAction
// ============================================================================
/**
* Causes an action to be triggered.
* @param {object} action action to be triggered
* @param {object} triggerParams received trigger parameters
*/
function TriggerAction (action, triggerParams)
{
if (action.paused)
return
if (serverConfig.autopilotenabled != "on")
{
console.log("autopilot turned off, ignoring triggers")
return;
}
// regular expression to test if input is a mathmatical equasion
// note this seems to get confused if a string has -1 in it.
// BUG::: need a better regex
const re = /(?:(?:^|[-+_*/])(?:\s*-?\d+(\.\d+)?(?:[eE][+-]?\d+)?\s*))+$/;
// tests to get round the bug above
const bannedRegex = ["process", "system", "for", "while", "loop"];
let params = {}
// store the trigger params in the action in case the extension has use for them
params.triggerparams = triggerParams.data
// loop through each action field
for (var i in action.data)
{
//loop through each action field name
for (const property in action.data[i])
{
// store the undmodifed field data
let tempAction = action.data[i][property]
// *************************
// check for user variables
// we need a better way to do this. messy code
// *************************
// check if we have %%var%% in the field
let nextVarIndex = action.data[i][property].indexOf("%%")
// loop through all %%vars%%
while (nextVarIndex > -1)
{
let endVarIndex = tempAction.indexOf("%%", nextVarIndex + 2)
// we have a user variable
if (endVarIndex > -1)
{
// get the full variable
let sourceVar = tempAction.substr(nextVarIndex + 2, endVarIndex - (nextVarIndex + 2))
// at this point we will have either (example uses the message field and word number 2)
// message,
// message:2
// message:2*
// check if we have a word number option
if (sourceVar.indexOf(":") > -1)
{
// get the position of the :
let stringIndex = sourceVar.indexOf(":")
// get the number the user entered after the : (minus one as non programmers don't count from 0 :P)
// (note currently only works with 1 digit so 0-9 words can be selected)
let wordNumber = (sourceVar.substr(stringIndex + 1, 1)) - 1
// check if we have the *
if ((sourceVar.substr(stringIndex + 2) == "*"))
{
// get the trigger field (ie the named variable data)
let sourceData = triggerParams.data.parameters[tempAction.substr(nextVarIndex + 2, stringIndex)]
sourceData.replaceAll("%%", "")
const sourceArray = sourceData.split(" ");
// remove the first number of words the user specified
for (var x = 0; x < wordNumber; x++)
sourceArray.splice(0, 1)
tempAction = sourceArray.join(" ").trim()
}
else
{
// get the trigger field (ie the named variable data)
let sourceData = triggerParams.data.parameters[tempAction.substr(nextVarIndex + 2, stringIndex)]
sourceData.replaceAll("%%", "")
// split the data into an array so we can index the work the user wants
const sourceArray = sourceData.split(" ");
tempAction = sourceArray[wordNumber]
}
}
else
{
let tmpData = triggerParams.data.parameters[sourceVar]
if (typeof triggerParams.data.parameters[sourceVar] == "string")
tmpData = tmpData.replaceAll("%%", "")
tempAction = tempAction.replace("%%" + sourceVar + "%%", tmpData)
}
}
if (typeof tempAction == "string")
nextVarIndex = tempAction.indexOf("%%", nextVarIndex + 2)
else
nextVarIndex = -1;
}
// is this a mathmatical expression
if (re.test(tempAction) && !bannedRegex.includes(tempAction))
{
try
{
tempAction = eval(tempAction).toString()
}
catch (err)
{
// this is for when the regex fails and we try to eval a string
tempAction = tempAction.toString()
}
}
params[property] = tempAction
}
}
// all actions are handled through the SR socket interface
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket("ExtensionMessage",
serverConfig.extensionname,
sr_api.ExtensionPacket(
action.messagetype,
serverConfig.extensionname,
params,
"",
action.extension),
"",
action.extension
),
);
}
// ============================================================================
// FUNCTION: ProcessUserPairings
// ============================================================================
/**
* Updates our userPairings array with the date received from the frontend webpage
* when a user changes/adds a new item
* @param {object} userPairings
*/
function ProcessUserPairings (userPairings)
{
if (userPairings != null
&& typeof (userPairings) != "undefined"
&& userPairings != ""
&& Object.keys(userPairings).length != 0)
{
serverData.userPairings = structuredClone(userPairings);
}
else
logger.err(serverConfig.extensionname + ".ProcessUserPairings", "empty userPairings received");
}
// ============================================================================
// FUNCTION: RequestExtList
// ============================================================================
/**
* Requests a list of extensions connected from the server
*/
function RequestExtList ()
{
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket(
"RequestExtensionsList",
serverConfig.extensionname,
));
}
/**
* Requests a channel list from the server
*/
// ============================================================================
// FUNCTION: RequestChList
// ============================================================================
function RequestChList ()
{
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket(
"RequestChannelsList",
serverConfig.extensionname,
));
}
// ============================================================================
// FUNCTION: SendUserPairings
// ============================================================================
/**
* Sends our user pairing lists to the given extension or broadcasts if we have
* just made a change and want to let everyone know
* @param {string} to
*/
function SendUserPairings (to)
{
if (to != "")
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket("ExtensionMessage",
serverConfig.extensionname,
sr_api.ExtensionPacket(
"UserPairings",
serverConfig.extensionname,
serverData.userPairings,
serverConfig.channel,
to),
serverConfig.channel,
to
),
);
else
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket("ChannelData",
serverConfig.extensionname,
sr_api.ExtensionPacket(
"UserPairings",
serverConfig.extensionname,
serverData.userPairings,
serverConfig.channel,
to),
serverConfig.channel,
to
),
);
}
// ============================================================================
// FUNCTION: SaveDataToServer
// ============================================================================
/**
* Save our data JSON to the server
*/
function SaveDataToServer ()
{
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket(
"SaveData",
serverConfig.extensionname,
serverData));
}
// ============================================================================
// FUNCTION: SendMacros
// ============================================================================
/**
* Sends out the current list of marcos
*/
function SendMacros ()
{
if (serverData.userPairings.macrotriggers != undefined)
{
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket("ChannelData",
serverConfig.extensionname,
sr_api.ExtensionPacket(
"UserMacros",
serverConfig.extensionname,
serverData.userPairings.macrotriggers.triggers,
serverConfig.channel),
serverConfig.channel
),
);
}
}
// ============================================================================
// FUNCTION: SendMacroImages
// ============================================================================
/**
* Sends out the current list of macro images the user can chose from
* @param {string} to
*/
function SendMacroImages (to)
{
let imagelist = fs.readdirSync(__dirname + "/../public/images/deckicons")
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket("ExtensionMessage",
serverConfig.extensionname,
sr_api.ExtensionPacket(
"MacroImages",
serverConfig.extensionname,
imagelist,
"",
to),
"",
to
),
);
}
// ============================================================================
// FUNCTION: actionAction_SetGroupPauseState
// ============================================================================
/**
* Handles the paused state actions to pause/unpause a trigger action pair
* @param {string} group group to toggle
* @param {string} state state to move to
*/
function actionAction_SetGroupPauseState (group, state)
{
if (Object.keys(serverData.userPairings).length != 0 && serverData.userPairings.pairings != undefined)
{
for (const [key, value] of Object.entries(serverData.userPairings.pairings))
{
//console.log("value.trigger.messagetype", value.trigger.messagetype)
if (value.group == group)
{
if (state == "paused")
value.action.paused = true;
else if (state == "unpaused")
value.action.paused = false;
else
logger.err(serverConfig.extensionname + ".actionAction_SetGroupPauseState", "group pause should be 'paused' or 'unpaused'. State was set to", state);
}
}
}
SaveDataToServer();
SendUserPairings("");
SendMacros()
}
// ============================================================================
// FUNCTION: triggerMacroButton
// ============================================================================
/**
* Triggers the given actions mapped to a macro button trigger
* @param {string} name
*/
function triggerMacroButton (name)
{
for (var i in serverData.userPairings.pairings)
{
if (serverData.userPairings.pairings[i].trigger.name == name)
{
let params = {}
for (var j in serverData.userPairings.pairings[i].action.data)
{
for (const property in serverData.userPairings.pairings[i].action.data[j])
params[property] = serverData.userPairings.pairings[i].action.data[j][property]
}
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket("ExtensionMessage",
serverConfig.extensionname,
sr_api.ExtensionPacket(
serverData.userPairings.pairings[i].action.messagetype,
serverConfig.extensionname,
params,
"",
serverData.userPairings.pairings[i].action.extension),
"",
serverData.userPairings.pairings[i].action.extension
),
);
}
}
}
// ============================================================================
// FUNCTION: parseUserRequestSaveDataFile
// ============================================================================
/**
* Handles a 'userRequestSaveDataFile' message triggered when a user uploads a new
* JSON data file containing the trigger/action pairings
* @param {object} data
*/
function parseUserRequestSaveDataFile (data)
{
//do something with the file. ie check version etc.
let response = "No Response from Server, please try again.";
try
{
if (data.__version__ === default_serverData.__version__)
{
// overwrite our data and save it to the server.
serverData = structuredClone(data);
SaveDataToServer()//we have the same version of the file so we should save it over our current one.
response = "Data saved."
}
else
response = "received file version doesn't match current version: " + data.__version__ + " == " + default_serverData.__version__
}
catch (err)
{
logger.err(serverConfig.extensionname + ".parseUserRequestSaveDataFile", "Error saving data to server.Error:", err, err.message);
response = "Error saving data to server.Error:", err, err.message;
}
sr_api.sendMessage(
localConfig.DataCenterSocket,
sr_api.ServerPacket(
"ExtensionMessage",
serverConfig.extensionname,
sr_api.ExtensionPacket(
"AutopilotUserSaveServerDataResponse",
serverConfig.extensionname,
{ response: response },
"",
"autopilot_frontend"
),
"",
"autopilot_frontend"
));
}
// ============================================================================
// FUNCTION: heartBeat
// ============================================================================
/**
* Sends out our heartbeat message so others can monitor the extensions status
*/
function heartBeatCallback ()
{
let status = false;
if (serverConfig.autopilotenabled == "on")
status = true;
sr_api.sendMessage(localConfig.DataCenterSocket,
sr_api.ServerPacket("ChannelData",
serverConfig.extensionname,
sr_api.ExtensionPacket(
"HeartBeat",
serverConfig.extensionname,
{ connected: status },
serverConfig.channel),
serverConfig.channel
),
);
localConfig.heartBeatHandle = setTimeout(heartBeatCallback, localConfig.heartBeatTimeout)
}
export { startServer, triggersandactions };