Merge branch 'feat/db' into feature/ui

This commit is contained in:
geekgeekrun
2024-03-29 09:31:02 +08:00
46 changed files with 1955 additions and 761 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
node_modules
database.sqlite

View File

@@ -16,6 +16,7 @@
"puppeteer": "20.1.0",
"puppeteer-extra": "3.3.6",
"puppeteer-extra-plugin-stealth": "2.11.2",
"rimraf": "^3.0.2",
"tapable": "^2.2.1"
}
}

View File

@@ -17,7 +17,9 @@ export default class DingtalkPlugin {
collectedMessageList[0].dingtalkRequestBody.text.content += `\n${dayjs(collectedMessageList[0].insertedTime).format('MM-DD HH:mm:ss')}\n\n【geekgeekrun】`
requestDingtalkNotify(
_this.dingtalkAccessToken, JSON.stringify(collectedMessageList[0].dingtalkRequestBody)
)
).then(res => res.json()).then((res) => {
console.log('[DingtalkPlugin] Response: ', res)
}, () => void 0)
} else {
requestDingtalkNotify(
_this.dingtalkAccessToken, JSON.stringify((createTextMessage(
@@ -25,7 +27,9 @@ export default class DingtalkPlugin {
return `${it.dingtalkRequestBody.text.content}\n${dayjs(it.insertedTime).format('MM-DD HH:mm:ss')}\n`
}).join('-----\n') + '\n【geekgeekrun】'
)).dingtalkRequestBody)
)
).then(res => res.json()).then((res) => {
console.log('[DingtalkPlugin] Response: ', res)
}, () => void 0)
}
collectedMessageList.length = 0
sendQueueTimer = setTimeout(sendMergedMessage, interval)

View File

