diff --git a/packages/ui/src/common/prerequisite-step-by-step-check.ts b/packages/ui/src/common/prerequisite-step-by-step-check.ts
index b36d9ca..1442ceb 100644
--- a/packages/ui/src/common/prerequisite-step-by-step-check.ts
+++ b/packages/ui/src/common/prerequisite-step-by-step-check.ts
@@ -1,17 +1,18 @@
-export const getAutoStartChatSteps = () => [{
- id: 'worker-launch',
- describe: '启动子进程',
-},
-// {
-// id: 'basic-cookie-check',
-// describe: 'Cookie 格式检查',
-// },
-{
- id: 'puppeteer-executable-check',
- describe: 'Puppeteer 可执行程序检查',
-},
-{
- id: 'login-status-check',
- describe: '登录状态检查',
-}
-]
\ No newline at end of file
+export const getAutoStartChatSteps = () => [
+ {
+ id: 'worker-launch',
+ describe: '启动子进程'
+ },
+ {
+ id: 'puppeteer-executable-check',
+ describe: 'Puppeteer 可执行程序检查'
+ },
+ {
+ id: 'basic-cookie-check',
+ describe: 'Cookie 格式检查'
+ },
+ {
+ id: 'login-status-check',
+ describe: '登录状态检查'
+ }
+]
diff --git a/packages/ui/src/main/features/login-with-cookie-assistant.ts b/packages/ui/src/main/features/login-with-cookie-assistant.ts
new file mode 100644
index 0000000..03cb8b5
--- /dev/null
+++ b/packages/ui/src/main/features/login-with-cookie-assistant.ts
@@ -0,0 +1,21 @@
+import { ipcMain } from 'electron'
+import { createCookieAssistantWindow, cookieAssistantWindow } from '../window/cookieAssistantWindow';
+
+export async function loginWithCookieAssistant({ windowOption } = {}) {
+ return new Promise((resolve, reject) => {
+ createCookieAssistantWindow({ ...windowOption })
+
+ let processDone = false
+ ipcMain.once('cookie-saved', function handler() {
+ processDone = true
+ cookieAssistantWindow.close()
+ })
+ cookieAssistantWindow.once('closed', () => {
+ if (processDone) {
+ resolve(true)
+ } else {
+ reject(new Error('User cancelled login'))
+ }
+ })
+ })
+}
diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts
index cc11b4e..d94bcdf 100644
--- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts
+++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts
@@ -3,12 +3,10 @@ import path from 'path'
import * as childProcess from 'node:child_process'
import {
ensureConfigFileExist,
- ensureStorageFileExist,
configFileNameList,
readConfigFile,
writeConfigFile,
readStorageFile,
- writeStorageFile,
storageFilePath
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import { ChildProcess } from 'child_process'
@@ -54,6 +52,7 @@ import { checkUpdateForUi } from '../../../features/updater'
import gtag from '../../../utils/gtag'
import { daemonEE, sendToDaemon } from '../connect-to-daemon'
import { runCommon } from '../../../features/run-common'
+import { loginWithCookieAssistant } from '../../../features/login-with-cookie-assistant'
export default function initIpc() {
ipcMain.handle('fetch-config-file-content', async () => {
@@ -189,17 +188,6 @@ export default function initIpc() {
return await Promise.all(promiseArr)
})
- 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))
- })
-
ipcMain.handle('run-geek-auto-start-chat-with-boss', async (ev) => {
const mode = 'geekAutoStartWithBossMain'
const { runRecordId } = await runCommon({ mode })
@@ -395,56 +383,6 @@ export default function initIpc() {
)
})
- let subProcessOfBossZhipinLoginPageWithPreloadExtension: ChildProcess | null = null
- ipcMain.on('launch-bosszhipin-login-page-with-preload-extension', async () => {
- try {
- subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill()
- } catch {
- //
- }
- const subProcessEnv = {
- ...process.env,
- PUPPETEER_EXECUTABLE_PATH: (await getAnyAvailablePuppeteerExecutable())!.executablePath
- }
- subProcessOfBossZhipinLoginPageWithPreloadExtension = childProcess.spawn(
- process.argv[0],
- [process.argv[1], `--mode=launchBossZhipinLoginPageWithPreloadExtension`],
- {
- 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)
@@ -666,6 +604,15 @@ export default function initIpc() {
const newRelease = await checkUpdateForUi()
return newRelease
})
+ ipcMain.handle('login-with-cookie-assistant', async () => {
+ return await loginWithCookieAssistant({
+ windowOption: {
+ parent: mainWindow!,
+ modal: true,
+ show: true
+ }
+ })
+ })
ipcMain.handle('exit-app-immediately', () => {
app.exit(0)
diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/bootstrap.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/bootstrap.ts
index 0b5ef97..1f3f7ce 100644
--- a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/bootstrap.ts
+++ b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/bootstrap.ts
@@ -7,8 +7,6 @@ import { setDomainLocalStorage } from '@geekgeekrun/utils/puppeteer/local-storag
const localStoragePageUrl = `https://www.zhipin.com/desktop/`
const bossChatUiUrl = `https://www.zhipin.com/web/geek/chat`
-const bossCookies = readStorageFile('boss-cookies.json')
-const bossLocalStorage = readStorageFile('boss-local-storage.json')
export async function bootstrap() {
const { puppeteer } = await initPuppeteer()
@@ -28,6 +26,8 @@ export async function bootstrap() {
export async function launchBoss(browser: Browser) {
const page = (await browser.pages())[0]
+ const bossCookies = readStorageFile('boss-cookies.json')
+ const bossLocalStorage = readStorageFile('boss-local-storage.json')
//set cookies
for (let i = 0; i < bossCookies.length; i++) {
await page.setCookie(bossCookies[i])
diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts
index 7655b8d..b7dcdac 100644
--- a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts
+++ b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts
@@ -16,19 +16,29 @@ import {
getJobHireStatusRecord,
saveJobHireStatusRecord
} from '@geekgeekrun/sqlite-plugin/dist/handlers'
-import { writeStorageFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
+import {
+ writeStorageFile,
+ readStorageFile
+} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import { BossInfo } from '@geekgeekrun/sqlite-plugin/dist/entity/BossInfo'
import { messageForSaveFilter } from '../../../common/utils/chat-list'
-import { AUTO_CHAT_ERROR_EXIT_CODE, RECHAT_CONTENT_SOURCE, RECHAT_LLM_FALLBACK } from '../../../common/enums/auto-start-chat'
+import {
+ AUTO_CHAT_ERROR_EXIT_CODE,
+ RECHAT_CONTENT_SOURCE,
+ RECHAT_LLM_FALLBACK
+} from '../../../common/enums/auto-start-chat'
import gtag from '../../utils/gtag'
import { JobHireStatus } from '@geekgeekrun/sqlite-plugin/dist/enums';
import dayjs from 'dayjs'
import cheerio from 'cheerio'
import { connectToDaemon, sendToDaemon } from '../OPEN_SETTING_WINDOW/connect-to-daemon'
-import { pushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS } from '../../utils/screenshot'
+// import { pushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS } from '../../utils/screenshot'
import { checkShouldExit } from '../../utils/worker'
import { getAnyAvailablePuppeteerExecutable } from '../CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable'
import minimist from 'minimist'
+import { checkCookieListFormat } from '../../../common/utils/cookie'
+import { loginWithCookieAssistant } from '../../features/login-with-cookie-assistant'
+import initPublicIpc from '../../utils/initPublicIpc'
const throttleIntervalMinutes =
readConfigFile('boss.json').autoReminder?.throttleIntervalMinutes ?? 10
@@ -232,6 +242,39 @@ const mainLoop = async () => {
browser = null
}
}
+ let bossCookies = readStorageFile('boss-cookies.json')
+ let cookieCheckResult = checkCookieListFormat(bossCookies)
+ while (!cookieCheckResult) {
+ try {
+ await loginWithCookieAssistant()
+ bossCookies = readStorageFile('boss-cookies.json')
+ cookieCheckResult = checkCookieListFormat(bossCookies)
+ } catch (err) {
+ sendToDaemon({
+ type: 'worker-to-gui-message',
+ data: {
+ type: 'prerequisite-step-by-step-checkstep-by-step-check',
+ step: {
+ id: 'basic-cookie-check',
+ status: 'rejected'
+ },
+ runRecordId
+ }
+ })
+ throw new Error('LOGIN_STATUS_INVALID')
+ }
+ }
+ sendToDaemon({
+ type: 'worker-to-gui-message',
+ data: {
+ type: 'prerequisite-step-by-step-checkstep-by-step-check',
+ step: {
+ id: 'basic-cookie-check',
+ status: 'fulfilled'
+ },
+ runRecordId
+ }
+ })
const canNotConfirmIfHasReadMsgTemplateList = [
'Boss还没查看你的消息',
'你与该职位竞争者PK情况',
@@ -250,17 +293,6 @@ const mainLoop = async () => {
// #region
if (currentPageUrl.startsWith('https://www.zhipin.com/web/user/')) {
writeStorageFile('boss-cookies.json', [])
- sendToDaemon({
- type: 'worker-to-gui-message',
- data: {
- type: 'prerequisite-step-by-step-checkstep-by-step-check',
- step: {
- id: 'login-status-check',
- status: 'rejected'
- },
- runRecordId
- }
- })
throw new Error('LOGIN_STATUS_INVALID')
}
if (
@@ -526,6 +558,8 @@ const rerunInterval = (() => {
const runRecordId = minimist(process.argv.slice(2))['run-record-id'] ?? null
export async function runEntry() {
app.dock?.hide()
+ await app.whenReady()
+ initPublicIpc()
await connectToDaemon()
await sendToDaemon({
type: 'ping'
@@ -588,13 +622,29 @@ export async function runEntry() {
// handle error
if (err instanceof Error) {
if (err.message.includes('LOGIN_STATUS_INVALID')) {
- await dialog.showMessageBox({
- type: `error`,
- message: `登录状态无效`,
- detail: `请重新登录Boss直聘`
- })
- process.exit(AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID)
- break
+ try {
+ // popup login dialog, then update login status
+ await loginWithCookieAssistant()
+ } catch (err) {
+ await dialog.showMessageBox({
+ type: `error`,
+ message: `登录状态无效`,
+ detail: `请重新登录Boss直聘`
+ })
+ sendToDaemon({
+ type: 'worker-to-gui-message',
+ data: {
+ type: 'prerequisite-step-by-step-checkstep-by-step-check',
+ step: {
+ id: 'login-status-check',
+ status: 'rejected'
+ },
+ runRecordId
+ }
+ })
+ process.exit(AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID)
+ break
+ }
}
if (err.message.includes('ERR_INTERNET_DISCONNECTED')) {
process.exit(AUTO_CHAT_ERROR_EXIT_CODE.ERR_INTERNET_DISCONNECTED)
@@ -656,3 +706,8 @@ async function storeStorage(page) {
writeStorageFile('boss-local-storage.json', localStorage)
])
}
+
+process.on('SIGTERM', () => {
+ console.log('收到SIGTERM信号,正在退出')
+ process.exit(0)
+})
diff --git a/packages/ui/src/main/utils/initPublicIpc.ts b/packages/ui/src/main/utils/initPublicIpc.ts
index 94a75aa..f27496d 100644
--- a/packages/ui/src/main/utils/initPublicIpc.ts
+++ b/packages/ui/src/main/utils/initPublicIpc.ts
@@ -2,6 +2,11 @@ import { BrowserWindow, ipcMain, shell } from 'electron'
import gtag from './gtag'
import buildInfo from '../../common/build-info.json'
import os from 'node:os'
+import {
+ ensureStorageFileExist,
+ readStorageFile,
+ writeStorageFile
+} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
export default function initPublicIpc() {
ipcMain.on(
@@ -56,4 +61,14 @@ export default function initPublicIpc() {
}
)
})
+
+ 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))
+ })
}
diff --git a/packages/ui/src/main/window/cookieAssistantWindow.ts b/packages/ui/src/main/window/cookieAssistantWindow.ts
new file mode 100644
index 0000000..dc927b7
--- /dev/null
+++ b/packages/ui/src/main/window/cookieAssistantWindow.ts
@@ -0,0 +1,106 @@
+import { ChildProcess } from 'child_process'
+import { BrowserWindow, ipcMain } from 'electron'
+import path from 'path'
+import { getAnyAvailablePuppeteerExecutable } from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable'
+import * as childProcess from 'node:child_process'
+import * as JSONStream from 'JSONStream'
+
+export let cookieAssistantWindow: BrowserWindow | null = null
+export function createCookieAssistantWindow(
+ opt?: Electron.BrowserWindowConstructorOptions
+): BrowserWindow {
+ // Create the browser window.
+ if (cookieAssistantWindow) {
+ cookieAssistantWindow!.show()
+ }
+ cookieAssistantWindow = new BrowserWindow({
+ width: 960,
+ height: 720,
+ resizable: true,
+ show: false,
+ autoHideMenuBar: true,
+ webPreferences: {
+ preload: path.join(__dirname, '../preload/index.js'),
+ sandbox: false
+ },
+ ...opt
+ })
+
+ cookieAssistantWindow.on('ready-to-show', () => {
+ cookieAssistantWindow!.show()
+ })
+
+ // HMR for renderer base on electron-vite cli.
+ // Load the remote URL for development or the local html file for production.
+ if (process.env.NODE_ENV === 'development' && process.env['ELECTRON_RENDERER_URL']) {
+ cookieAssistantWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '#/cookieAssistant')
+ } else {
+ cookieAssistantWindow.loadURL(
+ 'file://' + path.join(__dirname, '../renderer/index.html') + '#/cookieAssistant'
+ )
+ }
+
+ cookieAssistantWindow!.once('closed', () => {
+ cookieAssistantWindow = null
+ })
+
+ let subProcessOfBossZhipinLoginPageWithPreloadExtension: ChildProcess | null = null
+ const launchHandler = async () => {
+ try {
+ subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill()
+ } catch {
+ //
+ }
+ const subProcessEnv = {
+ ...process.env,
+ PUPPETEER_EXECUTABLE_PATH: (await getAnyAvailablePuppeteerExecutable())!.executablePath
+ }
+ subProcessOfBossZhipinLoginPageWithPreloadExtension = childProcess.spawn(
+ process.argv[0],
+ [process.argv[1], `--mode=launchBossZhipinLoginPageWithPreloadExtension`],
+ {
+ 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': {
+ cookieAssistantWindow?.webContents.send(data.type, data)
+ break
+ }
+ default: {
+ return
+ }
+ }
+ }
+ )
+
+ subProcessOfBossZhipinLoginPageWithPreloadExtension!.once('exit', () => {
+ cookieAssistantWindow?.webContents.send('BOSS_ZHIPIN_LOGIN_PAGE_CLOSED')
+ subProcessOfBossZhipinLoginPageWithPreloadExtension = null
+ })
+ }
+ ipcMain.on('launch-bosszhipin-login-page-with-preload-extension', launchHandler)
+
+ const killHandler = async () => {
+ try {
+ subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill()
+ } catch {
+ //
+ } finally {
+ subProcessOfBossZhipinLoginPageWithPreloadExtension = null
+ }
+ }
+ ipcMain.on('kill-bosszhipin-login-page-with-preload-extension', killHandler)
+ cookieAssistantWindow.on('closed', () => {
+ subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill()
+ ipcMain.off('launch-bosszhipin-login-page-with-preload-extension', launchHandler)
+ ipcMain.off('kill-bosszhipin-login-page-with-preload-extension', killHandler)
+ })
+
+ return cookieAssistantWindow!
+}
diff --git a/packages/ui/src/renderer/src/page/BootstrapSplash/index.vue b/packages/ui/src/renderer/src/page/BootstrapSplash/index.vue
index d100754..422fe6c 100644
--- a/packages/ui/src/renderer/src/page/BootstrapSplash/index.vue
+++ b/packages/ui/src/renderer/src/page/BootstrapSplash/index.vue
@@ -4,56 +4,48 @@