chrome插件开发 | 掘金签到助手
前言
碰巧掘金新上签到活动,碰巧喝了咖啡睡不着,碰巧双休不用上班,所以写了个插件,方便工作日签到(顺道练练手)
先来看看成品
image-20210717064346915 image-20210717103555717 image-20210717064223932总的来看,插件需要实现以下几个目标:
- 检测当前用户登录态
- 判断用户今天是否已签到
- 发送签到请求
- 展示信息(用户信息,奖励信息)
请求分析
用户登录凭证
通过登录请求[POST] /passport/web/user/login
可以看到,请求响应设置的Cookie
中有好几个键值对,选择一个普通的请求在postman
上分析,可以看到掘金
通过Cookie
中的sessionid
作为用户登录凭证
签到相关的接口
在签到功能的请求中,跟这次要实现功能相关的接口有4个,分别是:
- 获取签到天数的汇总信息
[GET]/growth_api/v1/get_counts
- 获取当前的矿石数量
[GET]/growth_api/v1/get_cur_point
- 判断用户今天是否已签到
[GET]/growth_api/v1/get_today_status
- 用户签到
[POST]/growth_api/v1/check_in
这里大致分析一下功能对应的请求即可,具体传参以及返回值的含义可以通过浏览器控制台查看(
F12
)
流程图
下面通过几个场景的时序图来阐述清楚插件的工作流程
未登录场景
image.png未签到场景
image.png已签到场景
image.png搭建chrome插件开发工程
通过vue-web-extension实现快速搭建chrome插件开发工程(Vue)
首先确保这两个已经安装了
npm install -g @vue/cli
npm install -g @vue/cli-init
然后通过vue-web-extension
创建工程,我选择vue-web-extension
的版本是v1
vue init kocal/vue-web-extension#v1 juejin-auto-sign
按需选择自己需要的功能(axios
必选)
安装element ui
(按需加载)
cd juejin-auto-sign && vue add element
image-20210717174346824
由于element ui
的配置会写在package.json
文件中babel
部分,跟工程原有的.babelrc
配置文件重叠了,需要将package.json
中关于babel
部分的配置合并到.babelrc
文件中
合并前
# .babelrc配置文件
{
"plugins": [
"@babel/plugin-proposal-optional-chaining"
],
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3,
"targets": {
// https://jamie.build/last-2-versions
"browsers": ["> 0.25%", "not ie 11", "not op_mini all"]
}
}]
]
}
# package.json配置文件
{
.......
"babel": {
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
}
合并后,将package.json
中babel
部分删除,.babelrc
配置文件如下
在工程根目录下执行yarn build
,能够正常打包
yarn build
![image-20210717175823529](/Users/luoxiongjian/Library/Application Support/typora-user-images/image-20210717175823529.png)
至此,工程已经基本搭建完成了!可以正式投入开发
工程常用命令:
- yarn build 构建插件,输出到dist目录下
- yarn build-zip 按照插件名+版本号的形式,构建插件压缩包
-
yarn watch
构建插件,输出到dist目录下,如果发生改动,会即时刷新
关键代码
manifest.json配置文件
manifest.json文件中记载着插件的原信息,其中包括插件的基础信息(插件名称,版本号,ICON等),以及插件涉及页面(popup,options,background等),还有插件需要向chrome申请的权限
{
// 插件名称
"name": "juejin-auto-sign",
// 插件描述
"description": "掘金签到助手",
// 插件版本号
"version": "1.0.0",
"manifest_version": 2,
"icons": {
"48": "icons/icon.png",
"128": "icons/icon.png"
},
......
// [1] 申请掘金的cookie、网络请求的权限
"permissions": [
"cookies",
"*://*.juejin.cn/",
"webRequest",
"webRequestBlocking"
]
}
[1]处可以看到,插件需要申请网络权限 webRequest
和 webRequestBlocking
,这两个权限是跟用户签到请求有关系的(POST请求),后面会详细介绍为什么需要这两个权限
popup页面
目前插件的功能实现都是在popup页面,所谓popup页面就是在浏览器插件栏处点击展示的页面
image-20210717224513506拿Google翻译
插件来看,红色箭头指向的页面就是popup页面
chrome插件开发有分好几种页面以及脚本
页面有:popup,optional,background,插件上不同页面的展示位置是不同,用途也不同,目前只需要了解到popup页面即可
脚本有:background.js,content script等,不同的脚本声明周期也是不同的
下面展示签到助手插件popup页面的主要代码
<template>
<div class="sign-body">
<div class="sign-image">
<el-avatar size="large" :src="imageUrl"></el-avatar>
</div>
<div class="sign-text">{{ nickName }}</div>
<div class="sign-label">
当前矿石数量:<el-tag size="mini" type="success">{{ currentPoint }}</el-tag>
</div>
<div class="sign-label">
连续签到天数:<el-tag size="mini" type="success">{{ continueSignDays }}</el-tag>
</div>
<div class="sign-btn" v-if="!loading">
<el-button v-if="!login" type="primary" @click="toLogin">去登录</el-button>
<el-button v-else type="primary" :loading="signing" :disabled="todaySign" @click="toSign">{{ todaySign ? '已签到' : '去签到' }}</el-button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
// 头像
imageUrl: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
// 昵称
nickName: 'null',
// 当前矿石数量
currentPoint: 0,
// 连续签到天数
continueSignDays: 0,
// 是否登录
login: false,
// 今日是否已签到
todaySign: true,
loading: true,
signing: false,
};
},
.......
async mounted() {
this.loading = true;
// 获取用户信息
let resp = await getUserInfo();
// 判断cookie有效性
this.login = !resp.data.err_no && resp.data.data;
if (!this.login) {
this.loading = false;
return;
}
// 头像,昵称
this.imageUrl = resp.data.data.avatar_large;
this.nickName = resp.data.data.user_name;
// 矿石数量
resp = await getCurrentPoint();
this.currentPoint = resp.data.data;
// 连续签到天数
resp = await getSignData();
this.continueSignDays = resp.data.data.cont_count;
// 当前签到状况
resp = await getTodaySign();
this.todaySign = resp.data.data;
this.loading = false;
},
};
</script>
主要的逻辑都包含在页面mounted
阶段,该阶段需要执行一系列操作,包括获取用户信息,判断cookie有效性,获取用户当前签到状态以及奖励信息等等
img忽略<template>中的一堆笨拙的<div>标签,前端我只会写<div>
修改请求头
签到请求/growth_api/v1/check_in
是一个POST
请求,浏览器会自动带上origin
请求头,其值为chrome-extension://xxxxx
,此时掘金会校验请求头中的origin
,非掘金的origin会直接报403
(应该是网关层做了请求来源的校验)
此时插件就需要修改请求头中的origin
字段,然而origin
字段并不能随意修改
比方说axios强制指定请求头中origin的值是不生效的,并且插件的控制台会有对应的错误提示,该操作不符合规范
此时就需要使用到manifest.json
中注册的网络请求的权限
一个正常响应的请求在chrome中会经历如下图所示的声明周期
Life cycle of a web request from the perspective of the webrequest API如果我们需要修改请求头字段的话,则可以在onBeforeSendHeaders
发送请求头之前通过事件监听的方式修改origin
字段,同时,该事件监听应该是贯穿整个插件的生命周期的,所以代码应该写在background.js
中
// background.js文件
chrome.webRequest.onBeforeSendHeaders.addListener(
function(details) {
details.requestHeaders.push({ name: 'origin', value: 'https://juejin.cn' });
return { requestHeaders: details.requestHeaders };
},
{ urls: ['*://*.juejin.cn/*'] },
['blocking', 'requestHeaders', 'extraHeaders']
);
上述代码中可以看到,chrome.webRequest.onBeforeSendHeaders.addListener
接受三个参数:
- 监听器回调方法,修改请求头的操作应该放在这个方法中
- 过滤器,用于控制监听的URL范围,这里选择监听掘金相关的请求
- 元信息(opt_extraInfoSpec),简单来说就是,这个参数填写的值代表监听器的回调方法的执行方式以及回调时传入的数据
本次监听器的元数据中包含['blocking', 'requestHeaders', 'extraHeaders']
,这三个值分别表示:-
blocking
表示回调方法是同步调用的,就是说一个请求的回调方法执行完之后才会轮到下一个请求的回调方法 -
requestHeaders
表示回调方法的参数details
中包含请求头的数据 -
extraHeaders
这个字段比较神奇,由于origin
请求头这玩意不是说改就改的,chrome
也不推荐,如果真的要修改的话,就必须要填写这个字段,这样对origin
的修改才会生效
-
在manifest配置文件中可以看到,插件申请的权限除了
webRequest
之外,还有webRequestBlocking
,添加这个权限是因为监听方法中使用blocking
同步的方式
总结
至此,这个插件的实现思路基本介绍完了,总的来看,插件的实现难度不高,有兴趣的可以自己试着尝试实现一下
当然,以插件的形式来实现签到的功能其实并没有极大幅度地提高签到的效率,最好的方式肯定还是在服务器中定时签到,这样即使不上掘金网站,也能收获满满的矿石用以抽奖,但是这样不可避免地将用户登录凭证暴露出去,可能存在一些安全风险,同时也丧失了掘金举办这个活动的意义
万一被别人拿到自己的cookie跑去删除自己的文章,那真的是欲哭无泪
这次掘金的签到活动个人觉得还是办的可以的
-
任务难度很低,规则简单,活动入口明显
-
签到奖励比较多,一个月签到下来,应该都有好几千矿石,应该能抽奖好几十次
唯一不好的地方就是我抽了好几天都没抽到switch,哈哈哈哈