GreasyFork优化

自动登录账号、快捷寻找自己库被其他脚本引用、更新自己的脚本列表、库、优化图片浏览、美化页面

目前为 2023-11-05 提交的版本。查看 最新版本

// ==UserScript==
// @name         GreasyFork优化
// @namespace    https://greasyfork.org/zh-CN/scripts/475722
// @supportURL   https://greasyfork.org/zh-CN/scripts/475722/feedback
// @version      2023.11.4.15
// @description  自动登录账号、快捷寻找自己库被其他脚本引用、更新自己的脚本列表、库、优化图片浏览、美化页面
// @author       WhiteSevs
// @license      MIT
// @icon         https://favicon.yandex.net/favicon/v2/https://greasyfork.org/?size=32
// @match        *://greasyfork.org/*
// @run-at       document-start
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_info
// @connect      greasyfork.org
// @require      https://greasyfork.org/scripts/449471-viewer/code/Viewer.js?version=1249086
// @require      https://greasyfork.org/scripts/462234-message/code/Message.js?version=1252081
// @require      https://greasyfork.org/scripts/455186-whitesevsutils/code/WhiteSevsUtils.js?version=1275447
// @require      https://greasyfork.org/scripts/465772-domutils/code/DOMUtils.js?version=1274595
// ==/UserScript==

