webview.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  1. /* Copyright (C) 2024 Richard Hao Cao
  2. Ebrowser is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
  3. Ebrowser 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 General Public License for more details.
  4. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
  5. */
  6. const {
  7. app, BrowserWindow, Menu, shell, clipboard,
  8. session, protocol, dialog, ipcMain
  9. } = require('electron')
  10. let win;
  11. if(!app.requestSingleInstanceLock())
  12. app.quit()
  13. else {
  14. app.on('ready', createWindow);
  15. app.on('second-instance', (event, args, cwd) => {
  16. if (win) {
  17. if (win.isMinimized()) {
  18. win.restore()
  19. }
  20. win.show()
  21. win.focus()
  22. cmdlineProcess(args,cwd,1);
  23. }else
  24. createWindow();
  25. })
  26. }
  27. Menu.setApplicationMenu(null);
  28. const fs = require('fs');
  29. const path = require('path')
  30. const https = require('https');
  31. const url = require('url');
  32. var downloadMillis = 0;
  33. var translateRes;
  34. {
  35. let langs = app.getPreferredSystemLanguages();
  36. if(langs.length==0 || langs[0].startsWith('en'))
  37. topMenu();
  38. else
  39. initTranslateRes(langs[0]);
  40. }
  41. var repositoryurl = "https://gitlab.com/jamesfengcao/uweb/-/raw/master/misc/ebrowser/";
  42. const readline = require('readline');
  43. const process = require('process')
  44. var gredirects = [];
  45. var gredirect;
  46. var redirects;
  47. var bRedirect = true;
  48. var bJS = true;
  49. var bForwardCookie = true;
  50. var proxies = {};
  51. var proxy;
  52. var useragents = {};
  53. var downloadMenus = [];
  54. var selectMenus = [];
  55. var defaultUA;
  56. {
  57. let sys = "X11; Linux x86_64";
  58. if (process.platform === "win32")
  59. sys = "Window NT 10.0; Win64; x64";
  60. else if (process.platform === "darwin")
  61. sys = "Macintosh; Intel Mac OS X 10_15_7";
  62. defaultUA =
  63. `Mozilla/5.0 (${sys}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/` +
  64. process.versions.chrome +" Safari/537.36";
  65. }
  66. app.userAgentFallback = defaultUA;
  67. fs.readFile(path.join(__dirname,'redirect.json'), 'utf8', (err, jsonString) => {
  68. if (err) return;
  69. try {
  70. redirects = JSON.parse(jsonString);
  71. } catch (e){console.log(e)}
  72. });
  73. async function createWindow () {
  74. try {
  75. let json = await fs.promises.readFile(path.join(__dirname,'uas.json'), 'utf8');
  76. useragents = JSON.parse(json);
  77. } catch (e){console.log(e)}
  78. protocol.handle("i",(req)=>{return null;});
  79. await (async ()=>{
  80. try{
  81. const readInterface = readline.createInterface ({
  82. input: fs.createReadStream (path.join(__dirname,'config'), 'utf8'),
  83. });
  84. for await (const line of readInterface) {
  85. addrCommand(line);
  86. }
  87. }catch(e){console.log(e);}
  88. })();
  89. win = new BrowserWindow(
  90. {show: false, autoHideMenuBar: true,
  91. webPreferences: {
  92. nodeIntegration: true,
  93. contextIsolation: false,
  94. webviewTag: true,
  95. }});
  96. win.setMenuBarVisibility(false);
  97. win.once('ready-to-show', () => {
  98. win.maximize();
  99. win.show();
  100. });
  101. win.on('closed', function () {
  102. win = null
  103. })
  104. win.loadFile('index.html');
  105. fs.readFile(path.join(__dirname,'gredirect.json'), 'utf8', (err, jsonString) => {
  106. if (err) return;
  107. try {
  108. gredirects = JSON.parse(jsonString);
  109. } catch (e){console.log(e)}
  110. });
  111. fs.readFile(path.join(__dirname,'proxy.json'), 'utf8', (err, jsonString) => {
  112. if (err) return;
  113. try {
  114. proxies = JSON.parse(jsonString);
  115. let match = jsonString.match(/"([^"]+)"/);
  116. if(match)
  117. proxy = proxies[match[1]];
  118. } catch (e){console.log(e)}
  119. });
  120. cmdlineProcess(process.argv, process.cwd(), 0);
  121. //app.commandLine.appendSwitch ('trace-warnings');
  122. fs.readFile(path.join(__dirname,'download.json'), 'utf8', (err, jsonStr) => {
  123. if (err) return;
  124. try {
  125. downloadMenus = JSON.parse(jsonStr);
  126. }catch (e){console.log(e)}
  127. });
  128. fs.readFile(path.join(__dirname,'select.json'), 'utf8', (err, jsonStr) => {
  129. if (err) return;
  130. try {
  131. selectMenus = JSON.parse(jsonStr);
  132. }catch (e){console.log(e)}
  133. });
  134. win.webContents.on('page-title-updated',(event,cmd)=>{
  135. addrCommand(cmd);
  136. });
  137. session.defaultSession.on("will-download", async (e, item) => {
  138. //item.setSavePath(save)
  139. let curMillis = Date.now();
  140. if(curMillis-downloadMillis<9000){
  141. item.on('updated', (event, state) => {
  142. const progress = item.getReceivedBytes() / item.getTotalBytes();
  143. win.setProgressBar(progress);
  144. });
  145. item.on('done', () => win.setProgressBar(-1));
  146. return;
  147. }
  148. e.preventDefault();
  149. let url = item.getURL();
  150. let menuT = downloadContextMenuTemp(url);
  151. const menu = Menu.buildFromTemplate(menuT);
  152. menu.popup();
  153. });
  154. win.webContents.on('console-message',cbConsoleMsg);
  155. }
  156. app.on('window-all-closed', function () {
  157. app.quit()
  158. })
  159. app.on('activate', function () {
  160. if (win === null) {
  161. createWindow()
  162. }
  163. })
  164. app.on('will-quit', () => {
  165. })
  166. app.on ('web-contents-created', (event, contents) => {
  167. if (contents.getType () === 'webview') {
  168. contents.setWindowOpenHandler(cbWindowOpenHandler);
  169. contents.on('context-menu',onContextMenu);
  170. contents.on('page-title-updated',cbTitleUpdate);
  171. contents.session.webRequest.onBeforeRequest(interceptRequest);
  172. }
  173. });
  174. ipcMain.on('command', (event, cmd) => {
  175. addrCommand(cmd);
  176. });
  177. async function addrCommand(cmd){
  178. if(cmd.length<3) return;
  179. let c0 = cmd.charCodeAt(0);
  180. switch(c0){
  181. case 33://"!"
  182. bangcommand(q,1);
  183. return;
  184. case 58: //':'
  185. let iS = cmd.indexOf(' ',1);
  186. if(iS<0) iS = cmd.length;
  187. let arg0 = cmd.substring(1,iS);
  188. switch(arg0){
  189. case "cert":
  190. if(cmd.length==iS)
  191. session.defaultSession.setCertificateVerifyProc((request, callback) => {
  192. callback(0);
  193. });
  194. else
  195. session.defaultSession.setCertificateVerifyProc(null);
  196. return;
  197. case "clear":
  198. if(cmd.length<=iS+1){
  199. session.defaultSession.clearData();
  200. return;
  201. }
  202. if(123===cmd.charCodeAt(iS+1)){//json
  203. try {
  204. let opts = JSON.parse(cmd.substring(iS+1));
  205. session.defaultSession.clearData(opts);
  206. }catch(e){console.log(e)}
  207. return;
  208. }
  209. let args = cmd.substring(iS+1).split(/\s+/);
  210. switch(args[0]){
  211. case "cache":
  212. session.defaultSession.clearCache();
  213. return;
  214. case "cookie":
  215. if(args.length==1){
  216. session.defaultSession.clearStorageData({ storages: ['cookies'] });
  217. return;
  218. }
  219. {
  220. let url = args[1];
  221. if(url.charCodeAt(0)!==104) url = "https://"+url;
  222. session.defaultSession.cookies.get({ url: url }).then((cookies) => {
  223. cookies.forEach((cookie) => {
  224. session.defaultSession.cookies.remove(targetUrl, cookie.name)})});
  225. }
  226. return;
  227. case "dns":
  228. session.defaultSession.clearHostResolverCache();
  229. return;
  230. case "storage":
  231. session.defaultSession.clearStorageData();
  232. return;
  233. default:
  234. }
  235. return;
  236. case "exit":
  237. win.close();
  238. return;
  239. case "ext":
  240. session.defaultSession.loadExtension(cmd.substring(iS+1));
  241. return;
  242. case "gr":
  243. if(cmd.length==iS) {
  244. gredirect_enable(0);
  245. return;
  246. }
  247. let i = parseInt(cmd.substring(iS+1));
  248. if(i>=0 && i<gredirects.length)
  249. gredirect_enable(i);
  250. else
  251. gredirect_disable();
  252. return;
  253. case "js"://execute js
  254. eval(cmd.slice(4));
  255. return;
  256. case "nc":
  257. bForwardCookie = false;
  258. msgbox_info("Cookie forwarding disabled");
  259. return;
  260. case "uc":
  261. if(bForwardCookie) {
  262. msgbox_info("Cookie forwarding enabled for global redirection");
  263. return;
  264. }
  265. forwardCookie();
  266. return;
  267. case "np":
  268. session.defaultSession.setProxy ({mode:"direct"});
  269. bRedirect = true;
  270. return;
  271. case "up":
  272. if(cmd.length>iS)
  273. proxy = proxies[cmd.substring(iS+1)]; //retrieve proxy
  274. if(proxy){
  275. session.defaultSession.setProxy(proxy)
  276. .then(() => {gredirect_disable()})
  277. .catch((error) => {
  278. console.error('Failed to set proxy:', error);
  279. });
  280. }
  281. return;
  282. case "nr":
  283. bRedirect = false; return;
  284. case "ur":
  285. bRedirect = true; return;
  286. case "sys":
  287. {
  288. let iHTTP = cmd.search(/https?:\/\//);
  289. if(iHTTP<0) return;
  290. let iEnd = cmd.indexOf(' ',iHTTP+10);
  291. if(iEnd<0) iEnd = cmd.length;
  292. let url = cmd.substring(iHTTP,iEnd);
  293. let cookies = await session.defaultSession.cookies.get({url: url});
  294. let cookieS = cookies.map (cookie => cookie.name + '='
  295. + cookie.value ).join(';');
  296. let args = cmd.substring(5).split(/\s+/);
  297. for(let i=1;i<args.length;i++){
  298. let iC = args[i].indexOf('%cookie');
  299. if(iC<0) continue;
  300. args[i] = args[i].substring(0,i)+cookieS+args[i].substring(i+7);
  301. break;
  302. }
  303. const { spawn } = require('child_process');
  304. const process = spawn(args[0],args.slice(1));
  305. process.stdout.on('data', (data) => {
  306. let str = data.toString();
  307. console.log(str);
  308. let js = "showHtml(`"+str+"`)";
  309. win.webContents.executeJavaScript(js,false);
  310. });
  311. }
  312. return;
  313. case "ua":
  314. if(cmd.length>iS){
  315. let ua = useragents[cmd.substring(iS+1)];
  316. if(ua)
  317. session.defaultSession.setUserAgent(ua);
  318. }else
  319. session.defaultSession.setUserAgent(defaultUA);
  320. return;
  321. case "update":
  322. let updateurl;
  323. if(cmd.length==iS)
  324. updateApp(repositoryurl);
  325. else {
  326. filename = cmd.substring(iS+1);
  327. let iSlash = filename.lastIndexOf('/');
  328. if(iSlash>0){
  329. let folder = path.join(__dirname,filename.substring(0,iSlash));
  330. fs.mkdirSync(folder,{ recursive: true });
  331. }
  332. fetch2file(repositoryurl,filename);
  333. }
  334. return;
  335. }
  336. }
  337. }
  338. function gredirect_disable(){
  339. if(gredirect){
  340. gredirect=null;
  341. unregisterHandler();
  342. }
  343. bRedirect = true;
  344. }
  345. function gredirect_enable(i){
  346. if(i>=gredirects.length) return;
  347. if(!gredirect) registerHandler();
  348. gredirect=gredirects[i];
  349. }
  350. function cbConsoleMsg(e, level, msg, line, sourceid){
  351. console.log(line);
  352. console.log(sourceid);
  353. console.log(msg);
  354. }
  355. function interceptRequest(details, callback){
  356. let url = details.url;
  357. if(58===url.charCodeAt(1) || (!bJS && url.endsWith(".js"))){
  358. callback({ cancel: true });
  359. return;
  360. }
  361. do {
  362. if(gredirect || !bRedirect ||(details.resourceType !== 'mainFrame' &&
  363. details.resourceType !== 'subFrame')) break;
  364. let oURL = new URL(url);
  365. let domain = oURL.hostname;
  366. let newUrl;
  367. try{
  368. let newDomain = redirects[domain];
  369. if(!newDomain) break;
  370. newUrl = "https://"+newDomain+oURL.pathname+oURL.search+oURL.hash;
  371. }catch(e){break;}
  372. callback({ cancel: false, redirectURL: newUrl });
  373. return;
  374. }while(false);
  375. callback({ cancel: false });
  376. }
  377. function cbWindowOpenHandler(details){
  378. let url = details.url;
  379. let js = "newTab();tabs.children[tabs.children.length-1].src='"+
  380. url+"';";
  381. switch(details.disposition){
  382. case "foreground-tab":
  383. case "new-window":
  384. js = js + "switchTab(tabs.children.length-1)";
  385. }
  386. win.webContents.executeJavaScript(js,false);
  387. return { action: "deny" };
  388. }
  389. function cbTitleUpdate(event,title){
  390. win.setTitle(title);
  391. }
  392. function menuSelection(menuTemplate, text){
  393. for(let i=0; i<selectMenus.length-1;i++){
  394. menuTemplate.push({
  395. label: selectMenus[i],
  396. click: () => {
  397. let cmd = selectMenus[i+1].replace('%s',text);
  398. let js = `handleQueries(\`${cmd}\`)`;
  399. win.webContents.executeJavaScript(js,false);
  400. }
  401. });
  402. }
  403. }
  404. function menuDownload(menuTemplate, labelprefix, linkUrl){
  405. for(let i=0; i<downloadMenus.length-1;i++){
  406. menuTemplate.push({
  407. label: labelprefix+downloadMenus[i],
  408. click: () => {
  409. let cmd = downloadMenus[i+1].replace('%u',linkUrl);
  410. let js = `handleQueries(\`${cmd}\`)`;
  411. win.webContents.executeJavaScript(js,false);
  412. }
  413. });
  414. }
  415. }
  416. function menuArray(labelprefix, linkUrl){
  417. let menuTemplate = [
  418. {
  419. label: labelprefix+translate('Open'),
  420. click: () => {
  421. shell.openExternal(linkUrl);
  422. }
  423. },
  424. {
  425. label: labelprefix+translate('Copy'),
  426. click: () => {
  427. clipboard.writeText(linkUrl);
  428. }
  429. },
  430. {
  431. label: labelprefix+translate('Download'),
  432. click: () => {
  433. downloadMillis = Date.now();
  434. win.webContents.downloadURL(linkUrl);
  435. }
  436. },
  437. ];
  438. if(downloadMenus)
  439. menuDownload(menuTemplate, labelprefix, linkUrl);
  440. return menuTemplate;
  441. }
  442. function onContextMenu(event, params){
  443. let url = params.linkURL;
  444. let mTemplate = [];
  445. if (url) {
  446. mTemplate.push({label:url,enabled:false});
  447. mTemplate.push.apply(mTemplate,menuArray("",url));
  448. if((url=params.srcURL))
  449. mTemplate.push.apply(mTemplate,menuArray("src: ",url));
  450. }else if((url=params.srcURL)){
  451. mTemplate.push({label:url,enabled:false});
  452. mTemplate.push.apply(mTemplate,menuArray("src: ",url));
  453. }else if((url=params.selectionText)){
  454. menuSelection(mTemplate,url);
  455. }else
  456. return;
  457. const contextMenu = Menu.buildFromTemplate(mTemplate);
  458. contextMenu.popup();
  459. }
  460. async function topMenu(){
  461. const menuTemplate = [];
  462. try {
  463. let json = await fs.promises.readFile(path.join(__dirname,'menu.json'), 'utf8');
  464. let menus = JSON.parse(json);
  465. if(menus.length>1){
  466. let submenu = [];
  467. for(let i=0;i<menus.length-1; i=i+2){
  468. let cmd = menus[i+1];
  469. let js = `handleQueries("${cmd}")`;
  470. submenu.push({
  471. label: menus[i], click: ()=>{
  472. win.webContents.executeJavaScript(js,false);
  473. }});
  474. }
  475. menuTemplate.push({
  476. label: translate('Tools'),
  477. submenu: submenu,
  478. });
  479. }
  480. }catch(e){console.log(e)}
  481. menuTemplate.push(
  482. {
  483. label: translate('Edit'),
  484. submenu: [
  485. { label: translate('Config folder'), click: ()=>{
  486. shell.openPath(__dirname);
  487. }},
  488. ]
  489. },
  490. {
  491. label: translate('Help'),
  492. submenu: [
  493. { label: translate('Check for updates'), click: ()=>{
  494. addrCommand(":update");
  495. }},
  496. { label: translate('Help'), accelerator: 'F1', click: ()=>{
  497. help();
  498. }},
  499. { label: translate('Stop'), accelerator: 'Ctrl+C', click: ()=>{
  500. let js="tabs.children[iTab].stop()"
  501. win.webContents.executeJavaScript(js,false)
  502. }},
  503. { label: translate('getURL'), accelerator: 'Ctrl+G', click: ()=>{
  504. let js="{let q=document.forms[0].q;q.focus();q.value=tabs.children[iTab].getURL();getWinTitle()}"
  505. win.webContents.executeJavaScript(js,false).then((r)=>{
  506. win.setTitle(r);
  507. });
  508. }},
  509. { label: translate('Select'), accelerator: 'Ctrl+L', click:()=>{
  510. win.webContents.executeJavaScript("document.forms[0].q.select()",false);
  511. }},
  512. { label: translate('New Tab'), accelerator: 'Ctrl+T', click:()=>{
  513. let js = "newTab();document.forms[0].q.select();switchTab(tabs.children.length-1)";
  514. win.webContents.executeJavaScript(js,false);
  515. }},
  516. { label: translate('Restore Tab'), accelerator: 'Ctrl+Shift+T', click:()=>{
  517. let js = "{let u=closedUrls.pop();if(u){newTab();switchTab(tabs.children.length-1);tabs.children[iTab].src=u}}";
  518. win.webContents.executeJavaScript(js,false);
  519. }},
  520. { label: translate('No redirect'), accelerator: 'Ctrl+R', click: ()=>{
  521. gredirect_disable();
  522. }},
  523. { label: translate('Redirect'), accelerator: 'Ctrl+Shift+R', click: ()=>{
  524. gredirect_enable(0);
  525. }},
  526. { label: translate('Close tab'), accelerator: 'Ctrl+W', click: ()=>{
  527. win.webContents.executeJavaScript("tabClose()",false).then((r)=>{
  528. if(""===r) win.close();
  529. else win.setTitle(r);
  530. });
  531. }},
  532. { label: translate('Next Tab'), accelerator: 'Ctrl+Tab', click: ()=>{
  533. let js="tabInc(1);getWinTitle()";
  534. win.webContents.executeJavaScript(js,false).then((r)=>{
  535. win.setTitle(r);
  536. });
  537. }},
  538. { label: translate('Previous Tab'), accelerator: 'Ctrl+Shift+Tab', click: ()=>{
  539. let js="tabDec(-1);getWinTitle()";
  540. win.webContents.executeJavaScript(js,false).then((r)=>{
  541. win.setTitle(r);
  542. });
  543. }},
  544. { label: translate('Go backward'), accelerator: 'Alt+Left', click: ()=>{
  545. let js="tabs.children[iTab].goBack()";
  546. win.webContents.executeJavaScript(js,false);
  547. }},
  548. { label: translate('Go forward'), accelerator: 'Alt+Right', click: ()=>{
  549. let js="tabs.children[iTab].goForward()";
  550. win.webContents.executeJavaScript(js,false);
  551. }},
  552. { label: translate('Zoom in'), accelerator: 'Ctrl+Shift+=', click: ()=>{
  553. let js="{let t=tabs.children[iTab];let s=t.getZoomFactor()*1.2;t.setZoomFactor(s)}";
  554. win.webContents.executeJavaScript(js,false);
  555. }},
  556. { label: translate('Zoom out'), accelerator: 'Ctrl+-', click: ()=>{
  557. let js="{let t=tabs.children[iTab];let s=t.getZoomFactor()/1.2;t.setZoomFactor(s)}";
  558. win.webContents.executeJavaScript(js,false);
  559. }},
  560. { label: translate('Default zoom'), accelerator: 'Ctrl+0', click: ()=>{
  561. let js="tabs.children[iTab].setZoomFactor(1)";
  562. win.webContents.executeJavaScript(js,false);
  563. }},
  564. { label: translate('No focus'), accelerator: 'Esc', click: ()=>{
  565. let js = `{let e=document.activeElement;
  566. if(e)e.blur();try{tabs.children[iTab].stopFindInPage('clearSelection')}catch(er){}}`;
  567. win.webContents.executeJavaScript(js,false);
  568. }},
  569. { label: translate('Reload'), accelerator: 'F5', click: ()=>{
  570. win.webContents.executeJavaScript("tabs.children[iTab].reload()",false);
  571. }},
  572. { label: translate('Devtools'), accelerator: 'F12', click: ()=>{
  573. let js = "try{tabs.children[iTab].openDevTools()}catch(e){console.log(e)}";
  574. win.webContents.executeJavaScript(js,false);
  575. }},
  576. ],
  577. },
  578. );
  579. const menu = Menu.buildFromTemplate(menuTemplate);
  580. Menu.setApplicationMenu(menu);
  581. }
  582. function cmdlineProcess(argv,cwd,extra){
  583. let i1st = 2+extra; //index for the first query item
  584. if(argv.length>i1st){
  585. if(i1st+1==argv.length){//local file
  586. let fname = path.join(cwd, argv[i1st]);
  587. if(fs.existsSync(fname)){
  588. let js = "tabs.children[iTab].src='file://"+fname+"'";
  589. win.webContents.executeJavaScript(js,false);
  590. win.setTitle(argv[i1st]);
  591. return;
  592. }
  593. }
  594. let url=argv.slice(i1st).join(" ");
  595. win.webContents.executeJavaScript("{let v=`"+url+"`;document.forms[0].q.value=v;handleQuery(v)}",false);
  596. win.setTitle(url);
  597. }
  598. }
  599. async function cbScheme_redir(req){
  600. if(!gredirect) return null;
  601. let oUrl = req.url;
  602. let newurl = gredirect+oUrl;
  603. const parsedUrl = url.parse(newurl);
  604. let headers = new Headers();
  605. for (var pair of req.headers.entries())
  606. headers.set(pair[0],pair[1]);
  607. if(bForwardCookie){
  608. let cookies = await session.defaultSession.cookies.get({url: oUrl});
  609. let cookieS = cookies.map (cookie => cookie.name + '=' + cookie.value ).join(';');
  610. headers.set('cookie',cookieS);
  611. }
  612. //missing referer header
  613. //headers.set('referer',);
  614. const options = {
  615. hostname: parsedUrl.hostname,
  616. port: parsedUrl.port,
  617. path: parsedUrl.path,
  618. method: req.method,
  619. headers: headers
  620. };
  621. return new Promise(async (resolve, reject) => {
  622. const nreq = https.request(options, (res) => {
  623. let body = [];
  624. res.on('data', (chunk) => {
  625. body.push(chunk);
  626. });
  627. res.on('end', () => {
  628. try {
  629. body = Buffer.concat(body);
  630. const response = new Response(body, res);
  631. resolve(response);
  632. } catch (e) {
  633. reject(e);
  634. }
  635. });
  636. });
  637. nreq.on('error', (err) => {
  638. reject(err);
  639. });
  640. if (req.body){
  641. try {
  642. const reader = req.body.getReader();
  643. do {
  644. const { done, value } = await reader.read();
  645. if (done) {
  646. nreq.end();
  647. break;
  648. }
  649. nreq.write(value);
  650. console.log(headers);
  651. console.log(new TextDecoder("iso-8859-1").decode(value));
  652. }while (true);
  653. }catch(e){reject(e)}
  654. }else
  655. nreq.end();
  656. });
  657. }
  658. function registerHandler(){
  659. protocol.handle("http",cbScheme_redir);
  660. protocol.handle("https",cbScheme_redir);
  661. protocol.handle("ws",cbScheme_redir);
  662. protocol.handle("wss",cbScheme_redir);
  663. }
  664. function unregisterHandler(){
  665. protocol.unhandle("http",cbScheme_redir);
  666. protocol.unhandle("https",cbScheme_redir);
  667. protocol.unhandle("ws",cbScheme_redir);
  668. protocol.unhandle("wss",cbScheme_redir);
  669. }
  670. function forwardCookie(){
  671. const choice = dialog.showMessageBoxSync(null, {
  672. type: 'warning',
  673. title: 'Confirm cookie forwarding with global redirection',
  674. message: 'Cookies are used to access your account. Forwarding cookies is vulnerable to global redirection server, proceed to enable cookie forwarding with global redirection?',
  675. buttons: ['No','Yes']
  676. })
  677. if(1===choice) bForwardCookie=true;
  678. }
  679. function msgbox_info(msg){
  680. dialog.showMessageBoxSync(null, {
  681. type: 'info',
  682. title: msg,
  683. message: msg,
  684. buttons: ['OK']
  685. })
  686. }
  687. async function updateApp(url){//url must ending with "/"
  688. let msg;
  689. do {
  690. try {
  691. let res = await fetch(url+"package.json");
  692. let packageS = await res.text();
  693. {//the last part of version string is the version number, must keep increasing
  694. let head = packageS.slice(2,40);
  695. let iV = head.indexOf("version");
  696. if(iV<0) {
  697. msg = "remote package.json corrupted"
  698. break;
  699. }
  700. iV = iV + 10;
  701. let iE = head.indexOf('"',iV+4);
  702. let iS = head.lastIndexOf('.',iE-1);
  703. let nLatestVer = parseInt(head.substring(iS+1,iE));
  704. let ver = app.getVersion();
  705. iS = ver.lastIndexOf('.');
  706. let nVer = parseInt(ver.substring(iS+1));
  707. if(nVer>=nLatestVer){
  708. msg = `Current version ${ver} is already up to date`;
  709. break;
  710. }
  711. const choice = dialog.showMessageBoxSync(null, {
  712. type: 'warning',
  713. title: `Update from ${url}`,
  714. message: `Proceed to update from ${ver} to ${head.substring(iV,iE)}?`,
  715. buttons: ['YES','NO']
  716. });
  717. if(1===choice) return;
  718. }
  719. writeFile("package.json", packageS);
  720. fetch2file(url,"webview.js");
  721. fetch2file(url,"index.html");
  722. msg = "Update completed";
  723. }catch(e){
  724. msg = "Fail to update"
  725. }
  726. }while(false);
  727. dialog.showMessageBoxSync(null, {
  728. type: 'info',
  729. title: `Update from ${url}`,
  730. message: msg,
  731. buttons: ['OK']
  732. })
  733. }
  734. async function fetch2file(urlFolder, filename, bOverwritten=true){
  735. let pathname=path.join(__dirname,filename);
  736. if(!bOverwritten && fs.existsSync(pathname)) return;
  737. let res = await fetch(urlFolder+filename);
  738. let str = await res.text();
  739. writeFile(pathname, str);
  740. }
  741. async function writeFile(filename, str){
  742. let pathname=filename+".new";
  743. fs.writeFile(pathname, str, (err) => {
  744. if(err) throw "Fail to write";
  745. fs.rename(pathname,filename,(e1)=>{
  746. if(e1) throw "Fail to rename";
  747. });
  748. });
  749. }
  750. function help(){
  751. const readme = "README.md";
  752. const htmlFN = path.join(__dirname,readme);
  753. let js=`{let t=tabs.children[iTab];t.dataset.jsonce=BML_md;t.src="file://${htmlFN}"}`;
  754. win.webContents.executeJavaScript(js,false)
  755. }
  756. function downloadContextMenuTemp(url){
  757. let mTemplate =
  758. [{label:url,enabled:false},
  759. {label: translate('Download'),
  760. click: () => {
  761. downloadMillis = Date.now();
  762. win.webContents.downloadURL(url);
  763. }
  764. },
  765. {
  766. label: translate('Copy'),
  767. click: () => {
  768. clipboard.writeText(url);
  769. }
  770. },
  771. ];
  772. menuDownload(mTemplate, "", url);
  773. return mTemplate;
  774. }
  775. async function initTranslateRes(lang){
  776. let basename=path.join(__dirname,"translate.");
  777. let fname = basename+lang;
  778. if(!fs.existsSync(fname))
  779. fname = basename+lang.slice(0,2);
  780. try {
  781. let json = await fs.promises.readFile(fname,'utf8');
  782. translateRes = JSON.parse(json);
  783. } catch (e){}
  784. topMenu();
  785. }
  786. function translate(str){
  787. let result;
  788. if(translateRes && (result=translateRes[str])) return result;
  789. return str;
  790. }
  791. function httpReq(url, method, filePath){
  792. fs.readFile(filePath, (err, fileData) => {
  793. if (err) {
  794. console.error(`Error reading file: ${err.message}`);
  795. return;
  796. }
  797. let opts = {
  798. method: method,
  799. headers: {
  800. "Content-Type":'application/octet-stream',
  801. },
  802. body: fileData,
  803. };
  804. fetch(url,opts);
  805. });
  806. }
  807. function bangcommand(q,offset){
  808. let iS = q.indexOf(' ',offset);
  809. if(iS<0) iS=q.length;
  810. let fname = q.substring(offset,iS);
  811. let fpath = path.join(__dirname,fname+'.js');
  812. if (fs.existsSync(fpath)) {
  813. fs.readFile(fpath, 'utf8',(err, js)=>{
  814. if (err) {
  815. console.log(err);
  816. return;
  817. }
  818. const prefix = "(function(){";
  819. const postfix = "})(`";
  820. const end ="`)";
  821. const fjs = `${prefix}${js}${postfix}${q}${end}`;
  822. eval(fjs);
  823. });
  824. }
  825. }