chrome插件开发 | 掘金签到助手

·  阅读 1552
chrome插件开发 | 掘金签到助手

前言

碰巧掘金新上签到活动,碰巧喝了咖啡睡不着,碰巧双休不用上班,所以写了个插件,方便工作日签到(顺道练练手)

先来看看成品

image-20210717064346915

image-20210717103555717

image-20210717064223932

总的来看,插件需要实现以下几个目标:

  1. 检测当前用户登录态
  2. 判断用户今天是否已签到
  3. 发送签到请求
  4. 展示信息(用户信息,奖励信息)

搞事情、搞事情、整天就知道搞事情!(熊猫人)

请求分析

用户登录凭证

通过登录请求[POST] /passport/web/user/login可以看到,请求响应设置的Cookie中有好几个键值对,选择一个普通的请求在postman上分析,可以看到掘金通过Cookie中的sessionid作为用户登录凭证

image-20210717143831542

签到相关的接口

在签到功能的请求中,跟这次要实现功能相关的接口有4个,分别是:

  1. 获取签到天数的汇总信息 [GET]/growth_api/v1/get_counts
  2. 获取当前的矿石数量 [GET]/growth_api/v1/get_cur_point
  3. 判断用户今天是否已签到 [GET]/growth_api/v1/get_today_status
  4. 用户签到 [POST]/growth_api/v1/check_in

这里大致分析一下功能对应的请求即可,具体传参以及返回值的含义可以通过浏览器控制台查看(F12

流程图

下面通过几个场景的时序图来阐述清楚插件的工作流程

未登录场景

sequenceDiagram
	participant user as 用户
	participant plugin as 插件
	participant chrome as 浏览器
	participant juejin as 掘金
	user->>+plugin: 打开插件
	plugin->>+chrome: 获取掘金cookie
	chrome-->>-plugin: return sessionid
	plugin->>+juejin: [GET] /user_api/v1/user/get
	juejin-->>-plugin: return 当前用户信息
	plugin->>plugin: 判断当前cookie无效
	plugin-->>-user: 用户未登录,显示登录按钮
	user->>+plugin: 点击登录按钮
	plugin->>+juejin: 跳转到登录页
	juejin->>juejin: 用户登录
	juejin-->>-plugin: 登录成功
	plugin-->>-user: 登录成功

未签到场景

sequenceDiagram
	participant user as 用户
	participant plugin as 插件
	participant chrome as 浏览器
	participant juejin as 掘金
	user->>+plugin: 打开插件
	plugin->>+chrome: 获取掘金cookie
	chrome-->>-plugin: return sessionid
	plugin->>+juejin: [GET] /user_api/v1/user/get
	juejin-->>-plugin: return 当前用户信息
	plugin->>plugin: 判断当前cookie有效
	plugin->>+juejin: [GET]/growth_api/v1/get_today_status
	juejin-->>-plugin: return 用户签到状态
	plugin->>plugin: 判断用户还没签到
	plugin-->>-user: 展示用户信息,显示签到按钮
	user->>+plugin: 点击签到按钮
	plugin->>+juejin: [POST]/growth_api/v1/check_in
	juejin-->>-plugin: return 奖励矿石数量 & 当前总矿石数量
	plugin-->>-user: 签到成功,展示奖励信息

已签到场景

sequenceDiagram
	participant user as 用户
	participant plugin as 插件
	participant chrome as 浏览器
	participant juejin as 掘金
	user->>+plugin: 打开插件
	plugin->>+chrome: 获取掘金cookie
	chrome-->>-plugin: return sessionid
	plugin->>+juejin: [GET] /user_api/v1/user/get
	juejin-->>-plugin: return 当前用户信息
	plugin->>plugin: 判断当前cookie有效
	plugin->>+juejin: [GET]/growth_api/v1/get_today_status
	juejin-->>-plugin: return 用户签到状态
	plugin->>plugin: 判断用户已签到
	plugin-->>-user: 展示用户信息,矿石数量

搭建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必选)

image-20210717152313855

安装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.jsonbabel部分删除,.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]处可以看到,插件需要申请网络权限 webRequestwebRequestBlocking,这两个权限是跟用户签到请求有关系的(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有效性,获取用户当前签到状态以及奖励信息等等

忽略<template>中的一堆笨拙的<div>标签,前端我只会写<div>

img

修改请求头

签到请求/growth_api/v1/check_in是一个POST请求,浏览器会自动带上origin请求头,其值为chrome-extension://xxxxx,此时掘金会校验请求头中的origin,非掘金的origin会直接报403(应该是网关层做了请求来源的校验)

image-20210717230835866

此时插件就需要修改请求头中的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接受三个参数:

  1. 监听器回调方法,修改请求头的操作应该放在这个方法中
  2. 过滤器,用于控制监听的URL范围,这里选择监听掘金相关的请求
  3. 元信息(opt_extraInfoSpec),简单来说就是,这个参数填写的值代表监听器的回调方法的执行方式以及回调时传入的数据 本次监听器的元数据中包含['blocking', 'requestHeaders', 'extraHeaders'],这三个值分别表示:
    • blocking 表示回调方法是同步调用的,就是说一个请求的回调方法执行完之后才会轮到下一个请求的回调方法
    • requestHeaders 表示回调方法的参数details 中包含请求头的数据
    • extraHeaders 这个字段比较神奇,由于origin请求头这玩意不是说改就改的,chrome也不推荐,如果真的要修改的话,就必须要填写这个字段,这样对origin的修改才会生效

在manifest配置文件中可以看到,插件申请的权限除了webRequest之外,还有webRequestBlocking,添加这个权限是因为监听方法中使用blocking同步的方式

总结

至此,这个插件的实现思路基本介绍完了,总的来看,插件的实现难度不高,有兴趣的可以自己试着尝试实现一下

当然,以插件的形式来实现签到的功能其实并没有极大幅度地提高签到的效率,最好的方式肯定还是在服务器中定时签到,这样即使不上掘金网站,也能收获满满的矿石用以抽奖,但是这样不可避免地将用户登录凭证暴露出去,可能存在一些安全风险,同时也丧失了掘金举办这个活动的意义

万一被别人拿到自己的cookie跑去删除自己的文章,那真的是欲哭无泪

这次掘金的签到活动个人觉得还是办的可以的

  • 任务难度很低,规则简单,活动入口明显

  • 签到奖励比较多,一个月签到下来,应该都有好几千矿石,应该能抽奖好几十次

唯一不好的地方就是我抽了好几天都没抽到switch,哈哈哈哈

分类:
前端
标签: