油猴腳本開發教程

前言

如何跟普通朋友介紹前端工程師是一個怎樣的職位? 我會毫不猶豫的給他的瀏覽器裝上 Tampermonkey,再裝一個去廣告插件 [1],他們肯定會覺得你很牛逼,然後再問問他們,有些時候想複製某度文檔,卻複製不了,再給他們裝一個 XX 文庫的選中複製插件 [2],瞬間成就感拉滿,前端工程師就是幹這個事的,筆者之前也寫了幾個油猴腳本,接下來我就分享下寫腳本的經驗,一起來看看吧。

什麼是油猴腳本

Tampermonkey[3] 是一款免費的瀏覽器擴展和最爲流行的用戶腳本管理器,它適用於 Chrome, Microsoft Edge, Safari, Opera Next, 和 Firefox。

「油猴腳本」是一段腳本代碼,通過它可以讓瀏覽器實現各種各樣的擴展功能,和瀏覽器擴展的作用類似。比如獲去鏈接重定向、微博頁面精簡、去廣告等,相當於給瀏覽器開了個掛,可以說是瀏覽器的輔助神器了!但瀏覽器擴展若要發佈到 chrome 擴展市場,需要支付 5 美元,但「油猴腳本」可以隨時隨地發佈,不需要支付任何費用費用。

新建腳本

在腳本管理控制檯,右上角的+添加按鈕,新建一個腳本,默認會包含以下代碼:

新建腳本

最上面的註釋不能刪除,Tampermonkey 就通過註釋代碼來配置腳本字段的

// @require https://code.jquery.com/jquery-2.1.4.min.js
// @require tampermonkey://vendor/jquery.js
// @require tampermonkey://vendor/jszip/jszip.js

CSDN 免登陸複製

比如 CSDN 中代碼塊複製必須要登錄後纔可以複製, 那麼我們就可以寫一個腳本,輸入以下代碼

// ==UserScript==
// @name         CSDN 免登錄複製
// @version      0.1
// @description  try to take over the world!
// @match        *://*.csdn.net/*
// @require      tampermonkey://vendor/jquery.js
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
     $("pre,code").css("user-select","auto");
})();

保存後,重新刷新瀏覽器,在右上角的擴展標誌中會向上一個1, 說明有一個擴展作用於這個網頁,腳本注入成功。

CSDN 免登陸複製

其實就是一行代碼,CSDN 的默認代碼塊的樣式是 user-select: none;,不能複製,改成 auto 後就可以複製了。

添加樣式

首先需要在最上面的註釋中開啓權限 @grant GM_addStyle,然後就可以使用內置的 GM_addStyle 方法了。

// ==UserScript==
// @grant        GM_addStyle
// ==/UserScript==
GM_addStyle(`pre,code{user-select:auto !important}.signin{display: none !important;}`)

這樣也可以解決 CSDN 代碼塊不能複製的問題,順便將代碼塊後面的登錄後複製按鈕隱藏;

當然我們也可以使用 JS 自己實現

const heads = document.querySelector('head');
const style = document.createElement('style');
style.setAttribute('type', 'text/css');
style.innerHTML = `pre,code{user-select:auto !important}.signin{display: none !important;}`;
heads.append(style);

網絡請求

一般前端腳本都是修改前端網頁內容,若複雜一點的腳本,可能會涉及到動態數據,比如我們在腳本中直接寫fetch 請求,這時瀏覽器肯定會阻止請求,因爲跨域了。

我們需要使用腳本的內置方法

// ==UserScript==
// @grant        GM_xmlhttpRequest
// ==/UserScript==
GM_xmlhttpRequest({
    headers: {
      'content-type': 'application/json',
    },
    responseType: 'json',
    url: 'https://api.juejin.cn/recommend_api/v1/article/recommend_cate_feed',
    data: '{"id_type":2,"sort_type":200,"cate_id":"6809637767543259144","cursor":"0","limit":20}',
    method: 'POST',
    onreadystatechange: (res) => {
      if (res.readyState === 4) {
        console.log(res.response)
      }
    },
  })

在最上面的註釋中開啓權限 @grant GM_xmlhttpRequest,然後就可以使用內置的 GM_xmlhttpRequest 方法了。

比如把接口換成有道翻譯的 api。通過 document.getSelection().toString(); 獲取網頁中的選中文本,便可以實現一個劃詞翻譯油猴插件了。

劃詞翻譯

右鍵搜索

接下來我們將結合右鍵菜單和打開新窗口的內置函數,實現一個快捷搜索的功能, 同樣內置函數需要在最上面的註釋中加入權限,代碼如下:

// ==UserScript==
// @grant        GM_registerMenuCommand
// @grant        GM_openInTab
// ==/UserScript==

GM_registerMenuCommand("GitHub 搜索", function () {
  const str = document.getSelection().toString();
  if (str) {
    GM_openInTab(`https://github.com/search?q=${str}`, { active: true });
  }
});

GM_registerMenuCommand("NPM 搜索", function () {
  const str = document.getSelection().toString();
  if (str) {
    GM_openInTab(`https://www.npmjs.com/search?q=${str}`, { active: true });
  }
});

右鍵搜索

這樣就有了快捷菜單搜索的功能,只是操作掛着 3 級目錄下,操作有些不方便。

以上簡單介紹了腳本的開發以及一些簡單實現,但這個腳本我們只能在自己電腦使用,若想讓其他小夥伴也能夠使用,我們需要將腳本發佈到腳本腳本市場,供其他小夥伴下載。

腳本發佈

一個非常流行的腳本共享網站便是 greasyfork.org[4],有着非常豐富的腳本,並且支持多語言。

greasyfork 腳本發佈

