main.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. // Modules to control application life and create native browser window
  2. const {app, BrowserWindow, dialog, ipcMain, screen} = require('electron');
  3. app.commandLine.appendSwitch("--disable-http-cache");
  4. const {Builder, By, Key, until} = require("selenium-webdriver");
  5. const chrome = require('selenium-webdriver/chrome');
  6. const {ServiceBuilder} = require('selenium-webdriver/chrome');
  7. const {rootCertificates} = require('tls');
  8. const {exit} = require('process');
  9. const path = require('path');
  10. const fs = require('fs');
  11. const {exec} = require('child_process');
  12. const iconPath = path.join(__dirname, 'favicon.ico');
  13. const task_server = require(path.join(__dirname, 'server.js'));
  14. let config = fs.readFileSync(path.join(task_server.getDir(), `config.json`), 'utf8');
  15. config = JSON.parse(config);
  16. task_server.start(config.webserver_port); //start local server
  17. let server_address = `${config.webserver_address}:${config.webserver_port}`;
  18. const websocket_port = 8084; //目前只支持8084端口,写死,因为扩展里面写死了
  19. console.log("server_address: " + server_address);
  20. let driverPath = "";
  21. let chromeBinaryPath = "";
  22. let execute_path = "";
  23. console.log(process.arch);
  24. if (process.platform === 'win32' && process.arch === 'ia32') {
  25. driverPath = path.join(__dirname, "chrome_win32/chromedriver_win32.exe");
  26. chromeBinaryPath = path.join(__dirname, "chrome_win32/chrome.exe");
  27. execute_path = path.join(__dirname, "chrome_win32/execute.bat");
  28. } else if (process.platform === 'win32' && process.arch === 'x64') {
  29. driverPath = path.join(__dirname, "chrome_win64/chromedriver_win64.exe");
  30. chromeBinaryPath = path.join(__dirname, "chrome_win64/chrome.exe");
  31. execute_path = path.join(__dirname, "chrome_win64/execute.bat");
  32. } else if (process.platform === 'darwin') {
  33. driverPath = path.join(__dirname, "chromedriver_mac64");
  34. chromeBinaryPath = path.join(__dirname, "chrome_mac64.app/Contents/MacOS/Google Chrome");
  35. execute_path = path.join(__dirname, "");
  36. } else if (process.platform === 'linux') {
  37. driverPath = path.join(__dirname, "chrome_linux64/chromedriver_linux64");
  38. chromeBinaryPath = path.join(__dirname, "chrome_linux64/chrome");
  39. execute_path = path.join(__dirname, "chrome_linux64/execute.sh");
  40. }
  41. console.log(driverPath, chromeBinaryPath, execute_path);
  42. let language = "en";
  43. let driver = null;
  44. let mainWindow = null;
  45. let flowchart_window = null;
  46. let current_handle = null;
  47. let old_handles = [];
  48. let handle_pairs = {};
  49. // var ffi = require('ffi-napi');
  50. // var libm = ffi.Library('libm', {
  51. // 'ceil': [ 'double', [ 'double' ] ]
  52. // });
  53. // libm.ceil(1.5); // 2
  54. // const {user32FindWindowEx,
  55. // winspoolGetDefaultPrinter,} = require('win32-api/fun');
  56. // async function testt(){
  57. // // 获取当前电脑当前用户默认打印机名
  58. // const printerName = await winspoolGetDefaultPrinter()
  59. // console.log(printerName);
  60. // }
  61. // testt();
  62. function createWindow() {
  63. // Create the browser window.
  64. mainWindow = new BrowserWindow({
  65. width: 520,
  66. height: 750,
  67. webPreferences: {
  68. preload: path.join(__dirname, 'src/js/preload.js')
  69. },
  70. icon: iconPath,
  71. // frame: false, //取消window自带的关闭最小化等
  72. resizable: false //禁止改变主窗口尺寸
  73. })
  74. // and load the index.html of the app.
  75. // mainWindow.loadFile('src/index.html');
  76. mainWindow.loadURL(server_address + '/index.html?user_data_folder=' + config.user_data_folder);
  77. // 隐藏菜单栏
  78. const {Menu} = require('electron');
  79. Menu.setApplicationMenu(null);
  80. mainWindow.on('close', function (e) {
  81. if (process.platform !== 'darwin') {
  82. app.quit();
  83. }
  84. });
  85. // mainWindow.webContents.openDevTools();
  86. // Open the DevTools.
  87. // mainWindow.webContents.openDevTools()
  88. }
  89. async function beginInvoke(msg, ws) {
  90. if (msg.type == 1) {
  91. if (msg.message.id != -1) {
  92. let url = "";
  93. if (language == "zh") {
  94. url = server_address + `/taskGrid/FlowChart_CN.html?id=${msg.message.id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address;
  95. } else if (language == "en") {
  96. url = server_address + `/taskGrid/FlowChart.html?id=${msg.message.id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address;
  97. }
  98. console.log(url);
  99. flowchart_window.loadURL(url);
  100. }
  101. mainWindow.hide();
  102. // Prints the currently focused window bounds.
  103. // This method has to be called on macOS before changing the window's bounds, otherwise it will throw an error.
  104. // It will prompt an accessibility permission request dialog, if needed.
  105. if(process.platform != "linux" && process.platform != "darwin"){
  106. const {windowManager} = require("node-window-manager");
  107. const window = windowManager.getActiveWindow();
  108. console.log(window);
  109. windowManager.requestAccessibility();
  110. // Sets the active window's bounds.
  111. let size = screen.getPrimaryDisplay().workAreaSize
  112. let width = parseInt(size.width)
  113. let height = parseInt(size.height * 0.6)
  114. window.setBounds({x: 0, y: size.height * 0.4, height: height, width: width});
  115. }
  116. flowchart_window.show();
  117. // flowchart_window.openDevTools();
  118. } else if (msg.type == 2) {
  119. //keyboard
  120. // const robot = require("@jitsi/robotjs");
  121. let keyInfo = msg.message.keyboardStr;
  122. let handles = await driver.getAllWindowHandles();
  123. console.log("handles", handles);
  124. let exit = false;
  125. let content_handle = handle_pairs[msg.message.id];
  126. console.log(msg.message.id, content_handle);
  127. let order = [...handles.filter(handle => handle != current_handle && handle != content_handle), current_handle, content_handle]; //搜索顺序
  128. let len = order.length;
  129. while (true) {
  130. // console.log("handles");
  131. try{
  132. let h = order[len - 1];
  133. console.log("current_handle", current_handle);
  134. if(h != null && handles.includes(h)){
  135. await driver.switchTo().window(h);
  136. current_handle = h;
  137. console.log("switch to handle: ", h);
  138. }
  139. // await driver.executeScript("window.stop();");
  140. // console.log("executeScript");
  141. let element = await driver.findElement(By.xpath(msg.message.xpath));
  142. console.log("Find Element at handle: ", current_handle);
  143. await element.sendKeys(Key.HOME, Key.chord(Key.SHIFT, Key.END), keyInfo);
  144. console.log("send key");
  145. break;
  146. } catch (error) {
  147. console.log("len", len);
  148. len = len - 1;
  149. if (len == 0) {
  150. break;
  151. }
  152. }
  153. // .then(function (element) {
  154. // console.log("element", element, handles);
  155. // element.sendKeys(Key.HOME, Key.chord(Key.SHIFT, Key.END), keyInfo);
  156. // exit = true;
  157. // }, function (error) {
  158. // console.log("error", error);
  159. // len = len - 1;
  160. // if (len == 0) {
  161. // exit = true;
  162. // }
  163. // }
  164. // );
  165. }
  166. // let handles = driver.getAllWindowHandles();
  167. // driver.switchTo().window(handles[handles.length - 1]);
  168. // driver.findElement(By.xpath(msg.message.xpath)).sendKeys(Key.HOME, Key.chord(Key.SHIFT, Key.END), keyInfo);
  169. // robot.keyTap("a", "control");
  170. // robot.keyTap("backspace");
  171. // robot.typeString(keyInfo);
  172. // robot.keyTap("shift");
  173. // robot.keyTap("shift");
  174. } else if (msg.type == 3) {
  175. try {
  176. if (msg.from == 0) {
  177. socket_flowchart.send(msg.message.pipe); //直接把消息转接
  178. let message = JSON.parse(msg.message.pipe);
  179. let type = message.type;
  180. console.log("FROM Browser: ", message);
  181. if(type.indexOf("Click")>=0){
  182. let handles = await driver.getAllWindowHandles();
  183. console.log("handles", handles);
  184. let exit = false;
  185. let content_handle = handle_pairs[message.id];
  186. console.log(message.id, content_handle);
  187. let order = [...handles.filter(handle => handle != current_handle && handle != content_handle), current_handle, content_handle]; //搜索顺序
  188. let len = order.length;
  189. while(true) {
  190. try{
  191. let h = order[len - 1];
  192. console.log("current_handle", current_handle);
  193. if(h != null && handles.includes(h)){
  194. await driver.switchTo().window(h);
  195. current_handle = h;
  196. console.log("switch to handle: ", h);
  197. }
  198. let element = await driver.findElement(By.xpath(message.xpath));
  199. await element.click();
  200. break;
  201. } catch (error) {
  202. console.log("len", len);
  203. len = len - 1;
  204. if (len == 0) {
  205. break;
  206. }
  207. }
  208. }
  209. }
  210. } else {
  211. socket_window.send(msg.message.pipe);
  212. console.log("FROM Flowchart: ", JSON.parse(msg.message.pipe));
  213. }
  214. } catch (e) {
  215. console.log(e);
  216. }
  217. } else if (msg.type == 5) {
  218. let child = require('child_process').execFile;
  219. // 参数顺序: 1. task id 2. server address 3. saved_file_name 4. "remote" or "local" 5. user_data_folder
  220. // var parameters = [msg.message.id, server_address];
  221. let parameters = [];
  222. console.log(msg.message)
  223. if (msg.message.user_data_folder == null || msg.message.user_data_folder == undefined || msg.message.user_data_folder == "") {
  224. parameters = ["--id", "[" + msg.message.id + "]", "--server_address", server_address, "--user_data", 0];
  225. } else {
  226. let user_data_folder_path = path.join(task_server.getDir(), msg.message.user_data_folder);
  227. parameters = ["--id", "[" + msg.message.id + "]", "--server_address", server_address, "--user_data", 1];
  228. config.user_data_folder = msg.message.user_data_folder;
  229. config.absolute_user_data_folder = user_data_folder_path;
  230. fs.writeFileSync(path.join(task_server.getDir(), "config.json"), JSON.stringify(config));
  231. }
  232. // child('Chrome/easyspider_executestage.exe', parameters, function(err,stdout, stderr) {
  233. // console.log(stdout);
  234. // });
  235. let spawn = require("child_process").spawn;
  236. if (process.platform != "darwin" && msg.message.execute_type == 1) {
  237. let child_process = spawn(execute_path, parameters);
  238. child_process.stdout.on('data', function (data) {
  239. console.log(data.toString());
  240. });
  241. } else {
  242. ws.send(JSON.stringify({"config_folder": task_server.getDir() + "/", "easyspider_location": task_server.getEasySpiderLocation()}));
  243. }
  244. } else if (msg.type == 6) {
  245. try{
  246. flowchart_window.openDevTools();
  247. } catch {
  248. }
  249. }
  250. }
  251. const WebSocket = require('ws');
  252. let socket_window = null;
  253. let socket_start = null;
  254. let socket_flowchart = null;
  255. let wss = new WebSocket.Server({port: websocket_port});
  256. wss.on('connection', function (ws) {
  257. ws.on('message', async function (message, isBinary) {
  258. let msg = JSON.parse(message.toString());
  259. console.log("\n\nGET A MESSAGE: ", msg);
  260. // console.log(msg, msg.type, msg.message);
  261. if (msg.type == 0) {
  262. if (msg.message.id == 0) {
  263. socket_window = ws;
  264. console.log("set socket_window")
  265. } else if (msg.message.id == 1) {
  266. socket_start = ws;
  267. console.log("set socket_start")
  268. } else if (msg.message.id == 2) {
  269. socket_flowchart = ws;
  270. console.log("set socket_flowchart");
  271. } else { //其他的ID是用来标识不同的浏览器标签页的
  272. await new Promise(resolve => setTimeout(resolve, 2300));
  273. let handles = await driver.getAllWindowHandles();
  274. if(arrayDifference(handles, old_handles).length > 0){
  275. old_handles = handles;
  276. current_handle = handles[handles.length - 1];
  277. console.log("New tab opened, change current_handle to: ", current_handle);
  278. }
  279. handle_pairs[msg.message.id] = current_handle;
  280. console.log("Set handle_pair for id: ", msg.message.id, " to ", current_handle, ", title is: ", msg.message.title);
  281. socket_flowchart.send(JSON.stringify({"type": "title", "data": {"title":msg.message.title}}));
  282. // console.log("handle_pairs: ", handle_pairs);
  283. }
  284. } else if (msg.type == 10) {
  285. let leave_handle = handle_pairs[msg.message.id];
  286. if (leave_handle!=null && leave_handle!=undefined && leave_handle!="")
  287. {
  288. await driver.switchTo().window(leave_handle);
  289. console.log("Switch to handle: ", leave_handle);
  290. current_handle = leave_handle;
  291. }
  292. }
  293. else {
  294. await beginInvoke(msg, ws);
  295. }
  296. });
  297. });
  298. console.log(process.platform);
  299. async function runBrowser(lang = "en", user_data_folder = '', mobile = false) {
  300. const serviceBuilder = new ServiceBuilder(driverPath);
  301. let options = new chrome.Options();
  302. options.addArguments('--disable-blink-features=AutomationControlled');
  303. language = lang;
  304. if (lang == "en") {
  305. options.addExtensions(path.join(__dirname, "EasySpider_en.crx"));
  306. } else if (lang == "zh") {
  307. options.addExtensions(path.join(__dirname, "EasySpider_zh.crx"));
  308. }
  309. options.addExtensions(path.join(__dirname, "XPathHelper.crx"));
  310. options.setChromeBinaryPath(chromeBinaryPath);
  311. options.add
  312. if (user_data_folder != "") {
  313. let dir = path.join(task_server.getDir(), user_data_folder);
  314. console.log(dir);
  315. options.addArguments("--user-data-dir=" + dir);
  316. config.user_data_folder = user_data_folder;
  317. fs.writeFileSync(path.join(task_server.getDir(), "config.json"), JSON.stringify(config));
  318. }
  319. if (mobile) {
  320. const mobileEmulation = {
  321. deviceName: 'iPhone XR'
  322. };
  323. 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"`);
  324. options.setMobileEmulation(mobileEmulation);
  325. }
  326. driver = new Builder()
  327. .forBrowser('chrome')
  328. .setChromeOptions(options)
  329. .setChromeService(serviceBuilder)
  330. .build();
  331. await driver.manage().setTimeouts({implicit: 10000, pageLoad: 10000, script: 10000});
  332. await driver.executeScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})");
  333. const cdpConnection = await driver.createCDPConnection("page");
  334. let stealth_path = path.join(__dirname, "stealth.min.js");
  335. let stealth = fs.readFileSync(stealth_path, 'utf8');
  336. await cdpConnection.execute('Page.addScriptToEvaluateOnNewDocument', {
  337. source: stealth,
  338. });
  339. try {
  340. if(mobile){
  341. await driver.get(server_address + "/taskGrid/taskList.html?wsport=" + websocket_port + "&backEndAddressServiceWrapper=" + server_address + "&mobile=1&lang=" + lang);
  342. } else {
  343. await driver.get(server_address + "/taskGrid/taskList.html?wsport=" + websocket_port + "&backEndAddressServiceWrapper=" + server_address + "&lang=" + lang);
  344. }
  345. old_handles = await driver.getAllWindowHandles();
  346. current_handle = old_handles[old_handles.length - 1];
  347. } finally {
  348. // await driver.quit(); // 退出浏览器
  349. }
  350. }
  351. function handleOpenBrowser(event, lang = "en", user_data_folder = "", mobile = false) {
  352. const webContents = event.sender;
  353. const win = BrowserWindow.fromWebContents(webContents);
  354. runBrowser(lang, user_data_folder, mobile);
  355. let size = screen.getPrimaryDisplay().workAreaSize;
  356. let width = parseInt(size.width);
  357. let height = parseInt(size.height * 0.7);
  358. flowchart_window = new BrowserWindow({
  359. x: 0,
  360. y: 0,
  361. width: width,
  362. height: height,
  363. icon: iconPath,
  364. });
  365. let url = "";
  366. let id = -1;
  367. if (lang == "en") {
  368. url = server_address + `/taskGrid/FlowChart.html?id=${id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address + "&mobile=" + mobile.toString();
  369. } else if (lang == "zh") {
  370. url = server_address + `/taskGrid/FlowChart_CN.html?id=${id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address+ "&mobile=" + mobile.toString();
  371. }
  372. // and load the index.html of the app.
  373. flowchart_window.loadURL(url);
  374. if(process.platform != "darwin"){
  375. flowchart_window.hide();
  376. }
  377. flowchart_window.on('close', function (event) {
  378. mainWindow.show();
  379. driver.quit();
  380. });
  381. }
  382. function handleOpenInvoke(event, lang = "en") {
  383. const window = new BrowserWindow({icon: iconPath});
  384. let url = "";
  385. language = lang;
  386. if (lang == "en") {
  387. url = server_address + `/taskGrid/taskList.html?type=1&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address;
  388. } else if (lang == "zh") {
  389. url = server_address + `/taskGrid/taskList.html?type=1&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address + "&lang=zh";
  390. }
  391. // and load the index.html of the app.
  392. window.loadURL(url);
  393. window.maximize();
  394. mainWindow.hide();
  395. window.on('close', function (event) {
  396. mainWindow.show();
  397. });
  398. }
  399. // This method will be called when Electron has finished
  400. // initialization and is ready to create browser windows.
  401. // Some APIs can only be used after this event occurs.
  402. app.whenReady().then(() => {
  403. ipcMain.on('start-design', handleOpenBrowser);
  404. ipcMain.on('start-invoke', handleOpenInvoke);
  405. createWindow();
  406. app.on('activate', function () {
  407. // On macOS it's common to re-create a window in the app when the
  408. // dock icon is clicked and there are no other windows open.
  409. if (BrowserWindow.getAllWindows().length === 0) {
  410. createWindow();
  411. }
  412. })
  413. })
  414. // Quit when all windows are closed, except on macOS. There, it's common
  415. // for applications and their menu bar to stay active until the user quits
  416. // explicitly with Cmd + Q.
  417. app.on('window-all-closed', function () {
  418. if (process.platform !== 'darwin') {
  419. app.quit();
  420. }
  421. })
  422. // In this file you can include the rest of your app's specific main process
  423. // code. You can also put them in separate files and require them here.
  424. function arrayDifference(arr1, arr2) {
  425. return arr1.filter(item => !arr2.includes(item));
  426. }