2023-07-16 02:45:26 +08:00

568 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Modules to control application life and create native browser window
const {app, BrowserWindow, dialog, ipcMain, screen, session} = require('electron');
app.commandLine.appendSwitch("--disable-http-cache");
const {Builder, By, Key, until} = require("selenium-webdriver");
const chrome = require('selenium-webdriver/chrome');
const {ServiceBuilder} = require('selenium-webdriver/chrome');
const {rootCertificates} = require('tls');
const {exit} = require('process');
const path = require('path');
const fs = require('fs');
const {exec, spawn} = require('child_process');
const iconPath = path.join(__dirname, 'favicon.ico');
const task_server = require(path.join(__dirname, 'server.js'));
const util = require('util');
let config = fs.readFileSync(path.join(task_server.getDir(), `config.json`), 'utf8');
config = JSON.parse(config);
if(config.debug){
let logPath = 'info.log'
let logFile = fs.createWriteStream(logPath, { flags: 'a' })
console.log = function() {
logFile.write(util.format.apply(null, arguments) + '\n')
process.stdout.write(util.format.apply(null, arguments) + '\n')
}
console.error = function() {
logFile.write(util.format.apply(null, arguments) + '\n')
process.stderr.write(util.format.apply(null, arguments) + '\n')
}
}
let allWindowSockets = [];
let allWindowScoketNames = [];
task_server.start(config.webserver_port); //start local server
let server_address = `${config.webserver_address}:${config.webserver_port}`;
const websocket_port = 8084; //目前只支持8084端口写死因为扩展里面写死了
console.log("server_address: " + server_address);
let driverPath = "";
let chromeBinaryPath = "";
let execute_path = "";
console.log(process.arch);
exec(`wmic os get Caption`, function(error, stdout, stderr) {
if (error) {
console.error(`执行的错误: ${error}`);
return;
}
if (stdout.includes('Windows 7')) {
console.log('Windows 7');
let sys_arch = config.sys_arch;
if (sys_arch === 'x64') {
dialog.showMessageBoxSync({
type: 'error',
title: 'Error',
message: 'Windows 7系统请下载使用x32版本的软件不论Win 7系统为x64还是x32版本。\nFor Windows 7, please download and use the x32 version of the software, regardless of whether the Win 7 system is x64 or x32 version.',
});
}
} else {
console.log('Not Windows 7');
}
});
if (process.platform === 'win32' && process.arch === 'ia32') {
driverPath = path.join(__dirname, "chrome_win32/chromedriver_win32.exe");
chromeBinaryPath = path.join(__dirname, "chrome_win32/chrome.exe");
execute_path = path.join(__dirname, "chrome_win32/execute.bat");
} else if (process.platform === 'win32' && process.arch === 'x64') {
driverPath = path.join(__dirname, "chrome_win64/chromedriver_win64.exe");
chromeBinaryPath = path.join(__dirname, "chrome_win64/chrome.exe");
execute_path = path.join(__dirname, "chrome_win64/execute.bat");
} else if (process.platform === 'darwin') {
driverPath = path.join(__dirname, "chromedriver_mac64");
chromeBinaryPath = path.join(__dirname, "chrome_mac64.app/Contents/MacOS/Google Chrome");
execute_path = path.join(__dirname, "");
} else if (process.platform === 'linux') {
driverPath = path.join(__dirname, "chrome_linux64/chromedriver_linux64");
chromeBinaryPath = path.join(__dirname, "chrome_linux64/chrome");
execute_path = path.join(__dirname, "chrome_linux64/execute.sh");
}
console.log(driverPath, chromeBinaryPath, execute_path);
let language = "en";
let driver = null;
let mainWindow = null;
let flowchart_window = null;
let current_handle = null;
let old_handles = [];
let handle_pairs = {};
let socket_window = null;
let socket_start = null;
let socket_flowchart = null;
let invoke_window = null;
// var ffi = require('ffi-napi');
// var libm = ffi.Library('libm', {
// 'ceil': [ 'double', [ 'double' ] ]
// });
// libm.ceil(1.5); // 2
// const {user32FindWindowEx,
// winspoolGetDefaultPrinter,} = require('win32-api/fun');
// async function testt(){
// // 获取当前电脑当前用户默认打印机名
// const printerName = await winspoolGetDefaultPrinter()
// console.log(printerName);
// }
// testt();
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 550,
height: 750,
webPreferences: {
preload: path.join(__dirname, 'src/js/preload.js')
},
icon: iconPath,
// frame: false, //取消window自带的关闭最小化等
resizable: false //禁止改变主窗口尺寸
})
// and load the index.html of the app.
// mainWindow.loadFile('src/index.html');
mainWindow.loadURL(server_address + '/index.html?user_data_folder=' + config.user_data_folder+"&copyright=" + config.copyright, { extraHeaders: 'pragma: no-cache\n' });
// 隐藏菜单栏
const {Menu} = require('electron');
Menu.setApplicationMenu(null);
mainWindow.on('close', function (e) {
if (process.platform !== 'darwin') {
app.quit();
}
});
// mainWindow.webContents.openDevTools();
// Open the DevTools.
// mainWindow.webContents.openDevTools()
}
async function beginInvoke(msg, ws) {
if (msg.type == 1) {
if (msg.message.id != -1) {
let url = "";
if (language == "zh") {
url = server_address + `/taskGrid/FlowChart_CN.html?id=${msg.message.id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address;
} else if (language == "en") {
url = server_address + `/taskGrid/FlowChart.html?id=${msg.message.id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address;
}
console.log(url);
flowchart_window.loadURL(url, { extraHeaders: 'pragma: no-cache\n' });
}
mainWindow.hide();
// Prints the currently focused window bounds.
// This method has to be called on macOS before changing the window's bounds, otherwise it will throw an error.
// It will prompt an accessibility permission request dialog, if needed.
if(process.platform != "linux" && process.platform != "darwin"){
const {windowManager} = require("node-window-manager");
const window = windowManager.getActiveWindow();
console.log(window);
windowManager.requestAccessibility();
// Sets the active window's bounds.
let size = screen.getPrimaryDisplay().workAreaSize
let width = parseInt(size.width)
let height = parseInt(size.height * 0.6)
window.setBounds({x: 0, y: size.height * 0.4, height: height, width: width});
}
flowchart_window.show();
// flowchart_window.openDevTools();
} else if (msg.type == 2) {
// 键盘输入事件
// const robot = require("@jitsi/robotjs");
let keyInfo = msg.message.keyboardStr;
let handles = await driver.getAllWindowHandles();
console.log("handles", handles);
let exit = false;
let content_handle = handle_pairs[msg.message.id];
console.log(msg.message.id, content_handle);
let order = [...handles.filter(handle => handle != current_handle && handle != content_handle), current_handle, content_handle]; //搜索顺序
let len = order.length;
while (true) {
// console.log("handles");
try{
let iframe = msg.message.iframe;
let enter = false;
if (/<enter>/i.test(keyInfo)) {
keyInfo = keyInfo.replace(/<enter>/gi, '');
enter = true;
}
let h = order[len - 1];
console.log("current_handle", current_handle);
if(h != null && handles.includes(h)){
await driver.switchTo().window(h);
current_handle = h;
console.log("switch to handle: ", h);
}
// await driver.executeScript("window.stop();");
// console.log("executeScript");
if(!iframe){
let element = await driver.findElement(By.xpath(msg.message.xpath));
console.log("Find Element at handle: ", current_handle);
// 使用正则表达式匹配 '<enter>',不论大小写
await element.sendKeys(Key.HOME, Key.chord(Key.SHIFT, Key.END), keyInfo);
if(enter){
await element.sendKeys(Key.ENTER);
}
console.log("send key");
break;
} else {
let iframes = await driver.findElements(By.tagName('iframe'));
// 遍历所有的 iframe 并点击里面的元素
for(let i = 0; i < iframes.length; i++) {
let iframe = iframes[i];
// 切换到 iframe
await driver.switchTo().frame(iframe);
// 在 iframe 中查找并点击元素
let element;
try {
element = await driver.findElement(By.xpath(msg.message.xpath));
} catch (error) {
console.log('No such element found in the iframe');
}
if (element) {
await element.sendKeys(Key.HOME, Key.chord(Key.SHIFT, Key.END), keyInfo);
if(enter){
await element.sendKeys(Key.ENTER);
}
}
// 完成操作后切回主文档
await driver.switchTo().defaultContent();
}
break;
}
} catch (error) {
console.log("len", len);
len = len - 1;
if (len == 0) {
break;
}
}
}
} else if (msg.type == 3) {
try {
if (msg.from == 0) {
socket_flowchart.send(msg.message.pipe); //直接把消息转接
let message = JSON.parse(msg.message.pipe);
let type = message.type;
console.log("FROM Browser: ", message);
console.log("Iframe:", message.iframe);
if(type.indexOf("Click")>=0){
// 鼠标点击事件
let iframe = message.iframe;
let handles = await driver.getAllWindowHandles();
console.log("handles", handles);
let exit = false;
let content_handle = handle_pairs[message.id];
console.log(message.id, content_handle);
let order = [...handles.filter(handle => handle != current_handle && handle != content_handle), current_handle, content_handle]; //搜索顺序
let len = order.length;
while(true) {
try{
let h = order[len - 1];
console.log("current_handle", current_handle);
if(h != null && handles.includes(h)){
await driver.switchTo().window(h); //执行失败会抛出异常
current_handle = h;
console.log("switch to handle: ", h);
}
//下面是找到窗口的情况下
if(!iframe){
let element = await driver.findElement(By.xpath(message.xpath));
await element.click();
break;
} else {
let iframes = await driver.findElements(By.tagName('iframe'));
// 遍历所有的 iframe 并点击里面的元素
for(let i = 0; i < iframes.length; i++) {
let iframe = iframes[i];
// 切换到 iframe
await driver.switchTo().frame(iframe);
// 在 iframe 中查找并点击元素
let element;
try {
element = await driver.findElement(By.xpath(message.xpath));
} catch (error) {
console.log('No such element found in the iframe');
}
if (element) {
await element.click();
}
// 完成操作后切回主文档
await driver.switchTo().defaultContent();
}
break;
}
} catch (error) {
console.log("len", len); //如果没有找到元素,就切换到下一个窗口
len = len - 1;
if (len == 0) {
break;
}
}
}
}
} else {
socket_window.send(msg.message.pipe);
for(let i in allWindowSockets){
try{
allWindowSockets[i].send(msg.message.pipe);
} catch {
console.log("Cannot send to socket with id: ", allWindowScoketNames[i]);
}
}
console.log("FROM Flowchart: ", JSON.parse(msg.message.pipe));
}
} catch (e) {
console.log(e);
}
} else if (msg.type == 5) {
let child = require('child_process').execFile;
// 参数顺序: 1. task id 2. server address 3. saved_file_name 4. "remote" or "local" 5. user_data_folder
// var parameters = [msg.message.id, server_address];
let parameters = [];
console.log(msg.message)
if (msg.message.user_data_folder == null || msg.message.user_data_folder == undefined || msg.message.user_data_folder == "") {
parameters = ["--id", "[" + msg.message.id + "]", "--server_address", server_address, "--user_data", 0];
} else {
let user_data_folder_path = path.join(task_server.getDir(), msg.message.user_data_folder);
parameters = ["--id", "[" + msg.message.id + "]", "--server_address", server_address, "--user_data", 1];
config.user_data_folder = msg.message.user_data_folder;
config.absolute_user_data_folder = user_data_folder_path;
fs.writeFileSync(path.join(task_server.getDir(), "config.json"), JSON.stringify(config));
}
if(msg.message.mysql_config_path != "-1"){
config.mysql_config_path = msg.message.mysql_config_path;
}
fs.writeFileSync(path.join(task_server.getDir(), "config.json"), JSON.stringify(config));
// child('Chrome/easyspider_executestage.exe', parameters, function(err,stdout, stderr) {
// console.log(stdout);
// });
let spawn = require("child_process").spawn;
if (process.platform != "darwin" && msg.message.execute_type == 1 && msg.message.id != -1) {
let child_process = spawn(execute_path, parameters);
child_process.stdout.on('data', function (data) {
console.log(data.toString());
});
}
ws.send(JSON.stringify({"config_folder": task_server.getDir() + "/", "easyspider_location": task_server.getEasySpiderLocation()}));
} else if (msg.type == 6) {
try{
flowchart_window.openDevTools();
} catch {
console.log("open devtools error");
}
try{
invoke_window.openDevTools();
} catch {
console.log("open devtools error");
}
} else if (msg.type == 7) {
// 获得当前页面Cookies
try{
let cookies = await driver.manage().getCookies();
console.log("Cookies: ", cookies);
let cookiesText = cookies.map(cookie => `${cookie.name}=${cookie.value}`).join('\n');
socket_flowchart.send(JSON.stringify({"type": "GetCookies", "message": cookiesText}));
} catch {
console.log("Cannot get Cookies");
}
}
}
const WebSocket = require('ws');
const {all} = require("express/lib/application");
let wss = new WebSocket.Server({port: websocket_port});
wss.on('connection', function (ws) {
ws.on('message', async function (message, isBinary) {
let msg = JSON.parse(message.toString());
console.log("\n\nGET A MESSAGE: ", msg);
// console.log(msg, msg.type, msg.message);
if (msg.type == 0) {
if (msg.message.id == 0) {
socket_window = ws;
console.log("set socket_window")
} else if (msg.message.id == 1) {
socket_start = ws;
console.log("set socket_start")
} else if (msg.message.id == 2) {
socket_flowchart = ws;
console.log("set socket_flowchart");
} else { //其他的ID是用来标识不同的浏览器标签页的
await new Promise(resolve => setTimeout(resolve, 2300));
let handles = await driver.getAllWindowHandles();
if(arrayDifference(handles, old_handles).length > 0){
old_handles = handles;
current_handle = handles[handles.length - 1];
console.log("New tab opened, change current_handle to: ", current_handle);
}
handle_pairs[msg.message.id] = current_handle;
console.log("Set handle_pair for id: ", msg.message.id, " to ", current_handle, ", title is: ", msg.message.title);
socket_flowchart.send(JSON.stringify({"type": "title", "data": {"title":msg.message.title}}));
allWindowSockets.push(ws);
allWindowScoketNames.push(msg.message.id);
// console.log("handle_pairs: ", handle_pairs);
}
} else if (msg.type == 10) {
let leave_handle = handle_pairs[msg.message.id];
if (leave_handle!=null && leave_handle!=undefined && leave_handle!="")
{
await driver.switchTo().window(leave_handle);
console.log("Switch to handle: ", leave_handle);
current_handle = leave_handle;
}
}
else {
await beginInvoke(msg, ws);
}
});
});
console.log(process.platform);
async function runBrowser(lang = "en", user_data_folder = '', mobile = false) {
const serviceBuilder = new ServiceBuilder(driverPath);
let options = new chrome.Options();
options.addArguments('--disable-blink-features=AutomationControlled');
language = lang;
if (lang == "en") {
options.addExtensions(path.join(__dirname, "EasySpider_en.crx"));
} else if (lang == "zh") {
options.addExtensions(path.join(__dirname, "EasySpider_zh.crx"));
}
options.addExtensions(path.join(__dirname, "XPathHelper.crx"));
options.setChromeBinaryPath(chromeBinaryPath);
if (user_data_folder != "") {
let dir = path.join(task_server.getDir(), user_data_folder);
console.log(dir);
options.addArguments("--user-data-dir=" + dir);
config.user_data_folder = user_data_folder;
fs.writeFileSync(path.join(task_server.getDir(), "config.json"), JSON.stringify(config));
}
if (mobile) {
const mobileEmulation = {
deviceName: 'iPhone XR'
};
options.addArguments(`--user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 13_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"`);
options.setMobileEmulation(mobileEmulation);
}
driver = new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.setChromeService(serviceBuilder)
.build();
await driver.manage().setTimeouts({implicit: 10000, pageLoad: 10000, script: 10000});
await driver.executeScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})");
// await driver.executeScript("localStorage.clear();"); //重置参数数量
const cdpConnection = await driver.createCDPConnection("page");
let stealth_path = path.join(__dirname, "stealth.min.js");
let stealth = fs.readFileSync(stealth_path, 'utf8');
await cdpConnection.execute('Page.addScriptToEvaluateOnNewDocument', {
source: stealth,
});
try {
if(mobile){
await driver.get(server_address + "/taskGrid/taskList.html?wsport=" + websocket_port + "&backEndAddressServiceWrapper=" + server_address + "&mobile=1&lang=" + lang);
} else {
await driver.get(server_address + "/taskGrid/taskList.html?wsport=" + websocket_port + "&backEndAddressServiceWrapper=" + server_address + "&lang=" + lang);
}
old_handles = await driver.getAllWindowHandles();
current_handle = old_handles[old_handles.length - 1];
} finally {
// await driver.quit(); // 退出浏览器
}
}
function handleOpenBrowser(event, lang = "en", user_data_folder = "", mobile = false) {
const webContents = event.sender;
const win = BrowserWindow.fromWebContents(webContents);
runBrowser(lang, user_data_folder, mobile);
let size = screen.getPrimaryDisplay().workAreaSize;
let width = parseInt(size.width);
let height = parseInt(size.height * 0.6);
flowchart_window = new BrowserWindow({
x: 0,
y: 0,
width: width,
height: height,
icon: iconPath,
});
let url = "";
let id = -1;
if (lang == "en") {
url = server_address + `/taskGrid/FlowChart.html?id=${id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address + "&mobile=" + mobile.toString();
} else if (lang == "zh") {
url = server_address + `/taskGrid/FlowChart_CN.html?id=${id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address+ "&mobile=" + mobile.toString();
}
// and load the index.html of the app.
flowchart_window.loadURL(url, { extraHeaders: 'pragma: no-cache\n' });
if(process.platform != "darwin"){
flowchart_window.hide();
}
flowchart_window.on('close', function (event) {
mainWindow.show();
driver.quit();
});
}
function handleOpenInvoke(event, lang = "en") {
invoke_window = new BrowserWindow({icon: iconPath});
let url = "";
language = lang;
if (lang == "en") {
url = server_address + `/taskGrid/taskList.html?type=1&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address;
} else if (lang == "zh") {
url = server_address + `/taskGrid/taskList.html?type=1&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address + "&lang=zh";
}
// and load the index.html of the app.
invoke_window.loadURL(url, { extraHeaders: 'pragma: no-cache\n' });
invoke_window.maximize();
mainWindow.hide();
invoke_window.on('close', function (event) {
mainWindow.show();
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
details.requestHeaders['Accept-Language'] = 'zh'
callback({ cancel: false, requestHeaders: details.requestHeaders })
})
ipcMain.on('start-design', handleOpenBrowser);
ipcMain.on('start-invoke', handleOpenInvoke);
ipcMain.on('accept-agreement', function (event, arg) {
config.copyright = 1;
fs.writeFileSync(path.join(task_server.getDir(), "config.json"), JSON.stringify(config));
});
createWindow();
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') {
app.quit();
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
function arrayDifference(arr1, arr2) {
return arr1.filter(item => !arr2.includes(item));
}