-
Notifications
You must be signed in to change notification settings - Fork 0
/
electron.js
583 lines (523 loc) · 24.1 KB
/
electron.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
import { app, BrowserWindow, ipcMain } from 'electron';
import WebSocket from 'ws';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import path from 'path';
import { exec } from 'child_process';
import { moveWindowToTopLeft } from './windowControl.js'
const commandQueue = [];
let isProcessingCommandQueue = false;
const intervals = new Map();
let mainWindow;
function createWindow() {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
mainWindow = new BrowserWindow({
width: 1600,
height: 1600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
},
});
// Load the index page of your app from your Express server.
mainWindow.loadURL('https://scumchatmonitorweb.azurewebsites.net');
// const content_security_policy = `
// default-src 'none';
// script-src 'self';
// connect-src 'self' wss://localhost:8080;
// img-src 'self';
// style-src 'self';
// font-src 'self';
// frame-src 'none';
// `
// mainWindow.webContents.on('did-finish-load', function() {
// mainWindow.webContents.session.webRequest.onHeadersReceived(function(details, callback) {
// callback({
// responseHeaders: {
// ...details.responseHeaders,
// 'Content-Security-Policy': [content_security_policy]
// }
// });
// });
// });
moveWindowToTopLeft("SCUM");
// Emitted when the window is closed.
mainWindow.on('closed', function () {
mainWindow = null;
});
}
async function processCommandQueue() {
if (isProcessingCommandQueue) {
return;
}
isProcessingCommandQueue = true;
while (commandQueue.length > 0) {
const { command, websocket_id, steam_id } = commandQueue.shift();
if (command && websocket_id && steam_id) {
await runCommand(command, websocket_id, steam_id);
}
}
isProcessingCommandQueue = false;
}
async function createWebSocketConnection(websocket_id) {
const websocket = new WebSocket(`ws://scumchatmonitorweb.azurewebsites.net?websocket_id=${encodeURIComponent(websocket_id)}`);
websocket.on('message', async (message) => {
const json_message_data = JSON.parse(message);
if (json_message_data.action === `announceMessage`) {
if (Array.isArray(json_message_data.messages)) {
for (const command of json_message_data.messages) {
commandQueue.push({ command: command, websocket_id: websocket_id, player_steam_id: 0 });
}
processCommandQueue();
}
}
if (json_message_data.action === `runCommand` && json_message_data.package_items && json_message_data.guild_id && json_message_data.steam_id) {
if (Array.isArray(json_message_data.package_items)) {
const commands_array = json_message_data.package_items;
const player_steam_id = json_message_data.steam_id;
for (const command of commands_array) {
commandQueue.push({ command: command, websocket_id: websocket_id, player_steam_id: player_steam_id });
}
processCommandQueue();
}
}
if (json_message_data.action === `teleport`) {
const teleport_coordinates = json_message_data.teleport_coordinates;
const player_steam_id = json_message_data.player_steam_id;
const x_coordinate = teleport_coordinates.x;
const y_coordinate = teleport_coordinates.y;
const z_coordinate = teleport_coordinates.z;
const teleport_command = `#Teleport ${x_coordinate} ${y_coordinate} ${z_coordinate} ${player_steam_id}`;
commandQueue.push({ command: teleport_command, websocket_id: websocket_id, player_steam_id: player_steam_id });
processCommandQueue();
}
if (json_message_data.action === `enable` && json_message_data.guild_id
&& json_message_data.ftp_server_data && json_message_data.game_server_data) {
const check_server_online_and_bot_connected_interval = setInterval(async function() {
const game_server_data = json_message_data.game_server_data;
const ftp_server_data = json_message_data.ftp_server_data;
const guild_id = json_message_data.guild_id;
try {
const isConnectedToServer = await checkWindowsHasTcpConnectionToGameServer(game_server_data.game_server_ipv4, game_server_data.game_server_port);
const isServerOnline = await checkWindowsCanPingGameServer(game_server_data.game_server_ipv4);
const localTimeISO = getLocalTimeInISO8601Format();
if (!isConnectedToServer && isServerOnline) {
await reinitializeBotOnServer(guild_id);
}
// Send response back through WebSocket
websocket.send(JSON.stringify({
action: 'statusUpdate',
guild_id: guild_id,
ftp_server_data: ftp_server_data,
connectedToServer: isConnectedToServer,
serverOnline: isServerOnline,
localTime: localTimeISO
}));
} catch (error) {
websocket.send(JSON.stringify({
action: 'error',
guild_id: json_message_data.guild_id,
message: 'Failed to check server status'
}));
}
}, 60000);
intervals.set(`enable_game_server_checks_interval_${guild_id}`, check_server_online_and_bot_connected_interval);
}
if (json_message_data.action === `disable`) {
console.log("Disable bot button has been clicked");
if (intervals.has(`enable_game_server_checks_interval`)) {
clearInterval(intervals.get(`enable_game_server_checks_interval`));
intervals.delete(`enable_game_server_checks_interval`);
}
}
});
websocket.on('close', function() {
});
websocket.on('error', function() {
});
return websocket;
}
function getLocalTimeInISO8601Format() {
const current_date = new Date();
return current_date.toISOString();
}
/**
* This function uses the native Windows command prompt shell to execute the command 'netstat -an' to fetch a list of current connections.
* It checks if the computer is connected to the game server IP address, which helps ensure the bot keeps trying to rejoin the game server if disconnected.
* The function now returns a Promise that resolves to true if connected, or false otherwise.
* @returns {Promise<boolean>} A promise that resolves to a boolean indicating the connection status.
*/
async function checkWindowsHasTcpConnectionToGameServer(game_server_address, game_server_port) {
return new Promise((resolve, reject) => {
exec(`netstat -an | find "${game_server_address}:${game_server_port}"`, (error, stdout) => {
if (error) reject(error);
resolve(stdout.includes(game_server_address + ':' + game_server_port));
});
});
}
/**
* This function uses the native Windows command prompt shell to execute a 'ping' command that will test to see if the game server is in an operational state.
* Because the game server restarts every day at approximately 18:00, we must ping the server to see if the server is online before doing anything on the server.
* @param {boolean} callback
*/
async function checkWindowsCanPingGameServer(game_server_address) {
return new Promise((resolve, reject) => {
exec(`ping ${game_server_address}`, (error, stdout) => {
if (error) reject(error);
resolve(stdout.includes('Reply from ' + game_server_address));
});
});
}
/**
* An asynchronous function which pauses the execution of the application for a specified number of milliseconds. This is required when you are using Windows powershell
* to prevent bottlenecks, slowdowns, or other abnormal system operations.
* In this instance, a promise is an operation that will prevent further execution of code.
* @param {number} milliseconds The total number of milliseconds to halt the application execution for.
* @returns Returns a promise that will resolve itself after a certain number of milliseconds.
*/
function sleep(milliseconds) {
return new Promise((resolve) => setTimeout(resolve, milliseconds));
}
/**
* This function utilizes all of the above utility functions that interact with Windows via powershell. As mentioned, each time on of those functions are used,
* an 'await sleep()' blocker must be used for system stability.
* @param {string} command A string value containing the SCUM command to run in-game
* @param websocket_id An integer containing the websocket id that is associated with a scum player's server for logging purposes
* @param steam_id An integer containing the steam id of the user that executed the command
* @returns if the system cannot detect the SCUM process currently running, the function will cease execution.
*/
async function runCommand(command, websocket_id, steam_id) {
await sleep(1000);
copyToClipboard(command, websocket_id);
await sleep(1000);
pressCharacterKeyT(websocket_id);
await sleep(1000);
pressBackspaceKey(websocket_id);
await sleep(1000);
pasteFromClipboard(websocket_id);
await sleep(1000);
pressEnterKey(websocket_id);
await sleep(1000);
}
/**
* This is the sequence of operations which executes after the server restarts, and the bot must log back into the server.
* The program detects if the server has restarted by checking for a specific TCP connection to the game server. Because the game server on SCUM has a static IP address and port,
* we can use the Windows command 'netstat -an' to check for existing connections on the computer and see if our target IP address exists in the list.
* await sleep(N) is an asynchronous operation used to block any further processing of this function until after N milliseconds.
* You can convert milliseconds to seconds by dividing N / 1000 (80000 milliseconds / 1000 milliseconds = 8 seconds).
* The SCUM game interface has a 'continue' button to join the server you were last on, so this operation moves to there.
*/
async function reinitializeBotOnServer(websocket_id) {
try {
await sendLogData (
`InfoLogs`,
`The scum bot monitor is offline. Attempting to log the bot back in to the server`,
websocket_id,
`${websocket_id}-info-logs`
);
await sleep(5000);
moveWindowToTopLeft("SCUM");
await sleep(100000);
moveMouseToContinueButtonXYLocation(websocket_id);
await sleep(10000);
pressMouseLeftClickButton(websocket_id);
await sleep(20000);
pressCharacterKeyT(websocket_id);
await sleep(20000);
pressTabKey(websocket_id);
await sleep(5000);
await sendLogData (
`InfoLogs`,
`The scum bot monitor has been activated and is ready to use`,
websocket_id,
`${websocket_id}-info-logs`
)
await sleep(5000);
} catch (error) {
return false;
}
return true;
}
/**
* Executes a Windows powershell command to simulate a user moving the cursor to a specific (X, Y) location on screen. This is an asynchronous operation.
*/
async function moveMouseToContinueButtonXYLocation(websocket_id) {
const x_cursor_position = 143;
const y_cursor_position = 505;
const command = `powershell.exe -command "Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class P { [DllImport(\\"user32.dll\\")] public static extern bool SetCursorPos(int x, int y); }'; [P]::SetCursorPos(${x_cursor_position}, ${y_cursor_position})"`;
try {
const { stderr } = await executeAsyncCommand(command);
if (stderr) {
await sendLogData(
`ErrorLogs`,
`Error when moving the mouse to x 143 and y 505: ${error}`,
`${websocket_id}`,
`${websocket_id}-error-logs`
);
return;
}
console.log('Moved mouse to continue button location');
} catch (error) {
console.error(`Failed to move mouse to continue button location`);
}
}
/**
* Executes a Windows powershell command to simulate a user moving the cursor to a specific (X, Y) location on screen. This is an asynchronous operation.
*/
// async function moveMouseToPressOkForMessage(websocket_id) {
// const command = `powershell.exe -command "Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class P { [DllImport(\\"user32.dll\\")] public static extern bool SetCursorPos(int x, int y); }'; [P]::SetCursorPos(${x_cursor_position}, ${y_cursor_position})"`;
// try {
// const { stderr } = await executeAsyncCommand(command);
// if (stderr) {
// await sendLogData(
// `ErrorLogs`,
// `Error when moving the mouse to x 958 and y 536: ${error}`,
// `${websocket_id}`,
// `${websocket_id}-error-logs`
// );
// return;
// }
// console.log(`Moved mouse to OK message popup`);
// } catch (error) {
// console.error(`Failed to move mouse to OK message popup`);
// }
// }
/**
* Executes a Windows powershell command to simulate a left mouse button click. This is as asynchronous operation.
*/
async function pressMouseLeftClickButton(websocket_id) {
const command = `powershell.exe -command "Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class P { [DllImport(\\"user32.dll\\")] public static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo); }'; $leftDown = 0x0002; $leftUp = 0x0004; [P]::mouse_event($leftDown, 0, 0, 0, 0); [P]::mouse_event($leftUp, 0, 0, 0, 0);"`;
try {
const { stderr } = await executeAsyncCommand(command);
if (stderr) {
await sendLogData(
`ErrorLogs`,
`Error when left clicking the mouse: ${stderr}`,
`${websocket_id}`,
`${websocket_id}-error-logs`
);
return;
}
console.log(`Clicked the left mouse button`);
} catch (error) {
console.error(`Failed to click the left mouse button`);
}
}
/**
* Uses the Windows powershell command 'System.Windows.Forms.Clipboard]::SetText() to copy some text to the system clipboard
* In the else clause, there is a debug log message if you want to uncomment that for development purposes
* @param {string} text A string of text which will be copied to the system clipboard
*/
async function copyToClipboard(text, websocket_id) {
const command = `powershell.exe -command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Clipboard]::SetText('${text.replace(/'/g, "''")}')"`;
try {
const { stderr } = await executeAsyncCommand(command);
if (stderr) {
await sendLogData(
`ErrorLogs`,
`There was an error when attempting to copy the bot text to the system clipboard: ${stderr}`,
`${websocket_id}`,
`${websocket_id}-error-logs`
);
return;
}
console.log(`Copied text to system clipboard operation successful`);
} catch (error) {
console.error(`Failed to copy text to the system clipboard: ${error}`);
}
}
/**
* Uses the Windows powershell command '[System.Windows.Forms.SendKeys]::SendWait('^v') to simulate a key press sequence that pastes text.
* In this application specifically, this function pastes text into the active window, which is SCUM.exe.
* If you are using this function in sequence with other ones which use powershell, you must use the sleep() function in between powershell uses so the system can
* change states appropriately and not cause bottlenecks.
*/
async function pasteFromClipboard(websocket_id) {
const command = `powershell.exe -command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('^v')"`
try {
const { stderr } = await executeAsyncCommand(command);
if (stderr) {
await sendLogData(
`ErrorLogs`,
`There was an error when pasting contents from the clipboard: ${stderr}`,
`${websocket_id}`,
`${websocket_id}-error-logs`
);
return;
}
console.log(`Pasted text from system clipboard successfully`);
} catch (error) {
console.error(`Failed to paste text from system clipboard: ${error}`);
}
}
/**
* Uses the Windows powershell command '[System.Windows.Forms.SendKeys]::SendWait('{TAB}') to simulate a tab key press on the active window. In this case, the active
* window is SCUM.exe. The tab key switches channels between 'Local', 'Global', 'Admin', and 'Squad'. Currently, this function is not in use and is here for your convenience.
* If you are using this function in sequence with other ones which use powershell, you must use the sleep() function in between powershell uses so the system can
* change states appropriately and not cause bottlenecks.
*/
async function pressTabKey(websocket_id) {
const command = `powershell.exe -command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('{TAB}')"`;
try {
const { stderr } = await executeAsyncCommand(command);
if (stderr) {
await sendLogData(
`ErrorLogs`,
`There was an error when pressing the tab key: ${stderr}`,
`${websocket_id}`,
`${websocket_id}-error-logs`
);
return;
}
console.log(`Pressed the tab key`);
} catch (error) {
console.error(`Failed to press the tab key: ${error}`);
}
}
/**
* Uses the Windows powershell command '[System.Windows.Forms.SendKeys]::SendWait('t') to simulate a 't' character key press on the active window.
* In this case, the active window is SCUM.exe. The t character brings actives the in-game chat menu if it is not already active.
* If you are using this function in sequence with other ones which use powershell, you must use the sleep() function in between powershell uses so the system can
* change states appropriately and not cause bottlenecks.
*/
async function pressCharacterKeyT(websocket_id) {
const command = `powershell.exe -command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('t')"`;
try {
const { stderr } = await executeAsyncCommand(command);
if (stderr) {
await sendLogData(
`ErrorLogs`,
`There was an error when pressing the T character key: ${stderr}`,
`${websocket_id}`,
`${websocket_id}-error-logs`
);
return;
}
console.log(`Pressed the t character key`);
} catch (error) {
console.error(`Failed to press the t character key: ${error}`);
}
}
/**
* Uses the Windows powershell command '[System.Windows.Forms.SendKeys]::SendWait('{BACKSPACE}') to simulate a backspace key press on the active window.
* In this case, the active window is SCUM.exe. The backspace key will execute immediately after pressCharacterT(), erasing the 't' character from chat if the chat window
* is already active.
* If you are using this function in sequence with other ones which use powershell, you must use the sleep() function in between powershell uses so the system can
* change states appropriately and not cause bottlenecks.
*/
async function pressBackspaceKey(websocket_id) {
const command = `powershell.exe -command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('{BACKSPACE}"')`;
try {
const { stderr } = await executeAsyncCommand(command);
if (stderr) {
await sendLogData(
`ErrorLogs`,
`There was an error when pressing the backspace key: ${stderr}`,
`${websocket_id}`,
`${websocket_id}-error-logs`
);
return;
}
console.log(`Pressed the backspace key`);
} catch (error) {
console.error(`Failed to press the backspace key: ${error}`);
}
}
/**
* Uses the Windows powershell command '[System.Windows.Forms.SendKeys]::SendWait('{Enter}') to simulate an 'enter' character key press on the active window.
* In this case, the active window is SCUM.exe. The enter key sends a message in chat when pressed.
* If you are using this function in sequence with other ones which use powershell, you must use the sleep() function in between powershell uses so the system can
* change states appropriately and not cause bottlenecks.
*/
async function pressEnterKey(websocket_id) {
const command = `powershell.exe -command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('{Enter}')"`;
try {
const { stderr } = await executeAsyncCommand(command);
if (stderr) {
await sendLogData(
`ErrorLogs`,
`There was an error when pressing the enter key: ${stderr}`,
`${websocket_id}`,
`${websocket_id}-error-logs`
);
return;
}
console.log(`Pressed the enter key`);
} catch (error) {
console.error(`Failed to press the enter key: ${error}`);
}
}
function executeAsyncCommand(command) {
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) {
reject(error);
} else {
resolve({ stdout, stderr });
}
});
});
}
async function sendLogData(log_type, message, guild_id, file_type) {
try {
const response = await fetch('https://scumchatmonitorweb.azurewebsites.net/admin/logdata', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
log_type,
message,
guild_id,
file_type
})
});
const response_data = await response.json();
if (response_data.success) {
console.log(`Log data send successfully: ${response_data.message}`);
} else {
console.error(`Failed to send log data: ${response_data.message}`);
}
} catch (error) {
console.error(`There was an error when attempting to send new log file data: ${error}`);
}
}
app.whenReady().then(() => {
ipcMain.handle('loginWebsocket', async (event, { login_response }) => {
await createWebSocketConnection(login_response.bot_id);
});
createWindow();
app.on('active', function() {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.whenReady().then(() => {
ipcMain.handle('checkUserLogin', async (event, { email, password }) => {
try {
const response = await fetch("https://scumchatmonitorweb.azurewebsites.net/admin/createwebsocket", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({ email, password })
});
const response_data = await response.json();
return response_data;
} catch (error) {
throw error;
return false;
}
});
});
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit();
});
app.on('activate', function() {
console.log(`Electron window was opened`);
});
export default app;