GreasyFork优化

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

当前为 2023-12-08 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GreasyFork优化
  3. // @namespace https://greasyfork.org/zh-CN/scripts/475722
  4. // @supportURL https://github.com/WhiteSevs/TamperMonkeyScript/issues
  5. // @version 2023.11.23
  6. // @description 自动登录账号、快捷寻找自己库被其他脚本引用、更新自己的脚本列表、库、优化图片浏览、美化页面、Markdown复制按钮
  7. // @author WhiteSevs
  8. // @license MIT
  9. // @icon https://favicon.yandex.net/favicon/v2/https://greasyfork.org/?size=32
  10. // @match *://greasyfork.org/*
  11. // @run-at document-start
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @grant GM_deleteValue
  15. // @grant GM_xmlhttpRequest
  16. // @grant GM_addStyle
  17. // @grant GM_registerMenuCommand
  18. // @grant GM_unregisterMenuCommand
  19. // @grant GM_info
  20. // @connect greasyfork.org
  21. // @require https://update.greasyfork.org/scripts/449471/1249086/Viewer.js
  22. // @require https://update.greasyfork.org/scripts/462234/1284140/Message.js
  23. // @require https://update.greasyfork.org/scripts/456485/1285662/pops.js
  24. // @require https://update.greasyfork.org/scripts/455186/1290431/WhiteSevsUtils.js
  25. // @require https://update.greasyfork.org/scripts/465772/1274595/DOMUtils.js
  26. // ==/UserScript==
  27.  
  28. (function () {
  29. /* -----------------↓公共配置↓----------------- */
  30. /**
  31. * @type {import("../库/Qmsg")}
  32. */
  33. const Qmsg = window.Qmsg;
  34. /**
  35. * @type {import("../库/pops")}
  36. */
  37. const pops = window.pops;
  38. /**
  39. * @type {import("../库/Utils")}
  40. */
  41. const utils = window.Utils.noConflict();
  42. /**
  43. * @type {import("../库/DOMUtils")}
  44. */
  45. const DOMUtils = window.DOMUtils.noConflict();
  46. Qmsg.config({
  47. position: "top",
  48. html: true,
  49. maxNums: 4,
  50. autoClose: true,
  51. showClose: false,
  52. showReverse: false,
  53. });
  54. const log = new utils.Log(GM_info);
  55. log.config({
  56. debug: false,
  57. });
  58. const httpx = new utils.Httpx(GM_xmlhttpRequest);
  59. httpx.config({
  60. onabort: function () {
  61. Qmsg.error("请求被取消");
  62. },
  63. ontimeout: function () {
  64. Qmsg.error("请求超时");
  65. },
  66. onerror: function (response) {
  67. Qmsg.error("请求异常");
  68. log.error(["httpx-onerror", response]);
  69. },
  70. });
  71. /* -----------------↑公共配置↑----------------- */
  72.  
  73. /* -----------------↓函数区域↓----------------- */
  74. /**
  75. * GreasyFork的配置
  76. */
  77. const GreasyforkApi = {
  78. /**
  79. * 获取代码搜索地址
  80. * @param {string} url
  81. * @returns {string}
  82. */
  83. getCodeSearchUrl(url) {
  84. return "https://greasyfork.org/zh-CN/scripts/code-search?c=" + url;
  85. },
  86. /**
  87. * 获取管理地址
  88. * @param {string} url
  89. * @returns {string}
  90. */
  91. getAdminUrl(url) {
  92. return url + "/admin";
  93. },
  94. /**
  95. * 从字符串中提取Id
  96. * @param {?string} url
  97. * @returns {string}
  98. */
  99. getScriptId(url) {
  100. return (url || window.location.pathname).match(/\/scripts\/([\d]+)/i)[1];
  101. },
  102. /**
  103. * 从字符串中提取脚本名
  104. * @param {?string} url
  105. * @returns {?string}
  106. */
  107. getScriptName(url) {
  108. let pathname = window.location.pathname;
  109. if (url != null) {
  110. pathname = new URL(url).pathname;
  111. }
  112. pathname = decodeURIComponent(pathname);
  113. pathname = pathname.split("/");
  114. for (const name of pathname) {
  115. if (name.match(/[\d]+/)) {
  116. return name.match(/[\d]+-(.+)/)[1];
  117. }
  118. }
  119. },
  120. /**
  121. * 获取脚本统计数据
  122. * @param {string} scriptId
  123. */
  124. async getScriptStats(scriptId) {
  125. return new Promise(async (resolve) => {
  126. let scriptStatsRequest = await httpx.get({
  127. url: `https://greasyfork.org/scripts/${scriptId}/stats.json`,
  128. onerror: function () {},
  129. ontimeout: function () {},
  130. });
  131. if (!scriptStatsRequest.status) {
  132. resolve(false);
  133. return;
  134. }
  135. let scriptStatsJSON = scriptStatsRequest.data;
  136. resolve(scriptStatsJSON);
  137. });
  138. },
  139. /**
  140. * 解析并获取admin内的源代码同步的配置表单
  141. * @param {string} scriptId
  142. * @returns {Promise<?FormData>}
  143. */
  144. async getSourceCodeSyncFormData(scriptId) {
  145. let getResp = await fetch(
  146. `https://greasyfork.org/zh-CN/scripts/${scriptId}/admin`
  147. );
  148. log.success(getResp);
  149. if (getResp.status !== 200) {
  150. Qmsg.error("请求admin内容失败");
  151. return;
  152. }
  153. let adminHTML = await getResp.text();
  154. let adminHTMLElement = DOMUtils.parseHTML(adminHTML, false, true);
  155. let formElement = adminHTMLElement.querySelector("form.edit_script");
  156. if (!formElement) {
  157. Qmsg.error("解析admin的源代码同步表单失败");
  158. return;
  159. }
  160. let formData = new FormData(formElement);
  161. return formData;
  162. },
  163. /**
  164. * 进行源代码同步,要求先getSourceCodeSyncFormData
  165. * @param {string} scriptId
  166. * @param {FormData} data
  167. * @returns {Promise<?Response>}
  168. */
  169. async sourceCodeSync(scriptId, data) {
  170. let postResp = await fetch(
  171. `https://greasyfork.org/zh-CN/scripts/${scriptId}/sync_update`,
  172. {
  173. method: "POST",
  174. body: data,
  175. }
  176. );
  177. log.success(postResp);
  178. if (postResp.status !== 200) {
  179. Qmsg.error("源代码同步失败");
  180. return;
  181. }
  182. return postResp;
  183. },
  184. };
  185.  
  186. /**
  187. * GreasyFork的菜单
  188. */
  189. const GreasyforkMenu = {
  190. /**
  191. * @class
  192. */
  193. menu: new utils.GM_Menu({
  194. GM_getValue,
  195. GM_setValue,
  196. GM_registerMenuCommand,
  197. GM_unregisterMenuCommand,
  198. }),
  199. /**
  200. * 当前是否已登录
  201. */
  202. isLogin: false,
  203. /**
  204. * 初始化菜单对象
  205. */
  206. initMenu() {
  207. this.menu.add([
  208. {
  209. key: "enterAccount_Password",
  210. text: "录入账号/密码",
  211. showText(_text_, _enable_) {
  212. let user = GM_getValue("user");
  213. if (user) {
  214. return `账号:${user} 点击重新录入`;
  215. } else {
  216. return "录入账号/密码";
  217. }
  218. },
  219. callback() {
  220. let user = prompt("请输入GreasyFork的账号");
  221. if (!user) {
  222. Qmsg.error("取消输入账号");
  223. return;
  224. }
  225. if (user && user.trim() === "") {
  226. Qmsg.error("输入为空或纯空格");
  227. return;
  228. }
  229. let pwd = prompt("请输入GreasyFork的密码");
  230.  
  231. if (!pwd) {
  232. Qmsg.error("取消输入密码");
  233. return;
  234. }
  235. if (pwd && pwd.trim() === "") {
  236. Qmsg.error("输入为空或纯空格");
  237. return;
  238. }
  239. GM_setValue("user", user);
  240. GM_setValue("pwd", pwd);
  241. Qmsg.success("成功录入账号/密码");
  242. },
  243. },
  244. {
  245. key: "clearAccount_Password",
  246. text: "⚙ 清空账号/密码",
  247. showText(text) {
  248. return text;
  249. },
  250. callback() {
  251. if (confirm("确定清空账号和密码?")) {
  252. GM_deleteValue("user");
  253. GM_deleteValue("pwd");
  254. Qmsg.success("已清空账号/密码");
  255. }
  256. },
  257. },
  258. {
  259. key: "autoLogin",
  260. text: "自动登录",
  261. enable: true,
  262. },
  263. {
  264. key: "beautifyPage",
  265. text: "美化页面",
  266. enable: true,
  267. },
  268. {
  269. key: "beautifyGreasyforkBeautify",
  270. text: "美化Greasyfork Beautify脚本",
  271. enable: true,
  272. },
  273. {
  274. key: "beautifyUploadImage",
  275. text: "美化上传图片",
  276. enable: true,
  277. },
  278. {
  279. key: "addCopyCodeButton",
  280. text: "添加复制代码按钮",
  281. enable: true,
  282. },
  283. ]);
  284. },
  285. /**
  286. * 初始化环境变量
  287. */
  288. initEnv() {
  289. let userLinkElement = this.getUserLinkElement();
  290. this.isLogin = Boolean(userLinkElement);
  291. },
  292. /**
  293. * 获取当前登录用户的a标签元素
  294. * @returns {?HTMLElement}
  295. */
  296. getUserLinkElement() {
  297. return document.querySelector("#nav-user-info span.user-profile-link a");
  298. },
  299. /**
  300. * 处理添加用户界面的菜单项
  301. */
  302. handleUserMenu() {
  303. log.success(["用户界面", this.menu]);
  304. this.menu.add([
  305. {
  306. key: "updateSettingsAndSynchronize_scriptList",
  307. text: "⚙ 源代码同步【脚本列表】",
  308. autoReload: false,
  309. showText(text) {
  310. return text;
  311. },
  312. callback() {
  313. if (!window.location.pathname.match(/\/.+\/users\/.+/gi)) {
  314. GM_setValue("goto_updateSettingsAndSynchronize_scriptList", true);
  315. if (GreasyforkMenu.getUserLinkElement()) {
  316. Qmsg.success("前往用户主页");
  317. window.location.href = GreasyforkMenu.getUserLinkElement().href;
  318. } else {
  319. Qmsg.error("获取当前已登录的用户主页失败");
  320. }
  321. return;
  322. }
  323. let scriptUrlList = [];
  324. document
  325. .querySelectorAll("#user-script-list-section li a.script-link")
  326. .forEach((item) => {
  327. scriptUrlList = scriptUrlList.concat(
  328. GreasyforkApi.getAdminUrl(item.href)
  329. );
  330. });
  331. GreasyforkMenu.updateScript(scriptUrlList);
  332. },
  333. },
  334. {
  335. key: "updateSettingsAndSynchronize_unlistedScriptList",
  336. text: "⚙ 源代码同步【未上架的脚本】",
  337. autoReload: false,
  338. showText(text) {
  339. return text;
  340. },
  341. callback() {
  342. if (!window.location.pathname.match(/\/.+\/users\/.+/gi)) {
  343. GM_setValue(
  344. "goto_updateSettingsAndSynchronize_unlistedScriptList",
  345. true
  346. );
  347. if (GreasyforkMenu.getUserLinkElement()) {
  348. Qmsg.success("前往用户主页");
  349. window.location.href = GreasyforkMenu.getUserLinkElement().href;
  350. } else {
  351. Qmsg.error("获取当前已登录的用户主页失败");
  352. }
  353. return;
  354. }
  355. let scriptUrlList = [];
  356. document
  357. .querySelectorAll("#user-unlisted-script-list li a.script-link")
  358. .forEach((item) => {
  359. scriptUrlList = scriptUrlList.concat(
  360. GreasyforkApi.getAdminUrl(item.href)
  361. );
  362. });
  363. GreasyforkMenu.updateScript(scriptUrlList);
  364. },
  365. },
  366. {
  367. key: "updateSettingsAndSynchronize_libraryScriptList",
  368. text: "⚙ 源代码同步【库】",
  369. autoReload: false,
  370. showText(text) {
  371. return text;
  372. },
  373. callback() {
  374. if (!window.location.pathname.match(/\/.+\/users\/.+/gi)) {
  375. GM_setValue(
  376. "goto_updateSettingsAndSynchronize_libraryScriptList",
  377. true
  378. );
  379. if (GreasyforkMenu.getUserLinkElement()) {
  380. Qmsg.success("前往用户主页");
  381. window.location.href = GreasyforkMenu.getUserLinkElement().href;
  382. } else {
  383. Qmsg.error("获取当前已登录的用户主页失败");
  384. }
  385. return;
  386. }
  387. let scriptUrlList = [];
  388. document
  389. .querySelectorAll("#user-library-script-list li a.script-link")
  390. .forEach((item) => {
  391. scriptUrlList = scriptUrlList.concat(
  392. GreasyforkApi.getAdminUrl(item.href)
  393. );
  394. });
  395. GreasyforkMenu.updateScript(scriptUrlList);
  396. },
  397. },
  398. ]);
  399. },
  400. /**
  401. * 更新脚本
  402. * @param {string[]} scriptUrlList
  403. */
  404. async updateScript(scriptUrlList) {
  405. let getLoadingHTML = function (scriptName, progress = 1) {
  406. return `
  407. <div style="display: flex;flex-direction: column;align-items: flex-start;">
  408. <div style="height: 30px;line-height: 30px;">名称:${scriptName}</div>
  409. <div style="height: 30px;line-height: 30px;">进度:${progress}/${scriptUrlList.length}</div>
  410. </div>`;
  411. };
  412. if (utils.isNull(scriptUrlList)) {
  413. Qmsg.error("未获取到【脚本列表】");
  414. } else {
  415. let loading = Qmsg.loading(
  416. getLoadingHTML(GreasyforkApi.getScriptName(scriptUrlList[0])),
  417. {
  418. html: true,
  419. }
  420. );
  421. let successNums = 0;
  422. let failedNums = 0;
  423. for (let index = 0; index < scriptUrlList.length; index++) {
  424. let scriptUrl = scriptUrlList[index];
  425. let scriptId = GreasyforkApi.getScriptId(scriptUrl);
  426. log.success("更新:" + scriptUrl);
  427. let scriptName = GreasyforkApi.getScriptName(scriptUrl);
  428. loading.setHTML(getLoadingHTML(scriptName, index + 1));
  429. let codeSyncFormData = await GreasyforkApi.getSourceCodeSyncFormData(
  430. scriptId
  431. );
  432. if (codeSyncFormData) {
  433. let syncUpdateStatus = await GreasyforkApi.sourceCodeSync(
  434. scriptId,
  435. codeSyncFormData
  436. );
  437. if (syncUpdateStatus) {
  438. Qmsg.success("源代码同步成功,3秒后更新下一个");
  439. await utils.sleep(3000);
  440. successNums++;
  441. } else {
  442. Qmsg.error("源代码同步失败");
  443. failedNums++;
  444. }
  445. } else {
  446. Qmsg.error("源代码同步失败");
  447. failedNums++;
  448. }
  449. }
  450. loading.close();
  451. if (successNums === 0) {
  452. Qmsg.error("全部更新失败");
  453. } else {
  454. Qmsg.success(
  455. `全部更新完毕<br >
  456. 成功:${successNums}<br >
  457. 失败:${failedNums}<br >
  458. 总计:${scriptUrlList.length}`,
  459. {
  460. html: true,
  461. }
  462. );
  463. }
  464. }
  465. },
  466. /**
  467. * 处理本地的goto事件
  468. */
  469. handleLocalGotoCallBack() {
  470. if (GM_getValue("goto_updateSettingsAndSynchronize_scriptList")) {
  471. let menuCallBack = this.menu.getCallBack(
  472. "updateSettingsAndSynchronize_scriptList"
  473. );
  474. GM_deleteValue("goto_updateSettingsAndSynchronize_scriptList");
  475. menuCallBack();
  476. } else if (
  477. GM_getValue("goto_updateSettingsAndSynchronize_unlistedScriptList")
  478. ) {
  479. let menuCallBack = this.menu.getCallBack(
  480. "updateSettingsAndSynchronize_unlistedScriptList"
  481. );
  482. GM_deleteValue("goto_updateSettingsAndSynchronize_unlistedScriptList");
  483. menuCallBack();
  484. } else if (
  485. GM_getValue("goto_updateSettingsAndSynchronize_libraryScriptList")
  486. ) {
  487. let menuCallBack = this.menu.getCallBack(
  488. "updateSettingsAndSynchronize_libraryScriptList"
  489. );
  490. GM_deleteValue("goto_updateSettingsAndSynchronize_libraryScriptList");
  491. menuCallBack();
  492. }
  493. },
  494. /**
  495. * 入口
  496. */
  497. init() {
  498. this.initMenu();
  499. this.handleUserMenu();
  500. },
  501. };
  502.  
  503. /**
  504. * GreasyFork的业务功能
  505. */
  506. const GreasyforkBusiness = {
  507. /**
  508. * 自动登录
  509. */
  510. autoLogin() {
  511. utils.waitNode("span.sign-in-link a[rel=nofollow]").then(async () => {
  512. let user = GM_getValue("user", null);
  513. let pwd = GM_getValue("pwd", null);
  514. if (!user) {
  515. Qmsg.error("请在菜单中录入账号");
  516. return;
  517. }
  518. if (!pwd) {
  519. Qmsg.error("请在菜单中录入密码");
  520. return;
  521. }
  522. let csrfToken = document.querySelector("meta[name='csrf-token']");
  523. if (!csrfToken) {
  524. Qmsg.error("获取csrf-token失败");
  525. return;
  526. }
  527. let loginTip = Qmsg.loading("正在登录中...");
  528. let postResp = null;
  529. try {
  530. postResp = await fetch("https://greasyfork.org/zh-CN/users/sign_in", {
  531. method: "POST",
  532. body: encodeURI(
  533. `authenticity_token=${csrfToken.getAttribute(
  534. "content"
  535. )}&user[email]=${user}&user[password]=${pwd}&user[remember_me]=1&commit=登录`
  536. ),
  537. headers: {
  538. "Content-Type": "application/x-www-form-urlencoded",
  539. },
  540. });
  541. } catch (error) {
  542. log.error(error);
  543. Qmsg.error("请求失败,请在控制台查看原因");
  544. return;
  545. }
  546. loginTip.destroy();
  547. if (!postResp.ok) {
  548. log.error(postResp);
  549. Qmsg.error("登录失败,请在控制台查看原因");
  550. return;
  551. }
  552. let respText = await postResp.text();
  553. let parseLoginHTMLNode = DOMUtils.parseHTML(respText, true, true);
  554. if (
  555. parseLoginHTMLNode.querySelectorAll(
  556. ".sign-out-link a[rel=nofollow][data-method='delete']"
  557. ).length
  558. ) {
  559. Qmsg.success("登录成功,1s后自动跳转");
  560. setTimeout(() => {
  561. window.location.reload();
  562. }, 1000);
  563. } else {
  564. log.error(postResp);
  565. log.error(`当前账号:${user}`);
  566. log.error(`当前密码:${pwd}`);
  567. Qmsg.error("登录失败,可能是账号/密码错误,请在控制台查看原因");
  568. }
  569. });
  570. },
  571. /**
  572. * 设置代码搜索按钮(对于库)
  573. */
  574. setFindCodeSearchBtn() {
  575. utils.waitNode("ul#script-links li.current span").then(() => {
  576. let searchBtn = DOMUtils.createElement("li", {
  577. innerHTML: `<a href="javascript:;"><span>寻找引用</span></a>`,
  578. });
  579. DOMUtils.append(document.querySelector("ul#script-links"), searchBtn);
  580. DOMUtils.on(searchBtn, "click", async function () {
  581. let scriptId = window.location.pathname.match(/scripts\/([\d]+)/i);
  582. if (!scriptId) {
  583. log.error([scriptId, window.location.pathname]);
  584. Qmsg.error("获取脚本id失败");
  585. return;
  586. }
  587. scriptId = scriptId[scriptId.length - 1];
  588. let getResp = null;
  589. try {
  590. getResp = await fetch(
  591. `https://greasyfork.org/zh-CN/scripts/${scriptId}.json`,
  592. {
  593. responseType: "json",
  594. }
  595. );
  596. } catch (error) {
  597. Qmsg.error("请求失败,请在控制台查看原因");
  598. return;
  599. }
  600. if (!getResp.ok) {
  601. Qmsg.error("获取脚本信息JSON失败");
  602. return;
  603. }
  604. let respData = await getResp.json();
  605. if (!respData) {
  606. Qmsg.error("解析fetch的JSON失败");
  607. return;
  608. }
  609. let url = respData.code_url;
  610. url = url.replace(/\?version.*/gi, "");
  611. url = url.replace(/^http(s|):\/\//gi, "");
  612. url = encodeURI(url);
  613. window.location.href = GreasyforkApi.getCodeSearchUrl(url);
  614. });
  615. });
  616. },
  617. /**
  618. * 修复图片显示问题
  619. */
  620. repairImgShow() {
  621. if (window.innerWidth < window.innerHeight) {
  622. GM_addStyle(`
  623. img.lum-img{
  624. width: 100% !important;
  625. height: 100% !important;
  626. }
  627. `);
  628. }
  629. },
  630. /**
  631. * 修复代码的行号显示不够问题
  632. * 超过1w行不会高亮代码
  633. */
  634. repairCodeLineNumber() {
  635. if (!window.location.pathname.split("/")?.includes("code")) {
  636. return;
  637. }
  638. utils
  639. .waitNode("#script-content div.code-container pre.prettyprint ol")
  640. .then((element) => {
  641. if (element.childElementCount >= 1000) {
  642. log.success(
  643. `当前代码行数${element.childElementCount}行,超过1000行,优化行号显示问题`
  644. );
  645. GM_addStyle(`
  646. pre.prettyprint{
  647. padding-left: 10px;
  648. font-family: Monaco,Consolas,'Lucida Console','Courier New',serif;
  649. font-size: 12px;
  650. }
  651. `);
  652. }
  653. });
  654. },
  655. /**
  656. * 优化图片浏览
  657. */
  658. optimizeImageBrowsing() {
  659. GM_addStyle(`
  660. @media (max-width: 460px) {
  661. .lum-lightbox-image-wrapper {
  662. display:flex;
  663. overflow: auto;
  664. -webkit-overflow-scrolling: touch
  665. }
  666. .lum-lightbox-caption {
  667. width: 100%;
  668. position: absolute;
  669. bottom: 0
  670. }
  671. .lum-lightbox-position-helper {
  672. margin: auto
  673. }
  674. .lum-lightbox-inner img {
  675. max-width:100%;
  676. max-height:100%;
  677. }
  678. }
  679. `);
  680. /**
  681. * 查看图片
  682. * @param {Array} imgList
  683. * @param {Number} _index_
  684. */
  685. function viewIMG(imgList = [], _index_ = 0) {
  686. let viewerULNodeHTML = "";
  687. imgList.forEach((item) => {
  688. viewerULNodeHTML += `<li><img data-src="${item}" loading="lazy"></li>`;
  689. });
  690. let viewerULNode = DOMUtils.createElement("ul", {
  691. innerHTML: viewerULNodeHTML,
  692. });
  693. /**
  694. * @type {import("../库/Viewer")}
  695. */
  696. let viewer = new Viewer(viewerULNode, {
  697. inline: false,
  698. url: "data-src",
  699. zIndex: utils.getMaxZIndex() + 100,
  700. hidden: () => {
  701. viewer.destroy();
  702. },
  703. });
  704. _index_ = _index_ < 0 ? 0 : _index_;
  705. viewer.view(_index_);
  706. viewer.zoomTo(1);
  707. viewer.show();
  708. }
  709. DOMUtils.on(document, "click", "img", function (event) {
  710. let clickElement = event.target;
  711. /* 在超链接标签里 */
  712. if (clickElement?.parentElement?.localName === "a") {
  713. return;
  714. }
  715. /* Viewer的图片浏览 */
  716. if (
  717. clickElement?.parentElement?.className === "viewer-canvas" ||
  718. clickElement?.parentElement?.hasAttribute("data-viewer-action")
  719. ) {
  720. return;
  721. }
  722. /* GreasFork自带的图片浏览 */
  723. if (
  724. clickElement?.parentElement?.className ===
  725. "lum-lightbox-position-helper"
  726. ) {
  727. return;
  728. }
  729. let imgSrc =
  730. clickElement.getAttribute("src") ||
  731. clickElement.getAttribute("data-src") ||
  732. clickElement.getAttribute("alt");
  733. log.success(["点击浏览图片👉", imgSrc]);
  734. viewIMG([imgSrc]);
  735. });
  736. },
  737. /**
  738. * 脚本首页新增今日更新
  739. */
  740. async scriptHomepageAddedTodaySUpdate() {
  741. if (
  742. !window.location.pathname.includes("/scripts/") ||
  743. !document.querySelector("#install-area")
  744. ) {
  745. return;
  746. }
  747. let scriptStatsJSON = await GreasyforkApi.getScriptStats(
  748. GreasyforkApi.getScriptId()
  749. );
  750. if (!scriptStatsJSON) {
  751. return;
  752. }
  753. scriptStatsJSON = utils.toJSON(scriptStatsJSON.responseText);
  754. log.info(["统计信息", scriptStatsJSON]);
  755. let todayStatsJSON =
  756. scriptStatsJSON[utils.formatTime(undefined, "yyyy-MM-dd")];
  757. if (!todayStatsJSON) {
  758. log.error("今日份的统计信息不存在");
  759. return;
  760. }
  761. let update_checks = todayStatsJSON["update_checks"];
  762. log.info(["今日统计信息", todayStatsJSON]);
  763. DOMUtils.after(
  764. "dd.script-show-daily-installs",
  765. DOMUtils.createElement("dt", {
  766. className: "script-show-daily-update_checks",
  767. innerHTML: "<span>今日检查</span>",
  768. })
  769. );
  770. DOMUtils.after(
  771. "dt.script-show-daily-update_checks",
  772. DOMUtils.createElement("dd", {
  773. className: "script-show-daily-update_checks",
  774. innerHTML: "<span>" + update_checks + "</span>",
  775. })
  776. );
  777. },
  778. /**
  779. * 美化页面markdown
  780. */
  781. beautifyPage() {
  782. if (!GreasyforkMenu.menu.get("beautifyPage")) {
  783. return;
  784. }
  785. log.success(GreasyforkMenu.menu.getShowTextValue("beautifyPage"));
  786. let beautifyMarkdownCSS = `
  787. code{font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:.85em;color:#000;background-color:#f0f0f0;border-radius:3px;padding:.2em 0}
  788. table{text-indent:initial}
  789. 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}
  790. 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}
  791. pre{padding:.8em;overflow:auto;border-radius:3px;background:#f5f5f5}
  792. :not(pre)>code{padding:.1em;border-radius:.3em;white-space:normal;background:#f5f5f5}
  793. 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}
  794. html body>:first-child{margin-top:0}
  795. 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}
  796. html body h1{font-size:2.25em;font-weight:300;padding-bottom:.3em}
  797. html body h2{font-size:1.75em;font-weight:400;padding-bottom:.3em}
  798. html body h3{font-size:1.5em;font-weight:500}
  799. html body h4{font-size:1.25em;font-weight:600}
  800. html body h5{font-size:1.1em;font-weight:600}
  801. html body h6{font-size:1em;font-weight:600}
  802. html body h1,html body h2,html body h3,html body h4,html body h5{font-weight:600}
  803. html body h5{font-size:1em}
  804. html body h6{color:#5c5c5c}
  805. html body strong{color:#000}
  806. html body del{color:#5c5c5c}
  807. html body a:not([href]){color:inherit;}
  808. html body a{text-decoration:underline;text-underline-offset: .2rem;}
  809. html body a:hover{color:#00a3f5;}
  810. html body img{max-width:100%}
  811. html body>p{margin-top:0;margin-bottom:16px;word-wrap:break-word}
  812. html body>ol,html body>ul{margin-bottom:16px}
  813. html body ol,html body ul{padding-left:2em}
  814. html body ol.no-list,html body ul.no-list{padding:0;list-style-type:none}
  815. html body ol ol,html body ol ul,html body ul ol,html body ul ul{margin-top:0;margin-bottom:0}
  816. html body li{margin-bottom:0}
  817. html body li.task-list-item{list-style:none}
  818. html body li>p{margin-top:0;margin-bottom:0}
  819. html body .task-list-item-checkbox{margin:0 .2em .25em -1.8em;vertical-align:middle}
  820. html body .task-list-item-checkbox:hover{cursor:pointer}
  821. html body blockquote{margin:16px 0;font-size:inherit;padding:0 15px;color:#5c5c5c;background-color:#f0f0f0;border-left:4px solid #d6d6d6 !important;}
  822. html body blockquote>:first-child{margin-top:0}
  823. html body blockquote>:last-child{margin-bottom:0}
  824. html body hr{height:4px;margin:32px 0;background-color:#d6d6d6;border:0 none}
  825. 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}
  826. html body table th{font-weight:700;color:#000}
  827. html body table td,html body table th{border:1px solid #d6d6d6;padding:6px 13px}
  828. html body dl{padding:0}
  829. html body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:700}
  830. html body dl dd{padding:0 16px;margin-bottom:16px}
  831. 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}
  832. html body code::after,html body code::before{letter-spacing:-.2em;content:"\\00a0"}
  833. html body pre>code{padding:0;margin:0;word-break:normal;white-space:pre;background:0 0;border:0}
  834. html body .highlight{margin-bottom:16px}
  835. html body .highlight pre,html body pre{padding:1em;overflow:auto;line-height:1.45;border:#d6d6d6;border-radius:3px}
  836. html body .highlight pre{margin-bottom:0;word-break:normal}
  837. 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}
  838. html body pre code:after,html body pre code:before,html body pre tt:after,html body pre tt:before{content:normal}
  839. html body blockquote,html body dl,html body ol,html body p,html body pre,html body ul{margin-top:0;margin-bottom:16px}
  840. html body kbd{color:#000;border:1px solid #d6d6d6;border-bottom:2px solid #c7c7c7;padding:2px 4px;background-color:#f0f0f0;border-radius:3px}
  841. @media print{html body{background-color:#fff}
  842. html body h1,html body h2,html body h3,html body h4,html body h5,html body h6{color:#000;page-break-after:avoid}
  843. html body blockquote{color:#5c5c5c}
  844. html body pre{page-break-inside:avoid}
  845. html body table{display:table}
  846. html body img{display:block;max-width:100%;max-height:100%}
  847. html body code,html body pre{word-wrap:break-word;white-space:pre}
  848. }
  849. .scrollbar-style::-webkit-scrollbar{width:8px}
  850. .scrollbar-style::-webkit-scrollbar-track{border-radius:10px;background-color:transparent}
  851. .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}
  852. `;
  853. let beautifyButtonCSS = `
  854. /* 美化按钮 */
  855. input[type="submit"],
  856. button {
  857. display: inline-flex;
  858. justify-content: center;
  859. align-items: center;
  860. line-height: 1;
  861. height: 32px;
  862. white-space: nowrap;
  863. cursor: pointer;
  864. /* color: #606266; */
  865. text-align: center;
  866. box-sizing: border-box;
  867. outline: none;
  868. transition: .1s;
  869. font-weight: 500;
  870. user-select: none;
  871. vertical-align: middle;
  872. -webkit-appearance: none;
  873. background-color: #ffffff;
  874. border: 1px solid #dcdfe6;
  875. border-color: #dcdfe6;
  876. padding: 8px 15px;
  877. font-size: 14px;
  878. border-radius: 4px;
  879. }
  880. input[type="submit"]:hover,
  881. input[type="submit"]:focus,
  882. button:hover,
  883. button:focus {
  884. color: #409eff;
  885. border-color: #c6e2ff;
  886. background-color: #ecf5ff;
  887. outline: none;
  888. }
  889.  
  890. input[type="url"] {
  891. position: relative;
  892. font-size: 14px;
  893. display: inline-flex;
  894. line-height: 32px;
  895. box-sizing: border-box;
  896. vertical-align: middle;
  897. -webkit-appearance: none;
  898. /* color: #606266; */
  899. padding: 0;
  900. outline: none;
  901. border: none;
  902. background: none;
  903. flex-grow: 1;
  904. align-items: center;
  905. justify-content: center;
  906. padding: 1px 11px;
  907. background-color: #ffffff;
  908. background-image: none;
  909. border-radius: 4px;
  910. cursor: text;
  911. transition: box-shadow .2s cubic-bezier(.645, .045, .355, 1);
  912. transform: translateZ(0);
  913. box-shadow: 0 0 0 1px #dcdfe6 inset;
  914.  
  915. width: 100%;
  916. width: -moz-available;
  917. width: -webkit-fill-available;
  918. width: fill-available;
  919.  
  920. }
  921. input[type="url"]::placeholder {
  922. color: #a8abb2;
  923. }
  924. input[type="url"]:hover {
  925. box-shadow: 0 0 0 1px #c0c4cc inset;
  926. }
  927. input[type="url"]:focus {
  928. box-shadow: 0 0 0 1px #409eff inset;
  929. }
  930. `;
  931. let beautifyRadioCSS = `
  932. label.radio-label {
  933. font-weight: 500;
  934. position: relative;
  935. cursor: pointer;
  936. display: inline-flex;
  937. align-items: center;
  938. white-space: nowrap;
  939. outline: none;
  940. font-size: 14px;
  941. user-select: none;
  942. margin-right: 32px;
  943. height: 32px;
  944. padding: 4px;
  945. border-radius: 4px;
  946. box-sizing: border-box;
  947. }
  948. label:has(input[type=radio]:checked),
  949. label:has(input[type=radio]:checked) a{
  950. color: #409eff;
  951. }
  952. label.radio-label input[type="radio"]{
  953. margin-right: 4px;
  954. width: 14px;
  955. height: 14px;
  956. }
  957. label.radio-label input[type="radio"]:checked{
  958. -webkit-appearance: none;
  959. -moz-appearance: none;
  960. appearance: none;
  961. border-radius: 50%;
  962. width: 14px;
  963. height: 14px;
  964. outline: none;
  965. border: 4px solid #409eff;
  966. cursor: pointer;
  967. }
  968. label.radio-label input[type="radio"]:checked + span{
  969. color: #409eff;
  970. }
  971. `;
  972. let beautifyTextAreaCSS = `
  973. textarea {
  974. position: relative;
  975. display: inline-block;
  976. width: 100%;
  977. vertical-align: bottom;
  978. font-size: 14px;
  979. position: relative;
  980. display: block;
  981. resize: vertical;
  982. padding: 5px 11px;
  983. line-height: 1.5;
  984. box-sizing: border-box;
  985. width: 100%;
  986. font-size: inherit;
  987. font-family: inherit;
  988. /* color: #606266; */
  989. background-color: #ffffff;
  990. background-image: none;
  991. -webkit-appearance: none;
  992. box-shadow: 0 0 0 1px #dcdfe6 inset;
  993. border-radius: 4px;
  994. transition: box-shadow .2s cubic-bezier(.645, .045, .355, 1);
  995. border: none;
  996. }
  997. textarea:focus{
  998. outline: none;
  999. box-shadow: 0 0 0 1px #409eff inset;
  1000. }
  1001. `;
  1002. /**
  1003. * 未派上用场的CSS
  1004. */
  1005. let notUseBeautifyCSS = `
  1006. .token.blockquote,.token.comment{color:#969896}
  1007. .token.cdata{color:#183691}
  1008. .token.doctype,.token.macro.property,.token.punctuation,.token.variable{color:#333}
  1009. .token.builtin,.token.important,.token.keyword,.token.operator,.token.rule{color:#a71d5d}
  1010. .token.attr-value,.token.regex,.token.string,.token.url{color:#183691}
  1011. .token.atrule,.token.boolean,.token.code,.token.command,.token.constant,.token.entity,.token.number,.token.property,.token.symbol{color:#0086b3}
  1012. .token.prolog,.token.selector,.token.tag{color:#63a35c}
  1013. .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}
  1014. .token.entity{cursor:help}
  1015. .token.title,.token.title .token.punctuation{font-weight:700;color:#1d3e81}
  1016. .token.list{color:#ed6a43}
  1017. .token.inserted{background-color:#eaffea;color:#55a532}
  1018. .token.deleted{background-color:#ffecec;color:#bd2c00}
  1019. .token.bold{font-weight:700}
  1020. .token.italic{font-style:italic}
  1021. .language-json .token.property{color:#183691}
  1022. .language-markup .token.tag .token.punctuation{color:#333}
  1023. .language-css .token.function,code.language-css{color:#0086b3}
  1024. .language-yaml .token.atrule{color:#63a35c}
  1025. code.language-yaml{color:#183691}
  1026. .language-ruby .token.function{color:#333}
  1027. .language-markdown .token.url{color:#795da3}
  1028. .language-makefile .token.symbol{color:#795da3}
  1029. .language-makefile .token.variable{color:#183691}
  1030. .language-makefile .token.builtin{color:#0086b3}
  1031. .language-bash .token.keyword{color:#0086b3}
  1032. pre[data-line]{position:relative;padding:1em 0 1em 3em}
  1033. pre[data-line] .line-highlight-wrapper{position:absolute;top:0;left:0;background-color:transparent;display:block;width:100%}
  1034. 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}
  1035. 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}
  1036. pre[data-line] .line-highlight[data-end]:after{content:attr(data-end);top:auto;bottom:.4em}
  1037. `;
  1038. GM_addStyle(beautifyMarkdownCSS);
  1039. GM_addStyle(beautifyButtonCSS);
  1040. GM_addStyle(beautifyRadioCSS);
  1041. GM_addStyle(beautifyTextAreaCSS);
  1042. DOMUtils.ready(function () {
  1043. let markupChoiceELement = document.querySelector(
  1044. 'a[target="markup_choice"][href*="daringfireball.net"]'
  1045. );
  1046. if (markupChoiceELement) {
  1047. markupChoiceELement.parentElement.replaceChild(
  1048. DOMUtils.createElement("span", {
  1049. textContent: "Markdown",
  1050. }),
  1051. markupChoiceELement
  1052. );
  1053. }
  1054. });
  1055. },
  1056. /**
  1057. * 美化 Greasyfork Beautify脚本
  1058. */
  1059. beautifyGreasyforkBeautify() {
  1060. if (!GreasyforkMenu.menu.get("beautifyGreasyforkBeautify")) {
  1061. return;
  1062. }
  1063. log.success(
  1064. GreasyforkMenu.menu.getShowTextValue("beautifyGreasyforkBeautify")
  1065. );
  1066. let compatibleBeautifyCSS = `
  1067. #main-header{
  1068. background-color: #670000 !important;
  1069. background-image: linear-gradient(#670000,#990000) !important;
  1070. }
  1071. #site-nav-vue{
  1072. flex-wrap: wrap;
  1073. justify-content: flex-end;
  1074. }
  1075. .open-sidebar{
  1076. border-width: 1px;
  1077. border-radius: 3px;
  1078. margin-right: 0;
  1079. }
  1080. input.search-submit{
  1081. transform: translateY(-5%) !important;
  1082. margin-left: 10px;
  1083. }
  1084. `;
  1085.  
  1086. GM_addStyle(compatibleBeautifyCSS);
  1087. if (utils.isPhone()) {
  1088. GM_addStyle(`
  1089. section#script-info,
  1090. section.text-content,
  1091. div.width-constraint table.text-content.log-table{
  1092. margin-top: 80px;
  1093. }
  1094. div.width-constraint div.sidebarred{
  1095. padding-top: 80px;
  1096. }
  1097. div.width-constraint div.sidebarred .sidebar{
  1098. top: 80px;
  1099. }`);
  1100. } else {
  1101. GM_addStyle(`
  1102. section#script-info{
  1103. margin-top: 10px;
  1104. }`);
  1105. }
  1106. },
  1107. /**
  1108. * 美化上传图片
  1109. */
  1110. beautifyUploadImage() {
  1111. if (!GreasyforkMenu.menu.get("beautifyUploadImage")) {
  1112. return;
  1113. }
  1114. log.success(GreasyforkMenu.menu.getShowTextValue("beautifyUploadImage"));
  1115. let beautifyCSS = `
  1116. /* 隐藏 添加: */
  1117. label[for="discussion_comments_attributes_0_attachments"],
  1118. label[for="comment_attachments"]{
  1119. display: none;
  1120. }
  1121. input[type="file"]{
  1122. width: 100%;
  1123. font-size: 20px;
  1124. background: #e2e2e2;
  1125. padding: 40px 20px;
  1126. border-radius: 10px;
  1127. text-align-last: center;
  1128. }
  1129. `;
  1130. GM_addStyle(beautifyCSS);
  1131. DOMUtils.ready(function () {
  1132. let clearErrorTip = function () {
  1133. while (fileElement.nextElementSibling) {
  1134. fileElement.parentElement.removeChild(
  1135. fileElement.nextElementSibling
  1136. );
  1137. }
  1138. };
  1139. let fileElement = document.querySelector('input[type="file"]');
  1140. DOMUtils.on(fileElement, "change", function (event) {
  1141. clearErrorTip();
  1142. /**
  1143. * @type {File[]}
  1144. */
  1145. let chooseImageFiles = event.currentTarget.files;
  1146. if (chooseImageFiles.length === 0) {
  1147. return;
  1148. }
  1149. log.info(["选择的图片", chooseImageFiles]);
  1150. if (chooseImageFiles.length > 5) {
  1151. DOMUtils.after(
  1152. fileElement,
  1153. DOMUtils.createElement("p", {
  1154. textContent: `❌ 最多同时长传5张图片`,
  1155. })
  1156. );
  1157. }
  1158. /**
  1159. * @type {File[]}
  1160. */
  1161. let notAllowImage = [];
  1162. Array.from(chooseImageFiles).forEach((imageFile) => {
  1163. if (
  1164. imageFile.size > 204800 ||
  1165. !imageFile.type.match(/png|gif|jpeg|webp/i)
  1166. ) {
  1167. notAllowImage.push(imageFile);
  1168. }
  1169. });
  1170. if (notAllowImage.length === 0) {
  1171. return;
  1172. }
  1173. notAllowImage.forEach((imageFile) => {
  1174. DOMUtils.after(
  1175. fileElement,
  1176. DOMUtils.createElement("p", {
  1177. textContent: `❌ 图片:${
  1178. imageFile.name
  1179. } 大小:${utils.formatByteToSize(imageFile.size)}`,
  1180. })
  1181. );
  1182. });
  1183. });
  1184. });
  1185. },
  1186. /**
  1187. * 添加复制代码按钮
  1188. */
  1189. addCopyCodeButton() {
  1190. if (!window.location.pathname.endsWith("/code")) {
  1191. return;
  1192. }
  1193. if (!GreasyforkMenu.menu.get("addCopyCodeButton")) {
  1194. return;
  1195. }
  1196. log.success(GreasyforkMenu.menu.getShowTextValue("addCopyCodeButton"));
  1197. utils
  1198. .waitNode("div#script-content div.code-container")
  1199. .then((element) => {
  1200. let copyButton = DOMUtils.createElement("button", {
  1201. style: "margin-bottom: 1em;",
  1202. textContent: "复制代码",
  1203. });
  1204. DOMUtils.on(copyButton, "click", async function () {
  1205. let loading = Qmsg.loading("加载文件中...");
  1206. let getResp = await httpx.get(
  1207. `https://greasyfork.org/zh-CN/scripts/${GreasyforkApi.getScriptId()}.json`,
  1208. {
  1209. responseType: "json",
  1210. }
  1211. );
  1212. if (!getResp.status) {
  1213. loading.close();
  1214. return;
  1215. }
  1216. let respJSON = utils.toJSON(getResp.data.responseText);
  1217. let code_url = respJSON["code_url"];
  1218. log.success(["代码地址:", code_url]);
  1219. let scriptJS = await httpx.get(code_url);
  1220. if (!scriptJS.status) {
  1221. loading.close();
  1222. return;
  1223. }
  1224. loading.close();
  1225. utils.setClip(scriptJS.data.responseText);
  1226. Qmsg.success("复制成功");
  1227. });
  1228. DOMUtils.before(element, copyButton);
  1229. });
  1230. },
  1231. /**
  1232. * 在Markdown右上角添加复制按钮
  1233. */
  1234. addMarkdownCopyButton() {
  1235. GM_addStyle(`
  1236. pre{
  1237. position: relative;
  1238. }
  1239. `);
  1240. GM_addStyle(`
  1241. .zeroclipboard-container {
  1242. right: 0;
  1243. top: 0;
  1244. position: absolute;
  1245. box-sizing: border-box;
  1246. display: flex;
  1247. font-size: 16px;
  1248. line-height: 24px;
  1249. text-size-adjust: 100%;
  1250. overflow-wrap: break-word;
  1251. }
  1252. .zeroclipboard-container svg{
  1253. vertical-align: text-bottom;
  1254. display: inline-block;
  1255. overflow: visible;
  1256. fill: currentColor;
  1257. margin: 8px;
  1258. }
  1259. .zeroclipboard-container svg[aria-hidden="true"]{
  1260. display: none;
  1261. }
  1262. clipboard-copy.js-clipboard-copy {
  1263. position: relative;
  1264. padding: 0px;
  1265. color: rgb(36, 41, 47);
  1266. background-color: rgb(246, 248, 250);
  1267. transition: 80ms cubic-bezier(0.33, 1, 0.68, 1);
  1268. transition-property: color,background-color,box-shadow,border-color;
  1269. display: inline-block;
  1270. font-size: 14px;
  1271. line-height: 20px;
  1272. white-space: nowrap;
  1273. vertical-align: middle;
  1274. cursor: pointer;
  1275. -webkit-user-select: none;
  1276. user-select: none;
  1277. border: 1px solid rgba(31, 35, 40, 0.15);
  1278. -webkit-appearance: none;
  1279. appearance: none;
  1280. box-shadow: rgba(31, 35, 40, 0.04) 0px 1px 0px 0px, rgba(255, 255, 255, 0.25) 0px 1px 0px 0px inset;
  1281. margin: 8px;
  1282. overflow-wrap: break-word;
  1283. text-wrap: nowrap;
  1284. border-radius: 6px;
  1285. }
  1286. clipboard-copy.js-clipboard-copy[success]{
  1287. border-color: rgb(31, 136, 61);
  1288. box-shadow: 0 0 0 0.2em rgba(52,208,88,.4);
  1289. }
  1290. clipboard-copy.js-clipboard-copy:hover{
  1291. background-color: rgb(243, 244, 246);
  1292. border-color: rgba(31, 35, 40, 0.15);
  1293. transition-duration: .1s;
  1294. }
  1295. clipboard-copy.js-clipboard-copy:active{
  1296. background-color: rgb(235, 236, 240);
  1297. border-color: rgba(31, 35, 40, 0.15);
  1298. transition: none;
  1299. }
  1300. `);
  1301. GM_addStyle(`
  1302. .pops-tip.github-tooltip {
  1303. border-radius: 6px;
  1304. padding: 6px 8px;
  1305. }
  1306. .pops-tip.github-tooltip, .pops-tip.github-tooltip .pops-tip-arrow::after {
  1307. background: rgb(36, 41, 47);
  1308. color: #fff;
  1309. }
  1310. .pops-tip.github-tooltip .pops-tip-arrow::after {
  1311. width: 8px;
  1312. height: 8px;
  1313. }
  1314. `);
  1315. let copyElement = DOMUtils.createElement("div", {
  1316. className: "zeroclipboard-container",
  1317. innerHTML: `
  1318. <clipboard-copy class="js-clipboard-copy">
  1319. <svg height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon-copy">
  1320. <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
  1321. </svg>
  1322. <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon-check-copy">
  1323. <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
  1324. </svg>
  1325. </clipboard-copy>
  1326. `,
  1327. });
  1328. let clipboardCopyElement =
  1329. copyElement.querySelector(".js-clipboard-copy");
  1330. let octiconCopyElement = copyElement.querySelector(".octicon-copy");
  1331. let octiconCheckCopyElement = copyElement.querySelector(
  1332. ".octicon-check-copy"
  1333. );
  1334. DOMUtils.on(copyElement, "click", function () {
  1335. let codeElement = copyElement.parentElement.querySelector("code");
  1336. if (!codeElement) {
  1337. Qmsg.error("未找到code元素");
  1338. return;
  1339. }
  1340. utils.setClip(codeElement.innerText);
  1341. clipboardCopyElement.setAttribute("success", "true");
  1342. octiconCopyElement.setAttribute("aria-hidden", true);
  1343. octiconCheckCopyElement.removeAttribute("aria-hidden");
  1344. let tooltip = pops.tooltip({
  1345. target: clipboardCopyElement,
  1346. content: "复制成功!",
  1347. position: "left",
  1348. className: "github-tooltip",
  1349. alwaysShow: true,
  1350. otherDistance: -8,
  1351. });
  1352. setTimeout(() => {
  1353. clipboardCopyElement.removeAttribute("success");
  1354. octiconCheckCopyElement.setAttribute("aria-hidden", true);
  1355. octiconCopyElement.removeAttribute("aria-hidden");
  1356. tooltip.close();
  1357. }, 2000);
  1358. });
  1359. document.querySelectorAll("pre").forEach((preElement) => {
  1360. if (preElement.querySelector("clipboard-copy.js-clipboard-copy")) {
  1361. return;
  1362. }
  1363. preElement.appendChild(copyElement);
  1364. });
  1365. },
  1366. };
  1367. /* -----------------↑函数区域↑----------------- */
  1368.  
  1369. /* -----------------↓执行入口↓----------------- */
  1370. GreasyforkMenu.init();
  1371. GreasyforkBusiness.beautifyPage();
  1372. GreasyforkBusiness.beautifyGreasyforkBeautify();
  1373. GreasyforkBusiness.beautifyUploadImage();
  1374. DOMUtils.ready(function () {
  1375. GreasyforkMenu.initEnv();
  1376. if (GreasyforkMenu.menu.get("autoLogin")) {
  1377. GreasyforkBusiness.autoLogin();
  1378. }
  1379. GreasyforkMenu.handleLocalGotoCallBack();
  1380. GreasyforkBusiness.setFindCodeSearchBtn();
  1381. GreasyforkBusiness.repairImgShow();
  1382. GreasyforkBusiness.repairCodeLineNumber();
  1383. GreasyforkBusiness.optimizeImageBrowsing();
  1384. GreasyforkBusiness.scriptHomepageAddedTodaySUpdate();
  1385. GreasyforkBusiness.addCopyCodeButton();
  1386. GreasyforkBusiness.addMarkdownCopyButton();
  1387. });
  1388. /* -----------------↑执行入口↑----------------- */
  1389. })();