@@ -113,8 +113,7 @@ export async function mainLoop (hooks) {
hooks.pageLoaded?.call()
let userInfoResponse = await userInfoPromise
hooks.userInfoResponse?.call(userInfoResponse)
await hooks.userInfoResponse?.promise(userInfoResponse)
if (userInfoResponse.code !== 0) {
autoStartChatEventBus.emit('LOGIN_STATUS_INVALID', {
userInfoResponse
@@ -176,68 +175,105 @@ export async function mainLoop (hooks) {
try {
const { targetJobElProxy, targetJobIndex } = await new Promise(async (resolve, reject) => {
// job list
const recommendJobListElProxy = await page.$('.job-list-container .rec-job-list')
let jobListData = await page.evaluate(
`
document.querySelector('.job-recommend-main')?.__vue__?.jobList
`
)
// when disable company allow list, we will believe that the first one in the list is your expect job.
let targetJobIndex = enableCompanyAllowList ? jobListData.findIndex(
it => !blockBossNotNewChat.has(it.encryptBossId) && [...expectCompanySet].find(name => it.brandName.includes(name))
) : jobListData.findIndex(
it => !blockBossNotNewChat.has(it.encryptBossId)
)
let hasReachLastPage = false
while (targetJobIndex < 0 && !hasReachLastPage) {
// fetch new
const recommendJobListElBBox = await recommendJobListElProxy.boundingBox()
const windowInnerHeight = await page.evaluate('window.innerHeight')
await page.mouse.move(
recommendJobListElBBox.x + recommendJobListElBBox.width / 2,
windowInnerHeight / 2
)
let scrolledHeight = 0
const targetHeight = 3000
const increase = 40 + Math.floor(30 * Math.random())
while (scrolledHeight < targetHeight) {
scrolledHeight += increase
await page.mouse.wheel({deltaY: increase});
await sleep(1)
}
hasReachLastPage = await page.evaluate(`
!(document.querySelector('.job-recommend-main')?.__vue__?.hasMore)
`)
if (hasReachLastPage) {
console.log(`Arrive the terminal of the job list.`)
}
await sleep(3000)
jobListData = await page.evaluate(
try {
// job list
const recommendJobListElProxy = await page.$('.job-list-container .rec-job-list')
let jobListData = await page.evaluate(
`
document.querySelector('.job-recommend-main')?.__vue__?.jobList
`
)
targetJobIndex = jobListData.findIndex(it => !blockBossNotNewChat.has(it.encryptBossId) && [...expectCompanySet].find(name => it.brandName.includes(name)))
}
// when disable company allow list, we will believe that the first one in the list is your expect job.
let targetJobIndex = enableCompanyAllowList ? jobListData.findIndex(
it => !blockBossNotNewChat.has(it.encryptBossId) && [...expectCompanySet].find(name => it.brandName.includes(name))
) : jobListData.findIndex(
it => !blockBossNotNewChat.has(it.encryptBossId)
)
if (targetJobIndex < 0 && hasReachLastPage) {
// has reach last page and not find target job
reject(new Error('CANNOT_FIND_EXCEPT_JOB'))
return
}
const recommendJobItemList = await recommendJobListElProxy.$$('ul.rec-job-list > li')
resolve(
{
targetJobElProxy: recommendJobItemList[targetJobIndex],
targetJobIndex
let hasReachLastPage = false
let requestNextPagePromiseWithResolver = null
page.on(
'request',
function reqHandler (request) {
if (request.url().startsWith('https://www.zhipin.com/wapi/zpgeek/pc/recommend/job/list.json')) {
requestNextPagePromiseWithResolver = (() => {
const o = {}
o.promise = new Promise((resolve, reject) => {
o.resolve = resolve
o.reject = reject
})
return o
})()
page.off(reqHandler)
page.on(
'response',
function resHandler (response) {
if (response.request() === request) {
requestNextPagePromiseWithResolver?.resolve()
page.off(resHandler)
}
}
)
}
}
)
while (targetJobIndex < 0 && !hasReachLastPage) {
// fetch new
const recommendJobListElBBox = await recommendJobListElProxy.boundingBox()
const windowInnerHeight = await page.evaluate('window.innerHeight')
await page.mouse.move(
recommendJobListElBBox.x + recommendJobListElBBox.width / 2,
windowInnerHeight / 2
)
let scrolledHeight = 0
const increase = 40 + Math.floor(30 * Math.random())
while (
!requestNextPagePromiseWithResolver &&
!hasReachLastPage
) {
scrolledHeight += increase
await page.mouse.wheel({deltaY: increase});
await sleep(1)
await requestNextPagePromiseWithResolver?.promise
hasReachLastPage = await page.evaluate(`
!(document.querySelector('.job-recommend-main')?.__vue__?.hasMore)
`)
if (hasReachLastPage) {
console.log(`Arrive the terminal of the job list.`)
}
}
requestNextPagePromiseWithResolver = null
await sleep(3000)
jobListData = await page.evaluate(
`
document.querySelector('.job-recommend-main')?.__vue__?.jobList
`
)
targetJobIndex = jobListData.findIndex(it => !blockBossNotNewChat.has(it.encryptBossId) && [...expectCompanySet].find(name => it.brandName.includes(name)))
}
)
if (targetJobIndex < 0 && hasReachLastPage) {
// has reach last page and not find target job
reject(new Error('CANNOT_FIND_EXCEPT_JOB'))
return
}
const recommendJobItemList = await recommendJobListElProxy.$$('ul.rec-job-list > li')
resolve(
{
targetJobElProxy: recommendJobItemList[targetJobIndex],
targetJobIndex
}
)
} catch(err) {
reject(err)
}
})
if (targetJobIndex >= 0) {
// scroll that target element into view
@@ -297,7 +333,7 @@ export async function mainLoop (hooks) {
throw new Error('STARTUP_CHAT_ERROR_WITH_UNKNOWN_ERROR')
}
} else {
hooks.newChatStartup?.call(jobData)
await hooks.newChatStartup?.promise(jobData)
blockBossNotNewChat.add(jobData.jobInfo.encryptUserId)
await storeStorage(page).catch(() => void 0)

View File

@@ -141,3 +141,7 @@ export const writeStorageFile = async (fileName, content) => {
fileContent
)
}
export const getPublicDbFilePath = () => {
return path.join(storageFilePath, 'public.db')
}

View File

@@ -1,351 +0,0 @@
{
"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

@@ -41,7 +41,7 @@ function runWithDaemon () {
process.exit(exitCode)
return
}
console.log(`[Run core daemon] Child process exit with code ${exitCode}, an internal may not be caught, and will be restarted in ${rerunInterval}ms.`)
console.log(`[Run core daemon] Child process exit with code ${exitCode}, an internal error may not be caught, and will be restarted in ${rerunInterval}ms.`)
await sleep(rerunInterval)
runWithDaemon()
}

View File

@@ -8,12 +8,17 @@ 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, readStorageFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import { readConfigFile, readStorageFile, getPublicDbFilePath } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
import {
AUTO_CHAT_ERROR_EXIT_CODE
} from './enums.mjs'
import SqlitePluginModule from '@geekgeekrun/sqlite-plugin'
const {
default: SqlitePlugin
} = SqlitePluginModule
const rerunInterval = (() => {
let v = Number(process.env.MAIN_BOSSGEEKGO_RERUN_INTERVAL)
if (isNaN(v)) {
@@ -32,6 +37,7 @@ const { groupRobotAccessToken: dingTalkAccessToken } = readConfigFile('dingtalk.
const initPlugins = (hooks) => {
new DingtalkPlugin(dingTalkAccessToken).apply(hooks)
new SqlitePlugin(getPublicDbFilePath()).apply(hooks)
}
const main = async () => {
@@ -43,8 +49,9 @@ const main = async () => {
puppeteerLaunched: new SyncHook(),
pageLoaded: new SyncHook(),
cookieWillSet: new SyncHook(['cookies']),
userInfoResponse: new AsyncSeriesHook(['userInfo']),
newChatWillStartup: new AsyncSeriesHook(['positionInfoDetail']),
newChatStartup: new SyncHook(['positionInfoDetail']),
newChatStartup: new AsyncSeriesHook(['positionInfoDetail']),
noPositionFoundForCurrentJob: new SyncHook(),
noPositionFoundAfterTraverseAllJob: new SyncHook(),
errorEncounter: new SyncHook(['errorInfo'])
@@ -68,8 +75,9 @@ const main = async () => {
break
}
}
closeBrowserWindow?.()
console.error(err)
console.log(`[Run core main] An internal is caught, and browser will be restarted in ${rerunInterval}ms.`)
console.log(`[Run core main] An internal error is caught, and browser will be restarted in ${rerunInterval}ms.`)
await sleep(rerunInterval)
}
}
@@ -78,5 +86,7 @@ const main = async () => {
(async () => {
try {
await main()
} catch {}
} catch(err) {
console.error(err)
}
})()

View File

@@ -14,6 +14,7 @@
"dependencies": {
"@geekgeekrun/dingtalk-plugin": "workspace:*",
"@geekgeekrun/geek-auto-start-chat-with-boss": "workspace:*",
"@geekgeekrun/utils": "workspace:*"
"@geekgeekrun/utils": "workspace:*",
"@geekgeekrun/sqlite-plugin": "workspace:*"
}
}

4
packages/sqlite-plugin/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules
src/**/*.js
src/**/*.js.map
dist

View File

@@ -0,0 +1 @@
src

View File

@@ -0,0 +1,19 @@
{
"name": "@geekgeekrun/sqlite-plugin",
"version": "0.0.1",
"description": "",
"main": "dist/index.js",
"dependencies": {
"cli-highlight": "^2.1.11",
"reflect-metadata": "^0.2.1",
"sqlite3": "5.1.6",
"ts-node": "^10.9.2",
"typeorm": "0.3.11",
"typescript": "^5.3.3"
},
"scripts": {
"dev": "rimraf dist && tsc --outDir dist --watch true",
"build": "rimraf dist && tsc --outDir dist",
"postinstall": "npm run build || exit 0;"
}
}

View File

@@ -0,0 +1,17 @@
import { requireTypeorm } from "../utils/module-loader";
const { Entity, Column, PrimaryGeneratedColumn } = requireTypeorm();
@Entity()
export class BossActiveStatusRecord {
@PrimaryGeneratedColumn()
id: number;
@Column()
encryptBossId: string;
@Column()
lastActiveStatus: string;
@Column()
updateDate: Date;
}

View File

@@ -0,0 +1,20 @@
import { requireTypeorm } from "../utils/module-loader";
const { Entity, Column, PrimaryColumn } = requireTypeorm();
@Entity()
export class BossInfo {
@PrimaryColumn()
encryptBossId: string;
@Column()
encryptCompanyId: string;
@Column()
name: string;
@Column()
date: Date;
@Column()
title: string;
}

View File

@@ -0,0 +1,17 @@
import { requireTypeorm } from "../utils/module-loader";
const { Entity, Column, PrimaryGeneratedColumn } = requireTypeorm()
@Entity()
export class BossInfoChangeLog {
@PrimaryGeneratedColumn()
id: number;
@Column()
encryptBossId: string;
@Column()
updateTime: Date;
@Column()
dataAsJson: string;
}

View File

@@ -0,0 +1,17 @@
import { requireTypeorm } from "../utils/module-loader";
const { Entity, Column, PrimaryGeneratedColumn } = requireTypeorm()
@Entity()
export class ChatStartupLog {
@PrimaryGeneratedColumn()
id: number;
@Column()
encryptJobId: string;
@Column()
encryptCurrentUserId: string;
@Column()
date: Date;
}

View File

@@ -0,0 +1,34 @@
import { requireTypeorm } from "../utils/module-loader";
const { Entity, Column, PrimaryColumn } = requireTypeorm()
@Entity()
export class CompanyInfo {
@PrimaryColumn()
encryptCompanyId: string;
@Column()
name: string;
@Column()
brandName: string;
@Column({
nullable: true
})
scaleLow?: number;
@Column({
nullable: true
})
scaleHeight?: number;
@Column({
nullable: true
})
stageName?: string;
@Column({
nullable: true
})
industryName?: string;
}

View File

@@ -0,0 +1,17 @@
import { requireTypeorm } from "../utils/module-loader";
const { Entity, PrimaryGeneratedColumn, Column } = requireTypeorm()
@Entity()
export class CompanyInfoChangeLog {
@PrimaryGeneratedColumn()
id: number;
@Column()
encryptCompanyId: string;
@Column()
updateTime: Date;
@Column()
dataAsJson: string;
}

View File

@@ -0,0 +1,54 @@
import { requireTypeorm } from "../utils/module-loader";
const { Entity, Column, PrimaryColumn } = requireTypeorm()
@Entity()
export class JobInfo {
@PrimaryColumn()
encryptJobId: string;
@Column()
jobName: string;
@Column()
positionName: string;
@Column({
nullable: true
})
salaryLow?: number;
@Column({
nullable: true
})
salaryHeight?: number;
@Column({
nullable: true
})
salaryMonth?: number;
@Column()
experienceName: string;
@Column({
nullable: true
})
publishDate?: Date;
@Column({
nullable: true
})
degreeName?: string;
@Column()
address: string;
@Column()
description: string;
@Column()
encryptBossId: string;
@Column()
encryptCompanyId: string;
}

View File

@@ -0,0 +1,17 @@
import { requireTypeorm } from "../utils/module-loader";
const { Entity, PrimaryGeneratedColumn, Column } = requireTypeorm()
@Entity()
export class JobInfoChangeLog {
@PrimaryGeneratedColumn()
id: number;
@Column()
encryptJobId: string;
@Column()
updateTime: Date;
@Column()
dataAsJson: string;
}

View File

@@ -0,0 +1,11 @@
import { requireTypeorm } from "../utils/module-loader";
const { Entity, Column, PrimaryColumn } = requireTypeorm()
@Entity()
export class UserInfo {
@PrimaryColumn()
encryptUserId: string;
@Column()
name: string;
}

View File

@@ -0,0 +1,146 @@
import "reflect-metadata";
import { type DataSource } from "typeorm";
import { parseCompanyScale, parseSalary } from "./utils/parser";
import { requireTypeorm } from "./utils/module-loader";
import { BossInfo } from "./entity/BossInfo";
import { BossInfoChangeLog } from "./entity/BossInfoChangeLog";
import { ChatStartupLog } from './entity/ChatStartupLog';
import { CompanyInfoChangeLog } from "./entity/CompanyInfoChangeLog";
import { CompanyInfo } from "./entity/CompanyInfo";
import { JobInfo } from "./entity/JobInfo";
import { JobInfoChangeLog } from "./entity/JobInfoChangeLog";
import { BossActiveStatusRecord } from "./entity/BossActiveStatusRecord";
import { UserInfo } from "./entity/UserInfo";
import sqlite3 from 'sqlite3';
import * as cliHighlight from 'cli-highlight';
Boolean(cliHighlight);
function initDb(dbFilePath) {
const { DataSource } = requireTypeorm()
const appDataSource = new DataSource({
type: "sqlite",
synchronize: true,
logging: true,
logger: "simple-console",
database: dbFilePath,
driver: sqlite3, // The important line
entities: [
ChatStartupLog,
BossInfo,
BossInfoChangeLog,
CompanyInfo,
CompanyInfoChangeLog,
JobInfo,
JobInfoChangeLog,
BossActiveStatusRecord,
UserInfo,
],
});
return appDataSource.initialize();
}
export default class SqlitePlugin {
initPromise: Promise<DataSource>;
constructor(dbFilePath) {
this.initPromise = initDb(dbFilePath);
}
userInfo = null
apply(hooks) {
hooks.userInfoResponse.tapPromise(
"SqlitePlugin",
async (userInfoResponse) => {
if (userInfoResponse.code !== 0) {
return;
}
const { zpData: userInfo } = userInfoResponse;
this.userInfo = userInfo
console.log(userInfo);
const ds = await this.initPromise;
const userInfoRepository = ds.getRepository(UserInfo);
const user = new UserInfo();
user.encryptUserId = userInfo.encryptUserId;
user.name = userInfo.name;
return await userInfoRepository.save(user);
}
);
hooks.newChatStartup.tapPromise("SqlitePlugin", async (_jobInfo) => {
console.log(_jobInfo);
const ds = await this.initPromise;
const { bossInfo, brandComInfo, jobInfo } = _jobInfo;
//#region boss
const boss = new BossInfo();
boss.encryptBossId = jobInfo.encryptUserId;
boss.encryptCompanyId = brandComInfo.encryptBrandId;
boss.name = bossInfo.name;
boss.title = bossInfo.title;
boss.date = new Date();
const bossInfoRepository = ds.getRepository(BossInfo);
await bossInfoRepository.save(boss);
//#endregion
//#region company
const company = new CompanyInfo();
company.encryptCompanyId = brandComInfo.encryptBrandId;
company.brandName = brandComInfo.brandName;
company.name = brandComInfo.customerBrandName;
company.industryName = brandComInfo.industryName;
company.stageName = brandComInfo.stageName;
const companyScale = parseCompanyScale(brandComInfo.scaleName)
company.scaleLow = companyScale[0]
company.scaleHeight = companyScale[1]
const companyInfoRepository = ds.getRepository(CompanyInfo);
await companyInfoRepository.save(company);
//#endregion
//#region job
const job = new JobInfo();
const jobSalary = parseSalary(jobInfo.salaryDesc)
const jobUpdatePayload: JobInfo = {
address: jobInfo.address,
degreeName: jobInfo.degreeName,
description: jobInfo.postDescription,
encryptBossId: jobInfo.encryptUserId,
encryptCompanyId: brandComInfo.encryptBrandId,
encryptJobId: jobInfo.encryptId,
jobName: jobInfo.jobName,
positionName: jobInfo.positionName,
experienceName: jobInfo.experienceName,
salaryHeight: jobSalary.heigh,
salaryLow: jobSalary.low,
salaryMonth: jobSalary.month,
};
Object.assign(job, jobUpdatePayload);
const jobInfoRepository = ds.getRepository(JobInfo);
await jobInfoRepository.save(job);
//#endregion
//#region chat-startup-log
const chatStartupLog = new ChatStartupLog()
const chatStartupLogPayload: Partial<ChatStartupLog> = {
date: new Date(),
encryptCurrentUserId: this.userInfo.encryptUserId,
encryptJobId: jobInfo.encryptId,
}
Object.assign(chatStartupLog, chatStartupLogPayload)
const chatStartupLogRepository = ds.getRepository(ChatStartupLog);
await chatStartupLogRepository.save(chatStartupLog);
//#endregion
return
});
}
}

View File

@@ -0,0 +1,9 @@
import * as path from 'node:path';
import type typeormType from 'typeorm'
const isRunFromUi = Boolean(process.env.MAIN_BOSSGEEKGO_UI_RUN_MODE)
const isUiDev = process.env.NODE_ENV === 'development'
export function requireTypeorm () {
const importResult = require('typeorm')
return importResult
}

View File

@@ -0,0 +1,67 @@
export const parseCompanyScale = (str: string): [number| null, number | null] => {
if (!str) {
return [null, null]
}
const betweenRangeMatchResult = str.match(
/(\d+)-(\d+)人/
);
if (betweenRangeMatchResult) {
const arr = [...betweenRangeMatchResult];
arr.shift();
return arr.map(Number) as [number, number]
}
const gtRangeMatchResult = str.match(
/(\d+)人以上/
);
if (gtRangeMatchResult) {
const arr = [...gtRangeMatchResult];
arr.shift();
return [Number(arr[0]), null]
}
return [null, null]
}
export const parseSalary = (str: string): { low: null | number, heigh: null | number, month: null | number } => {
const result = {
heigh: null,
low: null,
month: null
}
if (!str) {
return result
}
const baseMatchResult = str.match(
/([\.\d]+)-([\.\d]+)k/i
);
if (baseMatchResult) {
const arr = [...baseMatchResult];
arr.shift();
Object.assign(
result,
{
low: Number(arr[0]),
heigh: Number(arr[1]),
}
)
}
const month = str.match(
/([\.\d]+)薪/
)
if (month) {
const arr = [...month];
arr.shift();
Object.assign(
result,
{
month: Number(arr[0])
}
)
}
return result
}

View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"lib": ["ES2020"],
"target": "ES2020",
"module": "Node16",
"moduleResolution": "Node16",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": false,
},
"exclude": ["node_modules"],
}

View File

@@ -2,6 +2,7 @@ appId: com.geekgeekrun.ui
productName: GeekGeekRun
directories:
buildResources: build
icon: resources/icon.png
files:
- '!**/.vscode/*'
- '!src/*'

View File

@@ -1,6 +1,5 @@
Object.assign(module.exports, {
puppeteerExtra: require('puppeteer-extra'),
PuppeteerExtraPluginStealth: require('puppeteer-extra-plugin-stealth'),
puppeteerManager: require('@puppeteer/browsers'),
findChromeBin: require('find-chrome-bin')
puppeteerManager: require('@puppeteer/browsers')
})

View File

@@ -4,8 +4,7 @@
"description": "",
"main": "index.cjs",
"module": "index.mjs",
"scripts": {
},
"scripts": {},
"author": "",
"license": "ISC",
"dependencies": {

View File

@@ -4,6 +4,7 @@
"description": "An Electron application with Vue and TypeScript",
"main": "./out/main/index.js",
"author": "geekgeekrun",
"productName": "GeekGeekRun",
"scripts": {
"start": "electron-vite preview",
"dev": "electron-vite dev",
@@ -30,9 +31,11 @@
"@geekgeekrun/dingtalk-plugin": "workspace:*",
"@geekgeekrun/geek-auto-start-chat-with-boss": "workspace:*",
"@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension": "workspace:*",
"@geekgeekrun/sqlite-plugin": "workspace:*",
"@geekgeekrun/utils": "workspace:*",
"@puppeteer/browsers": "^2.0.0",
"JSONStream": "^1.3.5",
"animate.css": "^4.1.1",
"electron-updater": "^6.1.7",
"element-plus": "^2.5.5",
"find-chrome-bin": "^2.0.1",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -4,5 +4,7 @@ export enum AUTO_CHAT_ERROR_EXIT_CODE {
LOGIN_STATUS_INVALID = 82,
ERR_INTERNET_DISCONNECTED = 83,
ACCESS_IS_DENIED = 84,
PUPPETEER_IS_NOT_EXECUTABLE = 85
PUPPETEER_IS_NOT_EXECUTABLE = 85,
AUTO_START_CHAT_DAEMON_PROCESS_SUICIDE = 86,
AUTO_START_CHAT_MAIN_PROCESS_SUICIDE = 87,
}

View File

@@ -1,9 +1,11 @@
import path from 'node:path'
import * as url from 'url'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
import childProcess from 'node:child_process'
import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../../common/enums/auto-start-chat'
import { app } from 'electron'
import fs, { WriteStream } from 'node:fs'
import { pipeWriteRegardlessError } from '../utils/pipe'
import * as JSONStream from 'JSONStream'
import { initPowerSaveBlocker } from './power-saver-blocker'
const rerunInterval = (() => {
let v = Number(process.env.MAIN_BOSSGEEKGO_RERUN_INTERVAL)
@@ -22,6 +24,24 @@ function runWithDaemon() {
}
})
subProcessOfCore!.stdio[3]!.pipe(JSONStream.parse()).on('data', async (raw) => {
const data = raw
switch (data.type) {
case 'AUTO_START_CHAT_MAIN_PROCESS_STARTUP': {
pipeWriteRegardlessError(
subProcessOfCore!.stdio[3]! as WriteStream,
JSON.stringify({
type: 'GEEK_AUTO_START_CHAT_CAN_BE_RUN'
})
)
break
}
default: {
return
}
}
})
subProcessOfCore.once('exit', async (exitCode: number) => {
if (
[...Object.values(AUTO_CHAT_ERROR_EXIT_CODE)]
@@ -35,16 +55,64 @@ function runWithDaemon() {
return
}
console.log(
`[Run core daemon] Child process exit with code ${exitCode}, an internal may not be caught, and will be restarted in ${rerunInterval}ms.`
`[Run core daemon] Child process exit with code ${exitCode}, an internal error may not be caught, and will be restarted in ${rerunInterval}ms.`
)
await sleep(rerunInterval)
runWithDaemon()
})
}
// suicide timer for parent and child process don't have any communication after child process spawned.
let suicideTimer: NodeJS.Timeout | null = null
const setSuicideTimer = () =>
(suicideTimer = setTimeout(() => {
app.exit(AUTO_CHAT_ERROR_EXIT_CODE.AUTO_START_CHAT_DAEMON_PROCESS_SUICIDE)
}, 10000))
const clearSuicideTimer = () => {
if (suicideTimer) {
clearTimeout(suicideTimer)
}
suicideTimer = null
}
export function runAutoChatWithDaemon() {
app.dock?.hide()
process.on('disconnect', () => {
app.exit()
})
runWithDaemon()
setSuicideTimer()
let pipe: null | fs.WriteStream = null
try {
pipe = fs.createWriteStream(null, { fd: 3 })
} catch {
console.error('pipe is not available')
app.exit(1)
}
const disposePowerSaveBlocker = initPowerSaveBlocker()
app.once('quit', disposePowerSaveBlocker)
const pipeForRead: fs.ReadStream = fs.createReadStream(null, { fd: 3 })
const pipeForReadWithJsonParser = pipeForRead.pipe(JSONStream.parse())
pipeForReadWithJsonParser?.on('data', function waitForCanRun(data) {
if (data.type === 'GEEK_AUTO_START_CHAT_CAN_BE_RUN') {
pipeForReadWithJsonParser.off('data', waitForCanRun)
clearSuicideTimer()
runWithDaemon()
// if don't call close, when kill child process, child process will ANR.
pipeForRead.close()
}
})
process.on('SIGINT', () => {
process.exit()
})
pipeWriteRegardlessError(
pipe,
JSON.stringify({
type: 'AUTO_START_CHAT_DAEMON_PROCESS_STARTUP'
})
)
}

View File

@@ -0,0 +1,10 @@
import { powerSaveBlocker } from 'electron'
export const initPowerSaveBlocker = (
type: 'prevent-app-suspension' | 'prevent-display-sleep' = 'prevent-app-suspension'
) => {
const id = powerSaveBlocker.start(type)
return function disposePowerSaveBlocker() {
return powerSaveBlocker.stop(id)
}
}

View File

@@ -1,27 +1,47 @@
import DingtalkPlugin from '@geekgeekrun/dingtalk-plugin/index.mjs'
import { app } from 'electron'
import { SyncHook, AsyncSeriesHook } from 'tapable'
import { readConfigFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import {
readConfigFile,
getPublicDbFilePath
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import * as fs from 'fs'
import { pipeWriteRegardlessError } from '../utils/pipe'
import { getAnyAvailablePuppeteerExecutable } from '../CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../../common/enums/auto-start-chat'
import * as JSONStream from 'JSONStream'
import SqlitePluginModule from '@geekgeekrun/sqlite-plugin'
const { default: SqlitePlugin } = SqlitePluginModule
const rerunInterval = (() => {
let v = Number(process.env.MAIN_BOSSGEEKGO_RERUN_INTERVAL)
if (isNaN(v)) {
v = 3000
}
return v
})()
const { groupRobotAccessToken: dingTalkAccessToken } = readConfigFile('dingtalk.json')
const initPlugins = (hooks) => {
new DingtalkPlugin(dingTalkAccessToken).apply(hooks)
new SqlitePlugin(getPublicDbFilePath()).apply(hooks)
}
let isParentProcessDisconnect = false
process.once('disconnect', () => {
isParentProcessDisconnect = true
})
export const runAutoChat = async () => {
const runAutoChat = async () => {
const { initPuppeteer, mainLoop, closeBrowserWindow, autoStartChatEventBus } = await import(
'@geekgeekrun/geek-auto-start-chat-with-boss/index.mjs'
)
process.on('disconnect', () => {
isParentProcessDisconnect = true
closeBrowserWindow()
app.exit()
})
@@ -62,8 +82,9 @@ export const runAutoChat = async () => {
puppeteerLaunched: new SyncHook(),
pageLoaded: new SyncHook(),
cookieWillSet: new SyncHook(['cookies']),
userInfoResponse: new AsyncSeriesHook(['userInfo']),
newChatWillStartup: new AsyncSeriesHook(['positionInfoDetail']),
newChatStartup: new SyncHook(['positionInfoDetail']),
newChatStartup: new AsyncSeriesHook(['positionInfoDetail']),
noPositionFoundForCurrentJob: new SyncHook(),
noPositionFoundAfterTraverseAllJob: new SyncHook(),
errorEncounter: new SyncHook(['errorInfo'])
@@ -89,13 +110,72 @@ export const runAutoChat = async () => {
try {
await mainLoop(hooks)
} catch (err) {
console.log(err)
if (err instanceof Error && err.message.includes('LOGIN_STATUS_INVALID')) {
process.exit(AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID)
break
if (err instanceof Error) {
if (err.message.includes('LOGIN_STATUS_INVALID')) {
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)
break
}
if (err.message.includes('ACCESS_IS_DENIED')) {
process.exit(AUTO_CHAT_ERROR_EXIT_CODE.ACCESS_IS_DENIED)
break
}
}
await sleep(3000)
closeBrowserWindow?.()
console.error(err)
console.log(
`[Run core main] An internal error is caught, and browser will be restarted in ${rerunInterval}ms.`
)
await sleep(rerunInterval)
}
}
closeBrowserWindow()
}
// suicide timer for parent and child process don't have any communication after child process spawned.
let suicideTimer: NodeJS.Timeout | null = null
const setSuicideTimer = () =>
(suicideTimer = setTimeout(() => {
app.exit(AUTO_CHAT_ERROR_EXIT_CODE.AUTO_START_CHAT_MAIN_PROCESS_SUICIDE)
}, 10000))
const clearSuicideTimer = () => {
if (suicideTimer) {
clearTimeout(suicideTimer)
}
suicideTimer = null
}
export const waitForProcessHandShakeAndRunAutoChat = () => {
setSuicideTimer()
const pipeForRead: fs.ReadStream = fs.createReadStream(null, { fd: 3 })
pipeForRead.on('error', () => {
return
})
const pipeForReadWithJsonParser = pipeForRead.pipe(JSONStream.parse())
pipeForReadWithJsonParser?.on('data', function waitForCanRun(data) {
if (data.type === 'GEEK_AUTO_START_CHAT_CAN_BE_RUN') {
pipeForReadWithJsonParser.off('data', waitForCanRun)
clearSuicideTimer()
runAutoChat()
// if don't call close, when kill child process, child process will ANR.
pipeForRead.close()
}
})
let pipe: null | fs.WriteStream = null
try {
pipe = fs.createWriteStream(null, { fd: 3 })
} catch {
console.error('pipe is not available')
app.exit(1)
}
pipeWriteRegardlessError(
pipe,
JSON.stringify({
type: 'AUTO_START_CHAT_MAIN_PROCESS_STARTUP'
})
)
}

View File

@@ -0,0 +1,27 @@
import { app, Menu, MenuItemConstructorOptions, MenuItem } from 'electron'
const isMac = process.platform === 'darwin'
const template: (MenuItemConstructorOptions | MenuItem)[] = [
// { role: 'appMenu' }
...(isMac
? [
{
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
}
]
: [])
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

View File

@@ -1,7 +1,8 @@
import { app, BrowserWindow, ipcMain } from 'electron'
import { app, BrowserWindow, ipcMain, globalShortcut } from 'electron'
import { electronApp, optimizer } from '@electron-toolkit/utils'
import { createMainWindow } from '../window/mainWindow'
import { createMainWindow } from '../../window/mainWindow'
import './app-menu'
import initIpc from './ipc'
export function openSettingWindow() {
// TODO: singleton lock; how can we check if there is another process should run as singleton with arguments?
if (!app.requestSingleInstanceLock()) {
@@ -9,10 +10,12 @@ export function openSettingWindow() {
app.exit(0)
}
const whenReadyPromise = app.whenReady()
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
whenReadyPromise.then(() => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')
@@ -23,10 +26,11 @@ export function openSettingWindow() {
optimizer.watchWindowShortcuts(window)
})
createMainWindow()
// IPC test
ipcMain.on('ping', () => console.log('pong'))
createMainWindow()
initIpc()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
@@ -44,4 +48,16 @@ export function openSettingWindow() {
// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.
// short cut
whenReadyPromise.then(() => {
// Register a 'Command+Option+Shift+/' shortcut listener.
globalShortcut.register('Command+Option+Shift+/', () => {
console.log('Command+Option+Shift+/ is pressed')
app.exit(0)
})
app.once('quit', () => {
globalShortcut.unregister('Command+Option+Shift+/')
})
})
}

View File

@@ -0,0 +1,260 @@
import { ipcMain, shell } from 'electron'
import * as childProcess from 'node:child_process'
import {
ensureConfigFileExist,
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 { getAnyAvailablePuppeteerExecutable } from '../../../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable/index'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../../../common/enums/auto-start-chat'
import { mainWindow } from '../../../window/mainWindow'
export default function initIpc () {
ipcMain.on('open-external-link', (_, link) => {
shell.openExternal(link, {
activate: true
})
})
ipcMain.handle('fetch-config-file-content', async () => {
const configFileContentList = configFileNameList.map((fileName) => {
return readConfigFile(fileName)
})
const result = {
config: {}
}
configFileNameList.forEach((fileName, index) => {
result.config[fileName] = configFileContentList[index]
})
return result
})
ipcMain.handle('save-config-file-from-ui', async (ev, payload) => {
payload = JSON.parse(payload)
ensureConfigFileExist()
const dingtalkConfig = readConfigFile('dingtalk.json')
dingtalkConfig.groupRobotAccessToken = payload.dingtalkRobotAccessToken
return await Promise.all([
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)
ipcMain.handle('prepare-run-geek-auto-start-chat-with-boss', async () => {
mainWindow?.webContents.send('locating-puppeteer-executable')
const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable()
if (!puppeteerExecutable) {
return Promise.reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')
}
mainWindow?.webContents.send('puppeteer-executable-is-located')
})
let subProcessOfPuppeteer: ChildProcess | null = null
ipcMain.handle('run-geek-auto-start-chat-with-boss', async () => {
if (subProcessOfPuppeteer) {
return
}
const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable()
if (!puppeteerExecutable) {
return Promise.reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')
}
const subProcessEnv = {
...process.env,
MAIN_BOSSGEEKGO_UI_RUN_MODE: 'geekAutoStartWithBossDaemon',
PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable.executablePath
}
subProcessOfPuppeteer = childProcess.spawn(process.argv[0], process.argv.slice(1), {
env: subProcessEnv,
stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'ipc']
})
// console.log(subProcessOfPuppeteer)
return new Promise((resolve, reject) => {
subProcessOfPuppeteer!.stdio[3]!.pipe(JSONStream.parse()).on('data', async (raw) => {
const data = raw
switch (data.type) {
case 'AUTO_START_CHAT_DAEMON_PROCESS_STARTUP': {
subProcessOfPuppeteer!.stdio[3]!.write(
JSON.stringify({
type: 'GEEK_AUTO_START_CHAT_CAN_BE_RUN'
})
)
break
}
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
}
}
})
subProcessOfPuppeteer!.once('exit', (exitCode) => {
subProcessOfPuppeteer = null
if (exitCode === AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE) {
// means cannot find downloaded puppeteer
reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')
} else {
mainWindow?.webContents.send('geek-auto-start-chat-with-boss-stopped')
}
})
})
// TODO:
})
ipcMain.handle('check-dependencies', async () => {
const [anyAvailablePuppeteerExecutable] = await Promise.all([
getAnyAvailablePuppeteerExecutable()
])
return {
puppeteerExecutableAvailable: !!anyAvailablePuppeteerExecutable
}
})
let subProcessOfCheckAndDownloadDependencies: ChildProcess | null = null
ipcMain.handle('setup-dependencies', async () => {
if (subProcessOfCheckAndDownloadDependencies) {
return
}
const subProcessEnv = {
...process.env,
MAIN_BOSSGEEKGO_UI_RUN_MODE: 'checkAndDownloadDependenciesForInit'
}
subProcessOfCheckAndDownloadDependencies = childProcess.spawn(
process.argv[0],
process.argv.slice(1),
{
env: subProcessEnv,
stdio: [null, null, null, 'pipe', 'ipc']
}
)
return new Promise((resolve, reject) => {
subProcessOfCheckAndDownloadDependencies!.stdio[3]!.pipe(JSONStream.parse()).on(
'data',
(raw) => {
const data = raw
switch (data.type) {
case 'NEED_RESETUP_DEPENDENCIES':
case 'PUPPETEER_DOWNLOAD_PROGRESS': {
mainWindow?.webContents.send(data.type, data)
break
}
case 'PUPPETEER_DOWNLOAD_ENCOUNTER_ERROR': {
console.error(data)
break
}
default: {
return
}
}
}
)
subProcessOfCheckAndDownloadDependencies!.once('exit', (exitCode) => {
switch (exitCode) {
case 0: {
resolve(exitCode)
break
}
default: {
reject('PUPPETEER_DOWNLOAD_ENCOUNTER_ERROR')
break
}
}
subProcessOfCheckAndDownloadDependencies = null
})
})
})
ipcMain.handle('stop-geek-auto-start-chat-with-boss', async () => {
mainWindow?.webContents.send('geek-auto-start-chat-with-boss-stopping')
subProcessOfPuppeteer?.kill()
})
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)
})
}

View File

@@ -10,8 +10,12 @@ export const pipeWriteRegardlessError = async (
if (pipe && !pipeSet.has(pipe)) {
pipeSet.add(pipe)
pipe.on('error', (error) => {
console.log('pipe.write Error', error)
void error
})
}
return pipe?.write(chunk, option, () => {})
return pipe?.write(chunk, option, (error) => {
if (error) {
console.log('pipe.write Error', error)
}
})
}

View File

@@ -1,29 +1,39 @@
import { runAutoChat } from './flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index'
import { runAutoChatWithDaemon } from './flow/GEEK_AUTO_START_CHAT_WITH_BOSS_DAEMON/index'
import { openSettingWindow } from './flow/OPEN_SETTING_WINDOW'
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) {
case 'geekAutoStartWithBossMain': {
runAutoChat()
break
;(async () => {
switch (runMode) {
case 'geekAutoStartWithBossMain': {
const { waitForProcessHandShakeAndRunAutoChat } = await import(
'./flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index'
)
waitForProcessHandShakeAndRunAutoChat()
break
}
case 'geekAutoStartWithBossDaemon': {
const { runAutoChatWithDaemon } = await import(
'./flow/GEEK_AUTO_START_CHAT_WITH_BOSS_DAEMON/index'
)
runAutoChatWithDaemon()
break
}
case 'checkAndDownloadDependenciesForInit': {
const { checkAndDownloadDependenciesForInit } = await import(
'./flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index'
)
checkAndDownloadDependenciesForInit()
break
}
case 'launchBossZhipinLoginPageWithPreloadExtension': {
const { launchBossZhipinLoginPageWithPreloadExtension } = await import(
'./flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION'
)
launchBossZhipinLoginPageWithPreloadExtension()
break
}
default: {
const { openSettingWindow } = await import('./flow/OPEN_SETTING_WINDOW/index')
openSettingWindow()
break
}
}
case 'geekAutoStartWithBossDaemon': {
runAutoChatWithDaemon()
break
}
case 'checkAndDownloadDependenciesForInit': {
checkAndDownloadDependenciesForInit()
break
}
case 'launchBossZhipinLoginPageWithPreloadExtension': {
launchBossZhipinLoginPageWithPreloadExtension()
break
}
default: {
openSettingWindow()
break
}
}
})()

View File

@@ -1,23 +1,6 @@
import { BrowserWindow, ipcMain, shell } from 'electron'
import { BrowserWindow, shell } from 'electron'
import path from 'path'
import * as childProcess from 'node:child_process'
import {
ensureConfigFileExist,
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 { getAnyAvailablePuppeteerExecutable } from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable/index'
import { DOWNLOAD_ERROR_EXIT_CODE } from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../common/enums/auto-start-chat'
let mainWindow: BrowserWindow | null = null
export let mainWindow: BrowserWindow | null = null
export function createMainWindow(): void {
// Create the browser window.
@@ -56,237 +39,6 @@ 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 configFileContentList = configFileNameList.map((fileName) => {
return readConfigFile(fileName)
})
const result = {
config: {}
}
configFileNameList.forEach((fileName, index) => {
result.config[fileName] = configFileContentList[index]
})
return result
})
ipcMain.handle('save-config-file-from-ui', async (ev, payload) => {
payload = JSON.parse(payload)
ensureConfigFileExist()
const dingtalkConfig = readConfigFile('dingtalk.json')
dingtalkConfig.groupRobotAccessToken = payload.dingtalkRobotAccessToken
return await Promise.all([
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)
ipcMain.handle('prepare-run-geek-auto-start-chat-with-boss', async () => {
mainWindow?.webContents.send('locating-puppeteer-executable')
const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable()
if (!puppeteerExecutable) {
return Promise.reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')
}
mainWindow?.webContents.send('puppeteer-executable-is-located')
})
let subProcessOfPuppeteer: ChildProcess | null = null
ipcMain.handle('run-geek-auto-start-chat-with-boss', async () => {
if (subProcessOfPuppeteer) {
return
}
const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable()
if (!puppeteerExecutable) {
return Promise.reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')
}
const subProcessEnv = {
...process.env,
MAIN_BOSSGEEKGO_UI_RUN_MODE: 'geekAutoStartWithBossDaemon',
PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable.executablePath
}
subProcessOfPuppeteer = childProcess.spawn(process.argv[0], process.argv.slice(1), {
env: subProcessEnv,
stdio: [null, null, null, 'pipe', 'ipc']
})
console.log(subProcessOfPuppeteer)
return new Promise((resolve, reject) => {
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
}
}
})
subProcessOfPuppeteer!.once('exit', (exitCode) => {
subProcessOfPuppeteer = null
if (exitCode === AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE) {
// means cannot find downloaded puppeteer
reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')
} else {
mainWindow?.webContents.send('geek-auto-start-chat-with-boss-stopped')
}
})
})
// TODO:
})
ipcMain.handle('check-dependencies', async () => {
const [anyAvailablePuppeteerExecutable] = await Promise.all([
getAnyAvailablePuppeteerExecutable()
])
return {
puppeteerExecutableAvailable: !!anyAvailablePuppeteerExecutable
}
})
let subProcessOfCheckAndDownloadDependencies: ChildProcess | null = null
ipcMain.handle('setup-dependencies', async () => {
if (subProcessOfCheckAndDownloadDependencies) {
return
}
const subProcessEnv = {
...process.env,
MAIN_BOSSGEEKGO_UI_RUN_MODE: 'checkAndDownloadDependenciesForInit'
}
subProcessOfCheckAndDownloadDependencies = childProcess.spawn(
process.argv[0],
process.argv.slice(1),
{
env: subProcessEnv,
stdio: [null, null, null, 'pipe', 'ipc']
}
)
return new Promise((resolve, reject) => {
subProcessOfCheckAndDownloadDependencies!.stdio[3]!.pipe(JSONStream.parse()).on(
'data',
(raw) => {
const data = raw
switch (data.type) {
case 'NEED_RESETUP_DEPENDENCIES':
case 'PUPPETEER_DOWNLOAD_PROGRESS': {
mainWindow?.webContents.send(data.type, data)
break
}
case 'PUPPETEER_DOWNLOAD_ENCOUNTER_ERROR': {
console.error(data)
break
}
default: {
return
}
}
}
)
subProcessOfCheckAndDownloadDependencies!.once('exit', (exitCode) => {
switch (exitCode) {
case 0: {
resolve(exitCode)
break
}
default: {
reject('PUPPETEER_DOWNLOAD_ENCOUNTER_ERROR')
break
}
}
subProcessOfCheckAndDownloadDependencies = null
})
})
})
ipcMain.handle('stop-geek-auto-start-chat-with-boss', async () => {
mainWindow?.webContents.send('geek-auto-start-chat-with-boss-stopping')
subProcessOfPuppeteer?.kill('SIGINT')
})
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

@@ -6,5 +6,6 @@ import 'normalize.css'
import './style/public.scss'
import 'element-plus/dist/index.css'
import 'virtual:uno.css'
import 'animate.css'
createApp(App).use(router).use(ElementPlus).mount('#app')

View File

@@ -1,10 +1,24 @@
<template>
<div>
<div>愿你心想事成</div>
<RouterView
:dependencies-status="checkDependenciesResult"
:process-waitee="downloadProcessWaitee"
></RouterView>
<div class="h-screen flex flex-col flex-items-center flex-justify-center">
<div>
<img
class="block"
:class="{
'animate__animated animate__bounce animate__repeat-3':
Object.values(checkDependenciesResult).includes(false)
}"
:width="256"
src="@renderer/../../../resources/icon.png"
/>
</div>
<div mt24px>愿你薪想事成</div>
<div class="h60px mt14px">
<RouterView
class="h100%"
:dependencies-status="checkDependenciesResult"
:process-waitee="downloadProcessWaitee"
></RouterView>
</div>
</div>
</template>

View File

@@ -1,10 +1,11 @@
<template>
<div v-if="!dependenciesStatus.puppeteerExecutableAvailable">
<div mb14px>正在下载核心组件</div>
<div class="flex flex-col flex-items-start flex-justify-start" v-if="!dependenciesStatus.puppeteerExecutableAvailable">
<div mb14px>正在下载兼容的浏览器</div>
<el-progress
:percentage="browserDownloadPercentage"
:format="(n) => `${n.toFixed(1)}%`"
:stroke-width="10"
class="w400px"
/>
</div>
</template>

View File

@@ -50,11 +50,7 @@ onMounted(async () => {
ElMessage.error({
message: `核心组件损坏,正在尝试修复`
})
const checkDependenciesResult = await electron.ipcRenderer.invoke('check-dependencies')
if (Object.values(checkDependenciesResult).includes(false)) {
router.replace('/')
// TODO: should continue interrupted task
}
router.replace('/')
}
console.error(err)
}

View File

@@ -54,7 +54,7 @@ const routes: Array<RouteRecordRaw> = [
path: '/downloadingDependencies',
component: () => import('@renderer/page/BootstrapSplash/page/DownloadingDependencies.vue'),
meta: {
title: '正在下载浏览器'
title: '正在下载核心组件'
},
}
]

833
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff