Skip to content

Instantly share code, notes, and snippets.

@skylord123
Created August 1, 2024 21:10
Show Gist options
  • Select an option

  • Save skylord123/6de070b22e2231f419215774eeb504fb to your computer and use it in GitHub Desktop.

Select an option

Save skylord123/6de070b22e2231f419215774eeb504fb to your computer and use it in GitHub Desktop.
Node-RED telnet client for Torque Game Engine console
[
{
"id": "5eb5381789943947",
"type": "group",
"z": "f740c835e145407e",
"name": "AOT Telnet Console",
"style": {
"label": true
},
"nodes": [
"8fee83604ae9f94b",
"2725b21880fa7406",
"2423ee2b4b84e6f2",
"5c798a02c96d56d3",
"a4d6217fb50a0507",
"fde6528ee25bdb01",
"0b3cdb6bb0f6f102",
"aea19681196029c0",
"39756dd0b5365a1d"
],
"x": 254,
"y": 5119,
"w": 1292,
"h": 242
},
{
"id": "8fee83604ae9f94b",
"type": "function",
"z": "f740c835e145407e",
"g": "5eb5381789943947",
"name": "Torque TelnetConsole",
"func": "if(msg.payload && this.client) {\n node.warn(\"sending: \" + msg.payload);\n context.set('aot_telnet_client_last_cmd', msg.payload);\n node.status({ fill: \"orange\", shape: \"dot\", text: \"Sending command\" });\n this.last_cmd = msg.payload;\n this.client.write(msg.payload + \"\\n\");\n}",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "// telnet server details\nconst host = '192.168.18.2';\nconst port = 3333;\nconst password = 'Twig-Sappiness-Numbing1';\n\n// Reconnection configuration\nconst auto_reconnect = true; // Enable or disable auto-reconnect\nlet reconnect_attempts = 0;\nconst reconnect_delay = 5000; // Initial delay in milliseconds\nconst max_reconnect_delay = 30000; // Maximum delay in milliseconds\nconst attempts_before_increase = 3; // Attempts before increasing delay\nlet current_delay = reconnect_delay;\nlet self = this;\n\n// Initialize the connection status\nnode.status({ fill: \"orange\", shape: \"ring\", text: \"Connecting\" });\n\n// Function to create a TCP client and handle its events\nfunction connectToServer() {\n if(self.client) {\n self.client.end();\n if (typeof self.client[\"destroy\"] === \"function\") {\n self.client[\"destroy\"]();\n } else {\n node.warn(\"unknown destroy function\");\n }\n self.client = null;\n }\n if (context.get('aot_telnet_client')) {\n context.set('aot_telnet_client', undefined);\n }\n \n self.client = net.createConnection({ host: host, port: port }, () => {\n node.debug('Connected to Torque Game Engine TelnetConsole');\n node.status({ fill: \"green\", shape: \"dot\", text: \"Connected\" });\n reconnect_attempts = 0; // Reset reconnect attempts on successful connection\n current_delay = reconnect_delay; // Reset the delay to initial value\n });\n context.set('aot_telnet_client_last_cmd', null);\n self.last_cmd = null;\n context.set('aot_telnet_client', self.client);\n node.client = self.client;\n\n let isPasswordSent = false,\n authSuccess = false;\n\n self.client.on('data', (data) => {\n try {\n\n const response = data.toString();\n if (!isPasswordSent && response.includes('Password:')) {\n self.client.write(password + '\\n');\n isPasswordSent = true;\n node.status({ fill: \"green\", shape: \"dot\", text: \"Password sent\" });\n } else if (\n self.last_cmd\n && self.last_cmd.trim().length\n && (self.last_cmd.trim() === response.trim() || response.indexOf(self.last_cmd.trim() + \"\\r\\n\") >= 0)\n ) {\n // successfully sent our command\n node.status({ fill: \"green\", shape: \"dot\", text: \"Sent command\" });\n if (response.indexOf(self.last_cmd.trim() + \"\\r\") >= 0) {\n // our command only makes up part of this message\n // remove our command and send the rest\n let newResponse = response.replaceAll(self.last_cmd.trim() + \"\\r\\n\", \"\");\n if (newResponse.trim().length) {\n node.send({\n payload: response.replaceAll(self.last_cmd.trim() + \"\\r\\n\", \"\")\n });\n }\n }\n self.last_cmd = null;\n } else if (isPasswordSent && response.trim().length) {\n if (!authSuccess) {\n authSuccess = true;\n node.status({ fill: \"green\", shape: \"dot\", text: \"Authenticated\" });\n // send $Con::Prompt = \"<input> \";\n }\n\n let lastChar = response.charAt(response.length - 1).charCodeAt(0).toString(16);\n let firstChar = response.charAt(0).charCodeAt(0).toString(16);\n\n node.send({\n payload: response,\n first_char_hex: firstChar,\n last_char_hex: lastChar\n });\n }\n } catch(e) {\n node.warn(\"Error: \" + e);\n }\n });\n\n self.client.on('end', () => {\n node.status({ fill: \"red\", shape: \"ring\", text: \"Disconnected\" });\n isPasswordSent = false;\n authSuccess = false;\n if (typeof self.client[\"destroy\"] === \"function\") {\n self.client[\"destroy\"]();\n } else {\n node.warn(\"unknown destroy function\");\n }\n if (auto_reconnect) {\n node.warn(\"Disconnected for some reason, attempting reconnect..\");\n attemptReconnect();\n }\n });\n\n self.client.on('error', (err) => {\n node.status({ fill: \"red\", shape: \"ring\", text: \"Error: \" + err });\n node.error('Connection error: ' + err.message);\n self.client.end(); // Ensure the socket is closed on error\n if (auto_reconnect) {\n attemptReconnect();\n }\n });\n}\n\n// Function to handle reconnection attempts\nfunction attemptReconnect() {\n if (reconnect_attempts >= attempts_before_increase) {\n current_delay = Math.min(current_delay * 2, max_reconnect_delay);\n reconnect_attempts = 0;\n } else {\n reconnect_attempts++;\n }\n\n setTimeout(connectToServer, current_delay);\n node.status({ fill: \"yellow\", shape: \"ring\", text: \"Reconnecting in \" + (current_delay / 1000) + \"s\" });\n}\n\nconnectToServer(); // Initial connection attempt",
"finalize": "// Code added here will be run when the\n// node is being stopped or re-deployed.\n\nif(this.client) {\n this.client.end();\n this.client[\"destroy\"]();\n}",
"libs": [
{
"var": "net",
"module": "net"
}
],
"x": 900,
"y": 5220,
"wires": [
[
"2725b21880fa7406",
"aea19681196029c0"
]
]
},
{
"id": "2725b21880fa7406",
"type": "debug",
"z": "f740c835e145407e",
"g": "5eb5381789943947",
"name": "debug 186",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1090,
"y": 5280,
"wires": []
},
{
"id": "2423ee2b4b84e6f2",
"type": "inject",
"z": "f740c835e145407e",
"g": "5eb5381789943947",
"name": "echo(\"test\\nstring\\ntest\\nstringzzzz\");",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "echo(\"test\\nstring\\ntest\\nstringzzzz\");",
"payloadType": "str",
"x": 540,
"y": 5160,
"wires": [
[
"8fee83604ae9f94b"
]
]
},
{
"id": "5c798a02c96d56d3",
"type": "inject",
"z": "f740c835e145407e",
"g": "5eb5381789943947",
"name": "isFullScreen();",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "echo(\"[START]\"); isFullScreen(); echo(\"[END]\");",
"payloadType": "str",
"x": 610,
"y": 5200,
"wires": [
[
"8fee83604ae9f94b"
]
]
},
{
"id": "a4d6217fb50a0507",
"type": "inject",
"z": "f740c835e145407e",
"g": "5eb5381789943947",
"name": "echo(\"[start eval]\"); echo(\"test\"); echo(\"[end eval]\");",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "echo(\"[start eval]\"); echo(\"test\\n\\rtest2\\ntest3\\r\\r\\r\\r\\rtest4\"); echo(\"[end eval]\");",
"payloadType": "str",
"x": 490,
"y": 5240,
"wires": [
[
"8fee83604ae9f94b"
]
]
},
{
"id": "fde6528ee25bdb01",
"type": "inject",
"z": "f740c835e145407e",
"g": "5eb5381789943947",
"name": "$Con::Prompt = \"\";",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "$Con::Prompt = \"\";",
"payloadType": "str",
"x": 550,
"y": 5280,
"wires": [
[
"8fee83604ae9f94b"
]
]
},
{
"id": "0b3cdb6bb0f6f102",
"type": "inject",
"z": "f740c835e145407e",
"g": "5eb5381789943947",
"name": "$Con::Prompt = \"\";",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "$Con::Prompt = \"<input> \";",
"payloadType": "str",
"x": 550,
"y": 5320,
"wires": [
[
"8fee83604ae9f94b"
]
]
},
{
"id": "aea19681196029c0",
"type": "switch",
"z": "f740c835e145407e",
"g": "5eb5381789943947",
"name": "",
"property": "monitor_aot_logs",
"propertyType": "flow",
"rules": [
{
"t": "true"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 1110,
"y": 5220,
"wires": [
[
"39756dd0b5365a1d"
]
]
},
{
"id": "39756dd0b5365a1d",
"type": "function",
"z": "f740c835e145407e",
"g": "5eb5381789943947",
"name": "Push log line into local memory variable",
"func": "\nlet aot_logs = flow.get(\"aot_logs\");\naot_logs.push(msg.payload.trimEnd());\nflow.set(\"aot_logs\", aot_logs);\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1360,
"y": 5220,
"wires": [
[]
]
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment