diff --git a/README.md b/README.md index 4257b0e..e728812 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **->维护者正在疯狂求职中<-** -一款可以帮助你在Boss直聘上**自动批量开聊Boss**的脚本,基于Puppeteer。 +一款可以帮助你在BOSS直聘上**自动批量开聊BOSS**的脚本,基于Puppeteer。 与每一位牛人站在一起 @@ -11,10 +11,13 @@ 各行各业,无论你是小白还是大佬,都能通过几步简单的配置,快速开始求职! +## TO 有求职之外其他目的朋友 +本程序的目标,是帮助求职者尽快寻得一个能让他为之**挥洒汗水**、**努力拼搏**的**事业**。如果有其它目的,请勿下载使用。 + ## 程序有哪些功能?运行逻辑是什么?怎样使用? -### Boss炸弹 -扩列神器!按照你所设置的求职偏好,自动开聊推荐职位列表中的匹配的Boss。可以帮你开聊推荐列表中的职位、根据设置的求职期望推荐的职位以及根据你设置的关键词通过搜索找到的职位;可以通过拖拽的方式自定义搜索顺序哦~ +### 自动开聊 +扩列神器!按照你所设置的求职偏好,自动开聊推荐职位列表中的匹配的BOSS。可以帮你开聊推荐列表中的职位、根据设置的求职期望推荐的职位以及根据你设置的关键词通过搜索找到的职位;可以通过拖拽的方式自定义搜索顺序哦~ image @@ -24,10 +27,10 @@ image 2. 按照公司名称在列表中查找职位,查找到目标职位后,自动点击这个职位,右侧将会展示职位详情 -3. 对工作地、薪资、工作经验、职位名称、职位类型、职位描述、Boss活跃度进行匹配 +3. 对工作地、薪资、工作经验、职位名称、职位类型、职位描述、BOSS活跃度进行匹配 - 如果可以匹配,则自动点击开聊按钮 - 不匹配则根据你设置的标记不合适的策略将职位标记为不合适 - - 首先查看是否命中策略为“在Boss直聘上标记不合适”的配置,如果命中则在Boss直聘上点击相关UI,标记不合适,一段时间内你将不会在Boss上看到这个职位,且将会推荐新职位置换这个职位(对于“推荐列表中的职位”、“通过搜索找到的职位”,此项会回落到“在本地数据库中标记不合适,且7天内再遇到这个职位时直接跳过”) + - 首先查看是否命中策略为“在BOSS直聘上标记不合适”的配置,如果命中则在BOSS直聘上点击相关UI,标记不合适,一段时间内你将不会在BOSS上看到这个职位,且将会推荐新职位置换这个职位(对于“推荐列表中的职位”、“通过搜索找到的职位”,此项会回落到“在本地数据库中标记不合适,且7天内再遇到这个职位时直接跳过”) - 然后查看是否命中策略为“在本地数据库中标记不合适,且7天内再遇到这个职位时直接跳过”的配置,如果命中则仅在本地数据库中记录,且本次运行将无视这个职位 - 最后查看是否命中策略为了“仅在本次运行中记录不合适,且本次运行再遇到这个职位时直接跳过”的配置,如果命中则本次运行将无视这个职位,但下次运行可能还会查看这个职位 @@ -37,23 +40,23 @@ #### 运行效果 -> Boss直聘界面已改版,如下是旧版Boss直聘界面 +> BOSS直聘界面已改版,如下是旧版BOSS直聘界面 https://github.com/user-attachments/assets/672f1637-dd54-416c-922e-1ecf09b0874a -### 已读不回提醒器 -Boss不明原因已读不回?简历就是投不出去? +### 已读不回自动复聊 +BOSS不明原因已读不回?简历就是投不出去? -已读不回提醒器,有事没事提醒一下已读不回的 Ta,助力把握每次机会 +已读不回自动复聊,有事没事提醒一下已读不回的 Ta,助力沟通双向奔赴 image #### 匹配逻辑 -1. 在聊天列表中查找对你消息已读不回的Boss,再发一条消息,多次复聊;同时: +1. 在聊天列表中查找对你消息已读不回的BOSS,再发一条消息,多次复聊;同时: - 如果设置了“跟进时限”,那么在这个时间之前活跃的聊天将不会被检查 - - 如果设置了“跟进间隔”,且再次检查时发现Boss已读不回,且距离上次提醒时间间隔小于这个时间,那么聊天将暂时不会跟进,直到下次检查时距离上次提醒时间间隔大于这个时间 + - 如果设置了“跟进间隔”,且再次检查时发现BOSS已读不回,且距离上次提醒时间间隔小于这个时间,那么聊天将暂时不会跟进,直到下次检查时距离上次提醒时间间隔大于这个时间 #### 发送内容 以下二者之一: @@ -61,8 +64,8 @@ Boss不明原因已读不回?简历就是投不出去? 2. 由大语言模型(根据简历及当前聊天上下文)生成的内容 ## 辅助功能介绍 -### Boss 登录助手 -帮你用十分简单的方式,登录到 Boss 直聘,以进行自动开聊、已读不回提醒,避免和乱七八糟的技术名词(类似:Cookie、登录凭证、JSON……)打交道 +### BOSS 登录助手 +帮你用十分简单的方式,登录到 BOSS 直聘,以进行自动开聊、已读不回提醒,避免和乱七八糟的技术名词(类似:Cookie、登录凭证、JSON……)打交道 image @@ -73,20 +76,20 @@ Boss不明原因已读不回?简历就是投不出去? image -### 已读不回提醒器提示词模板编辑 +### 已读不回自动复聊提示词模板编辑 对生成的提醒消息不够满意?您不妨可以自己进行编辑提示词,来获得您期望的结果 image -### 已读不回提醒器提醒过程模拟 +### 已读不回自动复聊提醒过程模拟 想要测试配置的某一款模型可不可用?想测试使用当前配置生成的内容效果如何?那就来这里试试吧 image ### 配置模板选项 -初次见面,不会配置本程序?当心配置出现问题?本程序为一些关键的功能配置(例如:Boss炸弹中的期望公司设置、职位详情筛选设置;大语言模型设置)加入了配置模板,让本程序更易用,助你轻松完成配置,开始求职。不妨来试试吧 +初次见面,不会配置本程序?当心配置出现问题?本程序为一些关键的功能配置(例如:自动开聊中的期望公司设置、职位详情筛选设置;大语言模型设置)加入了配置模板,让本程序更易用,助你轻松完成配置,开始求职。不妨来试试吧 image @@ -135,25 +138,25 @@ Boss不明原因已读不回?简历就是投不出去? 在此先谢过了~ ## 为什么要写这个程序? -根据日常使用求职平台(Boss直聘)的经验,在已经开聊很多职位的情况下,经常会推荐: +根据日常使用求职平台(BOSS直聘)的经验,在已经开聊很多职位的情况下,经常会推荐: 1. 一些长时间不活跃的“僵尸”职位 - 活跃信息默认隐藏,需要点开职位详情才能看到 2. 牛头不对马嘴,不符合求职期望的职位 人工筛选这些职位会有很大的心智负担,真的会吐…… -因此,我在Boss炸弹代码中,加入了清理机制,将通过标记不合适的方式,尝试让这些不活跃职位、不合适职位消失;保证只开聊符合你口味的职位。 +因此,我在自动开聊代码中,加入了清理机制,将通过标记不合适的方式,尝试让这些不活跃职位、不合适职位消失;保证只开聊符合你口味的职位。 ## 使用必读及免责声明 如下是使用必读及免责声明,请您务必逐条阅读;本程序首次启动时,您还会再看到一次;若您不接受如下提到的任何内容,请不要使用本程序。 -- 本程序从某种程度上说属于辅助工具,与《Boss直聘用户协议》(2023年3月版)相关条款相违背,您在注册Boss直聘时已签署过这一条款;根据该条款`七、用户的平台使用义务`、`八、违约责任` 章节,如果一些非正常用户行为被风控监测到,您需要承受包括不仅限于**账号被强制退出登录**、**账号被限制使用**、**账号被封禁**等对您不利的风险;因此使用本程序即意味着**您愿意接受以上风险**,且如果相关风险发生,您需要自行承担相关后果,**本程序概不负责** 。 -- 本程序需要存储您的登录凭据,即Cookie,来模拟您在Boss直聘上开聊Boss的行为;本程序仅会把您的Cookie存储在本地,并在您访问Boss直聘时将其传输到Boss直聘,**不会泄露给第三方**,也不会进行除自动开聊Boss以外的行为;**请勿向他人泄漏您的Cookie**。 -- 本程序会通过尽可能模仿用户行为来规避相关风险,但并不能保证可以完全规避。建议您使用本程序时**注意节制**,建议当天开聊次数用尽后,隔几天再使用。建议您**注册一个本程序专用的新的Boss直聘账号**进行求职。 -- 本程序原理是模拟用户在Boss直聘网页上,寻找关键元素并进行点击操作;Boss直聘网站经常**发生改版**,且有可能**包含A/B实验**,这将导致本程序相关脚本失效(典型表现为本程序运行到某一步骤后,浏览器重复“闪退、重新启动”)。如果您在使用过程中遇上程序未按照预期执行的情况,请[点击这里](https://github.com/geekgeekrun/geekgeekrun/issues/new)进行反馈。 +- 本程序从某种程度上说属于辅助工具,与《BOSS直聘用户协议》(2023年3月版)相关条款相违背,您在注册BOSS直聘时已签署过这一条款;根据该条款`七、用户的平台使用义务`、`八、违约责任` 章节,如果一些非正常用户行为被风控监测到,您需要承受包括不仅限于**账号被强制退出登录**、**账号被限制使用**、**账号被封禁**等对您不利的风险;因此使用本程序即意味着**您愿意接受以上风险**,且如果相关风险发生,您需要自行承担相关后果,**本程序概不负责** 。 +- 本程序需要存储您的登录凭据,即Cookie,来模拟您在BOSS直聘上开聊BOSS的行为;本程序仅会把您的Cookie存储在本地,并在您访问BOSS直聘时将其传输到BOSS直聘,**不会泄露给第三方**,也不会进行除自动开聊BOSS以外的行为;**请勿向他人泄漏您的Cookie**。 +- 本程序会通过尽可能模仿用户行为来规避相关风险,但并不能保证可以完全规避。建议您使用本程序时**注意节制**,建议当天开聊次数用尽后,隔几天再使用。建议您**注册一个本程序专用的新的BOSS直聘账号**进行求职。 +- 本程序原理是模拟用户在BOSS直聘网页上,寻找关键元素并进行点击操作;BOSS直聘网站经常**发生改版**,且有可能**包含A/B实验**,这将导致本程序相关脚本失效(典型表现为本程序运行到某一步骤后,浏览器重复“闪退、重新启动”)。如果您在使用过程中遇上程序未按照预期执行的情况,请[点击这里](https://github.com/geekgeekrun/geekgeekrun/issues/new)进行反馈。 - 您所在公司可能会对您的计算机终端或网络进行**监控**,从而**审计**、**跟踪**您的行为;上级/HRBP团队可能会从 IT 团队处获取到监控数据,从而了解团队成员离职倾向。如果您不希望上级/HRBP团队了解到您正在求职,建议您**不要在您所在公司提供的计算机终端或网络上使用本程序**。 - 本程序尊重您的隐私,**不会参与任何钓鱼活动**、**不会上报能够识别出您身份的信息**、**不会向您所在公司及上级/HRBP报告您的求职行为**、**不会向任何猎头公司泄露您的个人信息**。 - 本程序**没有内置任何付费功能**,**下载**、**使用**是**免费**的,任何人可以**免费获得**、**免费使用**。**作者没有利用本程序赚到过任何收入**。如果您是从GitHub以外的地方付费后“购买”的本程序,或您被提示“必须付费后才能使用本程序”,那**您大概率被骗了**,或者**您下载到了本程序修改版**。**本程序对此概不负责,请勿找作者商讨退款、售后事宜,相关事宜请咨询卖方**。 -- 本程序**不对您的求职过程与结果负责**,为您开聊的职位均在Boss直聘上发布,职位信息真实性由Boss直聘负责;请**自行甄别为您开聊的公司**、**认真决定是否参加面试**、**慎重选择Offer**。 -- 请在Boss直聘上自行**屏蔽您不期望投递的公司**;如果您不希望您当前公司其它具有招聘账号的员工看到您在Boss直聘上活跃,请**在Boss直聘上屏蔽当前公司及与之关联的公司**。 +- 本程序**不对您的求职过程与结果负责**,为您开聊的职位均在BOSS直聘上发布,职位信息真实性由BOSS直聘负责;请**自行甄别为您开聊的公司**、**认真决定是否参加面试**、**慎重选择Offer**。 +- 请在BOSS直聘上自行**屏蔽您不期望投递的公司**;如果您不希望您当前公司其它具有招聘账号的员工看到您在BOSS直聘上活跃,请**在BOSS直聘上屏蔽当前公司及与之关联的公司**。 - 本程序经历过了多次测试,理论上来说大部分情况下可以正常运行,但可能也会出现测试用例覆盖不到位,导致程序不按预期运行的情况;如果您有顾虑,建议通过VMware Workstation/Fusion、Oracle VirtualBox、Microsoft Hyper-V等虚拟化技术运行本程序。如果您在使用过程中遇上程序未按照预期执行的情况,请[点击这里](https://github.com/geekgeekrun/geekgeekrun/issues/new)进行反馈。 ## 想联系作者?想反馈使用体验上的问题?想找求职搭子? diff --git a/package.json b/package.json index 7bcaecf..a27bec1 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "minimist": "^1.2.8", "puppeteer": "24.19.0", "puppeteer-extra": "3.3.6", + "puppeteer-extra-plugin-anonymize-ua": "2.4.6", "puppeteer-extra-plugin-stealth": "2.11.2", "rimraf": "^3.0.2", "tapable": "^2.2.1" @@ -29,11 +30,12 @@ "node": "20.16.0" }, "engines": { - "pnpm": "=8.15.9" + "pnpm": ">=8.15.9 <9.0.0" }, "pnpm": { "patchedDependencies": { - "find-chrome-bin@2.0.4": "patches/find-chrome-bin@2.0.4.patch" + "find-chrome-bin@2.0.4": "patches/find-chrome-bin@2.0.4.patch", + "puppeteer-extra-plugin-anonymize-ua@2.4.6": "patches/puppeteer-extra-plugin-anonymize-ua@2.4.6.patch" } } } diff --git a/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json b/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json index 437e47f..2b202d4 100644 --- a/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json +++ b/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json @@ -35,5 +35,6 @@ ], "isSageTimeEnabled": true, "sageTimeOpTimes": 100, - "sageTimePauseMinute": 15 + "sageTimePauseMinute": 15, + "blockCompanyNameRegExpStr": "" } \ No newline at end of file diff --git a/packages/geek-auto-start-chat-with-boss/default-config-file/sample-company-list.json b/packages/geek-auto-start-chat-with-boss/default-config-file/sample-company-list.json new file mode 100644 index 0000000..09c0944 --- /dev/null +++ b/packages/geek-auto-start-chat-with-boss/default-config-file/sample-company-list.json @@ -0,0 +1,4 @@ +[ + "青钱","软通动力","南天","睿服","中电金信","佰钧成","云链","博彦","汉克时代","柯莱特","拓保","亿达信息","纬创","微创","微澜","诚迈科技","法本","兆尹","诚迈","联合永道","新致软件","宇信科技","华为","德科","FESCO","科锐","科之锐", + "抖音","字节","字跳","有竹居","脸萌","头条","懂车帝","滴滴","嘀嘀","巨量引擎","小桔","网易","有道","腾讯","酷狗","酷我","阅文","搜狗","小鹅通","富途","京东","沃东天骏","达达","达冠","百度","昆仑芯","小度","度小满","爱奇艺","携程","趣拿","去哪儿","集度","智图","长地万方","瑞图万方","道道通","小熊博望","理想","蔚来","顺丰","丰巢","中通","圆通","申通","跨越","讯飞","同程","艺龙","马蜂窝","贝壳","自如","链家","我爱我家","相寓","多点","金山","小米","猎豹","新浪","微博","阿里","淘宝","淘麦郎","天猫","盒马","口碑","优视","夸克","UC","蚂蚁","高德","LAZADA","来赞达","飞猪","菜鸟","哈啰","钉钉","乌鸫","饿了么","美团","三快","猫眼","快手","映客","小红书","行吟","奇虎","360","三六零","鸿盈","奇富","奇元","亚信","启明星辰","奇安信","深信服","长亭","绿盟","天融信","商汤","SenseTime","大华","海康威视","hikvision","汽车之家","车好多","瓜子","易车","昆仑万维","昆仑天工","闲徕","趣加","FunPlus","完美","马上消费","轻松","水滴","白龙马","58","车欢欢","五八","红布林","致美","快狗","天鹅到家","转转","美餐","知乎","智者四海","易点云","搜狐","用友","畅捷通","猿辅导","小猿","猿力","好未来","学而思","希望学","新东方","东方甄选","东方优选","作业帮","高途","跟谁学","学科网","天学网","一起教育","一起作业","美术宝","火花思维","粉笔","51talk","爱学习","高思","老虎国际","一心向上","向上一意","联想","拉勾","乐视","欢聚","竞技世界","拼多多","寻梦","从鲸","TEMU","得物","有赞","Moka","希瑞亚斯","北森","OPPO","欧珀","vivo","维沃","小天才","步步高","读书郎","货拉拉","陌陌","探探","Shopee","虾皮","首汽租车","GoFun","神州租车","天眼查","旷视","小冰","美图","智谱华章","MiniMax","石头科技","迅雷","TP","锐捷","Tenda","腾达","斐讯","希音","SHEIN","稀宇","深言","百川智能","与爱为舞","牵手","Grab","爱回收","洋钱罐","瓴岳","得到","思维造物","地平线","咪咕","翼支付","电信","天翼","联通","蓝湖","墨刀","海尔","美的","米哈游","传音","同花顺","国美","TCL" +] \ No newline at end of file diff --git a/packages/geek-auto-start-chat-with-boss/default-config-file/target-company-list.json b/packages/geek-auto-start-chat-with-boss/default-config-file/target-company-list.json index 09c0944..0637a08 100644 --- a/packages/geek-auto-start-chat-with-boss/default-config-file/target-company-list.json +++ b/packages/geek-auto-start-chat-with-boss/default-config-file/target-company-list.json @@ -1,4 +1 @@ -[ - "青钱","软通动力","南天","睿服","中电金信","佰钧成","云链","博彦","汉克时代","柯莱特","拓保","亿达信息","纬创","微创","微澜","诚迈科技","法本","兆尹","诚迈","联合永道","新致软件","宇信科技","华为","德科","FESCO","科锐","科之锐", - "抖音","字节","字跳","有竹居","脸萌","头条","懂车帝","滴滴","嘀嘀","巨量引擎","小桔","网易","有道","腾讯","酷狗","酷我","阅文","搜狗","小鹅通","富途","京东","沃东天骏","达达","达冠","百度","昆仑芯","小度","度小满","爱奇艺","携程","趣拿","去哪儿","集度","智图","长地万方","瑞图万方","道道通","小熊博望","理想","蔚来","顺丰","丰巢","中通","圆通","申通","跨越","讯飞","同程","艺龙","马蜂窝","贝壳","自如","链家","我爱我家","相寓","多点","金山","小米","猎豹","新浪","微博","阿里","淘宝","淘麦郎","天猫","盒马","口碑","优视","夸克","UC","蚂蚁","高德","LAZADA","来赞达","飞猪","菜鸟","哈啰","钉钉","乌鸫","饿了么","美团","三快","猫眼","快手","映客","小红书","行吟","奇虎","360","三六零","鸿盈","奇富","奇元","亚信","启明星辰","奇安信","深信服","长亭","绿盟","天融信","商汤","SenseTime","大华","海康威视","hikvision","汽车之家","车好多","瓜子","易车","昆仑万维","昆仑天工","闲徕","趣加","FunPlus","完美","马上消费","轻松","水滴","白龙马","58","车欢欢","五八","红布林","致美","快狗","天鹅到家","转转","美餐","知乎","智者四海","易点云","搜狐","用友","畅捷通","猿辅导","小猿","猿力","好未来","学而思","希望学","新东方","东方甄选","东方优选","作业帮","高途","跟谁学","学科网","天学网","一起教育","一起作业","美术宝","火花思维","粉笔","51talk","爱学习","高思","老虎国际","一心向上","向上一意","联想","拉勾","乐视","欢聚","竞技世界","拼多多","寻梦","从鲸","TEMU","得物","有赞","Moka","希瑞亚斯","北森","OPPO","欧珀","vivo","维沃","小天才","步步高","读书郎","货拉拉","陌陌","探探","Shopee","虾皮","首汽租车","GoFun","神州租车","天眼查","旷视","小冰","美图","智谱华章","MiniMax","石头科技","迅雷","TP","锐捷","Tenda","腾达","斐讯","希音","SHEIN","稀宇","深言","百川智能","与爱为舞","牵手","Grab","爱回收","洋钱罐","瓴岳","得到","思维造物","地平线","咪咕","翼支付","电信","天翼","联通","蓝湖","墨刀","海尔","美的","米哈游","传音","同花顺","国美","TCL" -] \ No newline at end of file +[] \ No newline at end of file diff --git a/packages/geek-auto-start-chat-with-boss/index.mjs b/packages/geek-auto-start-chat-with-boss/index.mjs index 3657863..40bf6f0 100644 --- a/packages/geek-auto-start-chat-with-boss/index.mjs +++ b/packages/geek-auto-start-chat-with-boss/index.mjs @@ -86,31 +86,32 @@ export const autoStartChatEventBus = new EventEmitter() let puppeteer let StealthPlugin let LaodengPlugin +let AnonymizeUaPlugin export async function initPuppeteer () { // production const importResult = await Promise.all( [ import('puppeteer-extra'), import('puppeteer-extra-plugin-stealth'), - import('@geekgeekrun/puppeteer-extra-plugin-laodeng') + import('@geekgeekrun/puppeteer-extra-plugin-laodeng'), + import('puppeteer-extra-plugin-anonymize-ua') ] ) puppeteer = importResult[0].default StealthPlugin = importResult[1].default LaodengPlugin = importResult[2].default + AnonymizeUaPlugin = importResult[3].default puppeteer.use(StealthPlugin()) puppeteer.use(LaodengPlugin()) - + puppeteer.use(AnonymizeUaPlugin({ makeWindows: false })) return { puppeteer, StealthPlugin, - LaodengPlugin + LaodengPlugin, + AnonymizeUaPlugin } } -const bossCookies = readStorageFile('boss-cookies.json') -const bossLocalStorage = readStorageFile('boss-local-storage.json') - const targetCompanyList = readConfigFile('target-company-list.json').filter(it => !!it.trim()); const combineRecommendJobFilterType = readConfigFile('boss.json').combineRecommendJobFilterType ?? CombineRecommendJobFilterType.ANY_COMBINE @@ -246,6 +247,19 @@ const recommendJobPageUrl = `https://www.zhipin.com/web/geek/jobs` const expectCompanySet = new Set(targetCompanyList) const enableCompanyAllowList = Boolean(expectCompanySet.size) +const blockCompanyNameRegExpStr = readConfigFile('boss.json').blockCompanyNameRegExpStr ?? '' +const blockCompanyNameRegExp = (() => { + if (!blockCompanyNameRegExpStr?.trim()) { + return null + } + try { + return new RegExp(blockCompanyNameRegExpStr, 'im') + } + catch { + return null + } +})() +const blockCompanyNameRegMatchStrategy = readConfigFile('boss.json').blockCompanyNameRegMatchStrategy ?? MarkAsNotSuitOp.NO_OP /** * @type { import('puppeteer').Browser } @@ -297,6 +311,18 @@ async function markJobAsNotSuitInRecommendPage (reasonCode) { let isOptionChosen = false if (chooseReasonDialogProxy) { switch (reasonCode) { + case MarkAsNotSuitReason.COMPANY_NAME_NOT_SUIT: { + const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title*="公司"]`)) + ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`)) + ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`)) + ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="距离"]`)) + ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="薪资"]`)) + if (opProxy) { + await opProxy.click() + isOptionChosen = true + } + break + } case MarkAsNotSuitReason.BOSS_INACTIVE: { const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title="BOSS活跃度低"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="职位停招/招满"]`)) @@ -311,7 +337,7 @@ async function markJobAsNotSuitInRecommendPage (reasonCode) { case MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT: case MarkAsNotSuitReason.JOB_CITY_NOT_SUIT: { const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title$="城市"]`)) - ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="距离远"]`)) + ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="距离"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="公司"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`)) @@ -324,7 +350,7 @@ async function markJobAsNotSuitInRecommendPage (reasonCode) { case MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT: { const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title*="薪资"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title$="城市"]`)) - ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="距离远"]`)) + ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="距离"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="公司"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`)) @@ -983,6 +1009,14 @@ async function toRecommendPage (hooks) { ].includes(expectSalaryNotMatchStrategy) && strategyScopeOptionWhenMarkSalaryNotMatch === StrategyScopeOptionWhenMarkJobNotMatch.ALL_JOB ) ? !checkIfSalarySuit(it.salaryDesc) : false + ) || ( + // enter job detail to mark as not suit for company name filter + !!blockCompanyNameRegExp && + blockCompanyNameRegExp.test(it.brandName?.toLowerCase?.() ?? '') && + [ + MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS, + MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL + ].includes(blockCompanyNameRegMatchStrategy) ) ) }) @@ -1076,6 +1110,47 @@ async function toRecommendPage (hooks) { //#region collect not suit reasons const notSuitReasonIdToStrategyMap = {} const notSuitConditionHandleMap = { + async companyName() { + blockJobNotSuit.add(targetJobData.jobInfo.encryptId) + if (blockCompanyNameRegMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL && !await page.$('.job-detail-box .job-detail-operate .not-suitable')) { + try { + await hooks.jobMarkedAsNotSuit.promise( + targetJobData, + { + markFrom: ChatStartupFrom.AutoFromRecommendList, + markReason: MarkAsNotSuitReason.COMPANY_NAME_NOT_SUIT, + extInfo: null, + markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL, + jobSource: JobSource[computedSourceList[currentSourceIndex]?.type] + } + ) + } catch { + } + } + else if (blockCompanyNameRegMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) { + try { + await waitForSageTimeOrJustContinue({ + tag: 'beforeJobNotSuitMarked', + hooks + }) + const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.COMPANY_NAME_NOT_SUIT) + await hooks.jobMarkedAsNotSuit.promise( + targetJobData, + { + markFrom: ChatStartupFrom.AutoFromRecommendList, + markReason: MarkAsNotSuitReason.COMPANY_NAME_NOT_SUIT, + extInfo: { + chosenReasonInUi + }, + markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS, + jobSource: JobSource[computedSourceList[currentSourceIndex]?.type] + } + ) + } catch(err) { + console.log(`mark boss inactive failed`, err) + } + } + }, async active() { blockBossNotActive.add(targetJobData.jobInfo.encryptUserId) if (jobNotActiveStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL || !await page.$('.job-detail-box .job-detail-operate .not-suitable')) { @@ -1288,6 +1363,11 @@ async function toRecommendPage (hooks) { } } + if ( + !!blockCompanyNameRegExp && blockCompanyNameRegExp.test(selectedJobData.brandName ?? '') + ) { + notSuitReasonIdToStrategyMap.companyName = blockCompanyNameRegMatchStrategy + } //#region // null // 刚刚活跃 // 今日活跃 // 昨日活跃 // 3日内活跃 // 本周活跃 // 2周内活跃 @@ -1544,10 +1624,13 @@ export async function mainLoop (hooks) { height: 900 - 140, } }) - hooks.puppeteerLaunched?.call() + hooks.puppeteerLaunched?.call(browser) page = (await browser.pages())[0] + hooks.pageGotten?.call(page) //set cookies - hooks.cookieWillSet?.call(bossCookies) + const bossCookies = readStorageFile('boss-cookies.json') + const bossLocalStorage = readStorageFile('boss-local-storage.json') + await hooks.cookieWillSet?.promise(bossCookies) for(let i = 0; i < bossCookies.length; i++){ await page.setCookie(bossCookies[i]); } @@ -1576,7 +1659,10 @@ export async function closeBrowserWindow () { browser?.close() const browserProcess = browser?.process() if (browserProcess) { - process.kill(browserProcess.pid) + try { + process.kill(browserProcess.pid) + } + catch {} } browser = null page = null diff --git a/packages/launch-bosszhipin-login-page-with-preload-extension/extensions/EditThisCookie.zip b/packages/launch-bosszhipin-login-page-with-preload-extension/extensions/EditThisCookie.zip index 3f8724d..5b24d62 100644 Binary files a/packages/launch-bosszhipin-login-page-with-preload-extension/extensions/EditThisCookie.zip and b/packages/launch-bosszhipin-login-page-with-preload-extension/extensions/EditThisCookie.zip differ diff --git a/packages/launch-bosszhipin-login-page-with-preload-extension/utils.mjs b/packages/launch-bosszhipin-login-page-with-preload-extension/utils.mjs index aa66d5d..16b9c03 100644 --- a/packages/launch-bosszhipin-login-page-with-preload-extension/utils.mjs +++ b/packages/launch-bosszhipin-login-page-with-preload-extension/utils.mjs @@ -32,27 +32,28 @@ async function getEditThisCookieZipPath () { return editThisCookieZipPath } +const APP_GEEKGEEKRUN_EDIT_VERSION = 1 export async function ensureEditThisCookie () { let isNeedExtractEditThisCookie = false - const manifestFilePath = path.join(editThisCookieExtensionPath, 'manifest.json') - if (!fs.existsSync( - manifestFilePath - )) { + const GEEKGEEKRUN_EDIT_VERSION_FILE_PATH = path.join(editThisCookieExtensionPath, 'GEEKGEEKRUN_EDIT_VERSION') + let geekgeekrunEditVersion + try { + const fileContent = fs.readFileSync(GEEKGEEKRUN_EDIT_VERSION_FILE_PATH, { encoding: 'utf-8' }) + geekgeekrunEditVersion = Number(fileContent) || 0 + } + catch (err) { + geekgeekrunEditVersion = 0 + } + if (geekgeekrunEditVersion < APP_GEEKGEEKRUN_EDIT_VERSION) { isNeedExtractEditThisCookie = true - } else { - let manifest - try { - manifest = JSON.parse(fs.readFileSync(manifestFilePath, { encoding: 'utf-8' })) - if (!manifest.manifest_version || manifest.manifest_version <= 2) { - isNeedExtractEditThisCookie = true - } - } - catch { - console.log(`未能获取到文件内容`) - isNeedExtractEditThisCookie = true - } + } + const isExtractDoneFlagFilePath = path.join(editThisCookieExtensionPath, 'EXTRACT_DONE') + if ( + !isNeedExtractEditThisCookie && + !fs.existsSync(isExtractDoneFlagFilePath) + ) { + isNeedExtractEditThisCookie = true } - if (isNeedExtractEditThisCookie) { if ( fs.existsSync( @@ -73,5 +74,9 @@ export async function ensureEditThisCookie () { dir: extensionDir } ) + await fs.promises.writeFile( + isExtractDoneFlagFilePath, + '' + ) } } \ No newline at end of file diff --git a/packages/pm/daemon.js b/packages/pm/daemon.js new file mode 100644 index 0000000..8076929 --- /dev/null +++ b/packages/pm/daemon.js @@ -0,0 +1,486 @@ +// 如果是通过 Electron 运行,禁用 GUI 和 Dock 图标 +if (typeof require !== 'undefined') { + try { + const { app } = require('electron'); + if (app) { + // 隐藏 Dock 图标(macOS) + if (process.platform === 'darwin') { + app.dock?.hide(); + } + + // 防止显示窗口 + app.on('ready', () => { + // 不创建任何窗口,保持后台运行 + }); + + // 防止在没有窗口时退出 + app.on('window-all-closed', (e) => { + e.preventDefault(); + // 不退出应用,保持后台运行 + }); + } + } catch (e) { + // 不在 Electron 环境中,忽略 + } +} + +const net = require('net'); +const { spawn } = require('child_process'); +const path = require('path'); +const split2 = require('split2'); +const fs = require('fs') +const { tmpdir } = require('os') +const { randomUUID } = require('crypto') + +const ipcWritePipe = fs.createWriteStream(null, { fd: 3 }) +let ipcSocketName = process.env.GEEKGEEKRUND_PIPE_NAME +if (!ipcSocketName) { + process.env.GEEKGEEKRUND_PIPE_NAME = `geekgeekrun-d_${randomUUID()}` + ipcSocketName = process.env.GEEKGEEKRUND_PIPE_NAME +} +const ipcSocketPath = process.platform === 'win32' + ? `\\\\.\\pipe\\${ipcSocketName}` + : path.join(tmpdir(), `${ipcSocketName}.sock`) + +const workers = new Map(); // workerId -> { process, status, restartCount, socket, latestScreenshot, latestScreenshotAt } +const userProcessClients = new Set(); // GUI客户端连接集合 +const stoppedWorkers = new Set(); // 被用户主动停止的workerId集合,用于防止竞态条件 +const pidToProcessInfoMap = new Map() +const socketToWorkerIdSetMap = new WeakMap() + +// 创建TCP服务器 +const server = net.createServer((socket) => { + console.log('客户端已连接'); + + // 使用 split2 按行分割流式数据,处理 JSONL 格式(每行一个 JSON) + // split2 会自动处理 TCP 分包问题,确保每条完整的消息(以换行符结尾)才会触发 + const splitStream = split2(); + + socket.pipe(splitStream).on('data', (line) => { + const trimmedLine = line.toString().trim(); + if (!trimmedLine) { + return; // 跳过空行 + } + + let _callbackUuid + try { + const message = JSON.parse(trimmedLine); + _callbackUuid = message._callbackUuid + handleMessage(socket, message); + } catch (parseError) { + console.error('解析JSON消息失败:', parseError.message); + console.error('原始数据:', trimmedLine.substring(0, 100)); // 只打印前100个字符 + sendResponse(socket, _callbackUuid, { error: '无效的JSON格式', details: parseError.message }); + } + }); + + splitStream.on('error', (err) => { + console.error('split2 流处理错误:', err); + sendResponse(socket, null, { error: '流处理失败' }); + }); + + socket.on('error', (err) => { + console.error('Socket错误:', err); + }); + + socket.on('close', () => { + console.log('客户端已断开连接'); + // 清理GUI客户端连接 + userProcessClients.delete(socket); + // 清理工具进程连接 + for (const [workerId, workerInfo] of workers.entries()) { + if (workerInfo.socket === socket) { + console.log(`工具进程 ${workerId} 的连接已断开`); + workerInfo.socket = null; + } + } + const workerIdSet = socketToWorkerIdSetMap.get(socket) || new Set() + ;[...workerIdSet].forEach(workerId => { + stopWorker(workerId); + }) + }); +}); + +// 处理消息 +function handleMessage(socket, message) { + console.log('收到消息:', message); + const _callbackUuid = message._callbackUuid + if (message.type === 'ping') { + sendResponse(socket, _callbackUuid, { + success: true, + message: 'pong' + }); + return + } + if (message.type === 'user-process-register') { + // GUI客户端的控制消息 + // 标记为GUI客户端 + if (!userProcessClients.has(socket)) { + userProcessClients.add(socket); + } + sendResponse(socket, _callbackUuid, { + success: true, + }); + return; + } + + const workerId = message.workerId; + const workerInfo = workers.get(workerId); + switch (message.type) { + // 从工具进程发送的消息(数据、心跳、截图等) + case 'check-should-exit': { + const workerId = message.workerId; + const shouldExit = stoppedWorkers.has(workerId) || !workers.has(workerId); + + sendResponse(socket, _callbackUuid, { + workerId: workerId, + shouldExit: shouldExit + }); + + if (shouldExit) { + console.log(`工具进程 ${workerId} 查询是否应该退出,返回: 是`); + } + return + } + case 'worker-to-gui-message': { + // 转发工具进程消息到GUI客户端 + broadcastToGUI({ + type: 'worker-to-gui-message', + workerId: workerId, + data: message.data || message, + timestamp: Date.now() + }); + return + } + case 'worker-screenshot': { + if (workerInfo && message.data && message.data.screenshot /* && workerInfo.socket === socket */) { + // 如果携带截图信息,则在守护进程内缓存一份,供 get-status 使用 + try { + workerInfo.latestScreenshot = message.data.screenshot; + workerInfo.latestScreenshotAt = Date.now(); + } catch (e) { + console.error('缓存 worker 截图信息失败:', e); + } + } + return + } + + // 从GUI进程发送的消息(数据、心跳、截图等) + case 'start-worker': { + const { + workerId, + command, + args, + env + } = message + if (workers.has(workerId)) { + console.log(`工具进程 ${workerId} 已在运行`); + return; + } + startWorker({ + workerId, + command, + args, + env + }); + sendResponse(socket, _callbackUuid, { + success: true, + message: `工具进程 ${message.workerId} 已启动`, + workerId: message.workerId + }); + let socketToWorkerIdSet = socketToWorkerIdSetMap.get(socket) + if ( + !(socketToWorkerIdSet instanceof Set) + ) { + socketToWorkerIdSet = new Set() + socketToWorkerIdSetMap.set( + socket, + socketToWorkerIdSet + ) + } + socketToWorkerIdSet.add(workerId) + return + } + + case 'stop-worker': { + stopWorker(message.workerId); + sendResponse(socket, _callbackUuid, { + success: true, + message: `工具进程 ${message.workerId} 已停止`, + workerId: message.workerId + }); + return + } + + case 'get-status': { + const status = getWorkersStatus(); + sendResponse(socket, _callbackUuid, { + success: true, + workers: status + }); + return + } + + default: { + sendResponse(socket, _callbackUuid, { error: '未知的消息类型' }); + } + } +} + +// 启动工具进程 +function startWorker({ workerId, command, args, env }, restartCount = 0) { + const noAutoRestartExitCodeSet = new Set([0]); + (env.GEEKGEEKRUND_NO_AUTO_RESTART_EXIT_CODE ?? '') + .split(',') + .map(n => parseInt(n)) + .forEach(n => noAutoRestartExitCodeSet.add(n)) + + console.log(`启动工具进程: ${workerId}${restartCount > 0 ? ` (重启第${restartCount}次)` : ''}`); + // 添加参数使工具进程在后台运行,不显示 UI + const workerProcess = spawn(command, args, { + stdio: ['ignore', 'pipe', 'pipe'], + env: { + ...process.env, + ...env, + GEEKGEEKRUND_WORKER_ID: workerId, + GEEKGEEKRUND_RESTART_COUNT: restartCount.toString(), + } + }); + + let output = ''; + workerProcess.stdout.on('data', (data) => { + output += data.toString(); + console.log(`工具进程 ${workerId} 输出:`, data.toString().trim()); + }); + + workerProcess.stderr.on('data', (data) => { + console.error(`工具进程 ${workerId} 错误:`, data.toString().trim()); + }); + + workerProcess.on('exit', (code, signal) => { + console.log(`工具进程 ${workerId} 退出,代码: ${code}, 信号: ${signal}`); + const workerInfo = pidToProcessInfoMap.get(workerProcess.pid) + if (workerInfo) { + pidToProcessInfoMap.delete(workerProcess.pid) + // 关闭工具进程的TCP连接 + if (workerInfo.socket) { + workerInfo.socket.destroy(); + } + + const shouldRestart = !noAutoRestartExitCodeSet.has(code) // && code !== null; + // 使用当前的 restartCount 加1,而不是从 workerInfo 中取(因为可能已经被删除) + const restartCount = (workerInfo.restartCount || 0) + 1; + + workers.delete(workerId); + + // 通知GUI客户端工具进程已退出 + broadcastToGUI({ + type: 'worker-exited', + workerId: workerId, + code: code, + signal: signal, + restarting: shouldRestart && !stoppedWorkers.has(workerId), + restartCount: restartCount + }); + + // 如果进程意外退出(非正常停止),且不在停止列表中,自动重启 + if (shouldRestart && !stoppedWorkers.has(workerId)) { + console.log(`工具进程 ${workerId} 意外退出,准备重启 (第${restartCount}次)`); + + // 延迟重启,避免频繁重启 + setTimeout(() => { + // 再次检查:确保worker不在停止列表中,且当前没有运行 + if (!workers.has(workerId) && !stoppedWorkers.has(workerId)) { + startWorker({ workerId, command, args, env }, restartCount); + } else if (stoppedWorkers.has(workerId)) { + console.log(`工具进程 ${workerId} 在重启前被标记为停止,取消重启`); + // 从停止列表中移除,因为已经处理完毕 + stoppedWorkers.delete(workerId); + broadcastToGUI({ + type: 'worker-exited', + workerId: workerId, + code: code, + signal: signal, + restarting: false, + restartCount: restartCount + }); + } + }, 2000); + } else if (stoppedWorkers.has(workerId)) { + // 如果是在停止列表中,清理标记 + console.log(`工具进程 ${workerId} 已停止,清理停止标记`); + stoppedWorkers.delete(workerId); + } + } else { + // 如果workerInfo不存在,可能是已经被stopWorker删除 + // 检查停止列表,如果在则清理 + if (stoppedWorkers.has(workerId)) { + stoppedWorkers.delete(workerId); + } + } + }); + + workerProcess.on('error', (err) => { + console.log(err) + }) + + const workerInfo = { + process: workerProcess, + status: 'running', + startTime: Date.now(), + restartCount, // 使用传入的重启次数 + // socket: null, // 工具进程的TCP连接,稍后由工具进程注册 + // lastHeartbeat: null, + command, + args, + env, + workerId, + } + workers.set(workerId, workerInfo); + pidToProcessInfoMap.set(workerProcess.pid, workerInfo); + // 定期发送状态更新 + broadcastStatus(); +} + +// 停止工具进程 +function stopWorker(workerId) { + const workerInfo = workers.get(workerId); + + // 无论workerInfo是否存在,都添加到停止列表,防止竞态条件 + stoppedWorkers.add(workerId); + console.log(`停止工具进程: ${workerId} (已添加到停止列表)`); + if (!workerInfo) { + console.log(`工具进程 ${workerId} 不存在,但已标记为停止(防止重启)`); + // 通知GUI客户端 + broadcastToGUI({ + type: 'worker-disconnected', + workerId: workerId, + message: `工具进程 ${workerId} 已停止` + }); + // 延迟发送状态更新 + setTimeout(() => broadcastStatus(), 500); + return; + } + + // 关闭工具进程的TCP连接 + if (workerInfo.socket) { + workerInfo.socket.destroy(); + } + + workerInfo.process.kill('SIGTERM'); + workers.delete(workerId); + + // 通知GUI客户端 + broadcastToGUI({ + type: 'worker-disconnected', + workerId: workerId, + message: `工具进程 ${workerId} 已断开` + }); + + // 延迟发送状态更新 + setTimeout(() => broadcastStatus(), 500); +} + +// 获取所有工具进程状态 +function getWorkersStatus() { + const status = []; + for (const [workerId, workerInfo] of workers.entries()) { + status.push({ + workerId, + status: workerInfo.status, + uptime: Date.now() - workerInfo.startTime, + restartCount: workerInfo.restartCount || 0, + // connected: workerInfo.socket !== null && !workerInfo.socket.destroyed, + // lastHeartbeat: workerInfo.lastHeartbeat, + command: workerInfo.command, + args: workerInfo.args, + pid: workerInfo.process?.pid, + // 最新截图(通常是 data URL 或 base64 字符串),以及截图时间 + screenshot: workerInfo.latestScreenshot ?? null, + screenshotAt: workerInfo.latestScreenshotAt ?? null, + }); + } + return status; +} + +// 广播状态更新给所有GUI客户端 +function broadcastStatus() { + const status = getWorkersStatus(); + broadcastToGUI({ + type: 'status', + workers: status + }); +} + +// 广播消息给所有GUI客户端 +function broadcastToGUI(message) { + userProcessClients.forEach(socket => { + if (!socket.destroyed) { + try { + sendResponse(socket, null, message); + } catch (e) { + console.error('广播消息失败:', e); + userProcessClients.delete(socket); + } + } + }); +} + +// 发送响应 +function sendResponse(socket, _callbackUuid, response) { + try { + socket.write(JSON.stringify({ + ...response, + _callbackUuid + }) + '\n'); + } catch (e) { + console.error('发送响应失败:', e); + } +} + +// 启动服务器 +new Promise((resolve, reject) => { + server.once('error', (err) => { + ipcWritePipe.write( + JSON.stringify({ type: 'DAEMON_FATAL', error: err }), + (err) => void err + ) + reject(err) + }) + server.listen(ipcSocketPath, (err) => { + console.log(`守护进程服务器运行在端口 ${ipcSocketPath}`); + ipcWritePipe.write( + JSON.stringify({ type: 'DAEMON_READY' }), + (err) => void err + ) + if (!err) { + resolve(true) + } + else { + reject(err) + } + }); +}) + +// 优雅关闭 +process.on('SIGTERM', () => { + console.log('收到SIGTERM信号,正在关闭所有工具进程...'); + for (const [workerId, workerInfo] of workers.entries()) { + workerInfo.process.kill('SIGTERM'); + } + server.close(() => { + console.log('守护进程已关闭'); + process.exit(0); + }); +}); + +process.on('SIGINT', () => { + console.log('收到SIGINT信号,正在关闭所有工具进程...'); + for (const [workerId, workerInfo] of workers.entries()) { + workerInfo.process.kill('SIGTERM'); + } + server.close(() => { + console.log('守护进程已关闭'); + process.exit(0); + }); +}); diff --git a/packages/pm/package.json b/packages/pm/package.json new file mode 100644 index 0000000..093d9d9 --- /dev/null +++ b/packages/pm/package.json @@ -0,0 +1,25 @@ +{ + "name": "@geekgeekrun/pm", + "version": "0.0.1", + "description": "Electron多进程架构", + "main": "main.js", + "scripts": { + "start:daemon": "electron ./daemon", + "dev:daemon": "electron ./daemon --dev", + "start:worker": "electron ./worker", + "dev:worker": "electron ./worker --dev" + }, + "keywords": [ + "electron", + "multi-process" + ], + "author": "", + "license": "MIT", + "devDependencies": { + "electron": "28.2.0" + }, + "dependencies": { + "JSONStream": "^1.3.5", + "split2": "^4.2.0" + } +} diff --git a/packages/run-core-of-geek-auto-start-chat-with-boss/main.mjs b/packages/run-core-of-geek-auto-start-chat-with-boss/main.mjs index 8b75b90..6f19d1b 100644 --- a/packages/run-core-of-geek-auto-start-chat-with-boss/main.mjs +++ b/packages/run-core-of-geek-auto-start-chat-with-boss/main.mjs @@ -47,7 +47,8 @@ const main = async () => { } const hooks = { daemonInitialized: new AsyncSeriesHook(), - puppeteerLaunched: new SyncHook(), + puppeteerLaunched: new SyncHook(['browser']), + pageGotten: new SyncHook(['page']), pageLoaded: new SyncHook(), cookieWillSet: new SyncHook(['cookies']), userInfoResponse: new AsyncSeriesHook(['userInfo']), diff --git a/packages/sqlite-plugin/package.json b/packages/sqlite-plugin/package.json index d61ad33..f0da872 100644 --- a/packages/sqlite-plugin/package.json +++ b/packages/sqlite-plugin/package.json @@ -4,12 +4,15 @@ "description": "", "main": "dist/index.js", "dependencies": { + "better-sqlite3": "12.6.2", "cli-highlight": "^2.1.11", + "lodash": "^4.17.21", "reflect-metadata": "^0.2.1", - "sqlite3": "5.1.6", - "typeorm": "0.3.11" + "typeorm": "0.3.28" }, "devDependencies": { + "@types/lodash": "^4.17.23", + "node-gyp": "^12.2.0", "ts-node": "^10.9.2", "typescript": "^5.3.3" }, diff --git a/packages/sqlite-plugin/src/entity/AutoStartChatRunRecord.ts b/packages/sqlite-plugin/src/entity/AutoStartChatRunRecord.ts index fdeb0b3..41969b8 100644 --- a/packages/sqlite-plugin/src/entity/AutoStartChatRunRecord.ts +++ b/packages/sqlite-plugin/src/entity/AutoStartChatRunRecord.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { Entity, Column, PrimaryGeneratedColumn } = requireTypeorm(); +import * as typeorm from 'typeorm'; +const { Entity, Column, PrimaryGeneratedColumn } = typeorm; @Entity() export class AutoStartChatRunRecord { diff --git a/packages/sqlite-plugin/src/entity/BossActiveStatusRecord.ts b/packages/sqlite-plugin/src/entity/BossActiveStatusRecord.ts index bb81e30..a3740dd 100644 --- a/packages/sqlite-plugin/src/entity/BossActiveStatusRecord.ts +++ b/packages/sqlite-plugin/src/entity/BossActiveStatusRecord.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { Entity, Column, PrimaryGeneratedColumn } = requireTypeorm(); +import * as typeorm from 'typeorm'; +const { Entity, Column, PrimaryGeneratedColumn } = typeorm; @Entity() export class BossActiveStatusRecord { diff --git a/packages/sqlite-plugin/src/entity/BossInfo.ts b/packages/sqlite-plugin/src/entity/BossInfo.ts index 1971ff2..d51013b 100644 --- a/packages/sqlite-plugin/src/entity/BossInfo.ts +++ b/packages/sqlite-plugin/src/entity/BossInfo.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { Entity, Column, PrimaryColumn } = requireTypeorm(); +import * as typeorm from 'typeorm'; +const { Entity, Column, PrimaryColumn } = typeorm; @Entity() export class BossInfo { diff --git a/packages/sqlite-plugin/src/entity/BossInfoChangeLog.ts b/packages/sqlite-plugin/src/entity/BossInfoChangeLog.ts index 48173dc..f6defd9 100644 --- a/packages/sqlite-plugin/src/entity/BossInfoChangeLog.ts +++ b/packages/sqlite-plugin/src/entity/BossInfoChangeLog.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { Entity, Column, PrimaryGeneratedColumn } = requireTypeorm() +import * as typeorm from 'typeorm'; +const { Entity, Column, PrimaryGeneratedColumn } = typeorm @Entity() export class BossInfoChangeLog { diff --git a/packages/sqlite-plugin/src/entity/ChatMessageRecord.ts b/packages/sqlite-plugin/src/entity/ChatMessageRecord.ts index fc29e3d..d405a01 100644 --- a/packages/sqlite-plugin/src/entity/ChatMessageRecord.ts +++ b/packages/sqlite-plugin/src/entity/ChatMessageRecord.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { Entity, Column, PrimaryGeneratedColumn } = requireTypeorm() +import * as typeorm from 'typeorm'; +const { Entity, Column, PrimaryGeneratedColumn } = typeorm @Entity() export class ChatMessageRecord { diff --git a/packages/sqlite-plugin/src/entity/ChatStartupLog.ts b/packages/sqlite-plugin/src/entity/ChatStartupLog.ts index a8d2481..7cd868e 100644 --- a/packages/sqlite-plugin/src/entity/ChatStartupLog.ts +++ b/packages/sqlite-plugin/src/entity/ChatStartupLog.ts @@ -1,6 +1,6 @@ import { JobSource } from "../enums"; -import { requireTypeorm } from "../utils/module-loader"; -const { Entity, Column, PrimaryGeneratedColumn } = requireTypeorm() +import * as typeorm from 'typeorm'; +const { Entity, Column, PrimaryGeneratedColumn } = typeorm export enum ChatStartupFrom { AutoFromRecommendList = null, diff --git a/packages/sqlite-plugin/src/entity/CompanyInfo.ts b/packages/sqlite-plugin/src/entity/CompanyInfo.ts index 58ca313..7a9c3b2 100644 --- a/packages/sqlite-plugin/src/entity/CompanyInfo.ts +++ b/packages/sqlite-plugin/src/entity/CompanyInfo.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { Entity, Column, PrimaryColumn } = requireTypeorm() +import * as typeorm from 'typeorm'; +const { Entity, Column, PrimaryColumn } = typeorm @Entity() export class CompanyInfo { diff --git a/packages/sqlite-plugin/src/entity/CompanyInfoChangeLog.ts b/packages/sqlite-plugin/src/entity/CompanyInfoChangeLog.ts index 4d226ce..57f0118 100644 --- a/packages/sqlite-plugin/src/entity/CompanyInfoChangeLog.ts +++ b/packages/sqlite-plugin/src/entity/CompanyInfoChangeLog.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { Entity, PrimaryGeneratedColumn, Column } = requireTypeorm() +import * as typeorm from 'typeorm'; +const { Entity, PrimaryGeneratedColumn, Column } = typeorm @Entity() export class CompanyInfoChangeLog { diff --git a/packages/sqlite-plugin/src/entity/JobHireStatusRecord.ts b/packages/sqlite-plugin/src/entity/JobHireStatusRecord.ts index d2f1aaf..c7d3f29 100644 --- a/packages/sqlite-plugin/src/entity/JobHireStatusRecord.ts +++ b/packages/sqlite-plugin/src/entity/JobHireStatusRecord.ts @@ -1,6 +1,6 @@ import { JobHireStatus } from "../enums"; -import { requireTypeorm } from "../utils/module-loader"; -const { Entity, Column, PrimaryColumn } = requireTypeorm(); +import * as typeorm from 'typeorm'; +const { Entity, Column, PrimaryColumn } = typeorm; @Entity() export class JobHireStatusRecord { diff --git a/packages/sqlite-plugin/src/entity/JobInfo.ts b/packages/sqlite-plugin/src/entity/JobInfo.ts index d30f6e7..a88a3a3 100644 --- a/packages/sqlite-plugin/src/entity/JobInfo.ts +++ b/packages/sqlite-plugin/src/entity/JobInfo.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { Entity, Column, PrimaryColumn } = requireTypeorm() +import * as typeorm from 'typeorm'; +const { Entity, Column, PrimaryColumn } = typeorm @Entity() export class JobInfo { diff --git a/packages/sqlite-plugin/src/entity/JobInfoChangeLog.ts b/packages/sqlite-plugin/src/entity/JobInfoChangeLog.ts index a90190f..48ad06b 100644 --- a/packages/sqlite-plugin/src/entity/JobInfoChangeLog.ts +++ b/packages/sqlite-plugin/src/entity/JobInfoChangeLog.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { Entity, PrimaryGeneratedColumn, Column } = requireTypeorm() +import * as typeorm from 'typeorm'; +const { Entity, PrimaryGeneratedColumn, Column } = typeorm @Entity() export class JobInfoChangeLog { diff --git a/packages/sqlite-plugin/src/entity/LlmModelUsageRecord.ts b/packages/sqlite-plugin/src/entity/LlmModelUsageRecord.ts index db5c179..a1f0301 100644 --- a/packages/sqlite-plugin/src/entity/LlmModelUsageRecord.ts +++ b/packages/sqlite-plugin/src/entity/LlmModelUsageRecord.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { Entity, PrimaryGeneratedColumn, Column, Index } = requireTypeorm() +import * as typeorm from 'typeorm'; +const { Entity, PrimaryGeneratedColumn, Column, Index } = typeorm @Entity() @Index(["providerCompleteApiUrl", "model", "providerApiSecret"]) diff --git a/packages/sqlite-plugin/src/entity/MarkAsNotSuitLog.ts b/packages/sqlite-plugin/src/entity/MarkAsNotSuitLog.ts index c3325fc..bd679d5 100644 --- a/packages/sqlite-plugin/src/entity/MarkAsNotSuitLog.ts +++ b/packages/sqlite-plugin/src/entity/MarkAsNotSuitLog.ts @@ -1,7 +1,7 @@ import { JobSource, MarkAsNotSuitOp, MarkAsNotSuitReason } from "../enums"; -import { requireTypeorm } from "../utils/module-loader"; +import * as typeorm from 'typeorm'; import { ChatStartupFrom } from "./ChatStartupLog"; -const { Entity, Column, PrimaryGeneratedColumn } = requireTypeorm() +const { Entity, Column, PrimaryGeneratedColumn } = typeorm @Entity() export class MarkAsNotSuitLog { diff --git a/packages/sqlite-plugin/src/entity/UserInfo.ts b/packages/sqlite-plugin/src/entity/UserInfo.ts index 665f1f7..1f73de3 100644 --- a/packages/sqlite-plugin/src/entity/UserInfo.ts +++ b/packages/sqlite-plugin/src/entity/UserInfo.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { Entity, Column, PrimaryColumn } = requireTypeorm() +import * as typeorm from 'typeorm'; +const { Entity, Column, PrimaryColumn } = typeorm @Entity() export class UserInfo { diff --git a/packages/sqlite-plugin/src/entity/VBossLibrary.ts b/packages/sqlite-plugin/src/entity/VBossLibrary.ts index d14959d..cbe83ff 100644 --- a/packages/sqlite-plugin/src/entity/VBossLibrary.ts +++ b/packages/sqlite-plugin/src/entity/VBossLibrary.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { ViewEntity, ViewColumn } = requireTypeorm(); +import * as typeorm from 'typeorm'; +const { ViewEntity, ViewColumn } = typeorm; @ViewEntity({ expression: `SELECT boss_info.encryptBossId, diff --git a/packages/sqlite-plugin/src/entity/VChatStartupLog.ts b/packages/sqlite-plugin/src/entity/VChatStartupLog.ts index 49126c9..7aac526 100644 --- a/packages/sqlite-plugin/src/entity/VChatStartupLog.ts +++ b/packages/sqlite-plugin/src/entity/VChatStartupLog.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { ViewEntity, ViewColumn } = requireTypeorm(); +import * as typeorm from 'typeorm'; +const { ViewEntity, ViewColumn } = typeorm; @ViewEntity({ expression: `SELECT job_info.*, diff --git a/packages/sqlite-plugin/src/entity/VCompanyLibrary.ts b/packages/sqlite-plugin/src/entity/VCompanyLibrary.ts index 5ac52b2..5cfa87b 100644 --- a/packages/sqlite-plugin/src/entity/VCompanyLibrary.ts +++ b/packages/sqlite-plugin/src/entity/VCompanyLibrary.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { ViewEntity, ViewColumn } = requireTypeorm(); +import * as typeorm from 'typeorm'; +const { ViewEntity, ViewColumn } = typeorm; @ViewEntity({ expression: `SELECT company_info.* diff --git a/packages/sqlite-plugin/src/entity/VJobLibrary.ts b/packages/sqlite-plugin/src/entity/VJobLibrary.ts index c3c3c58..8757135 100644 --- a/packages/sqlite-plugin/src/entity/VJobLibrary.ts +++ b/packages/sqlite-plugin/src/entity/VJobLibrary.ts @@ -1,5 +1,5 @@ -import { requireTypeorm } from "../utils/module-loader"; -const { ViewEntity, ViewColumn } = requireTypeorm(); +import * as typeorm from 'typeorm'; +const { ViewEntity, ViewColumn } = typeorm; @ViewEntity({ expression: `SELECT job_info.*, diff --git a/packages/sqlite-plugin/src/entity/VMarkAsNotSuitLog.ts b/packages/sqlite-plugin/src/entity/VMarkAsNotSuitLog.ts index 81b7730..60d54bb 100644 --- a/packages/sqlite-plugin/src/entity/VMarkAsNotSuitLog.ts +++ b/packages/sqlite-plugin/src/entity/VMarkAsNotSuitLog.ts @@ -1,7 +1,7 @@ -import { requireTypeorm } from "../utils/module-loader"; +import * as typeorm from 'typeorm'; import { ChatStartupFrom } from "./ChatStartupLog"; import { MarkAsNotSuitReason } from "../enums"; -const { ViewEntity, ViewColumn } = requireTypeorm(); +const { ViewEntity, ViewColumn } = typeorm; @ViewEntity({ expression: `SELECT job_info.*, diff --git a/packages/sqlite-plugin/src/enums.ts b/packages/sqlite-plugin/src/enums.ts index c2ad8e1..0a32b27 100644 --- a/packages/sqlite-plugin/src/enums.ts +++ b/packages/sqlite-plugin/src/enums.ts @@ -6,6 +6,7 @@ export enum MarkAsNotSuitReason { JOB_CITY_NOT_SUIT = 4, JOB_WORK_EXP_NOT_SUIT = 5, JOB_SALARY_NOT_SUIT = 6, + COMPANY_NAME_NOT_SUIT = 7, } export enum MarkAsNotSuitOp { diff --git a/packages/sqlite-plugin/src/index.ts b/packages/sqlite-plugin/src/index.ts index d00457c..e9bb038 100644 --- a/packages/sqlite-plugin/src/index.ts +++ b/packages/sqlite-plugin/src/index.ts @@ -1,6 +1,5 @@ import "reflect-metadata"; import { type DataSource } from "typeorm"; -import { requireTypeorm } from "./utils/module-loader"; import { BossInfo } from "./entity/BossInfo"; import { BossInfoChangeLog } from "./entity/BossInfoChangeLog"; @@ -22,7 +21,6 @@ import { ChatMessageRecord } from './entity/ChatMessageRecord' import { LlmModelUsageRecord } from './entity/LlmModelUsageRecord' import { JobHireStatusRecord } from './entity/JobHireStatusRecord' -import sqlite3 from 'sqlite3'; import { saveChatStartupRecord, saveJobInfoFromRecommendPage, @@ -40,17 +38,17 @@ import { AddColumnForMarkAsNotSuitLog1746092370665 } from "./migrations/17460923 import { Init1000000000000 } from "./migrations/1000000000000-Init"; import { AddJobSourceColumnForChatStartupLogAndMarkAsNotSuitLog1752380078526 } from "./migrations/1752380078526-AddJobSourceColumnForChatStartupLogAndMarkAsNotSuitLog"; import { AddJobHireStatusTable1766466476822 } from "./migrations/1766466476822-AddJobHireStatusTable"; -const lodashImportPromise = import('lodash-es') +import chunk from 'lodash/chunk' +import * as typeorm from 'typeorm' export function initDb(dbFilePath) { - const { DataSource } = requireTypeorm() + const { DataSource } = typeorm const appDataSource = new DataSource({ - type: "sqlite", + type: "better-sqlite3", synchronize: false, logging: true, logger: "simple-console", database: dbFilePath, - driver: sqlite3, // The important line entities: [ ChatStartupLog, BossInfo, @@ -97,6 +95,25 @@ export default class SqlitePlugin { userInfo = null apply(hooks) { + hooks.pageGotten.tap( + 'SqlitePlugin', + (page) => { + page.on('response', async (response) => { + const ds = await this.initPromise; + if (response.url().startsWith('https://www.zhipin.com/wapi/zpgeek/job/detail.json')) { + const data = await response.json() + if (data.code === 0) { + await saveJobInfoFromRecommendPage(await ds, data.zpData) + await saveJobHireStatusRecord(await ds, { + encryptJobId: data.zpData.jobInfo.encryptId, + hireStatus: JobHireStatus.HIRING, + lastSeenDate: new Date() + }) + } + } + }) + } + ) hooks.userInfoResponse.tapPromise( "SqlitePlugin", async (userInfoResponse) => { @@ -183,7 +200,6 @@ export default class SqlitePlugin { if (chattedJobIds.length === 0) { return } - const { chunk } = await lodashImportPromise const chattedJobIdChunks = chunk(chattedJobIds, 200) const chattedBossIds = []; for (const chattedJobIdChunk of chattedJobIdChunks) { diff --git a/packages/sqlite-plugin/src/utils/module-loader.ts b/packages/sqlite-plugin/src/utils/module-loader.ts deleted file mode 100644 index ff7ea5d..0000000 --- a/packages/sqlite-plugin/src/utils/module-loader.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function requireTypeorm () { - const importResult = require('typeorm') - return importResult -} \ No newline at end of file diff --git a/packages/ui/electron.vite.config.ts b/packages/ui/electron.vite.config.ts index 5939052..eb529c4 100644 --- a/packages/ui/electron.vite.config.ts +++ b/packages/ui/electron.vite.config.ts @@ -18,8 +18,6 @@ export default defineConfig({ plugins: [ externalizeDepsPlugin({ exclude: [ - '@geekgeekrun/geek-auto-start-chat-with-boss', - '@geekgeekrun/dingtalk-plugin', '@geekgeekrun/utils', 'find-chrome-bin', '@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension' diff --git a/packages/ui/package.json b/packages/ui/package.json index 56a1eea..e63e74f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,7 +1,7 @@ { "name": "geekgeekrun-ui", - "version": "0.14.1", - "description": "Boss 炸弹 - 自动开聊Boss,助力每位打工人求职!", + "version": "0.14.2", + "description": "BOSS 炸弹 - 自动开聊BOSS,助力每位打工人求职!", "main": "./out/main/index.js", "author": "geekgeekrun", "productName": "GeekGeekRun", @@ -9,10 +9,6 @@ "scripts": { "start": "node scripts/run-build-sqlite-plugin.mjs && electron-vite preview", "dev": "node scripts/run-build-sqlite-plugin.mjs && electron-vite dev", - "dev:geek-auto-start-chat-with-boss-main-only": "cross-env MAIN_BOSSGEEKGO_UI_RUN_MODE=geekAutoStartWithBossMain pnpm run dev", - "dev:geek-auto-start-chat-with-boss-daemon-only": "cross-env MAIN_BOSSGEEKGO_UI_RUN_MODE=geekAutoStartWithBossDaemon pnpm run dev", - "dev:check-and-download-dependencies-for-init-only": "cross-env MAIN_BOSSGEEKGO_UI_RUN_MODE=checkAndDownloadDependenciesForInit pnpm run dev", - "dev:launch-bosszhipin-login-page-with-preload-extension-only": "cross-env MAIN_BOSSGEEKGO_UI_RUN_MODE=launchBossZhipinLoginPageWithPreloadExtension pnpm run dev", "build": "node scripts/run-build-sqlite-plugin.mjs && electron-vite build", "format": "prettier --write .", "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix", @@ -30,16 +26,15 @@ "dependencies": { "@electron-toolkit/preload": "^3.0.0", "@electron-toolkit/utils": "^3.0.0", - "@geekgeekrun/dingtalk-plugin": "workspace:*", - "@geekgeekrun/geek-auto-start-chat-with-boss": "workspace:*", "@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension": "workspace:*", + "@geekgeekrun/pm": "workspace:*", "@geekgeekrun/puppeteer-extra-plugin-laodeng": "workspace:*", "@geekgeekrun/sqlite-plugin": "workspace:*", "@geekgeekrun/utils": "workspace:*", "JSONStream": "^1.3.5", "diff": "^7.0.0", "electron-updater": "^6.1.7", - "lodash-es": "^4.17.21", + "lodash": "^4.17.21", "minimist": "^1.2.8", "mitt": "^3.0.1", "node-machine-id": "^1.1.12", @@ -53,10 +48,13 @@ "@electron-toolkit/eslint-config": "^1.0.2", "@electron-toolkit/eslint-config-ts": "^1.0.1", "@electron-toolkit/tsconfig": "^1.0.1", + "@geekgeekrun/dingtalk-plugin": "workspace:*", + "@geekgeekrun/geek-auto-start-chat-with-boss": "workspace:*", "@inquirer/prompts": "^7.0.1", "@inquirer/select": "^4.0.1", "@puppeteer/browsers": "^2.11.0", "@rushstack/eslint-patch": "^1.7.1", + "@types/lodash": "^4.17.23", "@types/node": "^18.19.9", "@unocss/preset-attributify": "^0.58.5", "@unocss/preset-icons": "^0.58.5", @@ -66,13 +64,13 @@ "@vue/eslint-config-typescript": "^12.0.0", "animate.css": "^4.1.1", "cross-env": "^7.0.3", - "electron": "28.2.0", + "electron": "39.2.7", "electron-builder": "^24.9.1", "electron-vite": "^2.0.0", "element-plus": "^2.8.4", "eslint": "^8.56.0", "eslint-plugin-vue": "^9.20.1", - "find-chrome-bin": "^2.0.4", + "find-chrome-bin": "2.0.4", "js-yaml": "^4.1.0", "normalize.css": "^8.0.1", "prettier": "^3.2.4", @@ -89,4 +87,4 @@ "vue-router": "^4.2.5", "vue-tsc": "^1.8.27" } -} \ No newline at end of file +} diff --git a/packages/ui/src/common/build-info.json b/packages/ui/src/common/build-info.json index ff23a81..8558c96 100644 --- a/packages/ui/src/common/build-info.json +++ b/packages/ui/src/common/build-info.json @@ -1,7 +1,7 @@ { - "version": "0.14.1", - "buildVersion": 31, - "buildTime": 1769428838471, - "buildHash": "a92b9ce0403a8fc816568cf3eb106a89a6afddf0", + "version": "0.14.2", + "buildVersion": 32, + "buildTime": 1770299394728, + "buildHash": "1d5fdb9d54c569cb53035dfef0f6157c899ccb06", "name": "geekgeekrun-ui" } \ No newline at end of file diff --git a/packages/ui/src/common/constant.ts b/packages/ui/src/common/constant.ts index 1570f00..6e8d81e 100644 --- a/packages/ui/src/common/constant.ts +++ b/packages/ui/src/common/constant.ts @@ -1 +1,2 @@ export const SINGLE_ITEM_DEFAULT_SERVE_WEIGHT = 1 +export const EXPECT_CHROMIUM_BUILD_ID = '139.0.7258.154' diff --git a/packages/ui/src/common/enums/auto-start-chat.ts b/packages/ui/src/common/enums/auto-start-chat.ts index ff0cc95..2be778a 100644 --- a/packages/ui/src/common/enums/auto-start-chat.ts +++ b/packages/ui/src/common/enums/auto-start-chat.ts @@ -5,8 +5,7 @@ export enum AUTO_CHAT_ERROR_EXIT_CODE { ERR_INTERNET_DISCONNECTED = 83, ACCESS_IS_DENIED = 84, PUPPETEER_IS_NOT_EXECUTABLE = 85, - AUTO_START_CHAT_DAEMON_PROCESS_SUICIDE = 86, - AUTO_START_CHAT_MAIN_PROCESS_SUICIDE = 87, + LLM_UNAVAILABLE = 86, } export enum RECHAT_CONTENT_SOURCE { @@ -18,3 +17,9 @@ export enum RECHAT_LLM_FALLBACK { SEND_LOOK_FORWARD_EMOTION = 1, EXIT_REMINDER_PROGRAM = 2 } + +export enum RUNNING_STATUS_ENUM { + RUNNING = 0, + NORMAL_EXITED = 1, + ERROR_EXITED = 2 +} diff --git a/packages/ui/src/common/prerequisite-step-by-step-check.ts b/packages/ui/src/common/prerequisite-step-by-step-check.ts new file mode 100644 index 0000000..1442ceb --- /dev/null +++ b/packages/ui/src/common/prerequisite-step-by-step-check.ts @@ -0,0 +1,18 @@ +export const getAutoStartChatSteps = () => [ + { + id: 'worker-launch', + describe: '启动子进程' + }, + { + id: 'puppeteer-executable-check', + describe: 'Puppeteer 可执行程序检查' + }, + { + id: 'basic-cookie-check', + describe: 'Cookie 格式检查' + }, + { + id: 'login-status-check', + describe: '登录状态检查' + } +] diff --git a/packages/ui/src/main/constant.ts b/packages/ui/src/main/constant.ts new file mode 100644 index 0000000..57de845 --- /dev/null +++ b/packages/ui/src/main/constant.ts @@ -0,0 +1,4 @@ +import path from 'node:path' +import os from 'node:os' + +export const cacheDir = path.join(os.homedir(), '.geekgeekrun', 'cache') diff --git a/packages/ui/src/main/features/config-with-browser-assistant.ts b/packages/ui/src/main/features/config-with-browser-assistant.ts new file mode 100644 index 0000000..27c315c --- /dev/null +++ b/packages/ui/src/main/features/config-with-browser-assistant.ts @@ -0,0 +1,26 @@ +import { ipcMain } from 'electron' +import { + createBrowserAssistantWindow, + browserAssistantWindow +} from '../window/browserAssistantWindow' + +export async function configWithBrowserAssistant({ windowOption, autoFind } = {}) { + return new Promise((resolve, reject) => { + createBrowserAssistantWindow({ ...windowOption }, { autoFind }) + + let processDone = false + function handler() { + processDone = true + browserAssistantWindow.close() + } + ipcMain.once('browser-config-saved', handler) + browserAssistantWindow.once('closed', () => { + ipcMain.off('browser-config-saved', handler) + if (processDone) { + resolve(true) + } else { + reject(new Error('USER_CANCELLED_CONFIG_BROWSER')) + } + }) + }) +} diff --git a/packages/ui/src/main/features/cookie-invalid-handle-plugin.ts b/packages/ui/src/main/features/cookie-invalid-handle-plugin.ts new file mode 100644 index 0000000..529292b --- /dev/null +++ b/packages/ui/src/main/features/cookie-invalid-handle-plugin.ts @@ -0,0 +1,93 @@ +import { sendToDaemon } from '../flow/OPEN_SETTING_WINDOW/connect-to-daemon' +import minimist from 'minimist' +import { loginWithCookieAssistant } from './login-with-cookie-assistant' +import { checkCookieListFormat } from '../../common/utils/cookie' +import { sleep } from '@geekgeekrun/utils/sleep.mjs' +import { readStorageFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' + +const runRecordId = minimist(process.argv.slice(2))['run-record-id'] ?? null +export class CookieInvalidHandlePlugin { + apply(hooks) { + hooks.cookieWillSet.tapPromise('CookieInvalidHandlePlugin', async (cookies) => { + let isValid = checkCookieListFormat(cookies) + while (!isValid) { + try { + // popup login dialog, then update login status + await loginWithCookieAssistant() + await sleep(2000) + const newCookies = readStorageFile('boss-cookies.json') + isValid = checkCookieListFormat(newCookies) + if (isValid) { + cookies.length = 0 + for (const cookie of newCookies) { + cookies.push(cookie) + } + } + } catch (e) { + if (e?.message === 'USER_CANCELLED_LOGIN') { + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'basic-cookie-check', + status: 'rejected' + }, + runRecordId + } + }) + throw new Error('LOGIN_STATUS_INVALID') + } + } + } + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'basic-cookie-check', + status: 'fulfilled' + }, + runRecordId + } + }) + }) + hooks.userInfoResponse.tapPromise('CookieInvalidHandlePlugin', async (userInfoResponse) => { + if (userInfoResponse.code === 0) { + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'login-status-check', + status: 'fulfilled' + }, + runRecordId + } + }) + return + } + try { + // popup login dialog, then update login status + await loginWithCookieAssistant() + } catch (e) { + if (e?.message === 'USER_CANCELLED_LOGIN') { + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'login-status-check', + status: 'rejected' + }, + runRecordId + } + }) + throw new Error('LOGIN_STATUS_INVALID') + } + } + // throw new Error('THROW_FOR_RETRY') + return Promise.reject(new Error('THROW_FOR_RETRY')) + }) + } +} diff --git a/packages/ui/src/main/features/first-launch-notice-window.ts b/packages/ui/src/main/features/first-launch-notice-window.ts index 5aa3055..4a25343 100644 --- a/packages/ui/src/main/features/first-launch-notice-window.ts +++ b/packages/ui/src/main/features/first-launch-notice-window.ts @@ -3,6 +3,11 @@ import os from 'os' import path from 'path' import buildInfo from '../../common/build-info.json' import { ensureStorageFileExist } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' +import { + createFirstLaunchNoticeWindow, + firstLaunchNoticeWindow +} from '../window/firstLaunchNoticeWindow' +import { ipcMain } from 'electron' export const firstLaunchNoticeApproveFlagPath = path.join( os.homedir(), @@ -10,8 +15,28 @@ export const firstLaunchNoticeApproveFlagPath = path.join( 'ui-first-launch-notice-flag' ) -export const isFirstLaunchNoticeApproveFlagExist = () => fs.existsSync(firstLaunchNoticeApproveFlagPath) +export const isFirstLaunchNoticeApproveFlagExist = () => + fs.existsSync(firstLaunchNoticeApproveFlagPath) export const createFirstLaunchNoticeApproveFlag = () => { ensureStorageFileExist() fs.writeFileSync(firstLaunchNoticeApproveFlagPath, buildInfo.version) } +export async function waitForUserApproveAgreement({ windowOption } = {}) { + return new Promise((resolve, reject) => { + createFirstLaunchNoticeWindow({ ...windowOption }) + let processDone = false + function handler() { + processDone = true + firstLaunchNoticeWindow.close() + } + ipcMain.once('first-launch-notice-approve', handler) + firstLaunchNoticeWindow.once('closed', () => { + ipcMain.off('first-launch-notice-approve', handler) + if (processDone) { + resolve(true) + } else { + reject(new Error('USER_CANCELLED')) + } + }) + }) +} diff --git a/packages/ui/src/main/features/login-with-cookie-assistant.ts b/packages/ui/src/main/features/login-with-cookie-assistant.ts new file mode 100644 index 0000000..5f475ee --- /dev/null +++ b/packages/ui/src/main/features/login-with-cookie-assistant.ts @@ -0,0 +1,23 @@ +import { ipcMain } from 'electron' +import { createCookieAssistantWindow, cookieAssistantWindow } from '../window/cookieAssistantWindow'; + +export async function loginWithCookieAssistant({ windowOption } = {}) { + return new Promise((resolve, reject) => { + createCookieAssistantWindow({ ...windowOption }) + + let processDone = false + function handler() { + processDone = true + cookieAssistantWindow.close() + } + ipcMain.once('cookie-saved', handler) + cookieAssistantWindow.once('closed', () => { + ipcMain.off('cookie-saved', handler) + if (processDone) { + resolve(true) + } else { + reject(new Error('USER_CANCELLED_LOGIN')) + } + }) + }) +} diff --git a/packages/ui/src/main/features/open-browser-download-window.ts b/packages/ui/src/main/features/open-browser-download-window.ts new file mode 100644 index 0000000..cb154bd --- /dev/null +++ b/packages/ui/src/main/features/open-browser-download-window.ts @@ -0,0 +1,28 @@ +import { ipcMain } from 'electron' +import { + createBrowserDownloadProgressWindow, + browserDownloadProgressWindow +} from '../window/browserDownloadProgressWindow' + +export async function openBrowserDownloadWindow({ windowOption } = {}) { + return new Promise((resolve, reject) => { + createBrowserDownloadProgressWindow({ ...windowOption }) + + let processDone = false + let pathOfDownloadedBrowser = null + function handler(_, executablePath) { + pathOfDownloadedBrowser = executablePath + processDone = true + browserDownloadProgressWindow.close() + } + ipcMain.once('browser-download-done', handler) + browserDownloadProgressWindow.once('closed', () => { + ipcMain.off('browser-download-done', handler) + if (processDone) { + resolve(pathOfDownloadedBrowser) + } else { + reject(new Error('USER_CANCELLED_CONFIG_BROWSER')) + } + }) + }) +} diff --git a/packages/ui/src/main/features/run-common.ts b/packages/ui/src/main/features/run-common.ts new file mode 100644 index 0000000..5b0e19c --- /dev/null +++ b/packages/ui/src/main/features/run-common.ts @@ -0,0 +1,74 @@ +import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../common/enums/auto-start-chat' +import { daemonEE, sendToDaemon } from '../flow/OPEN_SETTING_WINDOW/connect-to-daemon' +import { saveAndGetCurrentRunRecord } from '../flow/OPEN_SETTING_WINDOW/utils/db' +import minimist from 'minimist' + +export async function runCommon({ mode }) { + await sendToDaemon( + { + type: 'user-process-register' + }, + { + needCallback: true + } + ) + const taskList = ( + await sendToDaemon( + { + type: 'get-status' + }, + { + needCallback: true + } + ) + )?.workers + const runningTask = taskList?.find((it) => it.workerId === mode) + if (runningTask) { + const commandlineArgs = minimist(runningTask.args ?? []) + const runRecordId = Number(commandlineArgs['run-record-id']) + console.log('任务已在运行中') + return { + runRecordId, + isAlreadyRunning: true + } + } + const currentRunRecord = (await saveAndGetCurrentRunRecord())?.data + const subProcessEnv = { + ...process.env, + GEEKGEEKRUND_NO_AUTO_RESTART_EXIT_CODE: [ + AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE, + AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID, + AUTO_CHAT_ERROR_EXIT_CODE.LLM_UNAVAILABLE + ].join(',') + } + const args = + process.env.NODE_ENV === 'development' + ? [process.argv[1], `--mode=${mode}`, `--run-record-id=${currentRunRecord?.id || 0}`] + : [`--mode=${mode}`, `--run-record-id=${currentRunRecord?.id || 0}`] + await sendToDaemon( + { + type: 'start-worker', + workerId: mode, + command: process.argv[0], + args, + env: subProcessEnv + }, + { + needCallback: true + } + ) + daemonEE.on('message', (message) => { + if (message.type === 'worker-exited') { + if ( + message.workerId === mode && + !message.restarting && + globalThis.GEEKGEEKRUN_PROCESS_ROLE !== 'ui' + ) { + process.exit(0) + } + } + }) + return { + runRecordId: currentRunRecord?.id + } +} diff --git a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index.ts b/packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/index.ts similarity index 91% rename from packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index.ts rename to packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/index.ts index 11c24fc..7ff09ec 100644 --- a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index.ts +++ b/packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/index.ts @@ -9,7 +9,7 @@ export enum DOWNLOAD_ERROR_EXIT_CODE { DOWNLOAD_ERROR = 80 } -export const checkAndDownloadDependenciesForInit = async () => { +export const downloadDependenciesForInit = async () => { process.on('disconnect', () => app.exit()) process.on('uncaughtException', () => app.exit(DOWNLOAD_ERROR_EXIT_CODE.DOWNLOAD_ERROR)) app.dock?.hide() @@ -50,7 +50,7 @@ export const checkAndDownloadDependenciesForInit = async () => { timeoutTimer = setTimeout(() => { // will encounter this when network disconnected when downloading promiseWithResolver.reject(new Error('PROGRESS_NOT_CHANGED_TOO_LONG')) - }, 5 * 1000) + }, 10 * 1000) } else { clearTimeout(throttleProgressTimer) throttleProgressTimer = null @@ -73,7 +73,7 @@ export const checkAndDownloadDependenciesForInit = async () => { ) throttleProgressTimer = setTimeout(() => { throttleProgressTimer = null - }, 2500) + }, 500) } } }) @@ -94,11 +94,13 @@ export const checkAndDownloadDependenciesForInit = async () => { pipe, JSON.stringify({ type: 'PUPPETEER_DOWNLOAD_ENCOUNTER_ERROR', - ...err instanceof Error ? { - name: err.name, - message: err.message, - stack: err.stack - } : null + ...(err instanceof Error + ? { + name: err.name, + message: err.message, + stack: err.stack + } + : null) }) + '\r\n' ) await sleep(1000) diff --git a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/browser-history.ts b/packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/utils/browser-history.ts similarity index 87% rename from packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/browser-history.ts rename to packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/utils/browser-history.ts index 7408309..61e3636 100644 --- a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/browser-history.ts +++ b/packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/utils/browser-history.ts @@ -8,7 +8,7 @@ export interface BrowserInfo { executablePath: string } -const CONFIG_VSERION = 2 +const CONFIG_VERSION = 2 const runtimeFolderPath = path.join(os.homedir(), '.geekgeekrun') export const lastUsedBrowserRecordFilePath = path.join( @@ -35,18 +35,11 @@ export const getLastUsedAndAvailableBrowser = async (): Promise { const puppeteerManager = await import('@puppeteer/browsers') @@ -16,8 +16,6 @@ const getPuppeteerManagerModule = async () => { return puppeteerManager } -const EXPECT_CHROMIUM_BUILD_ID = '139.0.7258.154' -const cacheDir = path.join(os.homedir(), '.geekgeekrun', 'cache') const getExpectCachedPuppeteerExecutable = async (): Promise => { const puppeteerManager = await getPuppeteerManagerModule() @@ -85,41 +83,48 @@ export const checkAndDownloadPuppeteerExecutable = async ( }) ).find((it) => it.buildId === EXPECT_CHROMIUM_BUILD_ID)! } - await saveLastUsedAndAvailableBrowserInfo({ - executablePath: installedBrowser.executablePath, - browser: - installedBrowser.browser[0].toUpperCase() + - installedBrowser.browser.slice(1) + - ' ' + - EXPECT_CHROMIUM_BUILD_ID - }) + // await saveLastUsedAndAvailableBrowserInfo({ + // executablePath: installedBrowser.executablePath, + // browser: + // installedBrowser.browser[0].toUpperCase() + + // installedBrowser.browser.slice(1) + + // ' ' + + // EXPECT_CHROMIUM_BUILD_ID + // }) return installedBrowser } -export const getAnyAvailablePuppeteerExecutable = async (): Promise => { - const lastUsedOne = await getLastUsedAndAvailableBrowser() - if (lastUsedOne) { - return lastUsedOne +export const getAnyAvailablePuppeteerExecutable = async ({ + ignoreCached = false, + noSave = false +}: { + ignoreCached?: boolean + noSave?: boolean +} = {}): Promise => { + if (!ignoreCached) { + const lastUsedOne = await getLastUsedAndAvailableBrowser() + if (lastUsedOne) { + return lastUsedOne + } + } + // find existed browser - the fallback one + if (await checkCachedPuppeteerExecutable()) { + const cachedOne = await getExpectCachedPuppeteerExecutable() + !noSave && (await saveLastUsedAndAvailableBrowserInfo(cachedOne)) + + return cachedOne } // find existed browser - the one maybe actively installed by user or ship with os like Edge on windows try { const existedOne = await findAndLocateUserInstalledChromiumExecutableSync() - await saveLastUsedAndAvailableBrowserInfo(existedOne) + !noSave && (await saveLastUsedAndAvailableBrowserInfo(existedOne)) // save its path return existedOne } catch (err) { console.error(err) console.log('no existed browser path found') } - // find existed browser - the fallback one - if (await checkCachedPuppeteerExecutable()) { - const cachedOne = await getExpectCachedPuppeteerExecutable() - await saveLastUsedAndAvailableBrowserInfo(cachedOne) - - return cachedOne - } - // if no one available, then return null and remove last used browser await removeLastUsedAndAvailableBrowserPath() return null diff --git a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_DAEMON/index.ts b/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_DAEMON/index.ts deleted file mode 100644 index 0427f07..0000000 --- a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_DAEMON/index.ts +++ /dev/null @@ -1,129 +0,0 @@ -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, dialog } from 'electron' -import fs, { WriteStream } from 'node:fs' -import { pipeWriteRegardlessError } from '../utils/pipe' -import * as JSONStream from 'JSONStream' -import { initPowerSaveBlocker } from './power-saver-blocker' -import gtag from '../../utils/gtag' -import { initDb } from '@geekgeekrun/sqlite-plugin' -import { getPublicDbFilePath } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' -import { AutoStartChatRunRecord } from '@geekgeekrun/sqlite-plugin/dist/entity/AutoStartChatRunRecord' -import minimist from 'minimist' -import attachListenerForKillSelfOnParentExited from '../../utils/attachListenerForKillSelfOnParentExited' -const isUiDev = process.env.NODE_ENV === 'development' -const rerunInterval = (() => { - let v = Number(process.env.MAIN_BOSSGEEKGO_RERUN_INTERVAL) - if (isNaN(v)) { - v = 3000 - } - - return v -})() -function runWithDaemon({ runRecordId, runMode, parentProcessPipe }) { - const subProcessOfCore = childProcess.spawn( - process.argv[0], - isUiDev - ? [process.argv[1], `--run-record-id=${runRecordId}`, `--mode=${runMode}`] - : [`--run-record-id=${runRecordId}`, `--mode=${runMode}`], - { - stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'ipc'] - } - ) - - subProcessOfCore!.stdio[3]!.pipe(JSONStream.parse()).on('data', async (raw) => { - const data = raw - switch (data.type) { - case 'GEEK_AUTO_START_CHAT_WITH_BOSS_STARTED': { - pipeWriteRegardlessError( - parentProcessPipe as WriteStream, - JSON.stringify({ - type: data.type - }) - ) - break - } - default: { - return - } - } - }) - - subProcessOfCore.once('exit', async (exitCode: number) => { - if ( - [...Object.values(AUTO_CHAT_ERROR_EXIT_CODE)] - .filter((it) => typeof it === 'number') - .includes(exitCode) - ) { - console.log( - `[Run core daemon] Child process exit with reason ${AUTO_CHAT_ERROR_EXIT_CODE[exitCode]}.` - ) - process.exit(exitCode) - return - } - 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({ runRecordId, runMode, parentProcessPipe }) - }) -} - -export async function runAutoChatWithDaemon() { - const commandlineArgs = minimist(isUiDev ? process.argv.slice(2) : process.argv.slice(1)) - if (!['geekAutoStartWithBossMain'].includes(commandlineArgs['mode-to-daemon'])) { - await new Promise((resolve) => { - app.once('ready', () => resolve(undefined)) - }) - - dialog.showMessageBoxSync({ - type: 'error', - message: `守护进程不支持 ${commandlineArgs['mode-to-daemon'] ?? '(默认)'} 模式` - }) - app.exit() - return - } - - app.dock?.hide() - process.on('disconnect', () => { - app.exit() - }) - - 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) - - process.on('SIGINT', () => { - process.exit() - }) - - const ds = await initDb(getPublicDbFilePath()) - const autoStartChatRunRecord = new AutoStartChatRunRecord() - autoStartChatRunRecord.date = new Date() - const autoStartChatRunRecordRepository = ds.getRepository(AutoStartChatRunRecord) - const result = await autoStartChatRunRecordRepository.save(autoStartChatRunRecord) - runWithDaemon({ - runRecordId: result.id, - runMode: commandlineArgs['mode-to-daemon'], - parentProcessPipe: pipe - }) - - pipeWriteRegardlessError( - pipe, - JSON.stringify({ - type: 'AUTO_START_CHAT_DAEMON_PROCESS_STARTUP' - }) - ) - - gtag('daemon_ready', { mode: commandlineArgs['mode-to-daemon'] ?? '' }) -} - -attachListenerForKillSelfOnParentExited() diff --git a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_DAEMON/power-saver-blocker.ts b/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_DAEMON/power-saver-blocker.ts deleted file mode 100644 index f8940b5..0000000 --- a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_DAEMON/power-saver-blocker.ts +++ /dev/null @@ -1,10 +0,0 @@ -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) - } -} diff --git a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index.ts b/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index.ts index 1387f1f..eb7259b 100644 --- a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index.ts +++ b/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index.ts @@ -5,19 +5,28 @@ 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 { pipeWriteRegardlessError } from '../utils/pipe' import { sleep } from '@geekgeekrun/utils/sleep.mjs' import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../../common/enums/auto-start-chat' import attachListenerForKillSelfOnParentExited from '../../utils/attachListenerForKillSelfOnParentExited' - +import minimist from 'minimist' import SqlitePluginModule from '@geekgeekrun/sqlite-plugin' import gtag from '../../utils/gtag' import GtagPlugin from '../../utils/gtag/GtagPlugin' +import { connectToDaemon, sendToDaemon } from '../OPEN_SETTING_WINDOW/connect-to-daemon' +// import { PeriodPushCurrentPageScreenshotPlugin } from '../../utils/screenshot' +import { checkShouldExit } from '../../utils/worker' +import { CookieInvalidHandlePlugin } from '../../features/cookie-invalid-handle-plugin' +import initPublicIpc from '../../utils/initPublicIpc' +import { getLastUsedAndAvailableBrowser } from '../DOWNLOAD_DEPENDENCIES/utils/browser-history' +import { configWithBrowserAssistant } from '../../features/config-with-browser-assistant' const { default: SqlitePlugin } = SqlitePluginModule +process.on('SIGTERM', () => { + console.log('收到SIGTERM信号,正在退出') + process.exit(0) +}) + const rerunInterval = (() => { let v = Number(process.env.MAIN_BOSSGEEKGO_RERUN_INTERVAL) if (isNaN(v)) { @@ -33,14 +42,54 @@ const initPlugins = (hooks) => { new DingtalkPlugin(dingTalkAccessToken).apply(hooks) new SqlitePlugin(getPublicDbFilePath()).apply(hooks) new GtagPlugin().apply(hooks) + // new PeriodPushCurrentPageScreenshotPlugin().apply(hooks) + new CookieInvalidHandlePlugin().apply(hooks) } -let isParentProcessDisconnect = false -process.once('disconnect', () => { - isParentProcessDisconnect = true -}) - +const runRecordId = minimist(process.argv.slice(2))['run-record-id'] ?? null const runAutoChat = async () => { + app.dock?.hide() + let puppeteerExecutable = await getLastUsedAndAvailableBrowser() + if (!puppeteerExecutable) { + try { + await configWithBrowserAssistant({ autoFind: true }) + } catch (error) { + // + } + puppeteerExecutable = await getLastUsedAndAvailableBrowser() + } + if (!puppeteerExecutable) { + await dialog.showMessageBox({ + type: `error`, + message: `未找到可用的浏览器`, + detail: `请重新运行本程序,按照提示安装、配置浏览器` + }) + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'puppeteer-executable-check', + status: 'rejected' + }, + runRecordId + } + }) + app.exit(AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE) + return + } + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'puppeteer-executable-check', + status: 'fulfilled' + }, + runRecordId + } + }) + process.env.PUPPETEER_EXECUTABLE_PATH = puppeteerExecutable.executablePath const { initPuppeteer, mainLoop, closeBrowserWindow, autoStartChatEventBus } = await import( '@geekgeekrun/geek-auto-start-chat-with-boss/index.mjs' ) @@ -48,43 +97,13 @@ const runAutoChat = async () => { closeBrowserWindow() app.exit() }) - app.dock?.hide() - let pipe: null | fs.WriteStream = null - try { - pipe = fs.createWriteStream(null, { fd: 3 }) - } catch { - console.warn('pipe is not available') - } - pipeWriteRegardlessError( - pipe, - JSON.stringify({ - type: 'INITIALIZE_PUPPETEER' - }) + '\r\n' - ) - try { - await initPuppeteer() - pipeWriteRegardlessError( - pipe, - JSON.stringify({ - type: 'PUPPETEER_INITIALIZE_SUCCESSFULLY' - }) + '\r\n' - ) - } catch (err) { - console.error(err) - app.exit(AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE) - return - } - - const isPuppeteerExecutable = !!(await getAnyAvailablePuppeteerExecutable()) - if (!isPuppeteerExecutable) { - app.exit(AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE) - return - } + await initPuppeteer() const hooks = { - puppeteerLaunched: new SyncHook(), + puppeteerLaunched: new SyncHook(['browser']), + pageGotten: new SyncHook(['page']), pageLoaded: new SyncHook(), - cookieWillSet: new SyncHook(['cookies']), + cookieWillSet: new AsyncSeriesHook(['cookies']), userInfoResponse: new AsyncSeriesHook(['userInfo']), mainFlowWillLaunch: new AsyncSeriesHook(['args']), jobDetailIsGetFromRecommendList: new AsyncSeriesHook(['userInfo']), @@ -101,23 +120,11 @@ const runAutoChat = async () => { initPlugins(hooks) gtag('run_auto_chat_with_boss_main_ready') - pipeWriteRegardlessError( - pipe, - JSON.stringify({ - type: 'GEEK_AUTO_START_CHAT_WITH_BOSS_STARTED' //geek-auto-start-chat-with-boss-started - }) + '\r\n' - ) autoStartChatEventBus.once('LOGIN_STATUS_INVALID', () => { - pipeWriteRegardlessError( - pipe, - JSON.stringify({ - type: 'LOGIN_STATUS_INVALID' //geek-auto-start-chat-with-boss-started - }) + '\r\n' - ) }) - while (![isParentProcessDisconnect].includes(true)) { + while (true) { try { await mainLoop(hooks) } catch (err) { @@ -126,7 +133,7 @@ const runAutoChat = async () => { await dialog.showMessageBox({ type: `error`, message: `登录状态无效`, - detail: `请重新登录Boss直聘` + detail: `请重新登录BOSS直聘` }) process.exit(AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID) break @@ -139,9 +146,18 @@ const runAutoChat = async () => { process.exit(AUTO_CHAT_ERROR_EXIT_CODE.ACCESS_IS_DENIED) break } + if (err.message.includes(`Could not find Chrome`) || err.message.includes(`no executable was found`)) { + process.exit(AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE) + break + } } closeBrowserWindow?.() console.error(err) + const shouldExit = await checkShouldExit() + if (shouldExit) { + app.exit() + return + } console.log( `[Run core main] An internal error is caught, and browser will be restarted in ${rerunInterval}ms.` ) @@ -150,14 +166,32 @@ const runAutoChat = async () => { } } -export const waitForProcessHandShakeAndRunAutoChat = () => { - let pipe: null | fs.WriteStream = null - try { - pipe = fs.createWriteStream(null, { fd: 3 }) - } catch { - console.error('pipe is not available') - app.exit(1) - } +export const waitForProcessHandShakeAndRunAutoChat = async () => { + await app.whenReady() + app.on('window-all-closed', (e) => { + e.preventDefault() + }) + initPublicIpc() + await connectToDaemon() + await sendToDaemon( + { + type: 'ping' + }, + { + needCallback: true + } + ) + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'worker-launch', + status: 'fulfilled' + }, + runRecordId + } + }) runAutoChat() } diff --git a/packages/ui/src/main/flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION.ts b/packages/ui/src/main/flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION.ts index e8aa9f0..2dead26 100644 --- a/packages/ui/src/main/flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION.ts +++ b/packages/ui/src/main/flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION.ts @@ -1,7 +1,12 @@ -import { app } from 'electron' -import { main, loginEventBus } from '@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension' +import { app, dialog } from 'electron' +import { + main, + loginEventBus +} from '@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension' import { pipeWriteRegardlessError } from './utils/pipe' -import fs from "node:fs"; +import fs from 'node:fs' +import { getLastUsedAndAvailableBrowser } from './DOWNLOAD_DEPENDENCIES/utils/browser-history' +import { configWithBrowserAssistant } from '../features/config-with-browser-assistant' export const launchBossZhipinLoginPageWithPreloadExtension = async () => { process.on('disconnect', () => app.exit()) @@ -18,15 +23,26 @@ export const launchBossZhipinLoginPageWithPreloadExtension = async () => { type: 'INITIALIZE_PUPPETEER' }) + '\r\n' ) + let puppeteerExecutable = await getLastUsedAndAvailableBrowser() + if (!puppeteerExecutable) { + try { + await configWithBrowserAssistant({ autoFind: true }) + } catch (error) { + // + } + puppeteerExecutable = await getLastUsedAndAvailableBrowser() + } + if (!puppeteerExecutable) { + await dialog.showMessageBox({ + type: `error`, + message: `未找到可用的浏览器`, + detail: `请重新运行本程序,按照提示安装、配置浏览器` + }) + app.exit(1) + } const { initPuppeteer } = await import('@geekgeekrun/geek-auto-start-chat-with-boss/index.mjs') try { await initPuppeteer() - pipeWriteRegardlessError( - pipe, - JSON.stringify({ - type: 'PUPPETEER_INITIALIZE_SUCCESSFULLY' - }) + '\r\n' - ) } catch (err) { console.error(err) app.exit(1) diff --git a/packages/ui/src/main/flow/LAUNCH_DAEMON.ts b/packages/ui/src/main/flow/LAUNCH_DAEMON.ts new file mode 100644 index 0000000..9bd457e --- /dev/null +++ b/packages/ui/src/main/flow/LAUNCH_DAEMON.ts @@ -0,0 +1,5 @@ +;(async () => { + await import('@geekgeekrun/pm/daemon.js') +})() + +export {} diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/connect-to-daemon.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/connect-to-daemon.ts new file mode 100644 index 0000000..7ba7880 --- /dev/null +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/connect-to-daemon.ts @@ -0,0 +1,145 @@ +import { randomUUID } from 'node:crypto' +import { EventEmitter } from 'node:events' +import { tmpdir } from 'node:os' +import path from 'node:path' +import net from 'node:net' +import split2 from 'split2' + +let daemonClient = null +export const daemonEE = new EventEmitter() +const waitForCallbackTaskMap = new Map() + +export function getSocketPath(socketName) { + const ipcSocketPath = + process.platform === 'win32' + ? `\\\\.\\pipe\\${socketName}` + : path.join(tmpdir(), `${socketName}.sock`) + return ipcSocketPath +} + +// 连接到守护进程 +export async function connectToDaemon() { + daemonClient = new net.Socket() + let isConnected = false + await new Promise((resolve, reject) => { + const ipcSocketName = process.env.GEEKGEEKRUND_PIPE_NAME + const ipcSocketPath = getSocketPath(ipcSocketName) + daemonClient.connect(ipcSocketPath, 'localhost', () => { + isConnected = true + console.log('已连接到守护进程') + daemonEE.emit('connect') + // 使用 split2 按行分割流式数据,处理 JSONL 格式(每行一个 JSON) + const splitStream = split2() + daemonClient.pipe(splitStream).on('data', (line) => { + const trimmedLine = line.toString().trim() + if (!trimmedLine) { + return // 跳过空行 + } + try { + const message = JSON.parse(trimmedLine) + daemonEE.emit('message', message) + // FIXME: + // console.log('收到守护进程消息:', message) + if (message._callbackUuid) { + const callbackInfo = waitForCallbackTaskMap.get(message._callbackUuid) + if (callbackInfo) { + const isError = message._isError + if (isError) { + callbackInfo.reject(message) + } else { + callbackInfo.resolve(message) + } + waitForCallbackTaskMap.delete(message._callbackUuid) + } + } + // 转发消息到渲染进程 + // if (mainWindow) { + // mainWindow.webContents.send('daemon-message', message); + // } + } catch (parseError) { + console.error('解析守护进程消息失败:', parseError.message) + console.error('原始数据:', trimmedLine.substring(0, 100)) + } + }) + + splitStream.on('error', (err) => { + console.error('split2 流处理错误:', err) + }) + + daemonClient.on('close', () => { + if (!isConnected) { + return + } + console.log('守护进程连接已关闭') + daemonEE.emit('close') + }) + + resolve(true) + // 通知渲染进程连接成功 + // if (mainWindow) { + // mainWindow.webContents.send('daemon-connected'); + // } + }) + + daemonClient.on('close', () => { + if (isConnected) { + return + } + reject(new Error('连接到守护进程超时')) + }) + daemonClient.on('error', (err) => { + console.error('守护进程连接错误:', err) + // daemonEE.emit('error', err) + reject(err) + }) + }) +} + +// 向守护进程发送消息 +export function sendToDaemon(message, { needCallback = false, timeout = undefined } = {}) { + const _callbackUuid = randomUUID() + if (daemonClient && !daemonClient.destroyed) { + daemonClient.write( + JSON.stringify({ + ...message, + _callbackUuid + }) + '\n' + ) + if (needCallback) { + let resolve, reject + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve + reject = _reject + }) + waitForCallbackTaskMap.set(_callbackUuid, { resolve, reject }) + promise.finally(() => waitForCallbackTaskMap.delete(_callbackUuid)) + let timeoutTimer + if (!isNaN(parseInt(timeout))) { + timeoutTimer = setTimeout(() => { + reject(new Error(`Callback timeout after ${timeout}ms`)) + }, timeout) + } + promise.finally(() => { + clearTimeout(timeoutTimer) + }) + return promise + } + } else { + console.error('守护进程未连接') + } + return undefined +} + +// // IPC处理:从渲染进程接收消息并转发到守护进程 +// ipcMain.on('send-to-daemon', (event, message) => { +// sendToDaemon(message); +// }); + +// // IPC处理:启动工具进程 +// ipcMain.on('start-worker', (event, { workerId, command, args, env }) => { +// sendToDaemon({ type: 'start-worker', workerId, command, args, env }); +// }); + +export function closeDaemonClient() { + daemonClient?.destroy() +} diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/index.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/index.ts index 8d2409c..b3eef82 100644 --- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/index.ts +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/index.ts @@ -5,6 +5,8 @@ import './app-menu' import initIpc from './ipc' import gtag from '../../utils/gtag' import initPublicIpc from '../../utils/initPublicIpc' +import { sendToDaemon, closeDaemonClient } from './connect-to-daemon' + export function openSettingWindow() { // TODO: singleton lock; how can we check if there is another process should run as singleton with arguments? if (!app.requestSingleInstanceLock()) { @@ -65,4 +67,25 @@ export function openSettingWindow() { globalShortcut.unregister('Command+Option+Shift+/') }) }) + + whenReadyPromise.then(async () => { + await sendToDaemon( + { + type: 'ping' + }, + { + needCallback: true + } + ) + await sendToDaemon( + { + type: 'user-process-register' + }, + { + needCallback: true + } + ) + }) + app.on('window-all-closed', closeDaemonClient) + app.on('before-quit', closeDaemonClient) } diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts index 5b30682..3eb9a96 100644 --- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts @@ -1,22 +1,18 @@ -import { ipcMain, shell, app } from 'electron' +import { ipcMain, shell, app, dialog, BrowserWindow } from 'electron' import path from 'path' import * as childProcess from 'node:child_process' import { ensureConfigFileExist, - ensureStorageFileExist, configFileNameList, readConfigFile, writeConfigFile, readStorageFile, - writeStorageFile, storageFilePath } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' import { ChildProcess } from 'child_process' 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 { getAnyAvailablePuppeteerExecutable } from '../../DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable/index' import { mainWindow } from '../../../window/mainWindow' import { getAutoStartChatRecord, @@ -36,11 +32,11 @@ import { createResumeEditorWindow, resumeEditorWindow } from '../../../window/re import { getValidTemplate, requestNewMessageContent -} from '../../READ_NO_REPLY_AUTO_REMINDER/boss-operation' +} from '../../READ_NO_REPLY_AUTO_REMINDER_MAIN/boss-operation' import { autoReminderPromptTemplateFileName, writeDefaultAutoRemindPrompt -} from '../../READ_NO_REPLY_AUTO_REMINDER/boss-operation' +} from '../../READ_NO_REPLY_AUTO_REMINDER_MAIN/boss-operation' import { checkIsResumeContentValid, resumeContentEnoughDetect @@ -52,6 +48,16 @@ import { import { RequestSceneEnum } from '../../../features/llm-request-log' import { checkUpdateForUi } from '../../../features/updater' import gtag from '../../../utils/gtag' +import { daemonEE, sendToDaemon } from '../connect-to-daemon' +import { runCommon } from '../../../features/run-common' +import { loginWithCookieAssistant } from '../../../features/login-with-cookie-assistant' +import { configWithBrowserAssistant } from '../../../features/config-with-browser-assistant' +import { + createFirstLaunchNoticeApproveFlag, + isFirstLaunchNoticeApproveFlagExist, + waitForUserApproveAgreement +} from '../../../features/first-launch-notice-window' +import { getLastUsedAndAvailableBrowser } from '../../DOWNLOAD_DEPENDENCIES/utils/browser-history' export default function initIpc() { ipcMain.handle('fetch-config-file-content', async () => { @@ -175,6 +181,12 @@ export default function initIpc() { if (hasOwn(payload, 'sageTimePauseMinute')) { bossConfig.sageTimePauseMinute = payload.sageTimePauseMinute } + if (hasOwn(payload, 'blockCompanyNameRegExpStr')) { + bossConfig.blockCompanyNameRegExpStr = payload.blockCompanyNameRegExpStr + } + if (hasOwn(payload, 'blockCompanyNameRegMatchStrategy')) { + bossConfig.blockCompanyNameRegMatchStrategy = payload.blockCompanyNameRegMatchStrategy + } promiseArr.push(writeConfigFile('boss.json', bossConfig)) @@ -187,262 +199,111 @@ export default function initIpc() { return await Promise.all(promiseArr) }) - ipcMain.handle('read-storage-file', async (ev, payload) => { - ensureStorageFileExist() - return await readStorageFile(payload.fileName) - }) - - ipcMain.handle('write-storage-file', async (ev, payload) => { - ensureStorageFileExist() - - return await writeStorageFile(payload.fileName, JSON.parse(payload.data)) - }) - - // 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, - PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable.executablePath - } - subProcessOfPuppeteer = childProcess.spawn( - process.argv[0], - [ - process.argv[1], - `--mode=geekAutoStartWithBossDaemon`, - `--mode-to-daemon=geekAutoStartWithBossMain` - ], - { - env: subProcessEnv, - stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'ipc'] + ipcMain.handle('run-geek-auto-start-chat-with-boss', async (ev) => { + const mode = 'geekAutoStartWithBossMain' + const { runRecordId } = await runCommon({ mode }) + daemonEE.on('message', function handler(message) { + if (message.workerId !== mode) { + return + } + if (message.type === 'worker-exited') { + mainWindow?.webContents.send('worker-exited', message) } - ) - // 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: + return { runRecordId } }) ipcMain.handle('run-read-no-reply-auto-reminder', async () => { - if (subProcessOfPuppeteer) { - return - } - const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable() - if (!puppeteerExecutable) { - return Promise.reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES') - } - const subProcessEnv = { - ...process.env, - PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable.executablePath - } - subProcessOfPuppeteer = childProcess.spawn( - process.argv[0], - [process.argv[1], `--mode=readNoReplyAutoReminder`], - { - env: subProcessEnv, - stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'ipc'] + const mode = 'readNoReplyAutoReminderMain' + const { runRecordId } = await runCommon({ mode }) + daemonEE.on('message', function handler(message) { + if (message.workerId !== mode) { + return } - ) - // 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 'LOGIN_STATUS_INVALID': { - await sleep(500) - mainWindow?.webContents.send('check-boss-zhipin-cookie-file') - return - } - case 'ERR_INTERNET_DISCONNECTED': { - mainWindow?.webContents.send('toast-message', { - type: 'error', - message: '联网失败,请检查网络连接' - }) - 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') - } - }) - - resolve(true) - }) - // 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 - } - subProcessOfCheckAndDownloadDependencies = childProcess.spawn( - process.argv[0], - [process.argv[1], `--mode=checkAndDownloadDependenciesForInit`], - { - stdio: [null, null, null, 'pipe', 'ipc'] + if (message.type === 'worker-exited') { + mainWindow?.webContents.send('worker-exited', message) } - ) - 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 - }) }) + return { runRecordId } }) ipcMain.handle('stop-geek-auto-start-chat-with-boss', async () => { mainWindow?.webContents.send('geek-auto-start-chat-with-boss-stopping') - subProcessOfPuppeteer?.kill() - setTimeout(() => { - try { - subProcessOfPuppeteer?.kill('SIGKILL') - } catch { - // - } - }, 1000) - }) - - let subProcessOfBossZhipinLoginPageWithPreloadExtension: ChildProcess | null = null - ipcMain.on('launch-bosszhipin-login-page-with-preload-extension', async () => { - try { - subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill() - } catch { - // - } - const subProcessEnv = { - ...process.env, - PUPPETEER_EXECUTABLE_PATH: (await getAnyAvailablePuppeteerExecutable())!.executablePath - } - subProcessOfBossZhipinLoginPageWithPreloadExtension = childProcess.spawn( - process.argv[0], - [process.argv[1], `--mode=launchBossZhipinLoginPageWithPreloadExtension`], - { - env: subProcessEnv, - stdio: [null, null, null, 'pipe', 'ipc'] - } - ) - subProcessOfBossZhipinLoginPageWithPreloadExtension!.stdio[3]!.pipe(JSONStream.parse()).on( - 'data', - (raw) => { - const data = raw - switch (data.type) { - case 'BOSS_ZHIPIN_COOKIE_COLLECTED': { - mainWindow?.webContents.send(data.type, data) - break - } - default: { - return - } + const p = new Promise((resolve) => { + daemonEE.on('message', function handler(message) { + if (message.workerId !== 'geekAutoStartWithBossMain') { + return } + if (message.type === 'worker-exited') { + daemonEE.off('message', handler) + resolve(undefined) + } + }) + }) + await sendToDaemon( + { + type: 'stop-worker', + workerId: 'geekAutoStartWithBossMain' + }, + { + needCallback: true } ) - subProcessOfBossZhipinLoginPageWithPreloadExtension!.once('exit', () => { - mainWindow?.webContents.send('BOSS_ZHIPIN_LOGIN_PAGE_CLOSED') - subProcessOfBossZhipinLoginPageWithPreloadExtension = null - }) + await p + mainWindow?.webContents.send('geek-auto-start-chat-with-boss-stopped') }) - ipcMain.on('kill-bosszhipin-login-page-with-preload-extension', async () => { - try { - subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill() - } catch { - // - } finally { - subProcessOfBossZhipinLoginPageWithPreloadExtension = null - } + + ipcMain.handle('stop-read-no-reply-auto-reminder', async () => { + mainWindow?.webContents.send('read-no-reply-auto-reminder-stopping') + const p = new Promise((resolve) => { + daemonEE.on('message', function handler(message) { + if (message.workerId !== 'readNoReplyAutoReminderMain') { + return + } + if (message.type === 'worker-exited') { + daemonEE.off('message', handler) + resolve(undefined) + } + }) + }) + await sendToDaemon( + { + type: 'stop-worker', + workerId: 'readNoReplyAutoReminderMain' + }, + { + needCallback: true + } + ) + + await p + mainWindow?.webContents.send('read-no-reply-auto-reminder-stopped') + }) + + ipcMain.handle('get-task-manager-list', async () => { + const result = await sendToDaemon( + { + type: 'get-status' + }, + { + needCallback: true + } + ) + return result + }) + + // IPC处理:停止工具进程 + ipcMain.handle('stop-task', async (_, workerId) => { + await sendToDaemon( + { + type: 'stop-worker', + workerId + }, + { + needCallback: true + } + ) }) ipcMain.handle('check-boss-zhipin-cookie-file', () => { @@ -473,7 +334,7 @@ export default function initIpc() { let subProcessOfOpenBossSiteDefer: null | PromiseWithResolvers = null let subProcessOfOpenBossSite: null | ChildProcess = null - ipcMain.handle('open-site-with-boss-cookie', async (_, data) => { + ipcMain.handle('open-site-with-boss-cookie', async (ev, data) => { const url = data.url if ( !subProcessOfOpenBossSiteDefer || @@ -481,7 +342,31 @@ export default function initIpc() { subProcessOfOpenBossSite.killed ) { subProcessOfOpenBossSiteDefer = Promise.withResolvers() - const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable() + let puppeteerExecutable = await getLastUsedAndAvailableBrowser() + if (!puppeteerExecutable) { + try { + const parent = BrowserWindow.fromWebContents(ev.sender) || undefined + await configWithBrowserAssistant({ + autoFind: true, + windowOption: { + parent, + modal: !!parent, + show: true + } + }) + puppeteerExecutable = await getLastUsedAndAvailableBrowser() + } catch (error) { + // + } + } + if (!puppeteerExecutable) { + await dialog.showMessageBox({ + type: `error`, + message: `未找到可用的浏览器`, + detail: `请重新运行本程序,按照提示安装、配置浏览器` + }) + return + } const subProcessEnv = { ...process.env, PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable!.executablePath @@ -666,6 +551,60 @@ export default function initIpc() { const newRelease = await checkUpdateForUi() return newRelease }) + ipcMain.handle('login-with-cookie-assistant', async () => { + return await loginWithCookieAssistant({ + windowOption: { + parent: mainWindow!, + modal: true, + show: true + } + }) + }) + ipcMain.handle('config-with-browser-assistant', async () => { + return await configWithBrowserAssistant({ + windowOption: { + parent: mainWindow!, + modal: true, + show: true + } + }) + }) + + ipcMain.handle('pre-enter-setting-ui', async () => { + if (!isFirstLaunchNoticeApproveFlagExist()) { + try { + await waitForUserApproveAgreement({ + windowOption: { + parent: mainWindow!, + modal: true, + show: true + } + }) + createFirstLaunchNoticeApproveFlag() + } catch { + app.exit(0) + return + } + } + const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable() + if (!puppeteerExecutable) { + const lastBrowser = await getLastUsedAndAvailableBrowser() + if (!lastBrowser) { + try { + await configWithBrowserAssistant({ + windowOption: { + parent: mainWindow!, + modal: true, + show: true + }, + autoFind: true + }) + } catch (err) { + void err + } + } + } + }) ipcMain.handle('exit-app-immediately', () => { app.exit(0) diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/launch-daemon.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/launch-daemon.ts new file mode 100644 index 0000000..0b6a543 --- /dev/null +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/launch-daemon.ts @@ -0,0 +1,91 @@ +import { spawn } from 'child_process' +import { + ensureStorageFileExist, + writeStorageFile, + readStorageFile +} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' +import { randomUUID } from 'node:crypto' +import { connectToDaemon } from './connect-to-daemon' + +const isUiDev = process.env.NODE_ENV === 'development' +export async function ensureIpcPipeName({ isReset } = {}) { + if (isReset) { + await writeStorageFile('ipc-pipe-name', '', { isJson: false }) + } + let ipcPipeName = readStorageFile('ipc-pipe-name', { isJson: false }) + if (!ipcPipeName) { + ipcPipeName = `geekgeekrun-d_${randomUUID()}` + ensureStorageFileExist() + await writeStorageFile('ipc-pipe-name', ipcPipeName, { isJson: false }) + } + process.env.GEEKGEEKRUND_PIPE_NAME = ipcPipeName + return ipcPipeName +} + +export async function launchDaemon() { + let daemonProcess + async function startDaemon() { + console.log('启动守护进程...') + // 添加参数使守护进程在后台运行,不显示 UI + daemonProcess = spawn( + process.argv[0], + isUiDev ? [process.argv[1], `--mode=launchDaemon`] : [`--mode=launchDaemon`], + { + stdio: ['ignore', 'pipe', 'pipe', 'pipe'], + detached: true, + env: { + ...process.env + } + } + ) + + daemonProcess.stdout.on('data', (data) => { + console.log(`守护进程输出: ${data}`) + }) + + daemonProcess.stderr.on('data', (data) => { + console.error(`守护进程错误: ${data}`) + }) + + return new Promise((resolve, reject) => { + daemonProcess.stdio[3].on('data', (rawData) => { + let data + try { + data = JSON.parse(rawData.toString()) + if (data.type === 'DAEMON_READY') { + resolve(true) + } else if (data.type === 'DAEMON_FATAL') { + reject(new Error(data.error)) + } + } catch (err) { + console.error('', err) + } + }) + }) + } + await ensureIpcPipeName() + try { + await connectToDaemon() + } catch (err) { + let isDaemonLaunched = false + console.log('cannot connect to daemon, try to launch it', err) + // 启动守护进程 + try { + await startDaemon() + isDaemonLaunched = true + } catch (err) { + console.log('cannot launch to daemon, try to change port', err) + daemonProcess?.kill('SIGKILL') + await ensureIpcPipeName({ isReset: true }) + try { + await startDaemon() + isDaemonLaunched = true + } catch (err) { + console.log('cannot launch to daemon, try to change port failed', err) + } + } + if (isDaemonLaunched) { + await connectToDaemon() + } + } +} diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/utils/db/index.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/utils/db/index.ts index fe8e81e..349e2cc 100644 --- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/utils/db/index.ts +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/utils/db/index.ts @@ -20,7 +20,7 @@ export const initDbWorker = () => { // attach more event worker?.off('message', handler) } else if (data.type === 'DB_INIT_FAIL') { - reject(undefined) + reject(data.error) worker?.terminate() worker?.off('message', handler) worker = null @@ -102,3 +102,10 @@ export const getJobHistoryByEncryptId = async (encryptJobId) => { }) return res } + +export const saveAndGetCurrentRunRecord = async () => { + const res = await createWorkerPromise({ + type: 'saveAndGetCurrentRunRecord' + }) + return res +} diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/utils/db/worker/index.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/utils/db/worker/index.ts index c9d1d93..df3a684 100644 --- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/utils/db/worker/index.ts +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/utils/db/worker/index.ts @@ -11,6 +11,7 @@ import { VMarkAsNotSuitLog } from '@geekgeekrun/sqlite-plugin/dist/entity/VMarkA import { measureExecutionTime } from '../../../../../../common/utils/performance' import { PageReq, PagedRes } from '../../../../../../common/types/pagination' import { JobInfoChangeLog } from '@geekgeekrun/sqlite-plugin/dist/entity/JobInfoChangeLog' +import { AutoStartChatRunRecord } from '@geekgeekrun/sqlite-plugin/dist/entity/AutoStartChatRunRecord' const dbInitPromise = initDb(getPublicDbFilePath()) let dataSource: DataSource | null = null @@ -161,6 +162,13 @@ const payloadHandler = { }) ) return data + }, + async saveAndGetCurrentRunRecord() { + const autoStartChatRunRecord = new AutoStartChatRunRecord() + autoStartChatRunRecord.date = new Date() + const autoStartChatRunRecordRepository = dataSource!.getRepository(AutoStartChatRunRecord) + const result = await autoStartChatRunRecordRepository.save(autoStartChatRunRecord) + return result } } diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/bootstrap.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/bootstrap.ts similarity index 92% rename from packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/bootstrap.ts rename to packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/bootstrap.ts index 0b5ef97..1f3f7ce 100644 --- a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/bootstrap.ts +++ b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/bootstrap.ts @@ -7,8 +7,6 @@ import { setDomainLocalStorage } from '@geekgeekrun/utils/puppeteer/local-storag const localStoragePageUrl = `https://www.zhipin.com/desktop/` const bossChatUiUrl = `https://www.zhipin.com/web/geek/chat` -const bossCookies = readStorageFile('boss-cookies.json') -const bossLocalStorage = readStorageFile('boss-local-storage.json') export async function bootstrap() { const { puppeteer } = await initPuppeteer() @@ -28,6 +26,8 @@ export async function bootstrap() { export async function launchBoss(browser: Browser) { const page = (await browser.pages())[0] + const bossCookies = readStorageFile('boss-cookies.json') + const bossLocalStorage = readStorageFile('boss-local-storage.json') //set cookies for (let i = 0; i < bossCookies.length; i++) { await page.setCookie(bossCookies[i]) diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/boss-operation.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/boss-operation.ts similarity index 100% rename from packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/boss-operation.ts rename to packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/boss-operation.ts diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/index.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts similarity index 67% rename from packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/index.ts rename to packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts index 3a6ca66..c28430d 100644 --- a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/index.ts +++ b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts @@ -4,7 +4,6 @@ import { Browser, Page } from 'puppeteer' import { sendGptContent, sendLookForwardReplyEmotion } from './boss-operation' import { sleep, sleepWithRandomDelay } from '@geekgeekrun/utils/sleep.mjs' import { waitForPage } from '@geekgeekrun/utils/puppeteer/wait.mjs' -import attachListenerForKillSelfOnParentExited from '../../utils/attachListenerForKillSelfOnParentExited' import { app, dialog } from 'electron' import { initDb } from '@geekgeekrun/sqlite-plugin' import { @@ -17,16 +16,35 @@ import { getJobHireStatusRecord, saveJobHireStatusRecord } from '@geekgeekrun/sqlite-plugin/dist/handlers' -import { writeStorageFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' -import * as fs from 'fs' -import { pipeWriteRegardlessError } from '../utils/pipe' +import { + writeStorageFile, + readStorageFile +} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' import { BossInfo } from '@geekgeekrun/sqlite-plugin/dist/entity/BossInfo' import { messageForSaveFilter } from '../../../common/utils/chat-list' -import { RECHAT_CONTENT_SOURCE, RECHAT_LLM_FALLBACK } from '../../../common/enums/auto-start-chat' +import { + AUTO_CHAT_ERROR_EXIT_CODE, + RECHAT_CONTENT_SOURCE, + RECHAT_LLM_FALLBACK +} from '../../../common/enums/auto-start-chat' import gtag from '../../utils/gtag' import { JobHireStatus } from '@geekgeekrun/sqlite-plugin/dist/enums' import dayjs from 'dayjs' import cheerio from 'cheerio' +import { connectToDaemon, sendToDaemon } from '../OPEN_SETTING_WINDOW/connect-to-daemon' +// import { pushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS } from '../../utils/screenshot' +import { checkShouldExit } from '../../utils/worker' +import minimist from 'minimist' +import { checkCookieListFormat } from '../../../common/utils/cookie' +import { loginWithCookieAssistant } from '../../features/login-with-cookie-assistant' +import initPublicIpc from '../../utils/initPublicIpc' +import { getLastUsedAndAvailableBrowser } from '../DOWNLOAD_DEPENDENCIES/utils/browser-history' +import { configWithBrowserAssistant } from '../../features/config-with-browser-assistant' + +process.on('SIGTERM', () => { + console.log('收到SIGTERM信号,正在退出') + process.exit(0) +}) const throttleIntervalMinutes = readConfigFile('boss.json').autoReminder?.throttleIntervalMinutes ?? 10 @@ -42,7 +60,23 @@ const rechatLlmFallback = const expectJobTypeRegExpStr = readConfigFile('boss.json').expectJobTypeRegExpStr const onlyRemindBossWithExpectJobType = - readConfigFile('boss.json').autoReminder?.onlyRemindBossWithExpectJobType ?? !!expectJobTypeRegExpStr + readConfigFile('boss.json').autoReminder?.onlyRemindBossWithExpectJobType ?? + !!expectJobTypeRegExpStr + +const blockCompanyNameRegExpStr = readConfigFile('boss.json').blockCompanyNameRegExpStr ?? '' +const blockCompanyNameRegExp = (() => { + if (!blockCompanyNameRegExpStr?.trim()) { + return null + } + try { + return new RegExp(blockCompanyNameRegExpStr, 'im') + } catch { + return null + } +})() +const onlyRemindBossWithoutBlockCompanyName = + readConfigFile('boss.json').autoReminder?.onlyRemindBossWithoutBlockCompanyName ?? + !!blockCompanyNameRegExp const dbInitPromise = initDb(getPublicDbFilePath()) @@ -50,6 +84,30 @@ export const pageMapByName: { boss?: Page | null } = {} +// async function periodPushCurrentPageScreenshot () { +// try { +// if (pageMapByName.boss?.isClosed()) { +// return +// } +// const shouldExit = await checkShouldExit() +// if (shouldExit) { +// return +// } +// try { +// await pushCurrentPageScreenshot(pageMapByName.boss) +// } +// catch (err) { +// if (err?.message?.includes(`PAGE_CLOSED`)) { +// return +// } +// } +// setTimeout(periodPushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS) +// } +// catch {} +// } + +// periodPushCurrentPageScreenshot() + async function saveCurrentChatRecord(page) { const userInfo = await page.evaluate( 'document.querySelector(".main-wrap").__vue__.$store.state.userInfo' @@ -114,7 +172,8 @@ async function saveCurrentChatRecord(page) { async function checkJobIsClosed() { const encryptJobId = await pageMapByName.boss!.evaluate(() => { - return document.querySelector('.chat-conversation .chat-im.chat-editor')?.__vue__?.conversation$.encryptJobId + return document.querySelector('.chat-conversation .chat-im.chat-editor')?.__vue__?.conversation$ + .encryptJobId }) if (!encryptJobId) { return false @@ -214,6 +273,44 @@ const mainLoop = async () => { browser = null } } + let bossCookies = readStorageFile('boss-cookies.json') + let cookieCheckResult = checkCookieListFormat(bossCookies) + while (!cookieCheckResult) { + try { + await loginWithCookieAssistant() + bossCookies = readStorageFile('boss-cookies.json') + cookieCheckResult = checkCookieListFormat(bossCookies) + } catch (err) { + await dialog.showMessageBox({ + type: `error`, + message: `登录状态无效`, + detail: `请重新登录BOSS直聘` + }) + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'basic-cookie-check', + status: 'rejected' + }, + runRecordId + } + }) + throw new Error('LOGIN_STATUS_INVALID') + } + } + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'basic-cookie-check', + status: 'fulfilled' + }, + runRecordId + } + }) const canNotConfirmIfHasReadMsgTemplateList = [ 'Boss还没查看你的消息', '你与该职位竞争者PK情况', @@ -232,12 +329,45 @@ const mainLoop = async () => { // #region if (currentPageUrl.startsWith('https://www.zhipin.com/web/user/')) { writeStorageFile('boss-cookies.json', []) - throw new Error('LOGIN_STATUS_INVALID') + try { + // popup login dialog, then update login status + await loginWithCookieAssistant() + } catch (err) { + await dialog.showMessageBox({ + type: `error`, + message: `登录状态无效`, + detail: `请重新登录BOSS直聘` + }) + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'login-status-check', + status: 'rejected' + }, + runRecordId + } + }) + throw new Error('LOGIN_STATUS_INVALID') + } + throw new Error('THROW_FOR_RETRY') } if ( currentPageUrl.startsWith('https://www.zhipin.com/web/common/403.html') || currentPageUrl.startsWith('https://www.zhipin.com/web/common/error.html') ) { + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'login-status-check', + status: 'rejected' + }, + runRecordId + } + }) throw new Error('ACCESS_IS_DENIED') } if (currentPageUrl.startsWith('https://www.zhipin.com/web/user/safe/verify-slider')) { @@ -260,9 +390,31 @@ const mainLoop = async () => { }) if (validateRes.code === 0) { await storeStorage(pageMapByName.boss) + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'login-status-check', + status: 'rejected' + }, + runRecordId + } + }) throw new Error('CAPTCHA_PASSED_AND_NEED_RESTART') } } + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'login-status-check', + status: 'fulfilled' + }, + runRecordId + } + }) // #endregion // check set security question tip modal let setSecurityQuestionTipModelProxy = await pageMapByName.boss!.$( @@ -296,6 +448,9 @@ const mainLoop = async () => { const toCheckItemAtIndex = friendListData.findIndex((it, index) => { return ( index >= cursorToContinueFind && + (onlyRemindBossWithoutBlockCompanyName && blockCompanyNameRegExp + ? !blockCompanyNameRegExp.test(it.brandName) + : true) && (rechatLimitDay && it.updateTime ? +new Date() - it.updateTime < rechatLimitDay * 24 * 60 * 60 * 1000 : true) && @@ -452,10 +607,6 @@ const mainLoop = async () => { } } -let isParentProcessDisconnect = false -process.once('disconnect', () => { - isParentProcessDisconnect = true -}) const rerunInterval = (() => { let v = Number(process.env.MAIN_BOSSGEEKGO_RERUN_INTERVAL) if (isNaN(v)) { @@ -465,19 +616,75 @@ const rerunInterval = (() => { return v })() -let pipe -try { - pipe = fs.createWriteStream(null, { fd: 3 }) -} catch { - console.warn('pipe is not available') -} +const runRecordId = minimist(process.argv.slice(2))['run-record-id'] ?? null export async function runEntry() { - process.on('disconnect', () => { - app.exit() - }) app.dock?.hide() - - while (!isParentProcessDisconnect) { + await app.whenReady() + app.on('window-all-closed', (e) => { + e.preventDefault() + }) + initPublicIpc() + await connectToDaemon() + await sendToDaemon( + { + type: 'ping' + }, + { + needCallback: true + } + ) + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'worker-launch', + status: 'fulfilled' + }, + runRecordId + } + }) + let puppeteerExecutable = await getLastUsedAndAvailableBrowser() + if (!puppeteerExecutable) { + try { + await configWithBrowserAssistant({ autoFind: true }) + } catch (error) { + // + } + puppeteerExecutable = await getLastUsedAndAvailableBrowser() + } + if (!puppeteerExecutable) { + await dialog.showMessageBox({ + type: `error`, + message: `未找到可用的浏览器`, + detail: `请重新运行本程序,按照提示安装、配置浏览器` + }) + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'puppeteer-executable-check', + status: 'rejected' + }, + runRecordId + } + }) + throw new Error(`PUPPETEER_IS_NOT_EXECUTABLE`) + } + sendToDaemon({ + type: 'worker-to-gui-message', + data: { + type: 'prerequisite-step-by-step-checkstep-by-step-check', + step: { + id: 'puppeteer-executable-check', + status: 'fulfilled' + }, + runRecordId + } + }) + process.env.PUPPETEER_EXECUTABLE_PATH = puppeteerExecutable.executablePath + while (true) { try { await mainLoop() } catch (err) { @@ -487,31 +694,44 @@ export async function runEntry() { } catch { // } - // handle error - if ( - err instanceof Error && - ['LOGIN_STATUS_INVALID', 'ACCESS_IS_DENIED', 'ERR_INTERNET_DISCONNECTED'].includes( - err.message - ) - ) { - pipeWriteRegardlessError( - pipe, - JSON.stringify({ - type: err.message - }) + '\r\n' - ) - process.exit(1) + const shouldExit = await checkShouldExit() + if (shouldExit) { + app.exit() + return } - if (err instanceof Error && err.message === 'CANNOT_FIND_A_USABLE_MODEL') { - gtag('cannot_find_a_usable_model') - await dialog.showMessageBox({ - type: 'error', - message: - '未找到可以使用的模型,请确定您所配置的模型均可使用。重启本程序或许可以解决这个问题', - buttons: ['退出'] - }) - process.exit(0) - break; + // handle error + 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 + } + if ( + err.message.includes(`PUPPETEER_IS_NOT_EXECUTABLE`) || + err.message.includes(`Could not find Chrome`) || + err.message.includes(`no executable was found`) + ) { + process.exit(AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE) + break + } + if (err.message === 'CANNOT_FIND_A_USABLE_MODEL') { + gtag('cannot_find_a_usable_model') + await dialog.showMessageBox({ + type: 'error', + message: + '未找到可以使用的模型,请确定您所配置的模型均可使用。重启本程序或许可以解决这个问题', + buttons: ['退出'] + }) + process.exit(AUTO_CHAT_ERROR_EXIT_CODE.LLM_UNAVAILABLE) + break + } } } finally { pageMapByName['boss'] = null @@ -522,8 +742,6 @@ export async function runEntry() { process.exit(0) } -attachListenerForKillSelfOnParentExited() - process.once('uncaughtException', (error) => { console.error('uncaughtException', error) process.exit(1) @@ -533,6 +751,10 @@ process.once('unhandledRejection', (error) => { process.exit(1) }) +process.once('disconnect', () => { + process.exit(0) +}) + async function storeStorage(page) { const [cookies, localStorage] = await Promise.all([ page.cookies(), diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/types.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/types.ts similarity index 100% rename from packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/types.ts rename to packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/types.ts diff --git a/packages/ui/src/main/index.ts b/packages/ui/src/main/index.ts index f41dd58..dcf690f 100644 --- a/packages/ui/src/main/index.ts +++ b/packages/ui/src/main/index.ts @@ -1,12 +1,25 @@ import minimist from 'minimist' +import { runCommon } from './features/run-common' +import { launchDaemon } from './flow/OPEN_SETTING_WINDOW/launch-daemon' +import { app } from 'electron' + +// 捕获未处理的 EPIPE 错误 +process.on('uncaughtException', (err) => { + if (err?.code === 'EPIPE' || err?.code === 'ERR_STREAM_DESTROYED') { + return + } + throw err +}) + const isUiDev = process.env.NODE_ENV === 'development' const commandlineArgs = minimist(isUiDev ? process.argv.slice(2) : process.argv.slice(1)) console.log(commandlineArgs) -const runMode = commandlineArgs['mode']; +const runMode = commandlineArgs['mode'] ;(async () => { switch (runMode) { + // #region internal use case 'geekAutoStartWithBossMain': { const { waitForProcessHandShakeAndRunAutoChat } = await import( './flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index' @@ -14,18 +27,9 @@ const runMode = commandlineArgs['mode']; 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() + case 'downloadDependenciesForInit': { + const { downloadDependenciesForInit } = await import('./flow/DOWNLOAD_DEPENDENCIES/index') + downloadDependenciesForInit() break } case 'launchBossZhipinLoginPageWithPreloadExtension': { @@ -40,15 +44,43 @@ const runMode = commandlineArgs['mode']; launchBossSite() break } - case 'readNoReplyAutoReminder': { - const { runEntry } = await import('./flow/READ_NO_REPLY_AUTO_REMINDER/index') + case 'readNoReplyAutoReminderMain': { + const { runEntry } = await import('./flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index') runEntry() break } + case 'launchDaemon': { + await import('./flow/LAUNCH_DAEMON') + break + } + // #endregion + + // #region user entry + case 'geekAutoStartWithBoss': { + app.dock?.hide() + await launchDaemon() + const { isAlreadyRunning } = await runCommon({ mode: 'geekAutoStartWithBossMain' }) + if (isAlreadyRunning) { + process.exit(0) + } + break + } + case 'readNoReplyAutoReminder': { + app.dock?.hide() + await launchDaemon() + const { isAlreadyRunning } = await runCommon({ mode: 'readNoReplyAutoReminderMain' }) + if (isAlreadyRunning) { + process.exit(0) + } + break + } default: { + globalThis.GEEKGEEKRUN_PROCESS_ROLE = 'ui' + await launchDaemon() const { openSettingWindow } = await import('./flow/OPEN_SETTING_WINDOW/index') openSettingWindow() break } + // #region } })() diff --git a/packages/ui/src/main/utils/initPublicIpc.ts b/packages/ui/src/main/utils/initPublicIpc.ts index 94a75aa..305f99e 100644 --- a/packages/ui/src/main/utils/initPublicIpc.ts +++ b/packages/ui/src/main/utils/initPublicIpc.ts @@ -1,7 +1,13 @@ -import { BrowserWindow, ipcMain, shell } from 'electron' +import { BrowserWindow, dialog, ipcMain, shell } from 'electron' import gtag from './gtag' import buildInfo from '../../common/build-info.json' import os from 'node:os' +import fs from 'node:fs' +import { + ensureStorageFileExist, + readStorageFile, + writeStorageFile +} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' export default function initPublicIpc() { ipcMain.on( @@ -56,4 +62,52 @@ export default function initPublicIpc() { } ) }) + + ipcMain.handle('read-storage-file', async (ev, payload) => { + ensureStorageFileExist() + return await readStorageFile(payload.fileName) + }) + + ipcMain.handle('write-storage-file', async (ev, payload) => { + ensureStorageFileExist() + return await writeStorageFile(payload.fileName, JSON.parse(payload.data)) + }) + ipcMain.handle('get-os-platform', () => { + return os.platform() + }) + ipcMain.handle('choose-file', (ev, { fileChooserConfig }) => { + if (!fileChooserConfig) { + fileChooserConfig = {} + } + const win = BrowserWindow.fromWebContents(ev.sender) + if (!win) { + return dialog.showOpenDialog(fileChooserConfig) + } else { + return dialog.showOpenDialog(win, fileChooserConfig) + } + }) + ipcMain.handle('check-executable-file', (ev, filePath: string) => { + if (!filePath?.trim()) { + return { + message: '文件名无效' + } + } + if (!fs.existsSync(filePath)) { + return { + message: '文件不存在' + } + } + if (!fs.statSync(filePath).isFile()) { + const messageSeg = ['文件不是可执行文件'] + if (os.platform() === 'darwin') { + messageSeg.push( + 'macOS 平台,可执行文件位于“App包.app/Contents/MacOS/ 文件夹下”,而不是“App包.app”文件夹整体' + ) + } + return { + message: messageSeg.join(';') + } + } + return null + }) } diff --git a/packages/ui/src/main/utils/screenshot.ts b/packages/ui/src/main/utils/screenshot.ts new file mode 100644 index 0000000..2c1a57a --- /dev/null +++ b/packages/ui/src/main/utils/screenshot.ts @@ -0,0 +1,67 @@ +import { sendToDaemon } from "../flow/OPEN_SETTING_WINDOW/connect-to-daemon" +import { checkShouldExit } from "./worker" + +export const SCREENSHOT_INTERVAL_MS = 2500 + +export async function pushCurrentPageScreenshot (page) { + try { + if (!page) { + return + } + // 尝试截图当前页面(压缩为 jpeg + base64,避免文件写盘) + const screenshotBase64 = await page.screenshot({ + type: 'jpeg', + quality: 60, + encoding: 'base64', + fullPage: false + }) + const screenshotAt = Date.now() + await sendToDaemon({ + type: 'worker-screenshot', + workerId: process.env.GEEKGEEKRUND_WORKER_ID, + data: { + screenshot: `data:image/jpeg;base64,${screenshotBase64}`, + screenshotAt, + pageUrl: page.url?.() ?? null + } + }) + } catch (err) { + if (err?.message?.includes('Session closed')) { + throw new Error(`PAGE_CLOSED`) + } + // 截图失败不应影响主流程 + console.warn('[pushCurrentPageScreenshot] error', err) + } +} + +export class PeriodPushCurrentPageScreenshotPlugin { + apply(hooks) { + hooks.pageGotten.tap( + 'PeriodPushCurrentPageScreenshotPlugin', + (page) => { + async function periodPushCurrentPageScreenshot () { + try { + if (page.isClosed()) { + return + } + const shouldExit = await checkShouldExit() + if (shouldExit) { + return + } + try { + await pushCurrentPageScreenshot(page) + } + catch (err) { + if (err?.message?.includes(`PAGE_CLOSED`)) { + return + } + } + setTimeout(periodPushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS) + } + catch {} + } + periodPushCurrentPageScreenshot() + } + ) + } +} \ No newline at end of file diff --git a/packages/ui/src/main/utils/worker.ts b/packages/ui/src/main/utils/worker.ts new file mode 100644 index 0000000..2b009c0 --- /dev/null +++ b/packages/ui/src/main/utils/worker.ts @@ -0,0 +1,14 @@ +import { sendToDaemon } from "../flow/OPEN_SETTING_WINDOW/connect-to-daemon" + +export async function checkShouldExit () { + const shouldExitResponse = await sendToDaemon( + { + type: 'check-should-exit', + workerId: process.env.GEEKGEEKRUND_WORKER_ID, + }, + { + needCallback: true + } + ) + return shouldExitResponse?.shouldExit + } \ No newline at end of file diff --git a/packages/ui/src/main/window/browserAssistantWindow.ts b/packages/ui/src/main/window/browserAssistantWindow.ts new file mode 100644 index 0000000..7973351 --- /dev/null +++ b/packages/ui/src/main/window/browserAssistantWindow.ts @@ -0,0 +1,100 @@ +import { BrowserWindow, ipcMain } from 'electron' +import path from 'path' +import { getAnyAvailablePuppeteerExecutable } from '../flow/DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable' +import { + getLastUsedAndAvailableBrowser, + saveLastUsedAndAvailableBrowserInfo +} from '../flow/DOWNLOAD_DEPENDENCIES/utils/browser-history' +import { openBrowserDownloadWindow } from '../features/open-browser-download-window' + +export let browserAssistantWindow: BrowserWindow | null = null + +const registerHandleWithWindow = ( + win: BrowserWindow, + ...args: Parameters +) => { + const [channel, handler] = args + ipcMain.handle(channel, handler) + win.once('closed', () => ipcMain.removeHandler(channel)) +} + +export function createBrowserAssistantWindow( + opt?: Electron.BrowserWindowConstructorOptions, + { autoFind } = {} +): BrowserWindow { + // Create the browser window. + if (browserAssistantWindow) { + browserAssistantWindow!.close() + } + browserAssistantWindow = new BrowserWindow({ + width: 800, + minWidth: 800, + height: 400, + resizable: true, + show: false, + autoHideMenuBar: true, + webPreferences: { + preload: path.join(__dirname, '../preload/index.js'), + sandbox: false + }, + ...opt + }) + + browserAssistantWindow.on('ready-to-show', () => { + browserAssistantWindow!.show() + }) + + // HMR for renderer base on electron-vite cli. + // Load the remote URL for development or the local html file for production. + let routePath = '#/browserAssistant' + if (autoFind) { + routePath = '#/browserAutoFind' + } + if (process.env.NODE_ENV === 'development' && process.env['ELECTRON_RENDERER_URL']) { + browserAssistantWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + routePath) + } else { + browserAssistantWindow.loadURL( + 'file://' + path.join(__dirname, '../renderer/index.html') + routePath + ) + } + + browserAssistantWindow!.once('closed', () => { + browserAssistantWindow = null + }) + + registerHandleWithWindow( + browserAssistantWindow, + 'get-any-available-puppeteer-executable', + async (_, { ignoreCached, noSave } = {}) => { + return await getAnyAvailablePuppeteerExecutable({ ignoreCached, noSave }) + } + ) + + registerHandleWithWindow( + browserAssistantWindow, + 'get-last-used-and-available-browser', + async () => { + return await getLastUsedAndAvailableBrowser() + } + ) + + registerHandleWithWindow( + browserAssistantWindow, + 'save-last-used-and-available-browser-info', + async (_, payload) => { + return await saveLastUsedAndAvailableBrowserInfo(payload) + } + ) + + registerHandleWithWindow(browserAssistantWindow, 'download-browser-with-downloader', async () => { + return await openBrowserDownloadWindow({ + windowOption: { + parent: browserAssistantWindow!, + modal: true, + show: true + } + }) + }) + + return browserAssistantWindow! +} diff --git a/packages/ui/src/main/window/browserDownloadProgressWindow.ts b/packages/ui/src/main/window/browserDownloadProgressWindow.ts new file mode 100644 index 0000000..e915568 --- /dev/null +++ b/packages/ui/src/main/window/browserDownloadProgressWindow.ts @@ -0,0 +1,124 @@ +import { ChildProcess } from 'child_process' +import { BrowserWindow, ipcMain } from 'electron' +import path from 'path' +import * as childProcess from 'node:child_process' +import * as JSONStream from 'JSONStream' +import * as fs from 'node:fs' +import { cacheDir } from '../constant' +import { EXPECT_CHROMIUM_BUILD_ID } from '../../common/constant' +import * as puppeteerManager from '@puppeteer/browsers' + +export let browserDownloadProgressWindow: BrowserWindow | null = null + +const registerHandleWithWindow = ( + win: BrowserWindow, + ...args: Parameters +) => { + const [channel, handler] = args + ipcMain.handle(channel, handler) + win.once('closed', () => ipcMain.removeHandler(channel)) +} + +export function createBrowserDownloadProgressWindow( + opt?: Electron.BrowserWindowConstructorOptions +): BrowserWindow { + // Create the browser window. + if (browserDownloadProgressWindow) { + browserDownloadProgressWindow!.close() + } + browserDownloadProgressWindow = new BrowserWindow({ + width: 600, + height: 200, + resizable: false, + show: false, + autoHideMenuBar: true, + webPreferences: { + preload: path.join(__dirname, '../preload/index.js'), + sandbox: false + }, + ...opt + }) + + browserDownloadProgressWindow.on('ready-to-show', () => { + browserDownloadProgressWindow!.show() + }) + + // HMR for renderer base on electron-vite cli. + // Load the remote URL for development or the local html file for production. + if (process.env.NODE_ENV === 'development' && process.env['ELECTRON_RENDERER_URL']) { + browserDownloadProgressWindow.loadURL( + process.env['ELECTRON_RENDERER_URL'] + '#/browserDownloadProgress' + ) + } else { + browserDownloadProgressWindow.loadURL( + 'file://' + path.join(__dirname, '../renderer/index.html') + '#/browserDownloadProgress' + ) + } + + let subProcessOfCheckAndDownloadDependencies: ChildProcess | null = null + registerHandleWithWindow(browserDownloadProgressWindow, 'setup-dependencies', async () => { + if (subProcessOfCheckAndDownloadDependencies) { + return + } + subProcessOfCheckAndDownloadDependencies = childProcess.spawn( + process.argv[0], + [process.argv[1], `--mode=downloadDependenciesForInit`], + { + 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': { + browserDownloadProgressWindow?.webContents.send(data.type, data) + break + } + case 'PUPPETEER_DOWNLOAD_ENCOUNTER_ERROR': { + console.error(data) + break + } + default: { + return + } + } + } + ) + subProcessOfCheckAndDownloadDependencies!.once('exit', (exitCode) => { + const executablePath = puppeteerManager.computeExecutablePath({ + browser: puppeteerManager.Browser.CHROME, + cacheDir, + buildId: EXPECT_CHROMIUM_BUILD_ID + }) + if (exitCode === 0 && fs.existsSync(executablePath)) { + resolve(executablePath) + } else { + reject('PUPPETEER_DOWNLOAD_ENCOUNTER_ERROR') + } + subProcessOfCheckAndDownloadDependencies = null + }) + }) + }) + + const killHandler = async () => { + try { + subProcessOfCheckAndDownloadDependencies?.kill() + } catch { + // + } finally { + subProcessOfCheckAndDownloadDependencies = null + } + } + browserDownloadProgressWindow.once('closed', () => { + killHandler() + }) + browserDownloadProgressWindow.once('closed', () => { + browserDownloadProgressWindow = null + }) + + return browserDownloadProgressWindow! +} diff --git a/packages/ui/src/main/window/cookieAssistantWindow.ts b/packages/ui/src/main/window/cookieAssistantWindow.ts new file mode 100644 index 0000000..9631a48 --- /dev/null +++ b/packages/ui/src/main/window/cookieAssistantWindow.ts @@ -0,0 +1,132 @@ +import { ChildProcess } from 'child_process' +import { BrowserWindow, dialog, ipcMain } from 'electron' +import path from 'path' +import * as childProcess from 'node:child_process' +import * as JSONStream from 'JSONStream' +import { getLastUsedAndAvailableBrowser } from '../flow/DOWNLOAD_DEPENDENCIES/utils/browser-history' +import { configWithBrowserAssistant } from '../features/config-with-browser-assistant' + +export let cookieAssistantWindow: BrowserWindow | null = null +export function createCookieAssistantWindow( + opt?: Electron.BrowserWindowConstructorOptions +): BrowserWindow { + // Create the browser window. + if (cookieAssistantWindow) { + cookieAssistantWindow!.show() + } + cookieAssistantWindow = new BrowserWindow({ + width: 960, + height: 720, + resizable: true, + show: false, + autoHideMenuBar: true, + webPreferences: { + preload: path.join(__dirname, '../preload/index.js'), + sandbox: false + }, + ...opt + }) + + cookieAssistantWindow.on('ready-to-show', () => { + cookieAssistantWindow!.show() + }) + + // HMR for renderer base on electron-vite cli. + // Load the remote URL for development or the local html file for production. + if (process.env.NODE_ENV === 'development' && process.env['ELECTRON_RENDERER_URL']) { + cookieAssistantWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '#/cookieAssistant') + } else { + cookieAssistantWindow.loadURL( + 'file://' + path.join(__dirname, '../renderer/index.html') + '#/cookieAssistant' + ) + } + + cookieAssistantWindow!.once('closed', () => { + cookieAssistantWindow = null + }) + + let subProcessOfBossZhipinLoginPageWithPreloadExtension: ChildProcess | null = null + const launchHandler = async (ev) => { + try { + subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill() + } catch { + // + } + let puppeteerExecutable = await getLastUsedAndAvailableBrowser() + if (!puppeteerExecutable) { + try { + const parent = BrowserWindow.fromWebContents(ev.sender) || undefined + await configWithBrowserAssistant({ + autoFind: true, + windowOption: { + parent, + modal: !!parent, + show: true + } + }) + } catch (error) { + // + } + puppeteerExecutable = await getLastUsedAndAvailableBrowser() + } + if (!puppeteerExecutable) { + await dialog.showMessageBox({ + type: `error`, + message: `未找到可用的浏览器`, + detail: `请重新运行本程序,按照提示安装、配置浏览器` + }) + return + } + const subProcessEnv = { + ...process.env, + PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable.executablePath + } + subProcessOfBossZhipinLoginPageWithPreloadExtension = childProcess.spawn( + process.argv[0], + [process.argv[1], `--mode=launchBossZhipinLoginPageWithPreloadExtension`], + { + env: subProcessEnv, + stdio: [null, null, null, 'pipe', 'ipc'] + } + ) + subProcessOfBossZhipinLoginPageWithPreloadExtension!.stdio[3]!.pipe(JSONStream.parse()).on( + 'data', + (raw) => { + const data = raw + switch (data.type) { + case 'BOSS_ZHIPIN_COOKIE_COLLECTED': { + cookieAssistantWindow?.webContents.send(data.type, data) + break + } + default: { + return + } + } + } + ) + + subProcessOfBossZhipinLoginPageWithPreloadExtension!.once('exit', () => { + cookieAssistantWindow?.webContents.send('BOSS_ZHIPIN_LOGIN_PAGE_CLOSED') + subProcessOfBossZhipinLoginPageWithPreloadExtension = null + }) + } + ipcMain.on('launch-bosszhipin-login-page-with-preload-extension', launchHandler) + + const killHandler = async () => { + try { + subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill() + } catch { + // + } finally { + subProcessOfBossZhipinLoginPageWithPreloadExtension = null + } + } + ipcMain.on('kill-bosszhipin-login-page-with-preload-extension', killHandler) + cookieAssistantWindow.on('closed', () => { + subProcessOfBossZhipinLoginPageWithPreloadExtension?.kill() + ipcMain.off('launch-bosszhipin-login-page-with-preload-extension', launchHandler) + ipcMain.off('kill-bosszhipin-login-page-with-preload-extension', killHandler) + }) + + return cookieAssistantWindow! +} diff --git a/packages/ui/src/main/window/firstLaunchNoticeWindow.ts b/packages/ui/src/main/window/firstLaunchNoticeWindow.ts index 626f380..45dd4d8 100644 --- a/packages/ui/src/main/window/firstLaunchNoticeWindow.ts +++ b/packages/ui/src/main/window/firstLaunchNoticeWindow.ts @@ -1,6 +1,5 @@ -import { BrowserWindow, ipcMain } from 'electron' +import { BrowserWindow } from 'electron' import path from 'path' -import { createFirstLaunchNoticeApproveFlag } from '../features/first-launch-notice-window' export let firstLaunchNoticeWindow: BrowserWindow | null = null export function createFirstLaunchNoticeWindow( @@ -41,11 +40,3 @@ export function createFirstLaunchNoticeWindow( return firstLaunchNoticeWindow! } - -export const initIpc = () => { - ipcMain.handle('first-launch-notice-approve', () => { - createFirstLaunchNoticeApproveFlag() - firstLaunchNoticeWindow?.close() - }) -} -initIpc() diff --git a/packages/ui/src/main/window/mainWindow.ts b/packages/ui/src/main/window/mainWindow.ts index 79797a2..cb8cec0 100644 --- a/packages/ui/src/main/window/mainWindow.ts +++ b/packages/ui/src/main/window/mainWindow.ts @@ -1,8 +1,7 @@ import { BrowserWindow, shell } from 'electron' import path from 'path' import { openDevTools } from '../commands' -import { createFirstLaunchNoticeWindow } from './firstLaunchNoticeWindow' -import { isFirstLaunchNoticeApproveFlagExist } from '../features/first-launch-notice-window' +import { daemonEE } from '../flow/OPEN_SETTING_WINDOW/connect-to-daemon' export let mainWindow: BrowserWindow | null = null export function createMainWindow(): BrowserWindow { @@ -27,14 +26,6 @@ export function createMainWindow(): BrowserWindow { mainWindow.on('ready-to-show', () => { mainWindow.show() }) - mainWindow.on('ready-to-show', async () => { - !isFirstLaunchNoticeApproveFlagExist() && - createFirstLaunchNoticeWindow({ - parent: mainWindow!, - modal: true, - show: true - }) - }) mainWindow.on('ready-to-show', async () => { process.env.NODE_ENV === 'development' && setTimeout(() => { @@ -58,5 +49,13 @@ export function createMainWindow(): BrowserWindow { mainWindow!.once('closed', () => { mainWindow = null }) + daemonEE.on('message', (message) => { + if (message.type === 'worker-to-gui-message') { + mainWindow?.webContents?.send('worker-to-gui-message', message) + } + }) + daemonEE.on('error', (err) => { + console.log(err) + }) return mainWindow! } diff --git a/packages/ui/src/renderer/src/features/FlyingCompanyLogoList/index.vue b/packages/ui/src/renderer/src/features/FlyingCompanyLogoList/index.vue index c66eab7..a5b4c0b 100644 --- a/packages/ui/src/renderer/src/features/FlyingCompanyLogoList/index.vue +++ b/packages/ui/src/renderer/src/features/FlyingCompanyLogoList/index.vue @@ -11,7 +11,7 @@ + + diff --git a/packages/ui/src/renderer/src/features/TaskManager/Item.vue b/packages/ui/src/renderer/src/features/TaskManager/Item.vue new file mode 100644 index 0000000..21355bb --- /dev/null +++ b/packages/ui/src/renderer/src/features/TaskManager/Item.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/packages/ui/src/renderer/src/features/TaskManager/List.vue b/packages/ui/src/renderer/src/features/TaskManager/List.vue new file mode 100644 index 0000000..5f25401 --- /dev/null +++ b/packages/ui/src/renderer/src/features/TaskManager/List.vue @@ -0,0 +1,18 @@ + + + diff --git a/packages/ui/src/renderer/src/page/BootstrapSplash/index.vue b/packages/ui/src/renderer/src/page/BootstrapSplash/index.vue index d100754..bda8ad5 100644 --- a/packages/ui/src/renderer/src/page/BootstrapSplash/index.vue +++ b/packages/ui/src/renderer/src/page/BootstrapSplash/index.vue @@ -4,56 +4,37 @@
愿你薪想事成
-
- -
diff --git a/packages/ui/src/renderer/src/page/BootstrapSplash/page/DownloadingDependencies.vue b/packages/ui/src/renderer/src/page/BootstrapSplash/page/DownloadingDependencies.vue deleted file mode 100644 index 9714524..0000000 --- a/packages/ui/src/renderer/src/page/BootstrapSplash/page/DownloadingDependencies.vue +++ /dev/null @@ -1,107 +0,0 @@ - - - diff --git a/packages/ui/src/renderer/src/page/BrowserAssistant/index.vue b/packages/ui/src/renderer/src/page/BrowserAssistant/index.vue new file mode 100644 index 0000000..63a96ff --- /dev/null +++ b/packages/ui/src/renderer/src/page/BrowserAssistant/index.vue @@ -0,0 +1,420 @@ + + + + + diff --git a/packages/ui/src/renderer/src/page/BrowserAutoFind/index.vue b/packages/ui/src/renderer/src/page/BrowserAutoFind/index.vue new file mode 100644 index 0000000..aec70c1 --- /dev/null +++ b/packages/ui/src/renderer/src/page/BrowserAutoFind/index.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/packages/ui/src/renderer/src/page/BootstrapSplash/page/FailMessage.vue b/packages/ui/src/renderer/src/page/BrowserDownloadProgress/FailMessage.vue similarity index 51% rename from packages/ui/src/renderer/src/page/BootstrapSplash/page/FailMessage.vue rename to packages/ui/src/renderer/src/page/BrowserDownloadProgress/FailMessage.vue index 4e8807e..32e84ea 100644 --- a/packages/ui/src/renderer/src/page/BootstrapSplash/page/FailMessage.vue +++ b/packages/ui/src/renderer/src/page/BrowserDownloadProgress/FailMessage.vue @@ -1,7 +1,7 @@ diff --git a/packages/ui/src/renderer/src/page/BrowserDownloadProgress/index.vue b/packages/ui/src/renderer/src/page/BrowserDownloadProgress/index.vue new file mode 100644 index 0000000..6e49f00 --- /dev/null +++ b/packages/ui/src/renderer/src/page/BrowserDownloadProgress/index.vue @@ -0,0 +1,100 @@ + + + diff --git a/packages/ui/src/renderer/src/page/CookieAssistant/index.vue b/packages/ui/src/renderer/src/page/CookieAssistant/index.vue index 2df9a10..715bbb6 100644 --- a/packages/ui/src/renderer/src/page/CookieAssistant/index.vue +++ b/packages/ui/src/renderer/src/page/CookieAssistant/index.vue @@ -1,16 +1,11 @@ @@ -214,12 +218,13 @@ function handleViewJobSnapshotButtonClick(record: VMarkAsNotSuitLog) { } const markReasonTopicMap = { - [MarkAsNotSuitReason.BOSS_INACTIVE]: 'Boss不活跃', + [MarkAsNotSuitReason.BOSS_INACTIVE]: 'BOSS不活跃', [MarkAsNotSuitReason.USER_MANUAL_OPERATION_WITH_UNKNOWN_REASON]: '手动标记不合适', [MarkAsNotSuitReason.JOB_NOT_SUIT]: '职位不合适', [MarkAsNotSuitReason.JOB_CITY_NOT_SUIT]: '工作地不合适', [MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT]: '工作经验不合适', - [MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT]: '薪资不合适' + [MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT]: '薪资不合适', + [MarkAsNotSuitReason.COMPANY_NAME_NOT_SUIT]: '公司名称不匹配' } function formatMarkReason(row: VMarkAsNotSuitLog) { @@ -233,8 +238,8 @@ function formatMarkReason(row: VMarkAsNotSuitLog) { } })() return [ - extInfo?.bossActiveTimeDesc && `Boss活跃情况:${extInfo.bossActiveTimeDesc}`, - extInfo?.chosenReasonInUi?.text && `Boss选项内容:${extInfo.chosenReasonInUi.text}` + extInfo?.bossActiveTimeDesc && `BOSS活跃情况:${extInfo.bossActiveTimeDesc}`, + extInfo?.chosenReasonInUi?.text && `BOSS选项内容:${extInfo.chosenReasonInUi.text}` ] .filter(Boolean) .join('\n') @@ -247,7 +252,7 @@ function formatMarkReason(row: VMarkAsNotSuitLog) { return null } })() - return [extInfo?.chosenReasonInUi?.text && `Boss选项内容:${extInfo.chosenReasonInUi.text}`] + return [extInfo?.chosenReasonInUi?.text && `BOSS选项内容:${extInfo.chosenReasonInUi.text}`] .filter(Boolean) .join('\n') } @@ -260,7 +265,7 @@ function formatMarkReason(row: VMarkAsNotSuitLog) { return null } })() - return [extInfo?.chosenReasonInUi?.text && `Boss选项内容:${extInfo.chosenReasonInUi.text}`] + return [extInfo?.chosenReasonInUi?.text && `BOSS选项内容:${extInfo.chosenReasonInUi.text}`] .filter(Boolean) .join('\n') } @@ -274,11 +279,23 @@ function formatMarkReason(row: VMarkAsNotSuitLog) { })() return [ extInfo?.salaryDesc && `薪资:${extInfo.salaryDesc}`, - extInfo?.chosenReasonInUi?.text && `Boss选项内容:${extInfo.chosenReasonInUi.text}` + extInfo?.chosenReasonInUi?.text && `BOSS选项内容:${extInfo.chosenReasonInUi.text}` ] .filter(Boolean) .join('\n') } + case MarkAsNotSuitReason.COMPANY_NAME_NOT_SUIT: { + const extInfo = (() => { + try { + return JSON.parse(row.extInfo) + } catch { + return null + } + })() + return [extInfo?.chosenReasonInUi?.text && `BOSS选项内容:${extInfo.chosenReasonInUi.text}`] + .filter(Boolean) + .join('\n') + } default: { return '' } diff --git a/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue b/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue index 435d511..4da0765 100644 --- a/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue +++ b/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue @@ -1,225 +1,248 @@ @@ -229,11 +252,13 @@ import { dayjs, ElForm, ElMessage, ElMessageBox, ElSelect, ElOption } from 'elem import { useRouter } from 'vue-router' import { RECHAT_CONTENT_SOURCE, - RECHAT_LLM_FALLBACK + RECHAT_LLM_FALLBACK, + RUNNING_STATUS_ENUM } from '../../../../common/enums/auto-start-chat' import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag' import mittBus from '../../utils/mitt' import { QuestionFilled } from '@element-plus/icons-vue' +import RunningOverlay from '@renderer/features/RunningOverlay/index.vue' const gtagRenderer = (name, params?: object) => { return baseGtagRenderer(name, { scene: 'rnrr-config', @@ -248,7 +273,8 @@ const formContent = ref({ rechatContentSource: 1, recentMessageQuantityForLlm: 8, rechatLlmFallback: RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION, - onlyRemindBossWithExpectJobType: true + onlyRemindBossWithExpectJobType: true, + onlyRemindBossWithoutBlockCompanyName: true } }) @@ -286,15 +312,17 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => { }) const expectJobTypeRegExpStr = ref('') -async function fetchExpectJobTypeRegExpStr() { +const blockCompanyNameRegExpStr = ref('') +async function fetchAutoStartChatConfig() { await electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => { expectJobTypeRegExpStr.value = res.config['boss.json']?.expectJobTypeRegExpStr + blockCompanyNameRegExpStr.value = res.config['boss.json']?.blockCompanyNameRegExpStr }) } -fetchExpectJobTypeRegExpStr() -mittBus.on('auto-start-chat-with-boss-config-saved', fetchExpectJobTypeRegExpStr) +fetchAutoStartChatConfig() +mittBus.on('auto-start-chat-with-boss-config-saved', fetchAutoStartChatConfig) onUnmounted(() => { - mittBus.off('auto-start-chat-with-boss-config-saved', fetchExpectJobTypeRegExpStr) + mittBus.off('auto-start-chat-with-boss-config-saved', fetchAutoStartChatConfig) }) const resumeContent = ref(null) @@ -434,7 +462,8 @@ async function checkIsCanRun() { return true } - +const runRecordId = ref(null) +const runningOverlayRef = ref(null) const handleSubmit = async () => { gtagRenderer('run_read_no_reply_reminder_clicked', { throttle_interval_minutes: formContent.value.autoReminder.throttleIntervalMinutes, @@ -471,10 +500,29 @@ const handleSubmit = async () => { } } gtagRenderer('run_read_no_reply_reminder_launched') - router.replace({ - path: '/geekAutoStartChatWithBoss/prepareRun', - query: { flow: 'read-no-reply-reminder' } - }) + + try { + runningOverlayRef.value?.show() + const { runRecordId: rrId } = await electron.ipcRenderer.invoke( + 'run-read-no-reply-auto-reminder' + ) + runRecordId.value = rrId + } catch (err) { + if (err instanceof Error && err.message.includes('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')) { + gtagRenderer('rnrr_cannot_run_for_corrupt') + ElMessage.error({ + message: `核心组件损坏,正在尝试修复` + }) + router.replace('/') + } + console.error(err) + gtagRenderer('rnrr_cannot_run_for_unknown_error', { err }) + } + + // { + // path: '/geekAutoStartChatWithBoss/prepareRun', + // query: { flow: 'read-no-reply-reminder' } + // } } function handleThrottleIntervalMinutesBlur() { if (formContent.value.autoReminder.throttleIntervalMinutes < 3) { @@ -493,11 +541,6 @@ const restoreDefaultTemplate = async () => { }) } -const handleClickLaunchLogin = () => { - gtagRenderer('launch_login_clicked') - router.replace('/cookieAssistant') -} - const currentStamp = ref(new Date()) let timer = 0 function updateCurrentStamp() { @@ -515,15 +558,6 @@ const rechatLimitDateString = computed(() => { ).format('YYYY-MM-DD HH:mm:ss') }) -const handleClickConfigLlm = async () => { - gtagRenderer('config_llm_clicked') - try { - await electron.ipcRenderer.invoke('llm-config') - } catch (err) { - console.log(err) - } -} - const handleClickEditResume = async () => { gtagRenderer('edit_resume_clicked') try { @@ -545,7 +579,7 @@ const rechatLlmFallbackOptions = [ value: RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION }, { - name: '退出已读不回提醒器', + name: '退出已读不回自动复聊', value: RECHAT_LLM_FALLBACK.EXIT_REMINDER_PROGRAM } ] @@ -559,24 +593,54 @@ async function handleTestEffectClicked() { autoReminderConfig: JSON.parse(JSON.stringify(formContent.value.autoReminder)) }) } + +const needToCheckRuntimeDependenciesHandler = () => { + router.replace('/') +} +electron.ipcRenderer.on('need-to-check-runtime-dependencies', needToCheckRuntimeDependenciesHandler) +onUnmounted(() => { + electron.ipcRenderer.removeListener( + 'need-to-check-runtime-dependencies', + needToCheckRuntimeDependenciesHandler + ) +}) + +const isStopButtonLoading = ref(false) +const handleStopButtonClick = async () => { + gtagRenderer('rnrr_stop_button_clicked') + isStopButtonLoading.value = true + try { + electron.ipcRenderer.invoke('stop-read-no-reply-auto-reminder') + runningOverlayRef.value?.hide() + } finally { + isStopButtonLoading.value = false + } +} - diff --git a/packages/ui/src/renderer/src/page/MainLayout/TaskManager.vue b/packages/ui/src/renderer/src/page/MainLayout/TaskManager.vue new file mode 100644 index 0000000..479d52e --- /dev/null +++ b/packages/ui/src/renderer/src/page/MainLayout/TaskManager.vue @@ -0,0 +1,7 @@ + + + diff --git a/packages/ui/src/renderer/src/page/MainLayout/index.vue b/packages/ui/src/renderer/src/page/MainLayout/index.vue index 0340dff..7a8a0ed 100644 --- a/packages/ui/src/renderer/src/page/MainLayout/index.vue +++ b/packages/ui/src/renderer/src/page/MainLayout/index.vue @@ -1,99 +1,155 @@ diff --git a/packages/ui/src/renderer/src/page/MainLayout/resources/intro-of-job-entry.png b/packages/ui/src/renderer/src/page/MainLayout/resources/intro-of-job-entry.png index 33be529..6b5b3a5 100644 Binary files a/packages/ui/src/renderer/src/page/MainLayout/resources/intro-of-job-entry.png and b/packages/ui/src/renderer/src/page/MainLayout/resources/intro-of-job-entry.png differ diff --git a/packages/ui/src/renderer/src/router/index.ts b/packages/ui/src/renderer/src/router/index.ts index f03c94a..5561ec3 100644 --- a/packages/ui/src/renderer/src/router/index.ts +++ b/packages/ui/src/renderer/src/router/index.ts @@ -14,7 +14,28 @@ const routes: Array = [ path: '/cookieAssistant', component: () => import('@renderer/page/CookieAssistant/index.vue'), meta: { - title: 'Boss 登录助手' + title: 'BOSS登录助手' + } + }, + { + path: '/browserAssistant', + component: () => import('@renderer/page/BrowserAssistant/index.vue'), + meta: { + title: '浏览器助手' + } + }, + { + path: '/browserAutoFind', + component: () => import('@renderer/page/BrowserAutoFind/index.vue'), + meta: { + title: '浏览器助手 - 自动查找浏览器' + } + }, + { + path: '/browserDownloadProgress', + component: () => import('@renderer/page/BrowserDownloadProgress/index.vue'), + meta: { + title: '正在下载浏览器' } }, { @@ -35,7 +56,7 @@ const routes: Array = [ path: '/readNoReplyReminderLlmMock', component: () => import('@renderer/page/ReadNoReplyReminderLlmMock/index.vue'), meta: { - title: '已读不回提醒器 大语言模型测试' + title: '已读不回自动复聊 大语言模型测试' } }, { @@ -43,18 +64,25 @@ const routes: Array = [ component: () => import('@renderer/page/MainLayout/index.vue'), redirect: '/main-layout/GeekAutoStartChatWithBoss', children: [ + { + path: 'taskManager', + component: () => import('@renderer/page/MainLayout/TaskManager.vue'), + meta: { + title: '任务管理' + } + }, { path: 'GeekAutoStartChatWithBoss', component: () => import('@renderer/page/MainLayout/GeekAutoStartChatWithBoss/index.vue'), meta: { - title: 'BOSS炸弹' + title: '自动开聊' } }, { path: 'ReadNoReplyReminder', component: () => import('@renderer/page/MainLayout/ReadNoReplyReminder.vue'), meta: { - title: '已读不回提醒器' + title: '已读不回自动复聊' } }, { @@ -82,7 +110,7 @@ const routes: Array = [ path: 'BossLibrary', component: () => import('@renderer/page/MainLayout/BossLibrary.vue'), meta: { - title: 'Boss库' + title: 'BOSS库' } }, { @@ -102,14 +130,14 @@ const routes: Array = [ path: 'prepareRun', component: () => import('@renderer/page/GeekAutoStartChatWithBoss/PrepareRun.vue'), meta: { - title: 'BOSS炸弹 正在预热' + title: '自动开聊 正在预热' } }, { path: 'runningStatus', component: () => import('@renderer/page/GeekAutoStartChatWithBoss/RunningStatus.vue'), meta: { - title: 'BOSS炸弹 正在为你开聊BOSS' + title: '自动开聊 正在为你开聊BOSS' } }, { @@ -119,7 +147,7 @@ const routes: Array = [ '@renderer/page/GeekAutoStartChatWithBoss/RunningStatusForReadNoReplyReminder.vue' ), meta: { - title: '已读不回提醒器 正在为你开聊BOSS' + title: '已读不回自动复聊 正在为你开聊BOSS' } } ] @@ -128,17 +156,8 @@ const routes: Array = [ path: '/', component: BootstrapSplash, meta: { - title: '薪想事成' - }, - children: [ - { - path: '/downloadingDependencies', - component: () => import('@renderer/page/BootstrapSplash/page/DownloadingDependencies.vue'), - meta: { - title: '正在下载核心组件' - } - } - ] + title: '你的职场大机密' + } } ] diff --git a/packages/ui/src/renderer/src/store/index.ts b/packages/ui/src/renderer/src/store/index.ts index a8f9d3c..2858190 100644 --- a/packages/ui/src/renderer/src/store/index.ts +++ b/packages/ui/src/renderer/src/store/index.ts @@ -1,6 +1,7 @@ import { defineStore } from 'pinia' import { NewReleaseInfo } from '../../../common/types/update' import { ref } from 'vue' +import { throttle } from 'lodash' export const useUpdateStore = defineStore('update', () => { const availableNewRelease = ref(null) @@ -18,3 +19,16 @@ export const useUpdateStore = defineStore('update', () => { setInterval(checkUpdate, 30 * 30 * 1000) return { availableNewRelease } }) + +export const useTaskManagerStore = defineStore('taskManager', () => { + const runningTasks = ref([]) + function getRunningTasks() { + const { ipcRenderer } = electron + ipcRenderer.invoke('get-task-manager-list').then(res => { + runningTasks.value = res.workers ?? [] + }) + } + const throttledGetRunningTasks = throttle(getRunningTasks, 2000) + setInterval(throttledGetRunningTasks, 2 * 1000) + return { runningTasks, getRunningTasks: throttledGetRunningTasks } +}) diff --git a/patches/puppeteer-extra-plugin-anonymize-ua@2.4.6.patch b/patches/puppeteer-extra-plugin-anonymize-ua@2.4.6.patch new file mode 100644 index 0000000..0183c22 --- /dev/null +++ b/patches/puppeteer-extra-plugin-anonymize-ua@2.4.6.patch @@ -0,0 +1,39 @@ +diff --git a/index.js b/index.js +index 746525753e19878c0d4711e023e02a77eb5ecf8a..a5c323eb19311235df43c5ae9e0025b7bd1da25d 100644 +--- a/index.js ++++ b/index.js +@@ -21,6 +21,7 @@ const { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin') + * ) + * const browser = await puppeteer.launch() + */ ++ + class Plugin extends PuppeteerExtraPlugin { + constructor(opts = {}) { + super(opts) +@@ -38,7 +39,7 @@ class Plugin extends PuppeteerExtraPlugin { + } + } + +- async onPageCreated(page) { ++ async handler (page) { + let ua = await page.browser().userAgent() + if (this.opts.stripHeadless) { + ua = ua.replace('HeadlessChrome/', 'Chrome/') +@@ -52,6 +53,17 @@ class Plugin extends PuppeteerExtraPlugin { + this.debug('new ua', ua) + await page.setUserAgent(ua) + } ++ ++ async onBrowser(browser) { ++ const pages = await browser.pages() ++ for (const page of pages) { ++ await this.handler(page) ++ } ++ } ++ ++ async onPageCreated(page) { ++ await this.handler(page) ++ } + } + + module.exports = function(pluginConfig) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f110530..048214a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ patchedDependencies: find-chrome-bin@2.0.4: hash: hlvcqc4im4z7xkxtwwtz6sdzui path: patches/find-chrome-bin@2.0.4.patch + puppeteer-extra-plugin-anonymize-ua@2.4.6: + hash: 5bafoxpvwneq2ywcyjznkii37m + path: patches/puppeteer-extra-plugin-anonymize-ua@2.4.6.patch importers: @@ -34,6 +37,9 @@ importers: puppeteer-extra: specifier: 3.3.6 version: 3.3.6(puppeteer@24.19.0) + puppeteer-extra-plugin-anonymize-ua: + specifier: 2.4.6 + version: 2.4.6(patch_hash=5bafoxpvwneq2ywcyjznkii37m)(puppeteer-extra@3.3.6) puppeteer-extra-plugin-stealth: specifier: 2.11.2 version: 2.11.2(puppeteer-extra@3.3.6) @@ -86,6 +92,19 @@ importers: specifier: ^3.3.2 version: 3.3.2 + packages/pm: + dependencies: + JSONStream: + specifier: ^1.3.5 + version: 1.3.5 + split2: + specifier: ^4.2.0 + version: 4.2.0 + devDependencies: + electron: + specifier: 28.2.0 + version: 28.2.0 + packages/run-core-of-geek-auto-start-chat-with-boss: dependencies: '@geekgeekrun/dingtalk-plugin': @@ -103,19 +122,28 @@ importers: packages/sqlite-plugin: dependencies: + better-sqlite3: + specifier: 12.6.2 + version: 12.6.2 cli-highlight: specifier: ^2.1.11 version: 2.1.11 + lodash: + specifier: ^4.17.21 + version: 4.17.21 reflect-metadata: specifier: ^0.2.1 version: 0.2.1 - sqlite3: - specifier: 5.1.6 - version: 5.1.6 typeorm: - specifier: 0.3.11 - version: 0.3.11(sqlite3@5.1.6)(ts-node@10.9.2) + specifier: 0.3.28 + version: 0.3.28(better-sqlite3@12.6.2)(ts-node@10.9.2) devDependencies: + '@types/lodash': + specifier: ^4.17.23 + version: 4.17.23 + node-gyp: + specifier: ^12.2.0 + version: 12.2.0 ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@18.19.15)(typescript@5.3.3) @@ -127,19 +155,16 @@ importers: dependencies: '@electron-toolkit/preload': specifier: ^3.0.0 - version: 3.0.0(electron@28.2.0) + version: 3.0.0(electron@39.2.7) '@electron-toolkit/utils': specifier: ^3.0.0 - version: 3.0.0(electron@28.2.0) - '@geekgeekrun/dingtalk-plugin': - specifier: workspace:* - version: link:../dingtalk-plugin - '@geekgeekrun/geek-auto-start-chat-with-boss': - specifier: workspace:* - version: link:../geek-auto-start-chat-with-boss + version: 3.0.0(electron@39.2.7) '@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension': specifier: workspace:* version: link:../launch-bosszhipin-login-page-with-preload-extension + '@geekgeekrun/pm': + specifier: workspace:* + version: link:../pm '@geekgeekrun/puppeteer-extra-plugin-laodeng': specifier: workspace:* version: link:../laodeng @@ -158,7 +183,7 @@ importers: electron-updater: specifier: ^6.1.7 version: 6.1.7 - lodash-es: + lodash: specifier: ^4.17.21 version: 4.17.21 minimist: @@ -195,6 +220,12 @@ importers: '@electron-toolkit/tsconfig': specifier: ^1.0.1 version: 1.0.1(@types/node@18.19.9) + '@geekgeekrun/dingtalk-plugin': + specifier: workspace:* + version: link:../dingtalk-plugin + '@geekgeekrun/geek-auto-start-chat-with-boss': + specifier: workspace:* + version: link:../geek-auto-start-chat-with-boss '@inquirer/prompts': specifier: ^7.0.1 version: 7.0.1(@types/node@18.19.9) @@ -207,6 +238,9 @@ importers: '@rushstack/eslint-patch': specifier: ^1.7.1 version: 1.7.1 + '@types/lodash': + specifier: ^4.17.23 + version: 4.17.23 '@types/node': specifier: ^18.19.9 version: 18.19.9 @@ -235,8 +269,8 @@ importers: specifier: ^7.0.3 version: 7.0.3 electron: - specifier: 28.2.0 - version: 28.2.0 + specifier: 39.2.7 + version: 39.2.7 electron-builder: specifier: ^24.9.1 version: 24.9.1 @@ -253,7 +287,7 @@ importers: specifier: ^9.20.1 version: 9.20.1(eslint@8.56.0) find-chrome-bin: - specifier: ^2.0.4 + specifier: 2.0.4 version: 2.0.4(patch_hash=hlvcqc4im4z7xkxtwwtz6sdzui) js-yaml: specifier: ^4.1.0 @@ -619,13 +653,6 @@ packages: '@babel/plugin-transform-typescript': 7.23.6(@babel/core@7.23.9) dev: true - /@babel/runtime@7.24.1: - resolution: {integrity: sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.14.1 - dev: false - /@babel/template@7.23.9: resolution: {integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==} engines: {node: '>=6.9.0'} @@ -705,12 +732,12 @@ packages: eslint: 8.56.0 dev: true - /@electron-toolkit/preload@3.0.0(electron@28.2.0): + /@electron-toolkit/preload@3.0.0(electron@39.2.7): resolution: {integrity: sha512-DTyvNGits43J1RiQGFN00/OxjjcTcozLWMjgQBANt97FFptEYiLVasq7kMC24nMwwoOpdDYY9CE6C+4wQJ55/w==} peerDependencies: electron: '>=13.0.0' dependencies: - electron: 28.2.0 + electron: 39.2.7 dev: false /@electron-toolkit/tsconfig@1.0.1(@types/node@18.19.9): @@ -721,12 +748,12 @@ packages: '@types/node': 18.19.9 dev: true - /@electron-toolkit/utils@3.0.0(electron@28.2.0): + /@electron-toolkit/utils@3.0.0(electron@39.2.7): resolution: {integrity: sha512-GaXHDhiT7KCvMJjXdp/QqpYinq69T/Pdl49Z1XLf8mKGf63dnsODMWyrmIjEQ0z/vG7dO8qF3fvmI6Eb2lUNZA==} peerDependencies: electron: '>=13.0.0' dependencies: - electron: 28.2.0 + electron: 39.2.7 dev: false /@electron/asar@3.2.8: @@ -1065,12 +1092,6 @@ packages: resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} dev: true - /@gar/promisify@1.1.3: - resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} - requiresBuild: true - dev: false - optional: true - /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} @@ -1101,7 +1122,7 @@ packages: '@antfu/install-pkg': 0.1.1 '@antfu/utils': 0.7.7 '@iconify/types': 2.0.0 - debug: 4.3.4 + debug: 4.4.3 kolorist: 1.8.0 local-pkg: 0.5.0 mlly: 1.5.0 @@ -1281,6 +1302,18 @@ packages: '@types/node': 18.19.9 dev: true + /@isaacs/balanced-match@4.0.1: + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + dev: true + + /@isaacs/brace-expansion@5.0.1: + resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==} + engines: {node: 20 || >=22} + dependencies: + '@isaacs/balanced-match': 4.0.1 + dev: true + /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1291,6 +1324,12 @@ packages: strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 + + /@isaacs/fs-minipass@4.0.1: + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + dependencies: + minipass: 7.1.2 dev: true /@jridgewell/gen-mapping@0.3.3: @@ -1373,24 +1412,6 @@ packages: - supports-color dev: true - /@mapbox/node-pre-gyp@1.0.11: - resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} - hasBin: true - dependencies: - detect-libc: 2.0.2 - https-proxy-agent: 5.0.1 - make-dir: 3.1.0 - node-fetch: 2.6.7 - nopt: 5.0.0 - npmlog: 5.0.1 - rimraf: 3.0.2 - semver: 7.6.0 - tar: 6.2.0 - transitivePeerDependencies: - - encoding - - supports-color - dev: false - /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1412,31 +1433,30 @@ packages: fastq: 1.17.1 dev: true - /@npmcli/fs@1.1.1: - resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==} - requiresBuild: true + /@npmcli/agent@4.0.0: + resolution: {integrity: sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==} + engines: {node: ^20.17.0 || >=22.9.0} dependencies: - '@gar/promisify': 1.1.3 - semver: 7.7.3 - dev: false - optional: true + agent-base: 7.1.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 11.2.5 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + dev: true - /@npmcli/move-file@1.1.2: - resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==} - engines: {node: '>=10'} - deprecated: This functionality has been moved to @npmcli/fs - requiresBuild: true + /@npmcli/fs@5.0.0: + resolution: {integrity: sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==} + engines: {node: ^20.17.0 || >=22.9.0} dependencies: - mkdirp: 1.0.4 - rimraf: 3.0.2 - dev: false - optional: true + semver: 7.7.3 + dev: true /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} requiresBuild: true - dev: true optional: true /@pkgr/core@0.1.1: @@ -1647,13 +1667,6 @@ packages: dependencies: defer-to-connect: 2.0.1 - /@tootallnate/once@1.1.2: - resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} - engines: {node: '>= 6'} - requiresBuild: true - dev: false - optional: true - /@tootallnate/once@2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -1712,11 +1725,11 @@ packages: /@types/lodash-es@4.17.12: resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} dependencies: - '@types/lodash': 4.14.202 + '@types/lodash': 4.17.23 dev: true - /@types/lodash@4.14.202: - resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==} + /@types/lodash@4.17.23: + resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==} dev: true /@types/ms@0.7.34: @@ -1740,6 +1753,11 @@ packages: undici-types: 5.26.5 dev: true + /@types/node@22.19.7: + resolution: {integrity: sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==} + dependencies: + undici-types: 6.21.0 + /@types/plist@3.0.5: resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} requiresBuild: true @@ -1797,7 +1815,7 @@ packages: graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 - semver: 7.6.0 + semver: 7.7.3 ts-api-utils: 1.2.1(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: @@ -1873,7 +1891,7 @@ packages: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.0 + semver: 7.7.3 ts-api-utils: 1.2.1(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: @@ -1893,7 +1911,7 @@ packages: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) eslint: 8.56.0 - semver: 7.6.0 + semver: 7.7.3 transitivePeerDependencies: - supports-color - typescript @@ -2282,7 +2300,7 @@ packages: '@vue/compiler-dom': 3.4.18 '@vue/shared': 3.4.18 computeds: 0.0.1 - minimatch: 9.0.3 + minimatch: 9.0.5 muggle-string: 0.3.1 path-browserify: 1.0.1 typescript: 5.3.3 @@ -2362,10 +2380,10 @@ packages: through: 2.3.8 dev: false - /abbrev@1.1.1: - resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} - requiresBuild: true - dev: false + /abbrev@4.0.0: + resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} + engines: {node: ^20.17.0 || >=22.9.0} + dev: true /abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} @@ -2404,6 +2422,7 @@ packages: debug: 4.4.3 transitivePeerDependencies: - supports-color + dev: true /agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} @@ -2417,16 +2436,6 @@ packages: humanize-ms: 1.2.1 dev: false - /aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} - requiresBuild: true - dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 - dev: false - optional: true - /ajv-keywords@3.5.2(ajv@6.12.6): resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} peerDependencies: @@ -2462,7 +2471,6 @@ packages: /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: true /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -2479,7 +2487,11 @@ packages: /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true + + /ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + dev: false /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -2526,7 +2538,7 @@ packages: minimatch: 5.1.6 read-config-file: 6.3.2 sanitize-filename: 1.6.3 - semver: 7.6.0 + semver: 7.7.3 tar: 6.2.0 temp-file: 3.4.0 transitivePeerDependencies: @@ -2538,28 +2550,6 @@ packages: engines: {node: '>= 6.0.0'} dev: false - /aproba@2.0.0: - resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} - dev: false - - /are-we-there-yet@2.0.0: - resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} - engines: {node: '>=10'} - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 - dev: false - - /are-we-there-yet@3.0.1: - resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - requiresBuild: true - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 - dev: false - optional: true - /arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -2617,6 +2607,13 @@ packages: engines: {node: '>= 4.0.0'} dev: true + /available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + dependencies: + possible-typed-array-names: 1.1.0 + dev: false + /b4a@1.6.6: resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} @@ -2698,15 +2695,38 @@ packages: resolution: {integrity: sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==} engines: {node: '>=10.0.0'} + /better-sqlite3@12.6.2: + resolution: {integrity: sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==} + engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} + requiresBuild: true + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + dev: false + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} dev: true + /bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 + dev: false + /birpc@2.3.0: resolution: {integrity: sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==} dev: false + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + /bluebird-lst@1.0.9: resolution: {integrity: sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==} dependencies: @@ -2736,7 +2756,6 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: true /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -2774,8 +2793,6 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: true - optional: true /buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} @@ -2821,33 +2838,22 @@ packages: engines: {node: '>=8'} dev: true - /cacache@15.3.0: - resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} - engines: {node: '>= 10'} - requiresBuild: true + /cacache@20.0.3: + resolution: {integrity: sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==} + engines: {node: ^20.17.0 || >=22.9.0} dependencies: - '@npmcli/fs': 1.1.1 - '@npmcli/move-file': 1.1.2 - chownr: 2.0.0 - fs-minipass: 2.1.0 - glob: 7.2.3 - infer-owner: 1.0.4 - lru-cache: 6.0.0 - minipass: 3.3.6 - minipass-collect: 1.0.2 + '@npmcli/fs': 5.0.0 + fs-minipass: 3.0.3 + glob: 13.0.1 + lru-cache: 11.2.5 + minipass: 7.1.2 + minipass-collect: 2.0.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 - mkdirp: 1.0.4 - p-map: 4.0.0 - promise-inflight: 1.0.1 - rimraf: 3.0.2 - ssri: 8.0.1 - tar: 6.2.0 - unique-filename: 1.1.1 - transitivePeerDependencies: - - bluebird - dev: false - optional: true + p-map: 7.0.4 + ssri: 13.0.0 + unique-filename: 5.0.0 + dev: true /cacheable-lookup@5.0.4: resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} @@ -2865,6 +2871,32 @@ packages: normalize-url: 6.1.0 responselike: 2.0.1 + /call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + dev: false + + /call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + dev: false + + /call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + dev: false + /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -2931,9 +2963,19 @@ packages: fsevents: 2.3.3 dev: true + /chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + dev: false + /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} + dev: true + + /chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + dev: true /chromium-bidi@8.0.0(devtools-protocol@0.0.1495869): resolution: {integrity: sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==} @@ -2954,13 +2996,6 @@ packages: engines: {node: '>=8'} dev: true - /clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - requiresBuild: true - dev: false - optional: true - /cli-highlight@2.1.11: resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} engines: {node: '>=8.0.0', npm: '>=5.0.0'} @@ -3029,6 +3064,7 @@ packages: /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + requiresBuild: true dependencies: color-name: 1.1.4 @@ -3037,11 +3073,7 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - /color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} - hasBin: true - dev: false + requiresBuild: true /colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -3077,7 +3109,7 @@ packages: /config-file-ts@0.2.6: resolution: {integrity: sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w==} dependencies: - glob: 10.3.10 + glob: 10.5.0 typescript: 5.3.3 dev: true @@ -3086,10 +3118,6 @@ packages: engines: {node: ^14.18.0 || >=16.10.0} dev: true - /console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - dev: false - /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true @@ -3149,7 +3177,6 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: true /css-select@5.2.2: resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} @@ -3192,13 +3219,6 @@ packages: resolution: {integrity: sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==} engines: {node: '>= 14'} - /date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} - engines: {node: '>=0.11'} - dependencies: - '@babel/runtime': 7.24.1 - dev: false - /dayjs@1.11.10: resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} dev: false @@ -3207,6 +3227,10 @@ packages: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} dev: true + /dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + dev: false + /de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} dev: true @@ -3239,6 +3263,20 @@ packages: dependencies: mimic-response: 3.1.0 + /dedent@1.7.1: + resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + dev: false + + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dev: false + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true @@ -3263,6 +3301,15 @@ packages: has-property-descriptors: 1.0.1 optional: true + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + dev: false + /define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -3289,10 +3336,6 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - /delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - dev: false - /destr@2.0.3: resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} dev: true @@ -3405,8 +3448,8 @@ packages: resolution: {integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==} dev: true - /dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + /dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} dev: false @@ -3415,13 +3458,21 @@ packages: engines: {node: '>=10'} dev: true + /dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + dev: false + /duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} dev: true /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true /ejs@3.1.9: resolution: {integrity: sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==} @@ -3517,6 +3568,19 @@ packages: extract-zip: 2.0.1 transitivePeerDependencies: - supports-color + dev: true + + /electron@39.2.7: + resolution: {integrity: sha512-KU0uFS6LSTh4aOIC3miolcbizOFP7N1M46VTYVfqIgFiuA2ilfNaOHLDS9tCMvwwHRowAsvqBrh9NgMXcTOHCQ==} + engines: {node: '>= 12.20.55'} + hasBin: true + requiresBuild: true + dependencies: + '@electron/get': 2.0.3 + '@types/node': 22.19.7 + extract-zip: 2.0.1 + transitivePeerDependencies: + - supports-color /element-plus@2.8.4(vue@3.4.15): resolution: {integrity: sha512-ZlVAdUOoJliv4kW3ntWnnSHMT+u/Os7mXJjk2xzOlqNeHaI2/ozlF+R58ZCEak8ZnDi6+5A2viWEYRsq64IuiA==} @@ -3527,7 +3591,7 @@ packages: '@element-plus/icons-vue': 2.3.1(vue@3.4.15) '@floating-ui/dom': 1.6.1 '@popperjs/core': /@sxzz/popperjs-es@2.11.7 - '@types/lodash': 4.14.202 + '@types/lodash': 4.17.23 '@types/lodash-es': 4.17.12 '@vueuse/core': 9.13.0(vue@3.4.15) async-validator: 4.2.5 @@ -3545,17 +3609,17 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + requiresBuild: true /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: true /encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} requiresBuild: true dependencies: iconv-lite: 0.6.3 - dev: false + dev: true optional: true /end-of-stream@1.4.4: @@ -3578,6 +3642,7 @@ packages: /err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + dev: true /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -3585,11 +3650,22 @@ packages: is-arrayish: 0.2.1 dev: false + /es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + dev: false + /es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} requiresBuild: true - optional: true + + /es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + dev: false /es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} @@ -3841,6 +3917,15 @@ packages: strip-final-newline: 2.0.0 dev: true + /expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + dev: false + + /exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + dev: true + /external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} @@ -3911,6 +3996,18 @@ packages: dependencies: pend: 1.2.0 + /fdir@6.5.0(picomatch@4.0.3): + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + dependencies: + picomatch: 4.0.3 + dev: true + /fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} @@ -3926,6 +4023,10 @@ packages: flat-cache: 3.2.0 dev: true + /file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + dev: false + /filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} dependencies: @@ -3972,6 +4073,13 @@ packages: resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} dev: true + /for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + dev: false + /for-in@0.1.8: resolution: {integrity: sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==} engines: {node: '>=0.10.0'} @@ -3995,7 +4103,6 @@ packages: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - dev: true /form-data-encoder@1.7.2: resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} @@ -4024,6 +4131,10 @@ packages: fetch-blob: 3.2.0 dev: false + /fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + dev: false + /fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -4055,6 +4166,14 @@ packages: engines: {node: '>= 8'} dependencies: minipass: 3.3.6 + dev: true + + /fs-minipass@3.0.3: + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + minipass: 7.1.2 + dev: true /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -4070,37 +4189,6 @@ packages: /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - /gauge@3.0.2: - resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} - engines: {node: '>=10'} - dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - object-assign: 4.1.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - dev: false - - /gauge@4.0.4: - resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - requiresBuild: true - dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - dev: false - optional: true - /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -4120,7 +4208,30 @@ packages: has-proto: 1.0.1 has-symbols: 1.0.3 hasown: 2.0.1 - optional: true + + /get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + dev: false + + /get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + dev: false /get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} @@ -4144,6 +4255,10 @@ packages: transitivePeerDependencies: - supports-color + /github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + dev: false + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -4158,16 +4273,24 @@ packages: is-glob: 4.0.3 dev: true - /glob@10.3.10: - resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} - engines: {node: '>=16 || 14 >=14.17'} + /glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} hasBin: true dependencies: foreground-child: 3.1.1 - jackspeak: 2.3.6 - minimatch: 9.0.3 - minipass: 7.0.4 - path-scurry: 1.10.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + /glob@13.0.1: + resolution: {integrity: sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==} + engines: {node: 20 || >=22} + dependencies: + minimatch: 10.1.2 + minipass: 7.1.2 + path-scurry: 2.0.1 dev: true /glob@7.2.3: @@ -4232,6 +4355,11 @@ packages: get-intrinsic: 1.2.4 optional: true + /gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + dev: false + /got@11.8.6: resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} engines: {node: '>=10.19.0'} @@ -4277,20 +4405,32 @@ packages: get-intrinsic: 1.2.4 optional: true + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + dependencies: + es-define-property: 1.0.1 + dev: false + /has-proto@1.0.1: resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} engines: {node: '>= 0.4'} requiresBuild: true - optional: true /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} requiresBuild: true - optional: true - /has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + /has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + dev: false + + /has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 dev: false /hasown@2.0.1: @@ -4299,6 +4439,13 @@ packages: dependencies: function-bind: 1.1.2 + /hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + dependencies: + function-bind: 1.1.2 + dev: false + /he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -4331,19 +4478,6 @@ packages: /http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - /http-proxy-agent@4.0.1: - resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} - engines: {node: '>= 6'} - requiresBuild: true - dependencies: - '@tootallnate/once': 1.1.2 - agent-base: 6.0.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - dev: false - optional: true - /http-proxy-agent@5.0.0: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} engines: {node: '>= 6'} @@ -4379,6 +4513,7 @@ packages: debug: 4.4.3 transitivePeerDependencies: - supports-color + dev: true /https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} @@ -4398,7 +4533,7 @@ packages: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} requiresBuild: true dependencies: - ms: 2.1.2 + ms: 2.1.3 dev: false /iconv-corefoundation@1.1.7: @@ -4424,6 +4559,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 + dev: true /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -4448,19 +4584,7 @@ packages: /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - - /indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - requiresBuild: true - dev: false - optional: true - - /infer-owner@1.0.4: - resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} - requiresBuild: true - dev: false - optional: true + dev: true /inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} @@ -4471,16 +4595,14 @@ packages: /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: false + /ip-address@10.1.0: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} - /ip@2.0.0: - resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} - requiresBuild: true - dev: false - optional: true - /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: false @@ -4496,6 +4618,11 @@ packages: resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} dev: false + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: false + /is-ci@3.0.1: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true @@ -4522,6 +4649,7 @@ packages: /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + requiresBuild: true /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} @@ -4530,12 +4658,6 @@ packages: is-extglob: 2.1.1 dev: true - /is-lambda@1.0.1: - resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} - requiresBuild: true - dev: false - optional: true - /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -4558,11 +4680,22 @@ packages: engines: {node: '>=8'} dev: true + /is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + dependencies: + which-typed-array: 1.1.20 + dev: false + /is-what@4.1.16: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} dev: false + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: false + /isbinaryfile@4.0.10: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} engines: {node: '>= 8.0.0'} @@ -4576,19 +4709,22 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + /isexe@3.1.4: + resolution: {integrity: sha512-jCErc4h4RnTPjFq53G4whhjAMbUAqinGrCrTT4dmMNyi4zTthK+wphqbRLJtL4BN/Mq7Zzltr0m/b1X0m7PGFQ==} + engines: {node: '>=20'} + dev: true + /isobject@3.0.1: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} dev: false - /jackspeak@2.3.6: - resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} - engines: {node: '>=14'} + /jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: true /jake@10.8.7: resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==} @@ -4737,6 +4873,7 @@ packages: /lodash-es@4.17.21: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + dev: true /lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21): resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==} @@ -4764,7 +4901,6 @@ packages: /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: true /lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} @@ -4773,6 +4909,10 @@ packages: /lru-cache@10.2.0: resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} engines: {node: 14 || >=16.14} + + /lru-cache@11.2.5: + resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==} + engines: {node: 20 || >=22} dev: true /lru-cache@5.1.1: @@ -4803,42 +4943,27 @@ packages: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - /make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - dependencies: - semver: 6.3.1 - dev: false - /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - /make-fetch-happen@9.1.0: - resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} - engines: {node: '>= 10'} - requiresBuild: true + /make-fetch-happen@15.0.3: + resolution: {integrity: sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==} + engines: {node: ^20.17.0 || >=22.9.0} dependencies: - agentkeepalive: 4.5.0 - cacache: 15.3.0 + '@npmcli/agent': 4.0.0 + cacache: 20.0.3 http-cache-semantics: 4.1.1 - http-proxy-agent: 4.0.1 - https-proxy-agent: 5.0.1 - is-lambda: 1.0.1 - lru-cache: 6.0.0 - minipass: 3.3.6 - minipass-collect: 1.0.2 - minipass-fetch: 1.4.1 + minipass: 7.1.2 + minipass-fetch: 5.0.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 - negotiator: 0.6.3 + negotiator: 1.0.0 + proc-log: 6.1.0 promise-retry: 2.0.1 - socks-proxy-agent: 6.2.1 - ssri: 8.0.1 + ssri: 13.0.0 transitivePeerDependencies: - - bluebird - supports-color - dev: false - optional: true + dev: true /matcher@3.0.0: resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} @@ -4848,6 +4973,11 @@ packages: escape-string-regexp: 4.0.0 optional: true + /math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + dev: false + /mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} dev: true @@ -4911,6 +5041,13 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} + /minimatch@10.1.2: + resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==} + engines: {node: 20 || >=22} + dependencies: + '@isaacs/brace-expansion': 5.0.1 + dev: true + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -4930,79 +5067,84 @@ packages: brace-expansion: 2.0.1 dev: true + /minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - /minipass-collect@1.0.2: - resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} - engines: {node: '>= 8'} - requiresBuild: true + /minipass-collect@2.0.1: + resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} + engines: {node: '>=16 || 14 >=14.17'} dependencies: - minipass: 3.3.6 - dev: false - optional: true + minipass: 7.1.2 + dev: true - /minipass-fetch@1.4.1: - resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==} - engines: {node: '>=8'} - requiresBuild: true + /minipass-fetch@5.0.1: + resolution: {integrity: sha512-yHK8pb0iCGat0lDrs/D6RZmCdaBT64tULXjdxjSMAqoDi18Q3qKEUTHypHQZQd9+FYpIS+lkvpq6C/R6SbUeRw==} + engines: {node: ^20.17.0 || >=22.9.0} dependencies: - minipass: 3.3.6 - minipass-sized: 1.0.3 - minizlib: 2.1.2 + minipass: 7.1.2 + minipass-sized: 2.0.0 + minizlib: 3.1.0 optionalDependencies: encoding: 0.1.13 - dev: false - optional: true + dev: true /minipass-flush@1.0.5: resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} engines: {node: '>= 8'} - requiresBuild: true dependencies: minipass: 3.3.6 - dev: false - optional: true + dev: true /minipass-pipeline@1.2.4: resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} engines: {node: '>=8'} - requiresBuild: true dependencies: minipass: 3.3.6 - dev: false - optional: true + dev: true - /minipass-sized@1.0.3: - resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + /minipass-sized@2.0.0: + resolution: {integrity: sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==} engines: {node: '>=8'} - requiresBuild: true dependencies: - minipass: 3.3.6 - dev: false - optional: true + minipass: 7.1.2 + dev: true /minipass@3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} dependencies: yallist: 4.0.0 + dev: true /minipass@5.0.0: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} - - /minipass@7.0.4: - resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} - engines: {node: '>=16 || 14 >=14.17'} dev: true + /minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} dependencies: minipass: 3.3.6 yallist: 4.0.0 + dev: true + + /minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + dependencies: + minipass: 7.1.2 + dev: true /mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} @@ -5016,10 +5158,15 @@ packages: is-extendable: 0.1.1 dev: false + /mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + dev: false + /mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true + dev: true /mlly@1.5.0: resolution: {integrity: sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==} @@ -5063,31 +5210,36 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + /napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + dev: false + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true - /negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + /negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} - requiresBuild: true - dev: false - optional: true + dev: true /netmask@2.0.2: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} + /node-abi@3.87.0: + resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==} + engines: {node: '>=10'} + dependencies: + semver: 7.7.3 + dev: false + /node-addon-api@1.7.2: resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} requiresBuild: true dev: true optional: true - /node-addon-api@4.3.0: - resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} - dev: false - /node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -5118,27 +5270,24 @@ packages: formdata-polyfill: 4.0.10 dev: false - /node-gyp@8.4.1: - resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==} - engines: {node: '>= 10.12.0'} + /node-gyp@12.2.0: + resolution: {integrity: sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==} + engines: {node: ^20.17.0 || >=22.9.0} hasBin: true - requiresBuild: true dependencies: env-paths: 2.2.1 - glob: 7.2.3 + exponential-backoff: 3.1.3 graceful-fs: 4.2.11 - make-fetch-happen: 9.1.0 - nopt: 5.0.0 - npmlog: 6.0.2 - rimraf: 3.0.2 - semver: 7.6.0 - tar: 6.2.0 - which: 2.0.2 + make-fetch-happen: 15.0.3 + nopt: 9.0.0 + proc-log: 6.1.0 + semver: 7.7.3 + tar: 7.5.7 + tinyglobby: 0.2.15 + which: 6.0.0 transitivePeerDependencies: - - bluebird - supports-color - dev: false - optional: true + dev: true /node-machine-id@1.1.12: resolution: {integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==} @@ -5148,13 +5297,13 @@ packages: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} dev: true - /nopt@5.0.0: - resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} - engines: {node: '>=6'} + /nopt@9.0.0: + resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==} + engines: {node: ^20.17.0 || >=22.9.0} hasBin: true dependencies: - abbrev: 1.1.1 - dev: false + abbrev: 4.0.0 + dev: true /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -5180,29 +5329,6 @@ packages: path-key: 3.1.1 dev: true - /npmlog@5.0.1: - resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} - deprecated: This package is no longer supported. - dependencies: - are-we-there-yet: 2.0.0 - console-control-strings: 1.1.0 - gauge: 3.0.2 - set-blocking: 2.0.0 - dev: false - - /npmlog@6.0.2: - resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - requiresBuild: true - dependencies: - are-we-there-yet: 3.0.1 - console-control-strings: 1.1.0 - gauge: 4.0.4 - set-blocking: 2.0.0 - dev: false - optional: true - /nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: @@ -5297,14 +5423,10 @@ packages: p-limit: 3.1.0 dev: true - /p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} - requiresBuild: true - dependencies: - aggregate-error: 3.1.0 - dev: false - optional: true + /p-map@7.0.4: + resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} + engines: {node: '>=18'} + dev: true /pac-proxy-agent@7.2.0: resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} @@ -5328,6 +5450,9 @@ packages: degenerator: 5.0.1 netmask: 2.0.2 + /package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -5387,18 +5512,24 @@ packages: /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: true /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true - /path-scurry@1.10.1: - resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} - engines: {node: '>=16 || 14 >=14.17'} + /path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} dependencies: lru-cache: 10.2.0 - minipass: 7.0.4 + minipass: 7.1.2 + + /path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} + engines: {node: 20 || >=22} + dependencies: + lru-cache: 11.2.5 + minipass: 7.1.2 dev: true /path-type@4.0.0: @@ -5424,6 +5555,11 @@ packages: engines: {node: '>=8.6'} dev: true + /picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + dev: true + /pinia@3.0.2(typescript@5.3.3)(vue@3.4.15): resolution: {integrity: sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g==} peerDependencies: @@ -5472,6 +5608,11 @@ packages: xmlbuilder: 15.1.1 dev: true + /possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + dev: false + /postcss-selector-parser@6.0.15: resolution: {integrity: sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==} engines: {node: '>=4'} @@ -5488,6 +5629,25 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + dependencies: + detect-libc: 2.0.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.87.0 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + dev: false + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -5506,27 +5666,22 @@ packages: hasBin: true dev: true + /proc-log@6.1.0: + resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} + engines: {node: ^20.17.0 || >=22.9.0} + dev: true + /progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} - /promise-inflight@1.0.1: - resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} - requiresBuild: true - peerDependencies: - bluebird: '*' - peerDependenciesMeta: - bluebird: - optional: true - dev: false - optional: true - /promise-retry@2.0.1: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} dependencies: err-code: 2.0.3 retry: 0.12.0 + dev: true /proxy-agent@6.5.0: resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} @@ -5575,6 +5730,26 @@ packages: - utf-8-validate dev: false + /puppeteer-extra-plugin-anonymize-ua@2.4.6(patch_hash=5bafoxpvwneq2ywcyjznkii37m)(puppeteer-extra@3.3.6): + resolution: {integrity: sha512-EasOjKpPYFn72Yk7/RXM2q/CDrdPzjTnx8GfHPrwVmhcnh8Ull1DnONP73KZFF2bdwhitaQ3zRZ0yI1yFM2rzQ==} + engines: {node: '>=8'} + peerDependencies: + playwright-extra: '*' + puppeteer-extra: '*' + peerDependenciesMeta: + playwright-extra: + optional: true + puppeteer-extra: + optional: true + dependencies: + debug: 4.4.3 + puppeteer-extra: 3.3.6(puppeteer@24.19.0) + puppeteer-extra-plugin: 3.2.3(playwright-extra@4.3.6)(puppeteer-extra@3.3.6) + transitivePeerDependencies: + - supports-color + dev: false + patched: true + /puppeteer-extra-plugin-stealth@2.11.2(puppeteer-extra@3.3.6): resolution: {integrity: sha512-bUemM5XmTj9i2ZerBzsk2AN5is0wHMNE6K0hXBzBXOzP5m5G3Wl0RHhiqKeHToe/uIH8AoZiGhc1tCkLZQPKTQ==} engines: {node: '>=8'} @@ -5713,6 +5888,16 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} + /rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + dev: false + /read-config-file@6.3.2: resolution: {integrity: sha512-M80lpCjnE6Wt6zb98DoW8WHR09nzMSpu8XHtPkiTHrJ5Az9CybfeQhTJ8D7saeBHpGhLPIVyA8lcL6ZmdKwY6Q==} engines: {node: '>=12.0.0'} @@ -5741,16 +5926,12 @@ packages: picomatch: 2.3.1 dev: true - /reflect-metadata@0.1.14: - resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==} - dev: false - /reflect-metadata@0.2.1: resolution: {integrity: sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==} dev: false - /regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + /reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} dev: false /require-directory@2.1.1: @@ -5781,6 +5962,7 @@ packages: /retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} + dev: true /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} @@ -5845,6 +6027,7 @@ packages: /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: true /sanitize-filename@1.6.3: resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==} @@ -5898,16 +6081,26 @@ packages: type-fest: 0.13.1 optional: true - /set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + /set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 dev: false - /sha.js@2.4.11: - resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + /sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} hasBin: true dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 + to-buffer: 1.2.2 dev: false /shallow-clone@0.1.2: @@ -5925,26 +6118,36 @@ packages: engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - dev: true /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: true /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: true + + /simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + dev: false + + /simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + dev: false /simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} dependencies: - semver: 7.6.0 + semver: 7.7.3 dev: true /sirv@2.0.4: @@ -5976,19 +6179,6 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - /socks-proxy-agent@6.2.1: - resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} - engines: {node: '>= 10'} - requiresBuild: true - dependencies: - agent-base: 6.0.2 - debug: 4.4.3 - socks: 2.7.1 - transitivePeerDependencies: - - supports-color - dev: false - optional: true - /socks-proxy-agent@8.0.5: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} @@ -5999,16 +6189,6 @@ packages: transitivePeerDependencies: - supports-color - /socks@2.7.1: - resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==} - engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} - requiresBuild: true - dependencies: - ip: 2.0.0 - smart-buffer: 4.2.0 - dev: false - optional: true - /socks@2.8.7: resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} @@ -6045,34 +6225,27 @@ packages: engines: {node: '>=0.10.0'} dev: false + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: false + /sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} requiresBuild: true optional: true - /sqlite3@5.1.6: - resolution: {integrity: sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==} - requiresBuild: true - dependencies: - '@mapbox/node-pre-gyp': 1.0.11 - node-addon-api: 4.3.0 - tar: 6.2.0 - optionalDependencies: - node-gyp: 8.4.1 - transitivePeerDependencies: - - bluebird - - encoding - - supports-color + /sql-highlight@6.1.0: + resolution: {integrity: sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==} + engines: {node: '>=14'} dev: false - /ssri@8.0.1: - resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} - engines: {node: '>= 8'} - requiresBuild: true + /ssri@13.0.0: + resolution: {integrity: sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==} + engines: {node: ^20.17.0 || >=22.9.0} dependencies: - minipass: 3.3.6 - dev: false - optional: true + minipass: 7.1.2 + dev: true /stat-mode@1.0.0: resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==} @@ -6111,7 +6284,6 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - dev: true /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -6131,13 +6303,17 @@ packages: engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: true /strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} dev: true + /strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + dev: false + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -6194,6 +6370,15 @@ packages: engines: {node: '>=6'} dev: false + /tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 + dev: false + /tar-fs@3.1.1: resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} dependencies: @@ -6206,6 +6391,17 @@ packages: - bare-abort-controller - bare-buffer + /tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + /tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} dependencies: @@ -6223,6 +6419,18 @@ packages: minizlib: 2.1.2 mkdirp: 1.0.4 yallist: 4.0.0 + dev: true + + /tar@7.5.7: + resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==} + engines: {node: '>=18'} + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 + dev: true /temp-file@3.4.0: resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==} @@ -6274,6 +6482,14 @@ packages: resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==} dev: false + /tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + dev: true + /tmp-promise@3.0.3: resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} dependencies: @@ -6294,6 +6510,15 @@ packages: rimraf: 3.0.2 dev: true + /to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + dev: false + /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -6362,6 +6587,16 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + /tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + dev: false + + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -6385,32 +6620,40 @@ packages: engines: {node: '>=10'} dev: true + /typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + dev: false + /typed-query-selector@2.12.0: resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} dev: false - /typeorm@0.3.11(sqlite3@5.1.6)(ts-node@10.9.2): - resolution: {integrity: sha512-pzdOyWbVuz/z8Ww6gqvBW4nylsM0KLdUCDExr2gR20/x1khGSVxQkjNV/3YqliG90jrWzrknYbYscpk8yxFJVg==} - engines: {node: '>= 12.9.0'} + /typeorm@0.3.28(better-sqlite3@12.6.2)(ts-node@10.9.2): + resolution: {integrity: sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==} + engines: {node: '>=16.13.0'} hasBin: true peerDependencies: - '@google-cloud/spanner': ^5.18.0 - '@sap/hana-client': ^2.12.25 - better-sqlite3: ^7.1.2 || ^8.0.0 - hdb-pool: ^0.1.6 + '@google-cloud/spanner': ^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + '@sap/hana-client': ^2.14.22 + better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 ioredis: ^5.0.4 - mongodb: ^3.6.0 - mssql: ^7.3.0 - mysql2: ^2.2.5 - oracledb: ^5.1.0 + mongodb: ^5.8.0 || ^6.0.0 + mssql: ^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^6.3.0 pg: ^8.5.1 pg-native: ^3.0.0 pg-query-stream: ^4.0.0 - redis: ^3.1.1 || ^4.0.0 + redis: ^3.1.1 || ^4.0.0 || ^5.0.14 sql.js: ^1.4.0 sqlite3: ^5.0.3 ts-node: ^10.7.0 - typeorm-aurora-data-api-driver: ^2.0.0 + typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0 peerDependenciesMeta: '@google-cloud/spanner': optional: true @@ -6418,8 +6661,6 @@ packages: optional: true better-sqlite3: optional: true - hdb-pool: - optional: true ioredis: optional: true mongodb: @@ -6448,25 +6689,24 @@ packages: optional: true dependencies: '@sqltools/formatter': 1.2.5 + ansis: 4.2.0 app-root-path: 3.1.0 + better-sqlite3: 12.6.2 buffer: 6.0.3 - chalk: 4.1.2 - cli-highlight: 2.1.11 - date-fns: 2.30.0 - debug: 4.3.4 - dotenv: 16.4.5 - glob: 7.2.3 - js-yaml: 4.1.0 - mkdirp: 1.0.4 - reflect-metadata: 0.1.14 - sha.js: 2.4.11 - sqlite3: 5.1.6 + dayjs: 1.11.19 + debug: 4.4.3 + dedent: 1.7.1 + dotenv: 16.6.1 + glob: 10.5.0 + reflect-metadata: 0.2.2 + sha.js: 2.4.12 + sql-highlight: 6.1.0 ts-node: 10.9.2(@types/node@18.19.15)(typescript@5.3.3) - tslib: 2.6.2 - uuid: 8.3.2 - xml2js: 0.4.23 + tslib: 2.8.1 + uuid: 11.1.0 yargs: 17.7.2 transitivePeerDependencies: + - babel-plugin-macros - supports-color dev: false @@ -6491,6 +6731,9 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + /undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + /unimport@3.7.1: resolution: {integrity: sha512-V9HpXYfsZye5bPPYUgs0Otn3ODS1mDUciaBlXljI4C2fTwfFpvFZRywmlOu943puN9sncxROMZhsZCjNXEpzEQ==} dependencies: @@ -6511,21 +6754,19 @@ packages: - rollup dev: true - /unique-filename@1.1.1: - resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} - requiresBuild: true + /unique-filename@5.0.0: + resolution: {integrity: sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==} + engines: {node: ^20.17.0 || >=22.9.0} dependencies: - unique-slug: 2.0.2 - dev: false - optional: true + unique-slug: 6.0.0 + dev: true - /unique-slug@2.0.2: - resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==} - requiresBuild: true + /unique-slug@6.0.0: + resolution: {integrity: sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==} + engines: {node: ^20.17.0 || >=22.9.0} dependencies: imurmurhash: 0.1.4 - dev: false - optional: true + dev: true /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} @@ -6690,11 +6931,6 @@ packages: hasBin: true dev: false - /uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - dev: false - /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -6775,7 +7011,7 @@ packages: espree: 9.6.1 esquery: 1.5.0 lodash: 4.17.21 - semver: 7.6.0 + semver: 7.7.3 transitivePeerDependencies: - supports-color dev: true @@ -6866,6 +7102,19 @@ packages: webidl-conversions: 3.0.1 dev: false + /which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + dev: false + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -6873,11 +7122,13 @@ packages: dependencies: isexe: 2.0.0 - /wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + /which@6.0.0: + resolution: {integrity: sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true dependencies: - string-width: 4.2.3 - dev: false + isexe: 3.1.4 + dev: true /wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} @@ -6903,7 +7154,6 @@ packages: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: true /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -6926,19 +7176,6 @@ packages: engines: {node: '>=12'} dev: true - /xml2js@0.4.23: - resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==} - engines: {node: '>=4.0.0'} - dependencies: - sax: 1.3.0 - xmlbuilder: 11.0.1 - dev: false - - /xmlbuilder@11.0.1: - resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} - engines: {node: '>=4.0'} - dev: false - /xmlbuilder@15.1.1: resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} engines: {node: '>=8.0'} @@ -6956,6 +7193,11 @@ packages: /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + /yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + dev: true + /yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'}