Merge branch 'feat/login-with-edit-this-cookie' into feature/ui

This commit is contained in:
geekgeekrun
2024-03-05 10:07:17 +08:00
30 changed files with 1296 additions and 93 deletions

View File

@@ -1,3 +1,2 @@
{
"cookies": []
}

View File

@@ -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),
]
)
}

View File

@@ -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
)
}

View File

@@ -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)
})
}

View File

@@ -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=="
}
}
}

View File

@@ -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"
}
}

View File

@@ -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) => {

View File

@@ -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:

View File

@@ -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']
})
]
},

View File

@@ -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",

View File

@@ -0,0 +1,27 @@
export const checkCookieListFormat = (cookies: Array<Record<string, string>>) => {
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
})
}

View File

@@ -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()

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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

View File

@@ -26,7 +26,8 @@ const props = defineProps({
dependenciesStatus: {
type: Object as PropType<Record<string, boolean>>,
default: () => ({})
}
},
processWaitee: Object
})
// shallow copy
@@ -75,6 +76,7 @@ const processTasks = async () => {
try {
p.then(() => {
if (!promiseList.length) {
props.processWaitee?.resolve?.()
props.dispose?.()
}
})

View File

@@ -2,7 +2,7 @@ import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import DependenciesSetupProgressIndicatorDialog from './index.vue'
export const mountGlobalDialog = (dependenciesStatus: Record<string, boolean>) => {
export const mountGlobalDialog = (o: { dependenciesStatus: Record<string, boolean>, processWaitee? }) => {
const containerElId = `elForDependenciesSetupProgressIndicatorDialog`
if (document.getElementById(containerElId)) {
@@ -28,7 +28,8 @@ export const mountGlobalDialog = (dependenciesStatus: Record<string, boolean>) =
dispose()
},
dispose,
dependenciesStatus
dependenciesStatus: o?.dependenciesStatus,
processWaitee: o?.processWaitee
}).use(ElementPlus)
app.mount(containerEl)

View File

@@ -0,0 +1,245 @@
<template>
<el-dialog
v-bind="$attrs"
:close-on-click-modal="false"
:close-on-press-escape="!cookieInvalid"
title="Boss直聘 Cookie助手"
:width="720"
top="20px"
lock-scroll
:show-close="!cookieInvalid"
>
<el-alert
v-if="cookieInvalid"
type="warning"
:closable="false"
title="需要获取您的Boss直聘Cookie才能继续"
>
由于您是首次使用本程序或者您之前使用的Boss直聘账号登录状态失效因此您需要重新获取登录凭证
</el-alert>
<div ml1em mt1em line-height-normal>
如果您了解如何获取Cookie了解有效的Cookie格式可以直接在下方输入框中进行编辑<br />
手动编辑较为麻烦建议您打开已登录过Boss直聘的浏览器使用
<a
color-blue
decoration-none
href="javascript:void(0)"
@click.prevent="handleEditThisCookieExtensionStoreLinkClick"
>EditThisCookie 扩展程序</a
>
复制Cookie然后粘贴在下方输入框中<br />
格式为被序列化为JSON的数组不含两侧引号
</div>
<br />
<div ml1em line-height-normal>
如果您不了解Cookie相关概念或者不能访问Chrome扩展程序商店下载EditThisCookie来获取Cookie请按照以下步骤进行操作
</div>
<ol lh-2em mt-0>
<li>
<el-button size="small" type="primary" font-size-inherit @click="handleClickLaunchLogin"
>点击此处</el-button
>
启动浏览器
</li>
<li>按照正常流程通过 <b>短信验证码/二维码/微信小程序</b> 登录您的Boss直聘账号</li>
<li>接下来将自动进行一些页面跳转最终将会停留在首页</li>
<li>
登录后预计5-10秒内具体取决于您的网速您的Cookie将被自动填入下方输入框
<details>
<summary color-orange cursor-pointer>我已完成登录但Cookie一直没出现</summary>
<div ml-2em max-h-200px of-auto>
如果您确实已经在打开浏览器中看到您已登录了Boss直聘请尝试按照如图所示方式复制Cookie
<figure>
<figcaption>依次点击浏览器右上角扩展程序图标EditThisCookie图标</figcaption>
<img block max-w-full src="./resources/copy-cookie-step-1.png" />
</figure>
<figure>
<figcaption>点击EditThisCookie弹出框中的Export按钮</figcaption>
<img block max-w-full src="./resources/copy-cookie-step-2.png" />
</figure>
<figure>
<figcaption>在下方输入框执行粘贴操作</figcaption>
</figure>
</div>
</details>
</li>
</ol>
<el-form
ref="formRef"
inline-message
:model="formContent"
label-position="top"
:rules="formRules"
class="cookie-form"
>
<el-form-item prop="collectedCookies" mb-0>
<el-input
v-model="formContent.collectedCookies"
type="textarea"
:autosize="{
minRows: 10,
maxRows: 10
}"
font-size-12px
@input="hasUserMutateInput = true"
></el-input>
<el-alert
v-if="loginCookieWaitingStatus === LOGIN_COOKIE_WAITING_STATUS.WAITING_FOR_LOGIN"
:closable="false"
>正在等待登录</el-alert
>
<el-alert
v-if="loginCookieWaitingStatus === LOGIN_COOKIE_WAITING_STATUS.COOKIE_COLLECTED"
:closable="false"
type="success"
>已获取到Cookie<template v-if="hasUserMutateInput"
>看起来您似乎正在尝试手动输入Cookie<el-button
size="small"
type="primary"
font-size-inherit
@click="fillCollectedCookie"
>使用获取到的Cookie</el-button
></template
></el-alert
>
</el-form-item>
</el-form>
<template #footer>
<el-button v-if="!cookieInvalid" @click="dispose">关闭</el-button>
<el-button type="primary" @click="handleSubmit">保存Cookie</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ElForm, ElMessage } from 'element-plus'
import { ref, onUnmounted, onMounted } from 'vue'
import { checkCookieListFormat } from '../../../../common/utils/cookie'
const props = defineProps({
dispose: Function,
processWaitee: Object
})
const cookieInvalid = ref(false)
enum LOGIN_COOKIE_WAITING_STATUS {
INIT,
WAITING_FOR_LOGIN,
COOKIE_COLLECTED
}
const loginCookieWaitingStatus = ref(LOGIN_COOKIE_WAITING_STATUS.INIT)
const formRef = ref<InstanceType<typeof ElForm>>()
const formContent = ref({
collectedCookies: ''
})
const formRules = {
collectedCookies: [
{
required: true,
message: '请输入Cookie'
},
{
trigger: 'blur',
validator(rule, val, cb) {
let arr
try {
arr = JSON.parse(val)
} catch (err) {
cb(
new Error(
`JSON格式无效 - 存在语法错误: ${err.message}建议使用EditThisCookie扩展程序进行复制。`
)
)
return
}
if (!checkCookieListFormat(JSON.parse(formContent.value.collectedCookies))) {
cb(new Error(`Cookie格式无效 - 部分字段缺失建议使用EditThisCookie扩展程序进行复制。`))
return
}
cb()
}
}
]
}
const hasUserMutateInput = ref(false)
const collectedCookie = ref()
const handleCookieCollected = (_, payload) => {
loginCookieWaitingStatus.value = LOGIN_COOKIE_WAITING_STATUS.COOKIE_COLLECTED
collectedCookie.value = payload.cookies
if (!hasUserMutateInput.value) {
fillCollectedCookie()
}
}
const fillCollectedCookie = () => {
if (loginCookieWaitingStatus.value !== LOGIN_COOKIE_WAITING_STATUS.COOKIE_COLLECTED) {
return
}
formContent.value.collectedCookies = JSON.stringify(collectedCookie.value, null, 2)
hasUserMutateInput.value = false
}
const handleClickLaunchLogin = () => {
electron.ipcRenderer.send('launch-bosszhipin-login-page-with-preload-extension')
loginCookieWaitingStatus.value = LOGIN_COOKIE_WAITING_STATUS.WAITING_FOR_LOGIN
}
const handleEditThisCookieExtensionStoreLinkClick = () => {
electron.ipcRenderer.send(
'open-external-link',
'https://chromewebstore.google.com/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg'
)
}
const handleSubmit = async () => {
await formRef.value!.validate()
await electron.ipcRenderer.invoke('write-storage-file', {
fileName: 'boss-cookies.json',
data: formContent.value.collectedCookies
})
ElMessage.success('Boss直聘 Cookie 保存成功')
props.processWaitee?.resolve?.()
props.dispose()
}
const handleBossZhipinLoginPageClosed = () => {
if (loginCookieWaitingStatus.value === LOGIN_COOKIE_WAITING_STATUS.WAITING_FOR_LOGIN) {
loginCookieWaitingStatus.value = LOGIN_COOKIE_WAITING_STATUS.INIT
}
}
onMounted(async () => {
electron.ipcRenderer.once('BOSS_ZHIPIN_COOKIE_COLLECTED', handleCookieCollected)
electron.ipcRenderer.on('BOSS_ZHIPIN_LOGIN_PAGE_CLOSED', handleBossZhipinLoginPageClosed)
const cookieFileContent = await electron.ipcRenderer.invoke('read-storage-file', {
fileName: 'boss-cookies.json'
})
if (checkCookieListFormat(cookieFileContent)) {
formContent.value.collectedCookies = JSON.stringify(cookieFileContent, null, 2)
} else {
cookieInvalid.value = true
}
})
onUnmounted(() => {
electron.ipcRenderer.removeListener('BOSS_ZHIPIN_COOKIE_COLLECTED', handleCookieCollected)
electron.ipcRenderer.removeListener(
'BOSS_ZHIPIN_LOGIN_PAGE_CLOSED',
handleBossZhipinLoginPageClosed
)
electron.ipcRenderer.send('kill-bosszhipin-login-page-with-preload-extension')
})
</script>
<style lang="scss">
.cookie-form.el-form {
.el-form-item__error--inline {
margin-left: 0;
margin-top: 10px;
}
}
</style>