(function () {
  /* -----------------↓公共配置↓----------------- */
  /**
   * @type {import("../库/Qmsg")}
   */
  const Qmsg = window.Qmsg;
  /**
   * @type {import("../库/Utils")}
   */
  const utils = window.Utils.noConflict();
  /**
   * @type {import("../库/DOMUtils")}
   */
  const DOMUtils = window.DOMUtils.noConflict();
  Qmsg.config({
    position: "top",
    html: true,
    maxNums: 4,
    autoClose: true,
    showClose: false,
    showReverse: false,
  });
  const log = new utils.Log(GM_info);
  log.config({
    debug: false,
  });
  const httpx = new utils.Httpx(GM_xmlhttpRequest);
  httpx.config({
    onabort: function () {
      Qmsg.error("请求被取消");
    },
    ontimeout: function () {
      Qmsg.error("请求超时");
    },
    onerror: function (response) {
      Qmsg.error("请求异常");
      log.error(["httpx-onerror", response]);
    },
  });
  /* -----------------↑公共配置↑----------------- */

  /* -----------------↓函数区域↓----------------- */
  /**
   * GreasyFork的配置
   */
  const GreasyforkApi = {
    /**
     * 获取代码搜索地址
     * @param {string} url
     * @returns {string}
     */
    getCodeSearchUrl(url) {
      return "https://greasyfork.org/zh-CN/scripts/code-search?c=" + url;
    },
    /**
     * 获取管理地址
     * @param {string} url
     * @returns {string}
     */
    getAdminUrl(url) {
      return url + "/admin";
    },
    /**
     * 从字符串中提取Id
     * @returns {string}
     */
    getScriptId() {
      return window.location.pathname.match(/[0-9]+/i)[0];
    },
    /**
     * 获取脚本统计数据
     * @param {string} scriptId
     */
    async getScriptStats(scriptId) {
      return new Promise(async (resolve) => {
        let scriptStatsRequest = await httpx.get({
          url: `https://greasyfork.org/scripts/${scriptId}/stats.json`,
          onerror: function () {},
          ontimeout: function () {},
        });
        if (!scriptStatsRequest.status) {
          resolve(false);
          return;
        }
        let scriptStatsJSON = scriptStatsRequest.data;
        resolve(scriptStatsJSON);
      });
    },
  };

  /**
   * GreasyFork的菜单
   */
  const GreasyforkMenu = {
    /**
     * @class
     */
    menu: new utils.GM_Menu({
      GM_getValue,
      GM_setValue,
      GM_registerMenuCommand,
      GM_unregisterMenuCommand,
    }),
    /**
     * 当前是否已登录
     */
    isLogin: false,
    /**
     * 初始化菜单对象
     */
    initMenu() {
      this.menu.add([
        {
          key: "enterAccount_Password",
          text: "录入账号/密码",
          showText(_text_, _enable_) {
            let user = GM_getValue("user");
            if (user) {
              return `账号:${user} 点击重新录入`;
            } else {
              return "录入账号/密码";
            }
          },
          callback() {
            let user = prompt("请输入GreasyFork的账号");
            if (!user) {
              Qmsg.error("取消输入账号");
              return;
            }
            if (user && user.trim() === "") {
              Qmsg.error("输入为空或纯空格");
              return;
            }
            let pwd = prompt("请输入GreasyFork的密码");

            if (!pwd) {
              Qmsg.error("取消输入密码");
              return;
            }
            if (pwd && pwd.trim() === "") {
              Qmsg.error("输入为空或纯空格");
              return;
            }
            GM_setValue("user", user);
            GM_setValue("pwd", pwd);
            Qmsg.success("成功录入账号/密码");
          },
        },
        {
          key: "clearAccount_Password",
          text: "⚙ 清空账号/密码",
          showText(text) {
            return text;
          },
          callback() {
            if (confirm("确定清空账号和密码?")) {
              GM_deleteValue("user");
              GM_deleteValue("pwd");
              Qmsg.success("已清空账号/密码");
            }
          },
        },
        {
          key: "autoLogin",
          text: "自动登录",
          enable: true,
        },
        {
          key: "beautifyPage",
          text: "美化页面",
          enable: true,
        },
        {
          key: "beautifyGreasyforkBeautify",
          text: "美化Greasyfork Beautify脚本",
          enable: true,
        },
        {
          key: "beautifyUploadImage",
          text: "美化上传图片",
          enable: true,
        },
        {
          key: "addCopyCodeButton",
          text: "添加复制代码按钮",
          enable: true,
        },
      ]);
    },
    /**
     * 初始化环境变量
     */
    initEnv() {
      let userLinkElement = this.getUserLinkElement();
      this.isLogin = Boolean(userLinkElement);
    },
    /**
     * 获取当前登录用户的a标签元素
     * @returns {?HTMLElement}
     */
    getUserLinkElement() {
      return document.querySelector("#nav-user-info span.user-profile-link a");
    },
    /**
     * 处理添加用户界面的菜单项
     */
    handleUserMenu() {
      log.success(["用户界面", this.menu]);
      this.menu.add([
        {
          key: "updateSettingsAndSynchronize_scriptList",
          text: "⚙ 更新设置并同步【脚本列表】",
          showText(text) {
            return text;
          },
          callback() {
            if (!window.location.pathname.match(/\/.+\/users\/.+/gi)) {
              GM_setValue("goto_updateSettingsAndSynchronize_scriptList", true);
              if (GreasyforkMenu.getUserLinkElement()) {
                Qmsg.success("前往用户主页");
                window.location.href = GreasyforkMenu.getUserLinkElement().href;
              } else {
                Qmsg.error("获取当前已登录的用户主页失败");
              }
              return;
            }
            let scriptUrlList = [];
            document
              .querySelectorAll("#user-script-list-section li a.script-link")
              .forEach((item) => {
                scriptUrlList = scriptUrlList.concat(
                  GreasyforkApi.getAdminUrl(item.href)
                );
              });
            GreasyforkMenu.updateScript(scriptUrlList);
          },
        },
        {
          key: "updateSettingsAndSynchronize_unlistedScriptList",
          text: "⚙ 更新设置并同步【未上架的脚本】",
          showText(text) {
            return text;
          },
          callback() {
            if (!window.location.pathname.match(/\/.+\/users\/.+/gi)) {
              GM_setValue(
                "goto_updateSettingsAndSynchronize_unlistedScriptList",
                true
              );
              if (GreasyforkMenu.getUserLinkElement()) {
                Qmsg.success("前往用户主页");
                window.location.href = GreasyforkMenu.getUserLinkElement().href;
              } else {
                Qmsg.error("获取当前已登录的用户主页失败");
              }
              return;
            }
            let scriptUrlList = [];
            document
              .querySelectorAll("#user-unlisted-script-list li a.script-link")
              .forEach((item) => {
                scriptUrlList = scriptUrlList.concat(
                  GreasyforkApi.getAdminUrl(item.href)
                );
              });
            GreasyforkMenu.updateScript(scriptUrlList);
          },
        },
        {
          key: "updateSettingsAndSynchronize_libraryScriptList",
          text: "⚙ 更新设置并同步【库】",
          showText(text) {
            return text;
          },
          callback() {
            if (!window.location.pathname.match(/\/.+\/users\/.+/gi)) {
              GM_setValue(
                "goto_updateSettingsAndSynchronize_libraryScriptList",
                true
              );
              if (GreasyforkMenu.getUserLinkElement()) {
                Qmsg.success("前往用户主页");
                window.location.href = GreasyforkMenu.getUserLinkElement().href;
              } else {
                Qmsg.error("获取当前已登录的用户主页失败");
              }
              return;
            }
            let scriptUrlList = [];
            document
              .querySelectorAll("#user-library-script-list li a.script-link")
              .forEach((item) => {
                scriptUrlList = scriptUrlList.concat(
                  GreasyforkApi.getAdminUrl(item.href)
                );
              });
            GreasyforkMenu.updateScript(scriptUrlList);
          },
        },
      ]);
    },
    /**
     * 将要更新的脚本存储到本地
     * @param {[...string]} scriptUrlList
     */
    updateScript(scriptUrlList) {
      if (utils.isNull(scriptUrlList)) {
        Qmsg.error("未获取到【脚本列表】");
        GM_deleteValue("isUpdate");
        GM_deleteValue("nextUrlList");
        GM_deleteValue("nextUrlIndex");
      } else {
        Qmsg.success(
          "获取【脚本列表】 " + scriptUrlList.length + " 个,准备更新"
        );
        GM_setValue("isUpdate", true);
        GM_setValue("nextUrlList", scriptUrlList);
        GM_setValue("nextUrlIndex", 0);
        setTimeout(() => {
          window.location.href = scriptUrlList[0];
        }, 2500);
      }
    },
    /**
     * 处理本地的goto事件
     */
    handleLocalGotoCallBack() {
      if (GM_getValue("goto_updateSettingsAndSynchronize_scriptList")) {
        let menuCallBack = this.menu.getCallBack(
          "updateSettingsAndSynchronize_scriptList"
        );
        GM_deleteValue("goto_updateSettingsAndSynchronize_scriptList");
        menuCallBack();
      } else if (
        GM_getValue("goto_updateSettingsAndSynchronize_unlistedScriptList")
      ) {
        let menuCallBack = this.menu.getCallBack(
          "updateSettingsAndSynchronize_unlistedScriptList"
        );
        GM_deleteValue("goto_updateSettingsAndSynchronize_unlistedScriptList");
        menuCallBack();
      } else if (
        GM_getValue("goto_updateSettingsAndSynchronize_libraryScriptList")
      ) {
        let menuCallBack = this.menu.getCallBack(
          "updateSettingsAndSynchronize_libraryScriptList"
        );
        GM_deleteValue("goto_updateSettingsAndSynchronize_libraryScriptList");
        menuCallBack();
      }
    },
    /**
     * 入口
     */
    init() {
      this.initMenu();
      this.handleUserMenu();
    },
  };

  /**
   * GreasyFork的业务功能
   */
  const GreasyforkBusiness = {
    /**
     * 自动登录
     */
    autoLogin() {
      utils.waitNode("span.sign-in-link a[rel=nofollow]").then(async () => {
        let user = GM_getValue("user", null);
        let pwd = GM_getValue("pwd", null);
        if (!user) {
          Qmsg.error("请在菜单中录入账号");
          return;
        }
        if (!pwd) {
          Qmsg.error("请在菜单中录入密码");
          return;
        }
        let csrfToken = document.querySelector("meta[name='csrf-token']");
        if (!csrfToken) {
          Qmsg.error("获取csrf-token失败");
          return;
        }
        let loginTip = Qmsg.loading("正在登录中...");
        let postResp = null;
        try {
          postResp = await fetch("https://greasyfork.org/zh-CN/users/sign_in", {
            method: "POST",
            body: encodeURI(
              `authenticity_token=${csrfToken.getAttribute(
                "content"
              )}&user[email]=${user}&user[password]=${pwd}&user[remember_me]=1&commit=登录`
            ),
            headers: {
              "Content-Type": "application/x-www-form-urlencoded",
            },
          });
        } catch (error) {
          log.error(error);
          Qmsg.error("请求失败,请在控制台查看原因");
          return;
        }
        loginTip.destroy();
        if (!postResp.ok) {
          log.error(postResp);
          Qmsg.error("登录失败,请在控制台查看原因");
          return;
        }
        let respText = await postResp.text();
        let parseLoginHTMLNode = DOMUtils.parseHTML(respText, true, true);
        if (
          parseLoginHTMLNode.querySelectorAll(
            ".sign-out-link a[rel=nofollow][data-method='delete']"
          ).length
        ) {
          Qmsg.success("登录成功,1s后自动跳转");
          setTimeout(() => {
            window.location.reload();
          }, 1000);
        } else {
          log.error(postResp);
          log.error(`当前账号:${user}`);
          log.error(`当前密码:${pwd}`);
          Qmsg.error("登录失败,可能是账号/密码错误,请在控制台查看原因");
        }
      });
    },
    /**
     * 设置代码搜索按钮(对于库)
     */
    setFindCodeSearchBtn() {
      utils.waitNode("ul#script-links li.current span").then(() => {
        let searchBtn = DOMUtils.createElement("li", {
          innerHTML: `<a href="javascript:;"><span>寻找引用</span></a>`,
        });
        DOMUtils.append(document.querySelector("ul#script-links"), searchBtn);
        DOMUtils.on(searchBtn, "click", async function () {
          let scriptId = window.location.pathname.match(/scripts\/([\d]+)/i);
          if (!scriptId) {
            log.error([scriptId, window.location.pathname]);
            Qmsg.error("获取脚本id失败");
            return;
          }
          scriptId = scriptId[scriptId.length - 1];
          let getResp = null;
          try {
            getResp = await fetch(
              `https://greasyfork.org/zh-CN/scripts/${scriptId}.json`,
              {
                responseType: "json",
              }
            );
          } catch (error) {
            Qmsg.error("请求失败,请在控制台查看原因");
            return;
          }
          if (!getResp.ok) {
            Qmsg.error("获取脚本信息JSON失败");
            return;
          }
          let respData = await getResp.json();
          if (!respData) {
            Qmsg.error("解析fetch的JSON失败");
            return;
          }
          let url = respData.code_url;
          url = url.replace(/\?version.*/gi, "");
          url = url.replace(/^http(s|):\/\//gi, "");
          url = encodeURI(url);
          window.location.href = GreasyforkApi.getCodeSearchUrl(url);
        });
      });
    },
    /**
     * 更新脚本
     */
    updateScript() {
      let nextUrlInfo = {
        /**
         * @type {[...string]} 需要更新的地址列表
         */
        nextUrlList: GM_getValue("nextUrlList", []),
        /**
         * @type {Number} 当前的地址列表下标
         */
        nextUrlIndex: GM_getValue("nextUrlIndex", 0),
        /**
         * @type {string|null} 下一个的URL
         */
        nextUrl: null,
        /**
         * @type {string} 当前url
         */
        currentUrl: decodeURIComponent(window.location.href),
        /**
         * @type {boolean} 是否是更新中
         */
        isUpdate: GM_getValue("isUpdate", false),
      };
      nextUrlInfo.nextUrl = decodeURIComponent(
        nextUrlInfo.nextUrlList[nextUrlInfo.nextUrlIndex]
      );
      if (!nextUrlInfo.isUpdate) {
        /* 标志位不是更新中 */
        return;
      }
      if (!nextUrlInfo.nextUrlList.length) {
        /* 没获取到更新列表 */
        return;
      }
      if (nextUrlInfo.currentUrl === nextUrlInfo.nextUrl) {
        nextUrlInfo.nextUrlIndex++;
        nextUrlInfo.nextUrl = decodeURIComponent(
          nextUrlInfo.nextUrlList[nextUrlInfo.nextUrlIndex]
        );
        if (nextUrlInfo.nextUrlIndex >= nextUrlInfo.nextUrlList.length) {
          Qmsg.success("当前为最后一个,结束");
          GM_deleteValue("nextUrlList");
          GM_deleteValue("nextUrlIndex");
          GM_deleteValue("isUpdate");
        } else {
          log.info(["下一个的下标:", nextUrlInfo.nextUrlIndex]);
          log.info(["下一个:", nextUrlInfo.nextUrl]);
          Qmsg.success("下一个: " + nextUrlInfo.nextUrl);
          GM_setValue("nextUrlIndex", nextUrlInfo.nextUrlIndex);
        }
        log.success("1秒后点击同步按钮");
        setTimeout(() => {
          let existUpdate =
            document.querySelector("input#script_script_sync_type_id_2")
              .checked ||
            document.querySelector("input#script_script_sync_type_id_1")
              .checked;
          if (!existUpdate) {
            Qmsg.error("该脚本不存在检查更新并同步");
            Qmsg.success("下一个: " + nextUrlInfo.nextUrl);
            setTimeout(() => {
              window.location.href = nextUrlInfo.nextUrl;
            }, 1000);
            return;
          }
          let btnUpdateAndSync = document.querySelector(
            "input[name='update-and-sync']"
          );
          /* 更新设置并立即同步按钮 */
          btnUpdateAndSync.click();
        }, 1000);
      } else {
        setTimeout(() => {
          Qmsg.success(
            `进度 ${nextUrlInfo.nextUrlIndex}/${nextUrlInfo.nextUrlList.length}`
          );
          window.location.href = nextUrlInfo.nextUrl;
        }, 1000);
      }
    },
    /**
     * 修复图片显示问题
     */
    repairImgShow() {
      if (window.innerWidth < window.innerHeight) {
        GM_addStyle(`
        img.lum-img{
          width: 100% !important;
          height: 100% !important;
        }
        `);
      }
    },
    /**
     * 修复代码的行号显示不够问题
     * 超过1w行不会高亮代码
     */
    repairCodeLineNumber() {
      if (!window.location.pathname.split("/")?.includes("code")) {
        return;
      }
      utils
        .waitNode("#script-content div.code-container pre.prettyprint ol")
        .then((element) => {
          if (element.childElementCount >= 1000) {
            log.success(
              `当前代码行数${element.childElementCount}行,超过1000行,优化行号显示问题`
            );
            GM_addStyle(`
          pre.prettyprint{
            padding-left: 10px;
            font-family: Monaco,Consolas,'Lucida Console','Courier New',serif;
            font-size: 12px;
          }
          `);
          }
        });
    },
    /**
     * 优化图片浏览
     */
    optimizeImageBrowsing() {
      GM_addStyle(`
      @media (max-width: 460px) {
        .lum-lightbox-image-wrapper {
            display:flex;
            overflow: auto;
            -webkit-overflow-scrolling: touch
        }
    
        .lum-lightbox-caption {
            width: 100%;
            position: absolute;
            bottom: 0
        }
    
        .lum-lightbox-position-helper {
            margin: auto
        }
    
        .lum-lightbox-inner img {
            max-width:100%;
            max-height:100%;
        }
      }
      `);
      /**
       * 查看图片
       * @param {Array} imgList
       * @param {Number} _index_
       */
      function viewIMG(imgList = [], _index_ = 0) {
        let viewerULNodeHTML = "";
        imgList.forEach((item) => {
          viewerULNodeHTML += `<li><img data-src="${item}" loading="lazy"></li>`;
        });
        let viewerULNode = DOMUtils.createElement("ul", {
          innerHTML: viewerULNodeHTML,
        });
        /**
         * @type {import("../库/Viewer")}
         */
        let viewer = new Viewer(viewerULNode, {
          inline: false,
          url: "data-src",
          zIndex: utils.getMaxZIndex() + 100,
          hidden: () => {
            viewer.destroy();
          },
        });
        _index_ = _index_ < 0 ? 0 : _index_;
        viewer.view(_index_);
        viewer.zoomTo(1);
        viewer.show();
      }
      DOMUtils.on(document, "click", "img", function (event) {
        let clickElement = event.target;
        /* 在超链接标签里 */
        if (clickElement?.parentElement?.localName === "a") {
          return;
        }
        /* Viewer的图片浏览 */
        if (
          clickElement?.parentElement?.className === "viewer-canvas" ||
          clickElement?.parentElement?.hasAttribute("data-viewer-action")
        ) {
          return;
        }
        /* GreasFork自带的图片浏览 */
        if (
          clickElement?.parentElement?.className ===
          "lum-lightbox-position-helper"
        ) {
          return;
        }
        let imgSrc =
          clickElement.getAttribute("src") ||
          clickElement.getAttribute("data-src") ||
          clickElement.getAttribute("alt");
        log.success(["点击浏览图片👉", imgSrc]);
        viewIMG([imgSrc]);
      });
    },
    /**
     * 脚本首页新增今日更新
     */
    async scriptHomepageAddedTodaySUpdate() {
      if (
        !window.location.pathname.includes("/scripts/") ||
        !document.querySelector("#install-area")
      ) {
        return;
      }
      let scriptStatsJSON = await GreasyforkApi.getScriptStats(
        GreasyforkApi.getScriptId()
      );
      if (!scriptStatsJSON) {
        return;
      }
      scriptStatsJSON = utils.toJSON(scriptStatsJSON.responseText);
      log.info(["统计信息", scriptStatsJSON]);
      let todayStatsJSON =
        scriptStatsJSON[utils.formatTime(undefined, "yyyy-MM-dd")];
      if (!todayStatsJSON) {
        log.error("今日份的统计信息不存在");
        return;
      }
      let update_checks = todayStatsJSON["update_checks"];
      log.info(["今日统计信息", todayStatsJSON]);
      DOMUtils.after(
        "dd.script-show-daily-installs",
        DOMUtils.createElement("dt", {
          className: "script-show-daily-update_checks",
          innerHTML: "<span>今日检查</span>",
        })
      );
      DOMUtils.after(
        "dt.script-show-daily-update_checks",
        DOMUtils.createElement("dd", {
          className: "script-show-daily-update_checks",
          innerHTML: "<span>" + update_checks + "</span>",
        })
      );
    },
    /**
     * 美化页面markdown
     */
    beautifyPage() {
      if (!GreasyforkMenu.menu.get("beautifyPage")) {
        return;
      }
      log.success(GreasyforkMenu.menu.getShowTextValue("beautifyPage"));
      let beautifyMarkdownCSS = `
      code{font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:.85em;color:#000;background-color:#f0f0f0;border-radius:3px;padding:.2em 0}
      table{text-indent:initial}
      table{margin:10px 0 15px 0;border-collapse:collapse;border-spacing:0;display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all}
      code,pre{color:#333;background:0 0;font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.4;-moz-tab-size:8;-o-tab-size:8;tab-size:8;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}
      pre{padding:.8em;overflow:auto;border-radius:3px;background:#f5f5f5}
      :not(pre)>code{padding:.1em;border-radius:.3em;white-space:normal;background:#f5f5f5}
      html body{font-family:"Helvetica Neue",Helvetica,"Segoe UI",Arial,freesans,sans-serif;font-size:16px;line-height:1.6;color:#333;background-color:#fff;overflow:initial;box-sizing:border-box;word-wrap:break-word}
      html body>:first-child{margin-top:0}
      html body h1,html body h2,html body h3,html body h4,html body h5,html body h6{line-height:1.2;margin-top:1em;margin-bottom:16px;color:#000}
      html body h1{font-size:2.25em;font-weight:300;padding-bottom:.3em}
      html body h2{font-size:1.75em;font-weight:400;padding-bottom:.3em}
      html body h3{font-size:1.5em;font-weight:500}
      html body h4{font-size:1.25em;font-weight:600}
      html body h5{font-size:1.1em;font-weight:600}
      html body h6{font-size:1em;font-weight:600}
      html body h1,html body h2,html body h3,html body h4,html body h5{font-weight:600}
      html body h5{font-size:1em}
      html body h6{color:#5c5c5c}
      html body strong{color:#000}
      html body del{color:#5c5c5c}
      html body a:not([href]){color:inherit;text-decoration:none}
      html body a{text-decoration:none}
      html body a:hover{color:#00a3f5;text-decoration:none}
      html body img{max-width:100%}
      html body>p{margin-top:0;margin-bottom:16px;word-wrap:break-word}
      html body>ol,html body>ul{margin-bottom:16px}
      html body ol,html body ul{padding-left:2em}
      html body ol.no-list,html body ul.no-list{padding:0;list-style-type:none}
      html body ol ol,html body ol ul,html body ul ol,html body ul ul{margin-top:0;margin-bottom:0}
      html body li{margin-bottom:0}
      html body li.task-list-item{list-style:none}
      html body li>p{margin-top:0;margin-bottom:0}
      html body .task-list-item-checkbox{margin:0 .2em .25em -1.8em;vertical-align:middle}
      html body .task-list-item-checkbox:hover{cursor:pointer}
      html body blockquote{margin:16px 0;font-size:inherit;padding:0 15px;color:#5c5c5c;background-color:#f0f0f0;border-left:4px solid #d6d6d6 !important;}
      html body blockquote>:first-child{margin-top:0}
      html body blockquote>:last-child{margin-bottom:0}
      html body hr{height:4px;margin:32px 0;background-color:#d6d6d6;border:0 none}
      html body table{margin:10px 0 15px 0;border-collapse:collapse;border-spacing:0;display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all}
      html body table th{font-weight:700;color:#000}
      html body table td,html body table th{border:1px solid #d6d6d6;padding:6px 13px}
      html body dl{padding:0}
      html body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:700}
      html body dl dd{padding:0 16px;margin-bottom:16px}
      html body code{font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:.85em;color:#000;background-color:#f0f0f0;border-radius:3px;padding:.2em 0}
      html body code::after,html body code::before{letter-spacing:-.2em;content:"\\00a0"}
      html body pre>code{padding:0;margin:0;word-break:normal;white-space:pre;background:0 0;border:0}
      html body .highlight{margin-bottom:16px}
      html body .highlight pre,html body pre{padding:1em;overflow:auto;line-height:1.45;border:#d6d6d6;border-radius:3px}
      html body .highlight pre{margin-bottom:0;word-break:normal}
      html body pre code,html body pre tt{display:inline;max-width:initial;padding:0;margin:0;overflow:initial;line-height:inherit;word-wrap:normal;background-color:transparent;border:0}
      html body pre code:after,html body pre code:before,html body pre tt:after,html body pre tt:before{content:normal}
      html body blockquote,html body dl,html body ol,html body p,html body pre,html body ul{margin-top:0;margin-bottom:16px}
      html body kbd{color:#000;border:1px solid #d6d6d6;border-bottom:2px solid #c7c7c7;padding:2px 4px;background-color:#f0f0f0;border-radius:3px}
      @media print{html body{background-color:#fff}
      html body h1,html body h2,html body h3,html body h4,html body h5,html body h6{color:#000;page-break-after:avoid}
      html body blockquote{color:#5c5c5c}
      html body pre{page-break-inside:avoid}
      html body table{display:table}
      html body img{display:block;max-width:100%;max-height:100%}
      html body code,html body pre{word-wrap:break-word;white-space:pre}
      }
      .scrollbar-style::-webkit-scrollbar{width:8px}
      .scrollbar-style::-webkit-scrollbar-track{border-radius:10px;background-color:transparent}
      .scrollbar-style::-webkit-scrollbar-thumb{border-radius:5px;background-color:rgba(150,150,150,.66);border:4px solid rgba(150,150,150,.66);background-clip:content-box}
      `;
      let beautifyButtonCSS = `
      /* 美化按钮 */
      input[type="submit"],
      button {
        display: inline-flex;
        justify-content: center;
        align-items: center;
        line-height: 1;
        height: 32px;
        white-space: nowrap;
        cursor: pointer;
        /* color: #606266; */
        text-align: center;
        box-sizing: border-box;
        outline: none;
        transition: .1s;
        font-weight: 500;
        user-select: none;
        vertical-align: middle;
        -webkit-appearance: none;
        background-color: #ffffff;
        border: 1px solid #dcdfe6;
        border-color: #dcdfe6;
        padding: 8px 15px;
        font-size: 14px;
        border-radius: 4px;
      }
      
      input[type="submit"]:hover, 
      input[type="submit"]:focus,
      button:hover,
      button:focus {
          color: #409eff;
          border-color: #c6e2ff;
          background-color: #ecf5ff;
          outline: none;
      }

      input[type="url"] {
        position: relative;
        font-size: 14px;
        display: inline-flex;
        line-height: 32px;
        box-sizing: border-box;
        vertical-align: middle;
        -webkit-appearance: none;
        /* color: #606266; */
        padding: 0;
        outline: none;
        border: none;
        background: none;
        flex-grow: 1;
        align-items: center;
        justify-content: center;
        padding: 1px 11px;
        background-color: #ffffff;
        background-image: none;
        border-radius: 4px;
        cursor: text;
        transition: box-shadow .2s cubic-bezier(.645, .045, .355, 1);
        transform: translateZ(0);
        box-shadow: 0 0 0 1px #dcdfe6 inset;

        width: 100%;
        width: -moz-available;
        width: -webkit-fill-available;
        width: fill-available;

      }
      
      input[type="url"]::placeholder {
          color: #a8abb2;
      }
      
      input[type="url"]:hover {
          box-shadow: 0 0 0 1px #c0c4cc inset;
      }
      
      input[type="url"]:focus {
          box-shadow: 0 0 0 1px #409eff inset;
      }
    
      `;
      let beautifyRadioCSS = `
      label.radio-label {
        font-weight: 500;
        position: relative;
        cursor: pointer;
        display: inline-flex;
        align-items: center;
        white-space: nowrap;
        outline: none;
        font-size: 14px;
        user-select: none;
        margin-right: 32px;
        height: 32px;
        padding: 4px;
        border-radius: 4px;
        box-sizing: border-box;
      }
      label:has(input[type=radio]:checked),
      label:has(input[type=radio]:checked) a{
        color: #409eff;
      }
      label.radio-label input[type="radio"]{
          margin-right: 4px;
          width: 14px;
          height: 14px;
      }
      label.radio-label input[type="radio"]:checked{
          -webkit-appearance: none;
          -moz-appearance: none;
          appearance: none;
          border-radius: 50%;
          width: 14px;
          height: 14px;
          outline: none;
          border: 4px solid #409eff;
          cursor: pointer;
      }
      label.radio-label input[type="radio"]:checked + span{
          color: #409eff;
      }
      `;
      let beautifyTextAreaCSS = `
      textarea {
        position: relative;
        display: inline-block;
        width: 100%;
        vertical-align: bottom;
        font-size: 14px;
        position: relative;
        display: block;
        resize: vertical;
        padding: 5px 11px;
        line-height: 1.5;
        box-sizing: border-box;
        width: 100%;
        font-size: inherit;
        font-family: inherit;
        /* color: #606266; */
        background-color: #ffffff;
        background-image: none;
        -webkit-appearance: none;
        box-shadow: 0 0 0 1px #dcdfe6 inset;
        border-radius: 4px;
        transition: box-shadow .2s cubic-bezier(.645, .045, .355, 1);
        border: none;
      }
      textarea:focus{
        outline: none;
        box-shadow: 0 0 0 1px #409eff inset;
      }
      `;
      /**
       * 未派上用场的CSS
       */
      let notUseBeautifyCSS = `
      .token.blockquote,.token.comment{color:#969896}
      .token.cdata{color:#183691}
      .token.doctype,.token.macro.property,.token.punctuation,.token.variable{color:#333}
      .token.builtin,.token.important,.token.keyword,.token.operator,.token.rule{color:#a71d5d}
      .token.attr-value,.token.regex,.token.string,.token.url{color:#183691}
      .token.atrule,.token.boolean,.token.code,.token.command,.token.constant,.token.entity,.token.number,.token.property,.token.symbol{color:#0086b3}
      .token.prolog,.token.selector,.token.tag{color:#63a35c}
      .token.attr-name,.token.class,.token.class-name,.token.function,.token.id,.token.namespace,.token.pseudo-class,.token.pseudo-element,.token.url-reference .token.variable{color:#795da3}
      .token.entity{cursor:help}
      .token.title,.token.title .token.punctuation{font-weight:700;color:#1d3e81}
      .token.list{color:#ed6a43}
      .token.inserted{background-color:#eaffea;color:#55a532}
      .token.deleted{background-color:#ffecec;color:#bd2c00}
      .token.bold{font-weight:700}
      .token.italic{font-style:italic}
      .language-json .token.property{color:#183691}
      .language-markup .token.tag .token.punctuation{color:#333}
      .language-css .token.function,code.language-css{color:#0086b3}
      .language-yaml .token.atrule{color:#63a35c}
      code.language-yaml{color:#183691}
      .language-ruby .token.function{color:#333}
      .language-markdown .token.url{color:#795da3}
      .language-makefile .token.symbol{color:#795da3}
      .language-makefile .token.variable{color:#183691}
      .language-makefile .token.builtin{color:#0086b3}
      .language-bash .token.keyword{color:#0086b3}
      pre[data-line]{position:relative;padding:1em 0 1em 3em}
      pre[data-line] .line-highlight-wrapper{position:absolute;top:0;left:0;background-color:transparent;display:block;width:100%}
      pre[data-line] .line-highlight{position:absolute;left:0;right:0;padding:inherit 0;margin-top:1em;background:hsla(24,20%,50%,.08);background:linear-gradient(to right,hsla(24,20%,50%,.1) 70%,hsla(24,20%,50%,0));pointer-events:none;line-height:inherit;white-space:pre}
      pre[data-line] .line-highlight:before,pre[data-line] .line-highlight[data-end]:after{content:attr(data-start);position:absolute;top:.4em;left:.6em;min-width:1em;padding:0 .5em;background-color:hsla(24,20%,50%,.4);color:#f4f1ef;font:bold 65%/1.5 sans-serif;text-align:center;vertical-align:.3em;border-radius:999px;text-shadow:none;box-shadow:0 1px #fff}
      pre[data-line] .line-highlight[data-end]:after{content:attr(data-end);top:auto;bottom:.4em}
      `;
      GM_addStyle(beautifyMarkdownCSS);
      GM_addStyle(beautifyButtonCSS);
      GM_addStyle(beautifyRadioCSS);
      GM_addStyle(beautifyTextAreaCSS);
      DOMUtils.ready(function () {
        let markupChoiceELement = document.querySelector(
          'a[target="markup_choice"][href*="daringfireball.net"]'
        );
        if (markupChoiceELement) {
          markupChoiceELement.parentElement.replaceChild(
            DOMUtils.createElement("span", {
              textContent: "Markdown",
            }),
            markupChoiceELement
          );
        }
      });
    },
    /**
     * 美化 Greasyfork Beautify脚本
     */
    beautifyGreasyforkBeautify() {
      if (!GreasyforkMenu.menu.get("beautifyGreasyforkBeautify")) {
        return;
      }
      log.success(
        GreasyforkMenu.menu.getShowTextValue("beautifyGreasyforkBeautify")
      );
      let compatibleBeautifyCSS = `
      #main-header{
        background-color: #670000 !important;
        background-image: linear-gradient(#670000,#990000) !important;
      }
      #site-nav-vue{
        flex-wrap: wrap;
        justify-content: flex-end;
      }
      .open-sidebar{
        border-width: 1px;
        border-radius: 3px;
        margin-right: 0;
      }`;

      GM_addStyle(compatibleBeautifyCSS);
      if (utils.isPhone()) {
        GM_addStyle(`
        section#script-info,
        section.text-content,
        div.width-constraint table.text-content.log-table{
          margin-top: 80px;
        }
        
        div.width-constraint div.sidebarred{
          padding-top: 80px;
        }
        div.width-constraint div.sidebarred .sidebar{
          top: 80px;
        }`);
      } else {
        GM_addStyle(`
        section#script-info{
          margin-top: 10px;
        }`);
      }
    },
    /**
     * 美化上传图片
     */
    beautifyUploadImage() {
      if (!GreasyforkMenu.menu.get("beautifyUploadImage")) {
        return;
      }
      log.success(GreasyforkMenu.menu.getShowTextValue("beautifyUploadImage"));
      let beautifyCSS = `
      /* 隐藏 添加: */
      label[for="discussion_comments_attributes_0_attachments"],
      label[for="comment_attachments"]{
        display: none;
      }
      input[type="file"]{
        width: 100%;
        font-size: 20px;
        background: #e2e2e2;
        padding: 40px 20px;
        border-radius: 10px;
        text-align-last: center;
      }
      `;
      GM_addStyle(beautifyCSS);
      DOMUtils.ready(function () {
        let clearErrorTip = function () {
          while (fileElement.nextElementSibling) {
            fileElement.parentElement.removeChild(
              fileElement.nextElementSibling
            );
          }
        };
        let fileElement = document.querySelector('input[type="file"]');
        DOMUtils.on(fileElement, "change", function (event) {
          clearErrorTip();
          /**
           * @type {File[]}
           */
          let chooseImageFiles = event.currentTarget.files;
          if (chooseImageFiles.length === 0) {
            return;
          }
          log.info(["选择的图片", chooseImageFiles]);
          if (chooseImageFiles.length > 5) {
            DOMUtils.after(
              fileElement,
              DOMUtils.createElement("p", {
                textContent: `❌ 最多同时长传5张图片`,
              })
            );
          }
          /**
           * @type {File[]}
           */
          let notAllowImage = [];
          Array.from(chooseImageFiles).forEach((imageFile) => {
            if (
              imageFile.size > 204800 ||
              !imageFile.type.match(/png|gif|jpeg|webp/i)
            ) {
              notAllowImage.push(imageFile);
            }
          });
          if (notAllowImage.length === 0) {
            return;
          }
          notAllowImage.forEach((imageFile) => {
            DOMUtils.after(
              fileElement,
              DOMUtils.createElement("p", {
                textContent: `❌ 图片:${
                  imageFile.name
                } 大小:${utils.formatByteToSize(imageFile.size)}`,
              })
            );
          });
        });
      });
    },
    /**
     * 添加复制代码按钮
     */
    addCopyCodeButton() {
      if (!window.location.pathname.endsWith("/code")) {
        return;
      }
      if (!GreasyforkMenu.menu.get("addCopyCodeButton")) {
        return;
      }
      log.success(GreasyforkMenu.menu.getShowTextValue("addCopyCodeButton"));
      utils
        .waitNode("div#script-content div.code-container")
        .then((element) => {
          let copyButton = DOMUtils.createElement("button", {
            style: "margin-bottom: 1em;",
            textContent: "复制代码",
          });
          DOMUtils.on(copyButton, "click", async function () {
            let loading = Qmsg.loading("加载文件中...");
            let scriptJS = await httpx.get(
              `https://greasyfork.org/scripts/${GreasyforkApi.getScriptId()}.js`
            );
            if (!scriptJS.status) {
              return;
            }
            loading.close();
            utils.setClip(scriptJS.data.responseText);
            Qmsg.success("复制成功");
          });
          DOMUtils.before(element, copyButton);
        });
    },
  };
  /* -----------------↑函数区域↑----------------- */

  /* -----------------↓执行入口↓----------------- */
  GreasyforkMenu.init();
  GreasyforkBusiness.beautifyPage();
  GreasyforkBusiness.beautifyGreasyforkBeautify();
  GreasyforkBusiness.beautifyUploadImage();
  DOMUtils.ready(function () {
    GreasyforkMenu.initEnv();
    if (GreasyforkMenu.menu.get("autoLogin")) {
      GreasyforkBusiness.autoLogin();
    }
    GreasyforkMenu.handleLocalGotoCallBack();
    GreasyforkBusiness.setFindCodeSearchBtn();
    GreasyforkBusiness.updateScript();
    GreasyforkBusiness.repairImgShow();
    GreasyforkBusiness.repairCodeLineNumber();
    GreasyforkBusiness.optimizeImageBrowsing();
    GreasyforkBusiness.scriptHomepageAddedTodaySUpdate();
    GreasyforkBusiness.addCopyCodeButton();
  });
  /* -----------------↑执行入口↑----------------- */
})();