註冊賬號後,點擊右上角的用戶名,然後點擊控制檯中的發佈你編寫的腳本,貼入你寫的腳本代碼,便可以發佈成功!將發佈後的鏈接發送個小夥伴,就可以讓他們安裝你寫的腳本了,你也可以在上面根據匹配域名搜索相關腳本,看大神們是如何來寫腳本的。

前端工程化

現代前端開發已經離不開前端框架,若直接使用原生 JS 讓我們寫複雜的功能,難免會讓我們崩潰,比如需要在 JS 中寫 CSS,一不小心寫錯就會讓整個腳本無法執行。所以我們可以使用 webpack 來構建一個工程化的項目。並且使用 Typescript 和 eslint ,讓我們的前端工程健全。

下面是 webpack.config.js

const webpack = require("webpack");
const fs = require("fs");
const path = require("path");

const config = {
  entry: "./src/index.tsx",

  output: {
    clean: true,
    iife: true,
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
  },
  devServer: {
    static: {
      directory: path.join(__dirname, "public"),
    },
    compress: true,
    port: 9000,
  },
  // 腳本發佈後,會被舉報,不允許壓縮
  optimization: {
    minimize: false,
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: "babel-loader",
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: "style-loader",
            options: {},
          },
          { loader: "css-loader" },
        ],
      },
      {
        test: /\.ts(x)?$/,
        loader: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [new BannerPlugin()],
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
};

module.exports = config;

使用 style-loader,在 webpack 打包後,會自動將 css 樣式通過 style 標籤插入到 head 中,這樣就做到了 css 和 js 分離。這裏有個問題,就是油猴腳本特有的代碼前置註釋 ==UserScript==,在 webpack 打包後會被刪除,所以我們得自己實現一個插件,將這段註釋加回來。

const ConcatSource = require("webpack-sources").ConcatSource;

/**
 * 添加前綴註釋
 */
class BannerPlugin {
  apply(compiler) {
    let banner = "";
    const entryFile = compiler.options.entry.main.import[0];

    const res = fs.readFileSync(entryFile, "utf-8");
    const matched = res.match(
      /(\/\/\s==UserScript==)(?<content>(\n.+)+)(\n\/\/\s==\/UserScript==)/
    );
    if (matched && matched.groups.content) {
      banner =
        "// ==UserScript==" + matched.groups.content + "\n// ==/UserScript==\n";
    }
    compiler.hooks.emit.tap("BannerPlugin", (compilation) => {
      compilation.chunks.forEach((chunk) => {
        // 最終生成的文件的集合
        chunk.files.forEach((fileName) => {
          compilation.assets[fileName] = new ConcatSource(
            banner,
            compilation.assets[fileName]
          );
        });
      });
    });
  }
}

上述代碼通過正則匹配 ==UserScript==  之間的代碼,並且將匹配的內容合併到了最後的代碼 chunk 中。

CICD

若我們每次打包後的 JS 都需要手動拷貝到 greasyfork.org 未免有些麻煩,得意於 greasyfork.org 有個自動發佈的功能,我們可以配合 GitHub actions 來實現自動發佈。

在項目文件夾下建立 .github/workflows/build.yml, 輸入以下代碼

name: GitHub Pages

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  deploy:
    runs-on: ubuntu-20.04
    permissions:
      contents: write
    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: "14"

      - name: Get yarn cache
        id: yarn-cache
        run: echo "::set-output 

      - name: Cache dependencies
        uses: actions/cache@v2
        with:
          path: ${{ steps.yarn-cache.outputs.dir }}
          key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-

      - run: yarn install
      - run: yarn build

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        if: ${{ github.ref == 'refs/heads/main' }}
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./dist

當我們把代碼提交到 Github ,就會自動觸發 workflow ,依次執行 yarn install 、yarn build 並且自動將 dist 目錄下的代碼自動部署到 GitHub pages。

接下來我們複製 pages 中的 raw 源文件地址

github 源文件地址

貼入複製的源文件地址

webhook 配置

複製這裏對應的 webhook 地址和祕鑰

github 設置 webhook

在 GitHub 項目地址中添加 webhook

這樣只要我們一旦提交代碼, greasyfork.org 中的腳本便會自動更新最新版本。

以上所有配置我整理在 tampermonkey-starter[5] 中, 若你也想創建一個自己的腳本,可以直接 fork 項目,修改相關配置即可。

小結

本文簡單介紹了油猴腳本開發步驟以及實現,結合 webpack 讓腳本實現工程化,並且配合 Github action,讓腳本實現自動化構建和部署。

但是若想要實現一些有用的腳本,還需要具備更多的知識,比如JS逆向分析等,若你有好的想法,趕快行動起來吧!

以上就是本文全部內容,如果對你有幫助,可以隨手點個贊,這對我真的很重要,希望這篇文章對大家有所幫助,也可以參考我往期的文章或者在評論區交流你的想法和心得,歡迎一起探索前端。

評論區 @虛鯤菜菜子 推薦了一個基於 vite 的解決方案,比較完善,大家可以體驗下: https://github.com/lisonge/vite-plugin-monkey


[1] 去廣告插件:https://greasyfork.org/zh-CN/scripts/439420 - 屏蔽廣告 - 屏蔽谷歌廣告 - 百度廣告 - 知乎廣告 - 隱藏谷歌和百度搜索增強百度搜索結果的各種廣告等等 - 過濾所有采用谷歌聯盟和百度聯盟等廣告聯盟的廣告

[2] 選中複製插件:https://greasyfork.org/zh-CN/scripts/405130 - 文本選中複製

[3]Tampermonkey: https://www.tampermonkey.net

[4]greasyfork.org: https://greasyfork.org/zh-CN

[5]GitHub tampermonkey-starter: https://github.com/maqi1520/tampermonkey-starter

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/fUgiTixW6fAoGSf8Dont8g