diff --git a/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json b/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json index d2b80d7..7a73a41 100644 --- a/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json +++ b/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json @@ -1,3 +1,2 @@ { - "cookies": [] } \ No newline at end of file diff --git a/packages/geek-auto-start-chat-with-boss/default-storage-file/boss-cookies.json b/packages/geek-auto-start-chat-with-boss/default-storage-file/boss-cookies.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/packages/geek-auto-start-chat-with-boss/default-storage-file/boss-cookies.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/geek-auto-start-chat-with-boss/default-storage-file/boss-local-storage.json b/packages/geek-auto-start-chat-with-boss/default-storage-file/boss-local-storage.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/packages/geek-auto-start-chat-with-boss/default-storage-file/boss-local-storage.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/geek-auto-start-chat-with-boss/index.mjs b/packages/geek-auto-start-chat-with-boss/index.mjs index 95eec78..c1758be 100644 --- a/packages/geek-auto-start-chat-with-boss/index.mjs +++ b/packages/geek-auto-start-chat-with-boss/index.mjs @@ -8,12 +8,16 @@ import os from 'node:os' import { get__dirname } from '@geekgeekrun/utils/legacy-path.mjs'; import path from 'node:path'; import JSON5 from 'json5' +import { EventEmitter } from 'node:events' +import { setDomainLocalStorage } from '@geekgeekrun/utils/puppeteer/local-storage.mjs' -import { readConfigFile, ensureConfigFileExist } from './runtime-file-utils.mjs' +import { readConfigFile, writeStorageFile, ensureConfigFileExist, readStorageFile, ensureStorageFileExist } from './runtime-file-utils.mjs' ensureConfigFileExist() +ensureStorageFileExist() const isRunFromUi = Boolean(process.env.MAIN_BOSSGEEKGO_UI_RUN_MODE) const isUiDev = process.env.NODE_ENV === 'development' +export const autoStartChatEventBus = new EventEmitter() let puppeteer, StealthPlugin export async function initPuppeteer () { @@ -43,12 +47,19 @@ export async function initPuppeteer () { StealthPlugin = importResult[1].default } puppeteer.use(StealthPlugin()) + + return { + puppeteer, + StealthPlugin + } } -const { cookies: bossCookies } = readConfigFile('boss.json') +const bossCookies = readStorageFile('boss-cookies.json') +const bossLocalStorage = readStorageFile('boss-local-storage.json') const targetCompanyList = readConfigFile('target-company-list.json') +const localStoragePageUrl = `https://www.zhipin.com/desktop/` const recommendJobPageUrl = `https://www.zhipin.com/web/geek/job-recommend` const expectCompanySet = new Set(targetCompanyList) @@ -79,18 +90,39 @@ export async function mainLoop (hooks) { }) //set cookies - const copiedBossCookies = JSON.parse(JSON.stringify(bossCookies)) - - hooks.cookieWillSet?.call(copiedBossCookies) - for(let i = 0; i < copiedBossCookies.length; i++){ - await page.setCookie(copiedBossCookies[i]); + hooks.cookieWillSet?.call(bossCookies) + for(let i = 0; i < bossCookies.length; i++){ + await page.setCookie(bossCookies[i]); } - + await setDomainLocalStorage(browser, localStoragePageUrl, bossLocalStorage) + + let userInfoResponse await Promise.all([ page.goto(recommendJobPageUrl, { timeout: 0 }), + page.waitForResponse((response) => { + if (response.url().startsWith('https://www.zhipin.com/wapi/zpuser/wap/getUserInfo.json')) { + return true + } + return false + }).then((res) => { + return res.json() + }).then((res) => { + userInfoResponse = res + }), page.waitForNavigation(), ]) hooks.pageLoaded?.call() + hooks.userInfoResponse?.call(userInfoResponse) + + if (userInfoResponse.code !== 0) { + autoStartChatEventBus.emit('LOGIN_STATUS_INVALID', { + userInfoResponse + }) + writeStorageFile('boss-cookies.json', []) + throw new Error("LOGIN_STATUS_INVALID") + } else { + await storeStorage(page).catch(() => void 0) + } const INIT_START_EXCEPT_JOB_INDEX = 1 let currentExceptJobIndex = INIT_START_EXCEPT_JOB_INDEX @@ -123,6 +155,7 @@ export async function mainLoop (hooks) { return false } ); + await storeStorage(page).catch(() => void 0) await sleepWithRandomDelay(2000) } @@ -242,6 +275,7 @@ export async function mainLoop (hooks) { if (res.code !== 0) { // startup chat error, may the chance of today has used out if (res.zpData.bizCode === 1 && res.zpData.bizData?.chatRemindDialog?.blockLevel === 0 && res.zpData.bizData?.chatRemindDialog?.content === `今日沟通人数已达上限,请明天再试`) { + await storeStorage(page).catch(() => void 0) throw new Error('STARTUP_CHAT_ERROR_DUE_TO_TODAY_CHANCE_HAS_USED_OUT') } else { console.error(res) @@ -250,7 +284,8 @@ export async function mainLoop (hooks) { } else { hooks.newChatStartup?.call(jobData) blockBossNotNewChat.add(jobData.jobInfo.encryptUserId) - + + await storeStorage(page).catch(() => void 0) await sleepWithRandomDelay(750) const closeDialogButtonProxy = await page.$('.greet-boss-dialog .greet-boss-footer .cancel-btn') await closeDialogButtonProxy.click() @@ -317,6 +352,23 @@ export async function mainLoop (hooks) { export async function closeBrowserWindow () { browser?.close() - browse = null + browser = null page = null } + +async function storeStorage (page) { + const [ + cookies, localStorage + ] = await Promise.all([ + page.cookies(), + page.evaluate(() => { + return JSON.stringify(window.localStorage) + }).then(res => JSON.parse(res)) + ]) + return Promise.all( + [ + writeStorageFile('boss-cookies.json', cookies), + writeStorageFile('boss-local-storage.json', localStorage), + ] + ) +} diff --git a/packages/geek-auto-start-chat-with-boss/runtime-file-utils.mjs b/packages/geek-auto-start-chat-with-boss/runtime-file-utils.mjs index 6336294..311a767 100644 --- a/packages/geek-auto-start-chat-with-boss/runtime-file-utils.mjs +++ b/packages/geek-auto-start-chat-with-boss/runtime-file-utils.mjs @@ -7,6 +7,8 @@ import defaultDingtalkConf from './default-config-file/dingtalk.json' assert {ty import defaultBossConf from './default-config-file/boss.json' assert {type: 'json'} import defaultTargetCompanyListConf from './default-config-file/target-company-list.json' assert {type: 'json'} +import defaultBossCookieStorage from './default-storage-file/boss-cookies.json' assert { type: 'json' } +import defaultBossLocalStorageStorage from './default-storage-file/boss-local-storage.json' assert { type: 'json' } export const configFileNameList = ['boss.json', 'dingtalk.json', 'target-company-list.json'] const defaultConfigFileContentMap = { @@ -20,7 +22,7 @@ const ensureRuntimeFolderPathExist = () => { if (!fs.existsSync(runtimeFolderPath)) { fs.mkdirSync(runtimeFolderPath) } - ;['config'].forEach(dirPath => { + ;['config', 'storage'].forEach(dirPath => { if (!fs.existsSync( path.join(runtimeFolderPath, dirPath) )) { @@ -52,8 +54,9 @@ export const ensureConfigFileExist = () => { } export const readConfigFile = (fileName) => { + const joinedPath = path.join(configFolderPath, fileName) if (!fs.existsSync( - path.join(configFolderPath, fileName) + joinedPath )) { ensureConfigFileExist() } @@ -61,10 +64,10 @@ export const readConfigFile = (fileName) => { let o try { o = JSON.parse( - fs.readFileSync(path.join(configFolderPath, fileName)) + fs.readFileSync(joinedPath) ) } catch { - fs.unlinkSync(fs.readFileSync(path.join(configFolderPath, fileName))) + fs.existsSync(joinedPath) && fs.unlinkSync(joinedPath) ensureConfigFileExist() o = JSON.parse(defaultConfigFileContentMap[fileName]) } @@ -81,3 +84,60 @@ export const writeConfigFile = async (fileName, content) => { ) } +export const storageFilePath = path.join( + runtimeFolderPath, + 'storage' +) +export const storageFileNameList = ['boss-cookies.json', 'boss-local-storage.json'] + +const defaultStorageFileContentMap = { + 'boss-cookies.json': JSON.stringify(defaultBossCookieStorage), + 'boss-local-storage.json': JSON.stringify(defaultBossLocalStorageStorage) +} +export const ensureStorageFileExist = () => { + ensureRuntimeFolderPathExist() + ;storageFileNameList.forEach( + fileName => { + if (!fs.existsSync( + path.join(storageFilePath, fileName) + )) { + fs.writeFileSync( + path.join(storageFilePath, fileName), + defaultStorageFileContentMap[fileName] + ) + } + } + ) +} + +export const readStorageFile = (fileName) => { + const joinedPath = path.join(storageFilePath, fileName) + + if (!fs.existsSync( + joinedPath + )) { + ensureStorageFileExist() + } + + let o + try { + o = JSON.parse( + fs.readFileSync(joinedPath) + ) + } catch { + fs.existsSync(joinedPath) && fs.unlinkSync(joinedPath) + ensureStorageFileExist() + o = JSON.parse(defaultStorageFileContentMap[fileName]) + } + + return o +} + +export const writeStorageFile = async (fileName, content) => { + const filePath = path.join(storageFilePath, fileName) + const fileContent = JSON.stringify(content) + return fsPromise.writeFile( + filePath, + fileContent + ) +} diff --git a/packages/launch-bosszhipin-login-page-with-preload-extension/extensions/EditThisCookie.zip b/packages/launch-bosszhipin-login-page-with-preload-extension/extensions/EditThisCookie.zip new file mode 100644 index 0000000..d817ee9 Binary files /dev/null and b/packages/launch-bosszhipin-login-page-with-preload-extension/extensions/EditThisCookie.zip differ diff --git a/packages/launch-bosszhipin-login-page-with-preload-extension/index.mjs b/packages/launch-bosszhipin-login-page-with-preload-extension/index.mjs new file mode 100644 index 0000000..7bad52f --- /dev/null +++ b/packages/launch-bosszhipin-login-page-with-preload-extension/index.mjs @@ -0,0 +1,162 @@ + +import { + initPuppeteer +} from '@geekgeekrun/geek-auto-start-chat-with-boss/index.mjs' +import { + sleep, + sleepWithRandomDelay +} from '@geekgeekrun/utils/sleep.mjs' +import extractZip from 'extract-zip' +import { blockNavigation } from '@geekgeekrun/utils/puppeteer/block-navigation.mjs' +import { + writeStorageFile +} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' + +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path'; +import JSON5 from 'json5' +import url from 'url'; +import packageJson from './package.json' assert {type: 'json'} + +import { EventEmitter } from 'node:events' + +export const loginEventBus = new EventEmitter() + +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) +const isRunFromUi = Boolean(process.env.MAIN_BOSSGEEKGO_UI_RUN_MODE) +const isUiDev = process.env.NODE_ENV === 'development' + +const runtimeFolderPath = path.join(os.homedir(), '.geekgeekrun') +const extensionDir = path.join( + runtimeFolderPath, + 'chrome-extensions' +) +if (!fs.existsSync( + runtimeFolderPath +)) { + fs.mkdirSync(runtimeFolderPath) +} +if (!fs.existsSync(extensionDir)) { + fs.mkdirSync(extensionDir) +} +const editThisCookieExtensionPath = path.join(extensionDir, 'EditThisCookie') + +let editThisCookieZipPath +async function getEditThisCookieZipPath () { + if (editThisCookieZipPath) { + return editThisCookieZipPath + } + if (isRunFromUi) { + const { app } = await import('electron') + editThisCookieZipPath = path.join(app.getAppPath(), './node_modules', packageJson.name, 'extensions', 'EditThisCookie.zip') + } else { + editThisCookieZipPath = path.join(__dirname, 'extensions', 'EditThisCookie.zip') + } + return editThisCookieZipPath +} + +export async function main() { + if (!fs.existsSync( + path.join(editThisCookieExtensionPath, 'manifest.json') + )) { + await extractZip( + await getEditThisCookieZipPath(), + { + dir: extensionDir + } + ) + } + + const { puppeteer } = await initPuppeteer() + const browser = await puppeteer.launch({ + headless: false, + args: [ + `--load-extension=${editThisCookieExtensionPath}` + ] + }) + + const closeAttachedSet = new WeakSet() + browser.on('targetcreated', async function closeNewTabs(target) { + let targetBrowser = target.browser(); + const pages = await targetBrowser.pages() + console.log(pages) + for (let i = 1; i < pages.length; i++) { + const page = pages[i] + if (!closeAttachedSet.has(page)) { + closeAttachedSet.add(page) + page.once('domcontentloaded', () => { + page.close() + }) + } + } + }) + + const [page] = await browser.pages(); + + page.once('close', async () => { + browser.close() + if (isRunFromUi) { + const electron = await import('electron') + electron.app.quit() + } + }) + + const { dispose: disposeNavigationLock } = await blockNavigation(page, (req) => !req.url().startsWith('https://www.zhipin.com')) + await page.goto('https://www.zhipin.com/web/user/'); + + const loginSuccessPromiseList = [ + page.waitForResponse( + (response) => + response.url().startsWith('https://www.zhipin.com/wapi/zppassport/qrcode/loginConfirm'), + { + timeout: 0 + } + ), + page.waitForResponse( + (response) => + response.url().startsWith('https://www.zhipin.com/wapi/zppassport/qrcode/dispatcher'), + { + timeout: 0 + } + ), + page.waitForResponse( + (response) => + response.url().startsWith('https://www.zhipin.com/wapi/zppassport/login/phoneV2'), + { timeout: 0 } + ) + ] + + Promise.all([ + Promise.race(loginSuccessPromiseList), + page.waitForNavigation({ + timeout: 0 + }), + ]).then(async () => { + await sleep(2000) + const headerLogoAnchorHandler = await page.$('.header-home-logo') + return Promise.all([ + headerLogoAnchorHandler ? headerLogoAnchorHandler.click() : page.goto('https://www.zhipin.com/'), + page.waitForNavigation({ + timeout: 0, + }) + ]) + }).then(async () => { + if ( + page.url().startsWith('https://www.zhipin.com/web/common/security-check.html') + ) { + await page.waitForNavigation({ + timeout: 0, + }) + } + await sleep(2000) + const cookies = await page.cookies() + loginEventBus.emit( + 'cookie-collected', + cookies + ) + return writeStorageFile('boss-cookies.json', cookies) + }).catch((err) => { + console.log(err) + }) +} diff --git a/packages/launch-bosszhipin-login-page-with-preload-extension/package-lock.json b/packages/launch-bosszhipin-login-page-with-preload-extension/package-lock.json new file mode 100644 index 0000000..dea2a94 --- /dev/null +++ b/packages/launch-bosszhipin-login-page-with-preload-extension/package-lock.json @@ -0,0 +1,351 @@ +{ + "name": "@geekgeekrun/launch-browser-with-preload-extension", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@geekgeekrun/launch-browser-with-preload-extension", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "node-fetch": "^3.3.2", + "unzipper": "^0.10.14" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmmirror.com/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmmirror.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmmirror.com/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "engines": { + "node": "*" + } + }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmmirror.com/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + } +} diff --git a/packages/launch-bosszhipin-login-page-with-preload-extension/package.json b/packages/launch-bosszhipin-login-page-with-preload-extension/package.json new file mode 100644 index 0000000..a9167c7 --- /dev/null +++ b/packages/launch-bosszhipin-login-page-with-preload-extension/package.json @@ -0,0 +1,17 @@ +{ + "name": "@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension", + "private": true, + "version": "1.0.0", + "description": "launch-bosszhipin-login-page-with-preload-extension", + "module": "./index.mjs", + "type": "module", + "scripts": {}, + "author": "geekgeekrun", + "license": "ISC", + "dependencies": { + "@geekgeekrun/geek-auto-start-chat-with-boss": "workspace:*", + "@geekgeekrun/utils": "workspace:*", + "extract-zip": "^2.0.1", + "node-fetch": "^3.3.2" + } +} diff --git a/packages/run-core-of-geek-auto-start-chat-with-boss/index.mjs b/packages/run-core-of-geek-auto-start-chat-with-boss/index.mjs index 464524d..0a21dba 100644 --- a/packages/run-core-of-geek-auto-start-chat-with-boss/index.mjs +++ b/packages/run-core-of-geek-auto-start-chat-with-boss/index.mjs @@ -8,8 +8,8 @@ import fs from 'node:fs' import path from 'node:path' import { get__dirname } from '@geekgeekrun/utils/legacy-path.mjs'; import JSON5 from 'json5' -import { readConfigFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' -const { cookies: bossCookies } = readConfigFile('boss.json') +import { readConfigFile, readStorageFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' +const bossCookies = readStorageFile('boss-cookies.json') const { groupRobotAccessToken: dingTalkAccessToken } = readConfigFile('dingtalk.json') const initPlugins = (hooks) => { diff --git a/packages/ui/electron-builder.yml b/packages/ui/electron-builder.yml index 5db0d74..599db3f 100644 --- a/packages/ui/electron-builder.yml +++ b/packages/ui/electron-builder.yml @@ -10,7 +10,8 @@ files: - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' - '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}' asarUnpack: - - resources/** + - 'resources/**' + - 'node_modules/@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension/**' extraResources: - external-node-runtime-dependencies/** win: diff --git a/packages/ui/electron.vite.config.ts b/packages/ui/electron.vite.config.ts index 5145cf8..2209bb8 100644 --- a/packages/ui/electron.vite.config.ts +++ b/packages/ui/electron.vite.config.ts @@ -14,7 +14,7 @@ export default defineConfig({ }, plugins: [ externalizeDepsPlugin({ - exclude: ['@geekgeekrun/geek-auto-start-chat-with-boss', '@geekgeekrun/dingtalk-plugin', '@geekgeekrun/utils', 'find-chrome-bin'] + exclude: ['@geekgeekrun/geek-auto-start-chat-with-boss', '@geekgeekrun/dingtalk-plugin', '@geekgeekrun/utils', 'find-chrome-bin', '@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension'] }) ] }, diff --git a/packages/ui/package.json b/packages/ui/package.json index d688793..4e163c9 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -9,6 +9,7 @@ "dev": "electron-vite dev", "dev:geek-auto-start-chat-with-boss-only": "cross-env MAIN_BOSSGEEKGO_UI_RUN_MODE=geekAutoStartWithBoss electron-vite dev", "dev:check-and-download-dependencies-for-init-only": "cross-env MAIN_BOSSGEEKGO_UI_RUN_MODE=checkAndDownloadDependenciesForInit electron-vite dev", + "dev:launch-bosszhipin-login-page-with-preload-extension-only": "cross-env MAIN_BOSSGEEKGO_UI_RUN_MODE=launchBossZhipinLoginPageWithPreloadExtension electron-vite dev", "build": "electron-vite build", "format": "prettier --write .", "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix", @@ -19,13 +20,15 @@ "build:unpack": "npm run build && electron-builder --dir", "build:win": "npm run build && electron-builder --win", "build:mac": "npm run build && electron-builder --mac", - "build:linux": "npm run build && electron-builder --linux" + "build:linux": "npm run build && electron-builder --linux", + "install": "cd ./external-node-runtime-dependencies && cross-env PUPPETEER_SKIP_DOWNLOAD=geekAutoStartWithBoss npm install" }, "dependencies": { "@electron-toolkit/preload": "^3.0.0", "@electron-toolkit/utils": "^3.0.0", "@geekgeekrun/dingtalk-plugin": "workspace:*", "@geekgeekrun/geek-auto-start-chat-with-boss": "workspace:*", + "@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension": "workspace:*", "@geekgeekrun/utils": "workspace:*", "@puppeteer/browsers": "^2.0.0", "JSONStream": "^1.3.5", diff --git a/packages/ui/src/common/utils/cookie.ts b/packages/ui/src/common/utils/cookie.ts new file mode 100644 index 0000000..e3f0d3e --- /dev/null +++ b/packages/ui/src/common/utils/cookie.ts @@ -0,0 +1,27 @@ +export const checkCookieListFormat = (cookies: Array>) => { + const allExpectKeySet = new Set([ + 'name', + 'value', + 'domain', + 'path', + 'secure', + 'session', + 'httpOnly' + ]) + return Array.isArray(cookies) && + cookies.length && + cookies.every((it) => { + const currentOwnedKeySet = new Set(Object.keys(it)) + if (currentOwnedKeySet.size < allExpectKeySet.size) { + return false + } + + const allExpectKeyArr = [...allExpectKeySet] + for (let i = 0; i < allExpectKeyArr.length; i++) { + if (!currentOwnedKeySet.has(allExpectKeyArr[i])) { + return false + } + } + return true + }) +} diff --git a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS.ts b/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS.ts index 660b5cf..ede306d 100644 --- a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS.ts +++ b/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS.ts @@ -15,7 +15,7 @@ const initPlugins = (hooks) => { let isParentProcessDisconnect = false export const runAutoChat = async () => { - const { initPuppeteer, mainLoop, closeBrowserWindow } = await import( + const { initPuppeteer, mainLoop, closeBrowserWindow, autoStartChatEventBus } = await import( '@geekgeekrun/geek-auto-start-chat-with-boss/index.mjs' ) process.on('disconnect', () => { @@ -73,16 +73,27 @@ export const runAutoChat = async () => { type: 'GEEK_AUTO_START_CHAT_WITH_BOSS_STARTED' //geek-auto-start-chat-with-boss-started }) + '\r\n' ) - while (!([isParentProcessDisconnect].includes(true))) { + + autoStartChatEventBus.once('LOGIN_STATUS_INVALID', () => { + pipeWriteRegardlessError( + pipe, + JSON.stringify({ + type: 'LOGIN_STATUS_INVALID' //geek-auto-start-chat-with-boss-started + }) + '\r\n' + ) + }) + + while (![isParentProcessDisconnect].includes(true)) { try { await mainLoop(hooks) } catch (err) { console.log(err) - // if(err instanceof Error && err.message.includes('ERR_MODULE_NOT_FOUND')) { - // throw err - // } else { - void err - // } + if (err instanceof Error && err.message.includes('LOGIN_STATUS_INVALID')) { + process.exit(2) + break + } else { + throw err + } } } closeBrowserWindow() diff --git a/packages/ui/src/main/flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION.ts b/packages/ui/src/main/flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION.ts new file mode 100644 index 0000000..6a0d63b --- /dev/null +++ b/packages/ui/src/main/flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION.ts @@ -0,0 +1,51 @@ +import { app } from 'electron' +import { main, loginEventBus } from '@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension' +import { pipeWriteRegardlessError } from './utils/pipe' +import fs from "node:fs"; + +export enum DOWNLOAD_ERROR_EXIT_CODE { + NO_ERROR = 0, + DOWNLOAD_ERROR = 1 +} + +export const launchBossZhipinLoginPageWithPreloadExtension = async () => { + process.on('disconnect', () => app.exit()) + app.dock?.hide() + let pipe: null | fs.WriteStream = null + try { + pipe = fs.createWriteStream(null, { fd: 3 }) + } catch { + console.warn('pipe is not available') + } + pipeWriteRegardlessError( + pipe, + JSON.stringify({ + type: 'INITIALIZE_PUPPETEER' + }) + '\r\n' + ) + const { initPuppeteer } = await import('@geekgeekrun/geek-auto-start-chat-with-boss/index.mjs') + try { + await initPuppeteer() + pipeWriteRegardlessError( + pipe, + JSON.stringify({ + type: 'PUPPETEER_INITIALIZE_SUCCESSFULLY' + }) + '\r\n' + ) + } catch (err) { + console.error(err) + app.exit(1) + return + } + + loginEventBus.once('cookie-collected', (cookies) => { + pipeWriteRegardlessError( + pipe, + JSON.stringify({ + type: 'BOSS_ZHIPIN_COOKIE_COLLECTED', + cookies + }) + '\r\n' + ) + }) + main() +} diff --git a/packages/ui/src/main/index.ts b/packages/ui/src/main/index.ts index 9e0bc9b..1b52921 100644 --- a/packages/ui/src/main/index.ts +++ b/packages/ui/src/main/index.ts @@ -1,6 +1,7 @@ import { runAutoChat } from './flow/GEEK_AUTO_START_CHAT_WITH_BOSS' import { openSettingWindow } from './flow/OPEN_SETTING_WINDOW' -import { checkAndDownloadDependenciesForInit } from './flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index'; +import { checkAndDownloadDependenciesForInit } from './flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index' +import { launchBossZhipinLoginPageWithPreloadExtension } from './flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION' const runMode = process.env.MAIN_BOSSGEEKGO_UI_RUN_MODE switch (runMode) { @@ -12,6 +13,10 @@ switch (runMode) { checkAndDownloadDependenciesForInit() break } + case 'launchBossZhipinLoginPageWithPreloadExtension': { + launchBossZhipinLoginPageWithPreloadExtension() + break + } default: { openSettingWindow() break diff --git a/packages/ui/src/main/window/mainWindow.ts b/packages/ui/src/main/window/mainWindow.ts index 7923075..61259ce 100644 --- a/packages/ui/src/main/window/mainWindow.ts +++ b/packages/ui/src/main/window/mainWindow.ts @@ -3,17 +3,23 @@ import path from 'path' import * as childProcess from 'node:child_process' import { is } from '@electron-toolkit/utils' import { - readConfigFile, - configFileNameList, ensureConfigFileExist, - writeConfigFile + ensureStorageFileExist, + + configFileNameList, + readConfigFile, + writeConfigFile, + readStorageFile, + writeStorageFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' import { ChildProcess } from 'child_process' import * as JSONStream from 'JSONStream' +import { checkCookieListFormat } from '../../common/utils/cookie' import { DOWNLOAD_ERROR_EXIT_CODE, getAnyAvailablePuppeteerExecutable } from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES' +import { sleep } from '@geekgeekrun/utils/sleep.mjs' let mainWindow: BrowserWindow | null = null export function createMainWindow(): void { @@ -25,8 +31,8 @@ export function createMainWindow(): void { autoHideMenuBar: true, ...(process.platform === 'linux' ? { - /* icon */ - } + /* icon */ + } : {}), webPreferences: { preload: path.join(__dirname, '../preload/index.js'), @@ -53,15 +59,24 @@ export function createMainWindow(): void { mainWindow.loadFile(path.join(__dirname, '../renderer/index.html')) } + ipcMain.on('open-external-link', (_, link) => { + shell.openExternal(link, { + activate: true + }) + }) + ipcMain.handle('fetch-config-file-content', async () => { - const fileContentList = configFileNameList.map((fileName) => { + const configFileContentList = configFileNameList.map((fileName) => { return readConfigFile(fileName) }) - const result = {} + const result = { + config: {}, + } configFileNameList.forEach((fileName, index) => { - result[fileName] = fileContentList[index] + result.config[fileName] = configFileContentList[index] }) + return result }) @@ -72,16 +87,23 @@ export function createMainWindow(): void { const dingtalkConfig = readConfigFile('dingtalk.json') dingtalkConfig.groupRobotAccessToken = payload.dingtalkRobotAccessToken - const bossZhipinConfig = readConfigFile('boss.json') - bossZhipinConfig.cookies = JSON.parse(payload.bossZhipinCookies) - return await Promise.all([ - writeConfigFile('boss.json', bossZhipinConfig), writeConfigFile('dingtalk.json', dingtalkConfig), writeConfigFile('target-company-list.json', payload.expectCompanies.split(',')) ]) }) + ipcMain.handle('read-storage-file', async (ev, payload) => { + ensureStorageFileExist() + return await readStorageFile(payload.fileName) + }) + + ipcMain.handle('write-storage-file', async (ev, payload) => { + ensureStorageFileExist() + + return await writeStorageFile(payload.fileName, JSON.parse(payload.data)) + }) + // const currentExecutablePath = app.getPath('exe') // console.log(currentExecutablePath) @@ -102,13 +124,18 @@ export function createMainWindow(): void { }) console.log(subProcessOfPuppeteer) return new Promise((resolve, reject) => { - subProcessOfPuppeteer!.stdio[3]!.pipe(JSONStream.parse()).on('data', (raw) => { + subProcessOfPuppeteer!.stdio[3]!.pipe(JSONStream.parse()).on('data', async (raw) => { const data = raw switch (data.type) { case 'GEEK_AUTO_START_CHAT_WITH_BOSS_STARTED': { resolve(data) break } + case 'LOGIN_STATUS_INVALID': { + await sleep(500) + mainWindow?.webContents.send('check-boss-zhipin-cookie-file') + return + } default: { return } @@ -195,11 +222,62 @@ export function createMainWindow(): void { mainWindow?.webContents.send('geek-auto-start-chat-with-boss-stopping') subProcessOfPuppeteer?.kill('SIGINT') }) - ipcMain.on('open-project-homepage-on-github', () => { - shell.openExternal(`https://github.com/geekgeekrun`, { - activate: true + + let subProcessOfBossZhipinLoginPageWithPreloadExtension: ChildProcess | null = null + ipcMain.on('launch-bosszhipin-login-page-with-preload-extension', async () => { + try { + subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill() + } catch { + // + } + const subProcessEnv = { + ...process.env, + MAIN_BOSSGEEKGO_UI_RUN_MODE: 'launchBossZhipinLoginPageWithPreloadExtension', + PUPPETEER_EXECUTABLE_PATH: (await getAnyAvailablePuppeteerExecutable())!.executablePath + } + subProcessOfBossZhipinLoginPageWithPreloadExtension = childProcess.spawn( + process.argv[0], + process.argv.slice(1), + { + env: subProcessEnv, + stdio: [null, null, null, 'pipe', 'ipc'] + } + ) + subProcessOfBossZhipinLoginPageWithPreloadExtension!.stdio[3]!.pipe(JSONStream.parse()).on( + 'data', + (raw) => { + const data = raw + switch (data.type) { + case 'BOSS_ZHIPIN_COOKIE_COLLECTED': { + mainWindow?.webContents.send(data.type, data) + break + } + default: { + return + } + } + } + ) + + subProcessOfBossZhipinLoginPageWithPreloadExtension!.once('exit', () => { + mainWindow?.webContents.send('BOSS_ZHIPIN_LOGIN_PAGE_CLOSED') + subProcessOfBossZhipinLoginPageWithPreloadExtension = null }) }) + ipcMain.on('kill-bosszhipin-login-page-with-preload-extension', async () => { + try { + subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill() + } catch { + // + } finally { + subProcessOfBossZhipinLoginPageWithPreloadExtension = null + } + }) + + ipcMain.handle('check-boss-zhipin-cookie-file', () => { + const cookies = readStorageFile('boss-cookies.json') + return checkCookieListFormat(cookies) + }) mainWindow!.once('closed', () => { mainWindow = null diff --git a/packages/ui/src/renderer/src/features/DependenciesSetupProgressIndicatorDialog/index.vue b/packages/ui/src/renderer/src/features/DependenciesSetupProgressIndicatorDialog/index.vue index 12c7645..64eddb8 100644 --- a/packages/ui/src/renderer/src/features/DependenciesSetupProgressIndicatorDialog/index.vue +++ b/packages/ui/src/renderer/src/features/DependenciesSetupProgressIndicatorDialog/index.vue @@ -26,7 +26,8 @@ const props = defineProps({ dependenciesStatus: { type: Object as PropType>, default: () => ({}) - } + }, + processWaitee: Object }) // shallow copy @@ -75,6 +76,7 @@ const processTasks = async () => { try { p.then(() => { if (!promiseList.length) { + props.processWaitee?.resolve?.() props.dispose?.() } }) diff --git a/packages/ui/src/renderer/src/features/DependenciesSetupProgressIndicatorDialog/operations.ts b/packages/ui/src/renderer/src/features/DependenciesSetupProgressIndicatorDialog/operations.ts index 23e49c1..4ecbed1 100644 --- a/packages/ui/src/renderer/src/features/DependenciesSetupProgressIndicatorDialog/operations.ts +++ b/packages/ui/src/renderer/src/features/DependenciesSetupProgressIndicatorDialog/operations.ts @@ -2,7 +2,7 @@ import { createApp } from 'vue' import ElementPlus from 'element-plus' import DependenciesSetupProgressIndicatorDialog from './index.vue' -export const mountGlobalDialog = (dependenciesStatus: Record) => { +export const mountGlobalDialog = (o: { dependenciesStatus: Record, processWaitee? }) => { const containerElId = `elForDependenciesSetupProgressIndicatorDialog` if (document.getElementById(containerElId)) { @@ -28,7 +28,8 @@ export const mountGlobalDialog = (dependenciesStatus: Record) = dispose() }, dispose, - dependenciesStatus + dependenciesStatus: o?.dependenciesStatus, + processWaitee: o?.processWaitee }).use(ElementPlus) app.mount(containerEl) diff --git a/packages/ui/src/renderer/src/features/WaitForLoginDialog/index.vue b/packages/ui/src/renderer/src/features/WaitForLoginDialog/index.vue new file mode 100644 index 0000000..040f7f2 --- /dev/null +++ b/packages/ui/src/renderer/src/features/WaitForLoginDialog/index.vue @@ -0,0 +1,245 @@ + + + + + diff --git a/packages/ui/src/renderer/src/features/WaitForLoginDialog/operations.ts b/packages/ui/src/renderer/src/features/WaitForLoginDialog/operations.ts new file mode 100644 index 0000000..98f773b --- /dev/null +++ b/packages/ui/src/renderer/src/features/WaitForLoginDialog/operations.ts @@ -0,0 +1,38 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import WaitForLogin from './index.vue' + +export const mountGlobalDialog = (o: { processWaitee? }) => { + const containerElId = `elForWaitForLogin` + + if (document.getElementById(containerElId)) { + return + } + let containerEl: null | HTMLElement = (() => { + const el = document.createElement('div') + el.id = containerElId + return el + })() + document.body.append(containerEl) + + const dispose = () => { + app?.unmount() + containerEl?.remove() + + app = null + containerEl = null + } + let app: null | ReturnType = createApp(WaitForLogin, { + modelValue: true, + onClosed() { + dispose() + }, + dispose, + processWaitee: o?.processWaitee + }).use(ElementPlus) + app.mount(containerEl) + + return { + dispose + } +} diff --git a/packages/ui/src/renderer/src/features/WaitForLoginDialog/resources/copy-cookie-step-1.png b/packages/ui/src/renderer/src/features/WaitForLoginDialog/resources/copy-cookie-step-1.png new file mode 100644 index 0000000..a6fd53d Binary files /dev/null and b/packages/ui/src/renderer/src/features/WaitForLoginDialog/resources/copy-cookie-step-1.png differ diff --git a/packages/ui/src/renderer/src/features/WaitForLoginDialog/resources/copy-cookie-step-2.png b/packages/ui/src/renderer/src/features/WaitForLoginDialog/resources/copy-cookie-step-2.png new file mode 100644 index 0000000..914da86 Binary files /dev/null and b/packages/ui/src/renderer/src/features/WaitForLoginDialog/resources/copy-cookie-step-2.png differ diff --git a/packages/ui/src/renderer/src/page/Configuration/GeekAutoStartChatWithBoss.vue b/packages/ui/src/renderer/src/page/Configuration/GeekAutoStartChatWithBoss.vue index d1fc705..15ab35a 100644 --- a/packages/ui/src/renderer/src/page/Configuration/GeekAutoStartChatWithBoss.vue +++ b/packages/ui/src/renderer/src/page/Configuration/GeekAutoStartChatWithBoss.vue @@ -1,15 +1,10 @@