这是一个什么项目
省流版

GIthub地址: github.com/lastIndexOf...
插件下载地址: chromewebstore.google.com/detail/aibo...
一句话省流:不用看代码,不用装环境,填个Key,上传一份简历,点击一个按钮, 插件自动帮你投递简历
正经介绍
前段看到了自动找工作脚本变得有点流行,我试图将其代码拉到本地并运行,但由于环境等问题遇到了一些困难。我一直想对它进行一些优化,但一直没有尝试。
几天后,我偶然看到了@932829447855975发布的文章《GPT自动投简历,一周斩获三个offer,开源分享!》。我灵机一动,既然已经有人将脚本从 Python 迁移到了 Node.js ,为什么不进一步将其迁移到浏览器插件呢?这样不就省去了查看代码、配置环境和调试的步骤了吗?
想到咱就干,于是有了这篇文章的内容。
思路
首先,前两个版本的脚本都使用了 Browser driver(一种通常用于自动化测试的工具),通过操作浏览器实例来推进流程。这种方法的好处是可以利用驱动程序的API控制整个流程,无需直接与网页页面交互,也不需要额外注入JavaScript代码。相对而言,这种方式更少干扰性,并且更不容易出现错误。
然而,这种方法的一个缺点是它不适合作为公共服务供更多人使用(自动找工作插件如果能通过调用一个服务的方式来使用那自然是最方便的)。原因在于它依赖于 Browser driver,意味着需要在服务器本地启动一个浏览器实例。如果将此工具作为服务提供出去,则每个请求都需要在服务器上创建一个浏览器实例,并且无法查看该实例的图形界面。
那么,最适合「0成本使用,不用理解代码,不用装环境」,同时又不太好作为服务提供出去的方式,自然就非浏览器插件莫属了。
实现思路也很简单,就是通过在 Boss 的页面注入 JS 脚本,分步骤依次点击按钮,模拟人类投递简历的行为即可,唯一有区别的就是在投递简历前会先把岗位介绍和简历先发送给 GPT 过目,让它审核一下然后生成一段与岗位介绍最匹配的问候语。
上代码
由于是第一次写 chrome extension,所以还是花了点时间读了下文档(在半懂不懂的情况下让 GPT 生成代码很容易写成 💩3️⃣)。
关于插件开发的省流版提炼:
- 理解三个文件
-
- content_script.js, 可以简单地理解成这个脚本会注入到你打开 url 的那个页面的 html 里。传统 js 的超集。
- background.js,理解成一个打开浏览器就会一直运行的 service worker。每开启一个 Tab,都会触发里面的一些特殊事件,从而触发回调,而这个 background.js 是可以与 content_script.js 通信的。在打开特定 Tab 的时候可以通过这个机制来触发一些很神奇的行为。
- popup.html,点击插件后会打开的一个面板,这个面板其实也是 html(它也可以用 webpack 来打包,因此可以用 react 来写)
- chrome extension, 启动! 直接克隆 官方的模板仓库 ,就可以开始开发了,它已经默认集成了 react + ts + webpack(没有配置除了 tsx 外的任何 loader,因此如果要引入 css 或者图片需要自己额外配置 webpack)。
- 开发 popup 组件。点击插件后弹出的小框相对来说是比较独立的,在第一次启动一次新的 extension 项目时,可以先构思好这个小框应该是什么样的,先开发即可,由于这里我们的功能是GPT自动投递简历,因此这个窗口里面其实只需要设置一下 OpenAI 的 ApiKey,再加一个上传简历的按钮即可。
- 点击 popup 里的按钮,要触发页面内的某些逻辑(UI 变动,监听原页面的事件等),可以通过发送事件来通知 content_script.js。
- 在 content_script.js 中接收到事件后,执行处理逻辑,将结果再发送给 background.js。
基于这个思路,我们来看下这个插件的实现:
实现插件窗口 popup
这里直接使用 React 来开发(模板仓库已经配置好了)
typescript
const Popup = () => {
const [value, setValue] = useState(0);
const [running, setRunning] = useState(false);
const { showExtension } = useExtension();
useEffect(() => {
const getIsRunning = async () => {
// some code...
};
getIsRunning();
}, []);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
const run = () => {
chrome.tabs.query({ currentWindow: true, active: true }, async (tabs) => {
// 通知 content_script.js 来要开始自动投递简历了
});
};
return (
<div style={{ width: "345px", margin: 0, padding: 0, marginBottom: 20 }}>
<Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={value}
onChange={handleChange}
>
<Tab label="设置" {...a11yProps(0)} />
<Tab label="开始" {...a11yProps(1)} />
</Tabs>
</Box>
<CustomTabPanel value={value} index={0}>
<Settings />
</CustomTabPanel>
<CustomTabPanel value={value} index={1}>
<Workflow run={run} running={running} />
</CustomTabPanel>
</Box>
</div>
);
};
在 content_script.js 中处理 popup 发送的事件
typescript
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
const { type } = msg;
switch (type) {
case FindJobExtensionMessageType.StartFindJob:
startFindJob();
break;
case FindJobExtensionMessageType.StartLogin:
startLogin();
break;
case FindJobExtensionMessageType.StartChat:
startChat();
break;
case FindJobExtensionMessageType.GetKey:
getKey(msg.data).then(sendResponse);
break;
case FindJobExtensionMessageType.SetKey:
setKey(msg.data.key, msg.data.value).then(sendResponse);
break;
case FindJobExtensionMessageType.Render:
render(msg.data);
break;
case FindJobExtensionMessageType.Log:
console.info(msg.data);
break;
case OpenAIMessageType.UploadResume:
uploadResume().then(sendResponse);
break;
case OpenAIMessageType.ApiKeyError:
alert(
"连接 OpenAI 失败,请打开插件 「设置」 面板确认正确填入了 OpenAI API Key,且本地代理服务器没有异常。\\n"
);
break;
case OpenAIMessageType.Error:
alert(
`OpenAI 调用失败,请打开插件 「设置」 面板确认正确填入了 OpenAI API Key,且本地代理服务器没有异常。\\n错误:${msg.data}`
);
break;
case FindJobExtensionMessageType.OpenRecommend:
window.open(BOSS_FIND_JOB_URL, "_blank");
break;
case FindJobExtensionMessageType.Reset:
resetLocalStatus();
location.reload();
break;
case FindJobExtensionMessageType.Stop:
location.reload();
break;
default:
return true;
}
});
实现逻辑
这里是纯体力活,就不展开了,基本上就是 auto_find_job 的翻译。
缺陷
由于这是一个免费使用的插件,因此需要使用者自己提供 API Key,这个问题可以参考一下 《GPT自动投简历》 提到的获取免费 OpenAI API Key 的方法(我没有使用过,需要读者自行尝试)。
写在最后
好了,以上就是本文的所有内容,也欢迎有新 IDEA 的小伙伴速来贡献你的代码。
github 地址: github.com/lastIndexOf...