油猴腳本開發教程

前言

如何跟普通朋友介紹前端工程師是一個怎樣的職位? 我會毫不猶豫的給他的瀏覽器裝上 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 installyarn 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