View File

@@ -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<typeof createApp> = createApp(WaitForLogin, {
modelValue: true,
onClosed() {
dispose()
},
dispose,
processWaitee: o?.processWaitee
}).use(ElementPlus)
app.mount(containerEl)
return {
dispose
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -1,15 +1,10 @@
<template>
<div class="form-wrap">
<el-form ref="formRef" :model="formContent" label-position="top" :rules="formRules">
<el-form-item
label="BOSS直聘 Cookie 使用EditThisCookie扩展程序从你已登录过BOSS直聘的浏览器复制"
prop="bossZhipinCookies"
>
<el-input
v-model="formContent.bossZhipinCookies"
:autosize="{ minRows: 4 }"
type="textarea"
/>
<el-form-item label="BOSS直聘 Cookie">
<el-button size="small" type="primary" font-size-inherit @click="handleClickLaunchLogin"
>编辑Cookie</el-button
>
</el-form-item>
<el-form-item label="钉钉机器人 AccessToken" prop="dingtalkRobotAccessToken">
<el-input v-model="formContent.dingtalkRobotAccessToken" />
@@ -32,47 +27,23 @@
<script setup lang="ts">
import { ref } from 'vue'
import JSON5 from 'json5'
import { ElForm, ElMessage } from 'element-plus'
import router from '../../router/index'
import { mountGlobalDialog as mountDependenciesSetupProgressIndicatorDialog } from '@renderer/features/DependenciesSetupProgressIndicatorDialog/operations'
import { mountGlobalDialog as mountWaitForLoginDialog } from '@renderer/features/WaitForLoginDialog/operations'
const formContent = ref({
bossZhipinCookies: '',
dingtalkRobotAccessToken: '',
expectCompanies: ''
})
electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
console.log(res)
formContent.value.bossZhipinCookies = JSON.stringify(res['boss.json'].cookies, null, 2)
formContent.value.dingtalkRobotAccessToken = res['dingtalk.json']['groupRobotAccessToken']
formContent.value.expectCompanies = res['target-company-list.json'].join(',')
formContent.value.dingtalkRobotAccessToken = res.config['dingtalk.json']['groupRobotAccessToken']
formContent.value.expectCompanies = res.config['target-company-list.json'].join(',')
})
const formRules = {
bossZhipinCookies: [
{
required: true
},
{
trigger: 'blur',
validator(rule, val, cb) {
let arr
try {
arr = JSON5.parse(val)
} catch (err) {
cb(new Error(`JSON content is invalid: ${err.message}`))
return
}
if (!Array.isArray(arr) || !arr.length) {
cb(new Error(`Invalid cookies. Please copy with EditThisCookie extension`))
return
}
cb()
}
}
]
}
const formRef = ref<InstanceType<typeof ElForm>>()
@@ -116,11 +87,15 @@ const handleExpectCompaniesInputBlur = (event) => {
.filter(Boolean)
.join(',')
}
const handleClickLaunchLogin = () => {
mountWaitForLoginDialog()
}
</script>
<style scoped lang="scss">
.form-wrap {
padding-top: 100px;
padding-top: 60px;
margin: 0 auto;
max-width: 640px;
.last-form-item {

View File

@@ -1,8 +1,9 @@
<template><RouterView /></template>
<script lang="ts" setup>
import { onUnmounted } from 'vue'
import { onMounted, onUnmounted } from 'vue'
import { mountGlobalDialog as mountDependenciesSetupProgressIndicatorDialog } from '@renderer/features/DependenciesSetupProgressIndicatorDialog/operations'
import { mountGlobalDialog as mountWaitForLoginDialog } from '@renderer/features/WaitForLoginDialog/operations'
const unmountedCbs: Array<InstanceType<typeof Function>> = []
onUnmounted(() => {
@@ -13,10 +14,31 @@ onUnmounted(() => {
} catch {}
}
})
onMounted(() => {
electron.ipcRenderer.on('check-boss-zhipin-cookie-file', mountWaitForLoginDialog)
})
onUnmounted(() => {
electron.ipcRenderer.removeListener('check-boss-zhipin-cookie-file', mountWaitForLoginDialog)
})
;(async () => {
const checkDependenciesResult = await electron.ipcRenderer.invoke('check-dependencies')
if (Object.values(checkDependenciesResult).includes(false)) {
mountDependenciesSetupProgressIndicatorDialog(checkDependenciesResult)
const processWaitee = Promise.withResolvers()
mountDependenciesSetupProgressIndicatorDialog({
checkDependenciesResult, processWaitee
})
await processWaitee.promise
}
const isCookieFileValid = await electron.ipcRenderer.invoke('check-boss-zhipin-cookie-file')
if (!isCookieFileValid) {
const processWaitee = Promise.withResolvers()
mountWaitForLoginDialog({
processWaitee
})
await processWaitee.promise
}
})()
</script>

View File

@@ -1,6 +1,7 @@
html, body {
--monospace-font-family: Monaco, Consolas, Menlo, monospace;
font-family: var(--monospace-font-family);
--monospace-font-family: Monaco, Consolas, Menlo;
--sans-serif-font-family: 'PingFang SC', 'Microsoft Yahei', Helvetica, Arial;
font-family: var(--monospace-font-family), var(--sans-serif-font-family);
position: relative;
width: 100%;
height: 100%;

View File

@@ -0,0 +1,25 @@
export async function blockNavigation(page, predictor = (url) => true) {
console.log(`block navigation for puppeteer page from url ${page.url()}`)
await page.setRequestInterception(true)
const handler = (req) => {
if (req.isNavigationRequest() && req.frame() === page.mainFrame() && predictor(req)) {
req.abort('aborted')
} else {
try {
req.continue()
} catch {
//
}
}
}
page.on('request', handler)
return {
dispose: async () => {
page.off('request', handler)
await page.setRequestInterception(false)
}
}
}

View File

@@ -0,0 +1,18 @@
export const setDomainLocalStorage = async (browser, url, kv) => {
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', r => {
r.respond({
status: 200,
contentType: 'text/plain',
body: ':)',
});
});
await page.goto(url);
await page.evaluate(kv => {
Object.keys(kv).forEach(k => {
localStorage.setItem(k, kv[k]);
})
}, kv);
await page.close();
};

67
pnpm-lock.yaml generated
View File

@@ -1,9 +1,5 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
@@ -35,6 +31,21 @@ importers:
specifier: workspace:*
version: link:../utils
packages/launch-bosszhipin-login-page-with-preload-extension:
dependencies:
'@geekgeekrun/geek-auto-start-chat-with-boss':
specifier: workspace:*
version: link:../geek-auto-start-chat-with-boss
'@geekgeekrun/utils':
specifier: workspace:*
version: link:../utils
extract-zip:
specifier: ^2.0.1
version: 2.0.1
node-fetch:
specifier: ^3.3.2
version: 3.3.2
packages/run-core-of-geek-auto-start-chat-with-boss:
dependencies:
'@geekgeekrun/dingtalk-plugin':
@@ -61,6 +72,9 @@ importers:
'@geekgeekrun/geek-auto-start-chat-with-boss':
specifier: workspace:*
version: link:../geek-auto-start-chat-with-boss
'@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension':
specifier: workspace:*
version: link:../launch-bosszhipin-login-page-with-preload-extension
'@geekgeekrun/utils':
specifier: workspace:*
version: link:../utils
@@ -1271,7 +1285,7 @@ packages:
/@types/fs-extra@9.0.13:
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
dependencies:
'@types/node': 18.19.9
'@types/node': 18.19.15
dev: true
/@types/http-cache-semantics@4.0.4:
@@ -2460,6 +2474,11 @@ packages:
/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
/data-uri-to-buffer@4.0.1:
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
engines: {node: '>= 12'}
dev: false
/data-uri-to-buffer@6.0.1:
resolution: {integrity: sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==}
engines: {node: '>= 14'}
@@ -3077,6 +3096,14 @@ packages:
dependencies:
pend: 1.2.0
/fetch-blob@3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.3.3
dev: false
/file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
@@ -3161,6 +3188,13 @@ packages:
mime-types: 2.1.35
dev: true
/formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
dependencies:
fetch-blob: 3.2.0
dev: false
/fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
dev: false
@@ -4018,6 +4052,11 @@ packages:
dev: true
optional: true
/node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
dev: false
/node-fetch-native@1.6.2:
resolution: {integrity: sha512-69mtXOFZ6hSkYiXAVB5SqaRvrbITC/NPyqv7yuu/qw0nmgPyYbIMYYNIDhNtwPrzk0ptrimrLz/hhjvm4w5Z+w==}
dev: true
@@ -4034,6 +4073,15 @@ packages:
whatwg-url: 5.0.0
dev: false
/node-fetch@3.3.2:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
data-uri-to-buffer: 4.0.1
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
dev: false
/node-releases@2.0.14:
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
dev: true
@@ -5286,6 +5334,11 @@ packages:
'@vue/shared': 3.4.15
typescript: 5.3.3
/web-streams-polyfill@3.3.3:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
dev: false
/webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false
@@ -5408,3 +5461,7 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
dev: true
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false