V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
lhasa
V2EX  ›  程序员

写一个 Chrome 表单自动化插件(博客评论表单)

  •  
  •   lhasa ·
    achuanya · 43 天前 · 1104 次点击
    这是一个创建于 43 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在刷博客的时候,最麻烦的事情之一就是手动填写各种表单。为了提高我的冲浪体验,诞生了这款表单自动化插件。经过爬虫上百次调教,兼容 95%博客,另外 5%的网站正常人写不出来,autocomplete 小伎俩都上不了台面,各种防止逆向、防调试测试,心累。

    项目结构

    插件纯绿色,无隐私可言。除 images 外,全部资源和代码文件都经过 Webpack 打包,下面是项目的目录结构以及各部分的说明:

    Form-automation-plugin
    │  index.html
    │  LICENSE
    │  manifest.json
    │  package-lock.json
    │  package.json
    │  README.md
    │  webpack.config.js
    │  
    ├─dist
    │      33a80cb13f78b37acb39.woff2
    │      8093dd36a9b1ec918992.ttf
    │      8521c461ad88443142d9.woff
    │      autoFill.min.js
    │      eventHandler.min.js
    │      formManager.min.js
    │      main.min.css
    │
    └─src
        │  autoFill.js
        │  eventHandler.js
        │  formManager.js
        │  template.css
        │  template.html
        │
        ├─fonts
        │      iconfont.css
        │      iconfont.ttf
        │      iconfont.woff
        │      iconfont.woff2
        │
        └─images
                Appreciation-code.jpg
                icon128.png
                icon16.png
                icon48.png
    

    webpack.config.js

    const path = require('path');
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      entry: {
        autoFill: './src/autoFill.js',
        eventHandler: './src/eventHandler.js',
        formManager: './src/formManager.js',
      },
      output: {
        filename: '[name].min.js',
        path: path.resolve(__dirname, 'dist'),
        clean: true,
      },
      mode: 'production',
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              MiniCssExtractPlugin.loader,
              'css-loader',
            ],
          },
        ],
      },
      plugins: [
        new MiniCssExtractPlugin({
          filename: 'main.min.css',
        }),
        new HtmlWebpackPlugin({
          template: path.resolve(__dirname, 'src', 'template.html'),
          filename: '../index.html',
          inject: 'body',
        }),
      ],
      resolve: {
        extensions: ['.js', '.css'],
      },
    };
    

    autoFill.js

    // autoFill.js 文件是插件的最重要的核心模块,涉及到了插件的主要输出功能
    // 遍历当前页面所有 input ,将 autocomplete 值设置为 on ,监听 Textarea 输入时触发
    function handleAutocomplete() {
      const inputs = document.querySelectorAll('input');
      inputs.forEach(input => {
        const autocompleteAttr = input.getAttribute('autocomplete');
        if (autocompleteAttr) {
          input.setAttribute('autocomplete', 'on');
        } else {
          input.setAttribute('autocomplete', 'on');
        }
      });
    }
    
    // 这个函数有些臃肿,马上要去骑车,懒得搞了,现在的逻辑已经完善到 9 成了,大多数意外情况都卷了进去,但是一些防逆向防调试,我暂时无法解决,前端菜鸟,还望大哥指点一二
    function fillInputFields() {
      chrome.storage.sync.get(["name", "email", "url"], (data) => {
        // console.log(data);
    
        const hasValidName = data.name !== undefined && data.name !== "";
        const hasValidEmail = data.email !== undefined && data.email !== "";
        const hasValidUrl = data.url !== undefined && data.url !== "";
    
        // 关键字
        const nameKeywords = [
          "name", "author", "display_name", "full-name", "username", "nick", "displayname", 
          "first-name", "last-name", "full name", "real-name", "given-name", 
          "family-name", "user-name", "pen-name", "alias", "name-field", "displayname"
        ];
        const emailKeywords = [
          "email", "mail", "contact", "emailaddress", "mailaddress", 
          "email-address", "mail-address", "email-addresses", "mail-addresses", 
          "emailaddresses", "mailaddresses", "contactemail", "useremail", 
          "contact-email", "user-mail"
        ];
        const urlKeywords = [
          "url", "link", "website", "homepage", "site", "web", "address", 
          "siteurl", "webaddress", "homepageurl", "profile", "homepage-link"
        ];
    
        const inputs = document.querySelectorAll("input, textarea");
    
        inputs.forEach((input) => {
          const typeAttr = input.getAttribute("type")?.toLowerCase() || "";
          const nameAttr = input.getAttribute("name")?.toLowerCase() || "";
          let valueToSet = "";
    
          // 处理 URL
          if (urlKeywords.some(keyword => nameAttr.includes(keyword))) {
            if (hasValidUrl) {
              valueToSet = data.url;
            }
          }
          // 处理邮箱
          else if (emailKeywords.some(keyword => nameAttr.includes(keyword))) {
            if (hasValidEmail) {
              valueToSet = data.email;
            }
          }
          // 处理名称
          else if (nameKeywords.some(keyword => nameAttr.includes(keyword))) {
            if (hasValidName) {
              valueToSet = data.name;
            }
          }
    
          // 处理没有 type 属性或者 type 为 text 的情况
          if ((typeAttr === "" || typeAttr === "text") && valueToSet === "") {
            if (nameAttr && nameKeywords.some(keyword => nameAttr.includes(keyword))) {
              if (hasValidName) {
                valueToSet = data.name;
              }
            } else if (nameAttr && urlKeywords.some(keyword => nameAttr.includes(keyword))) {
              if (hasValidUrl) {
                valueToSet = data.url;
              }
            }
          }
    
          // 处理 type 为 email
          if (typeAttr === "email" && valueToSet === "") {
            if (nameAttr && emailKeywords.some(keyword => nameAttr.includes(keyword))) {
              if (hasValidEmail) {
                valueToSet = data.email;
              }
            }
          }
    
          // 处理 type 为 url
          if (typeAttr === "url" && valueToSet === "") {
            if (nameAttr && urlKeywords.some(keyword => nameAttr.includes(keyword))) {
              if (hasValidUrl) {
                valueToSet = data.url;
              }
            }
          }
    
          // 填充输入字段
          if (valueToSet !== "") {
            input.value = valueToSet;
          }
        });
      });
    }
    
    function clearInputFields() {
      const inputs = document.querySelectorAll("input");
      inputs.forEach((input) => {
        const typeAttr = input.getAttribute("type")?.toLowerCase();
        if (typeAttr === "text" || typeAttr === "email") {
          input.value = "";
        }
      });
    }
    
    // 监听 textarea 标签的输入事件
    document.addEventListener("input", (event) => {
      if (event.target.tagName.toLowerCase() === "textarea") {
        handleAutocomplete();
        fillInputFields();
      }
    });
    

    formManager.js

    该文件负责向 Chrome 本地存储和修改,就 CURD ,没啥含量

    import './fonts/iconfont.css';
    import './template.css';
    
    document.getElementById("save").addEventListener("click", () => {
      const saveButton = document.getElementById("save");
      if (saveButton.textContent === "更改") {
        unlockInputFields();
        changeButtonText("保存");
        return;
      }
    
      const name = document.getElementById("name").value.trim();
      const email = document.getElementById("email").value.trim();
      const url = document.getElementById("url").value.trim();
    
      // 验证邮箱格式的正则表达式
      const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      if (name === "" || email === "") {
        alert("请填写必填字段:姓名和邮箱!");
        return;
      }
    
      if (!emailPattern.test(email)) {
        alert("请输入有效的邮箱地址!");
        return;
      }
    
      // 从 Chrome 存储中读取当前的值
      chrome.storage.sync.get(["name", "email", "url"], (data) => {
        const isNameAndEmailChanged = name !== data.name || email !== data.email;
        const isUrlChanged = url !== data.url;
    
        if (isNameAndEmailChanged || isUrlChanged) {
          chrome.storage.sync.set({ name, email, url }, () => {
            lockInputFields();
            changeButtonText("更改");
          });
        } else {
          lockInputFields();
          changeButtonText("更改");
        }
      });
    });
    
    // 页面加载完成时执行
    document.addEventListener("DOMContentLoaded", () => {
      chrome.storage.sync.get(["name", "email", "url"], (data) => {
        document.getElementById("name").value = data.name || "";
        document.getElementById("email").value = data.email || "";
        document.getElementById("url").value = data.url || "";
    
        if (data.name || data.email || data.url) {
          lockInputFields();
          changeButtonText("更改");
        }
      });
    
      const menuItems = document.querySelectorAll('.dl-menu li a');
      const tabContents = document.querySelectorAll('.tab-content');
    
      menuItems.forEach(menuItem => {
        menuItem.addEventListener('click', (event) => {
          event.preventDefault();
          tabContents.forEach(tab => tab.classList.remove('active'));
          const targetId = menuItem.getAttribute('href').substring(1);
          document.getElementById(targetId).classList.add('active');
          menuItems.forEach(item => item.parentElement.classList.remove('active'));
          menuItem.parentElement.classList.add('active');
        });
      });
    });
    
    // 锁定输入框
    function lockInputFields() {
      document.getElementById("name").setAttribute("disabled", "true");
      document.getElementById("email").setAttribute("disabled", "true");
      document.getElementById("url").setAttribute("disabled", "true");
    }
    
    // 解锁输入框
    function unlockInputFields() {
      document.getElementById("name").removeAttribute("disabled");
      document.getElementById("email").removeAttribute("disabled");
      document.getElementById("url").removeAttribute("disabled");
    }
    
    // 更改按钮文本
    function changeButtonText(text) {
      document.getElementById("save").textContent = text;
    }
    

    使用方法

    git clone 到本地,浏览器打开:chrome://extensions/,加载已解压的扩展程序

    由于我没有注册 Chrome 应用商店开发者,目前只能本地运行,过几天上线应用商店,Tampermonkey 等骑车回来再做

    Github

    Form-automation-plugin:https://github.com/achuanya/Form-automation-plugin

    博客原文: https://lhasa.icu/FormAutomationPlugin.html

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   953 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 22:00 · PVG 06:00 · LAX 15:00 · JFK 18:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.