mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-06-01 05:30:52 +08:00
Merge branch 'feat/db' into feature/ui
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
node_modules
|
||||
database.sqlite
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -141,3 +141,7 @@ export const writeStorageFile = async (fileName, content) => {
|
||||
fileContent
|
||||
)
|
||||
}
|
||||
|
||||
export const getPublicDbFilePath = () => {
|
||||
return path.join(storageFilePath, 'public.db')
|
||||
}
|
||||
@@ -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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})()
|
||||
@@ -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
4
packages/sqlite-plugin/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
src/**/*.js
|
||||
src/**/*.js.map
|
||||
dist
|
||||
1
packages/sqlite-plugin/.npmignore
Normal file
1
packages/sqlite-plugin/.npmignore
Normal file
@@ -0,0 +1 @@
|
||||
src
|
||||
19
packages/sqlite-plugin/package.json
Normal file
19
packages/sqlite-plugin/package.json
Normal 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;"
|
||||
}
|
||||
}
|
||||
17
packages/sqlite-plugin/src/entity/BossActiveStatusRecord.ts
Normal file
17
packages/sqlite-plugin/src/entity/BossActiveStatusRecord.ts
Normal 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;
|
||||
}
|
||||
20
packages/sqlite-plugin/src/entity/BossInfo.ts
Normal file
20
packages/sqlite-plugin/src/entity/BossInfo.ts
Normal 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;
|
||||
}
|
||||
17
packages/sqlite-plugin/src/entity/BossInfoChangeLog.ts
Normal file
17
packages/sqlite-plugin/src/entity/BossInfoChangeLog.ts
Normal 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;
|
||||
}
|
||||
17
packages/sqlite-plugin/src/entity/ChatStartupLog.ts
Normal file
17
packages/sqlite-plugin/src/entity/ChatStartupLog.ts
Normal 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;
|
||||
}
|
||||
34
packages/sqlite-plugin/src/entity/CompanyInfo.ts
Normal file
34
packages/sqlite-plugin/src/entity/CompanyInfo.ts
Normal 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;
|
||||
}
|
||||
17
packages/sqlite-plugin/src/entity/CompanyInfoChangeLog.ts
Normal file
17
packages/sqlite-plugin/src/entity/CompanyInfoChangeLog.ts
Normal 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;
|
||||
}
|
||||
54
packages/sqlite-plugin/src/entity/JobInfo.ts
Normal file
54
packages/sqlite-plugin/src/entity/JobInfo.ts
Normal 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;
|
||||
}
|
||||
17
packages/sqlite-plugin/src/entity/JobInfoChangeLog.ts
Normal file
17
packages/sqlite-plugin/src/entity/JobInfoChangeLog.ts
Normal 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;
|
||||
}
|
||||
11
packages/sqlite-plugin/src/entity/UserInfo.ts
Normal file
11
packages/sqlite-plugin/src/entity/UserInfo.ts
Normal 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;
|
||||
}
|
||||
146
packages/sqlite-plugin/src/index.ts
Normal file
146
packages/sqlite-plugin/src/index.ts
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
9
packages/sqlite-plugin/src/utils/module-loader.ts
Normal file
9
packages/sqlite-plugin/src/utils/module-loader.ts
Normal 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
|
||||
}
|
||||
67
packages/sqlite-plugin/src/utils/parser.ts
Normal file
67
packages/sqlite-plugin/src/utils/parser.ts
Normal 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
|
||||
}
|
||||
12
packages/sqlite-plugin/tsconfig.json
Normal file
12
packages/sqlite-plugin/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ES2020"],
|
||||
"target": "ES2020",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": false,
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
}
|
||||
@@ -2,6 +2,7 @@ appId: com.geekgeekrun.ui
|
||||
productName: GeekGeekRun
|
||||
directories:
|
||||
buildResources: build
|
||||
icon: resources/icon.png
|
||||
files:
|
||||
- '!**/.vscode/*'
|
||||
- '!src/*'
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
"description": "",
|
||||
"main": "index.cjs",
|
||||
"module": "index.mjs",
|
||||
"scripts": {
|
||||
},
|
||||
"scripts": {},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
|
||||
@@ -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 |
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
27
packages/ui/src/main/flow/OPEN_SETTING_WINDOW/app-menu.ts
Normal file
27
packages/ui/src/main/flow/OPEN_SETTING_WINDOW/app-menu.ts
Normal 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)
|
||||
@@ -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+/')
|
||||
})
|
||||
})
|
||||
}
|
||||
260
packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts
Normal file
260
packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts
Normal 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)
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
833
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user