GitHub镜像

GitHub镜像,加速访问GitHub,支持Clone、Release、Raw、Zip加速。

  1. // ==UserScript==
  2. // @name GitHub镜像
  3. // @name:en GitHub Mirror
  4. // @description GitHub镜像,加速访问GitHub,支持Clone、Release、Raw、Zip加速。
  5. // @description:en GitHub mirror. Accelerate access to GitHub. Support Clone, Release, RAW and ZIP acceleration.
  6. // @namespace https://github.com/HaleShaw
  7. // @version 1.4.0
  8. // @author HaleShaw
  9. // @copyright 2021+, HaleShaw (https://github.com/HaleShaw)
  10. // @license AGPL-3.0-or-later
  11. // @homepage https://github.com/HaleShaw/TM-GitHubMirror
  12. // @supportURL https://github.com/HaleShaw/TM-GitHubMirror/issues
  13. // @contributionURL https://www.jianwudao.com/
  14. // @icon https://github.githubassets.com/favicon.ico
  15. // @require https://cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js
  16. // @include *://github.com/*
  17. // @compatible Chrome
  18. // @run-at document-end
  19. // @grant GM_addStyle
  20. // @grant GM_getValue
  21. // @grant GM_setValue
  22. // ==/UserScript==
  23.  
  24. // ==OpenUserJS==
  25. // @author HaleShaw
  26. // @collaborator HaleShaw
  27. // ==/OpenUserJS==
  28.  
  29. (function () {
  30. ("use strict");
  31.  
  32. const style = `
  33. /* The menu container */
  34. .menuContainer {
  35. width: 600px;
  36. }
  37.  
  38. .menuBlock {
  39. padding: 4px 0;
  40. color: #990000;
  41. }
  42.  
  43. .menuLeftIcon{
  44. margin-right:5px;
  45. }
  46.  
  47. .menuButtonLabel{
  48. margin-right: 2rem;
  49. }
  50.  
  51. .menuButtonCheck{
  52. vertical-align: text-bottom;
  53. margin: 0 3px;
  54. }
  55.  
  56. .SelectMenu-list {
  57. padding: 0 16px;
  58. }
  59.  
  60. .SelectMenu-list > a.SelectMenu-item {
  61. padding-left: 0;
  62. padding-right: 0;
  63. margin-top: 4px;
  64. }
  65.  
  66. .clone {
  67. padding-left: 3px 12px !important;
  68. }
  69.  
  70. .Box-body.download-box {
  71. border-bottom: none;
  72. width: 100%;
  73. text-align: right;
  74. padding: unset;
  75. }
  76.  
  77. .Box-body.download-box > a {
  78. font-size: 11px;
  79. margin: 0 3px;
  80. padding: 0 6px;
  81. }
  82. `;
  83.  
  84. const mirrors = [
  85. {
  86. id: 0,
  87. name: "CnpmJS",
  88. url: "https://github.com.cnpmjs.org",
  89. description: "cnpmjs.org",
  90. },
  91. {
  92. id: 1,
  93. name: "FastGit",
  94. url: "https://hub.fastgit.org",
  95. description: "KevinZonda",
  96. },
  97. {
  98. id: 2,
  99. name: "FastGit",
  100. url: "https://download.fastgit.org",
  101. description: "KevinZonda",
  102. },
  103. {
  104. id: 3,
  105. name: "FastGit",
  106. url: "https://raw.fastgit.org",
  107. description: "KevinZonda",
  108. },
  109. {
  110. id: 4,
  111. name: "WuYanZheShui",
  112. url: "https://github.wuyanzheshui.workers.dev",
  113. description: "WuYanZheShui. Maximum of 100,000 calls per day",
  114. },
  115. {
  116. id: 5,
  117. name: "RC1844",
  118. url: "https://github.rc1844.workers.dev",
  119. description: "RC1844. Maximum of 100,000 calls per day",
  120. },
  121. {
  122. id: 6,
  123. name: "jsDelivr",
  124. url: "https://cdn.jsdelivr.net/gh",
  125. description: "The total file size of the current branch of the project cannot exceed 50MB",
  126. },
  127. {
  128. id: 7,
  129. name: "IAPK",
  130. url: "https://github.iapk.cc",
  131. description: "IAPK",
  132. },
  133. {
  134. id: 8,
  135. name: "Ecalose",
  136. url: "https://gh.haval.gq",
  137. description: "Ecalose. Maximum of 100,000 calls per day",
  138. },
  139. {
  140. id: 9,
  141. name: "IAPK",
  142. url: "https://iapk.cc/github?url=https://github.com",
  143. description: "IAPK",
  144. },
  145. {
  146. id: 10,
  147. name: "Statically",
  148. url: "https://cdn.staticaly.com/gh",
  149. description:
  150. "Only images and source code files are supported, and the file size is limited to 30MB",
  151. },
  152. {
  153. id: 11,
  154. name: "Github 原生",
  155. url: "ssh://git@ssh.github.com:443/",
  156. description: "Github 官方提供的 443 端口的 SSH,适用于限制访问 22 端口的网络环境",
  157. },
  158. {
  159. id: 12,
  160. name: "FastGit",
  161. url: "git@ssh.fastgit.org:",
  162. description: "FastGit 香港",
  163. },
  164. ];
  165.  
  166. //添加对应索引即可使用
  167. const cloneSet = [0, 1, 4];
  168. const sshSet = [11, 12];
  169. const browseSet = [0, 1, 4, 5, 7, 8];
  170. const downloadSet = [2, 4, 5, 8, 9];
  171. const rawSet = [3, 4, 5, 6, 8, 9, 10];
  172.  
  173. const messages = {
  174. en: {
  175. menuButton: {
  176. name: "CloneMirror",
  177. title: "Open List",
  178. header: "Quickly clone and Mirror sites",
  179. block:
  180. "Please do not login in the mirror site. I will not be responsible for any loss caused by this.",
  181. },
  182. },
  183. zh: {
  184. menuButton: {
  185. name: "克隆与镜像",
  186. title: "打开列表",
  187. header: "快速克隆与镜像站点",
  188. block: "请不要在镜像网站登录(不可用)账号,若因此造成任何损失本人概不负责",
  189. },
  190. },
  191. };
  192.  
  193. const icons = {
  194. closeIcon: `
  195. <svg aria-label="Close menu" class="octicon octicon-x" width="16" height="16" role="img">
  196. <path fill-rule="evenodd" d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z"></path>
  197. </svg>`,
  198. copyIcon: `
  199. <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon d-inline-block">
  200. <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>
  201. </svg>`,
  202. copiedIcon: `
  203. <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-inline-block d-sm-none">
  204. <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>
  205. </svg>`,
  206. };
  207.  
  208. const clonePrefix = "git clone ";
  209. const depthPrefix = "--depth=1 ";
  210. let message;
  211. let settingHtml;
  212.  
  213. main();
  214. $(document).on("pjax:success", function () {
  215. $("#mirror-menu").remove();
  216. main();
  217. });
  218.  
  219. function main() {
  220. GM_addStyle(style);
  221. logInfo(GM_info.script.name, GM_info.script.version);
  222. const prefix = getClonePrefix();
  223. addMenu(prefix);
  224. setTimeout(() => {
  225. addHttpsClone(prefix);
  226. addSSHClone(prefix);
  227. addRawList();
  228. }, 1000);
  229. if (isPC()) {
  230. addDownloadZip();
  231. }
  232.  
  233. // The Release page loads element dynamically.
  234. const callback = (mutationsList, observer) => {
  235. if (location.pathname.indexOf("/releases") === -1) return;
  236. for (const mutation of mutationsList) {
  237. for (const target of mutation.addedNodes) {
  238. if (target.nodeType !== 1) return;
  239. if (
  240. target.tagName === "DIV" &&
  241. target.dataset.viewComponent === "true" &&
  242. target.classList[0] === "Box"
  243. )
  244. addReleasesList();
  245. }
  246. }
  247. };
  248. const observer = new MutationObserver(callback);
  249. observer.observe(document, { childList: true, subtree: true });
  250. }
  251.  
  252. /**
  253. * Initialize setting.
  254. */
  255. function initSetting() {
  256. let lang = GM_getValue("lang");
  257. let clone = GM_getValue("clone");
  258. let depth = GM_getValue("depth");
  259. if (lang == undefined) {
  260. GM_setValue("lang", "zh");
  261. }
  262. if (clone == undefined) {
  263. GM_setValue("clone", true);
  264. }
  265. if (depth == undefined) {
  266. GM_setValue("depth", true);
  267. }
  268. }
  269.  
  270. function addMenu(prefix) {
  271. initSetting();
  272. message = getMessage(true, true);
  273. settingHtml = getSettingHtml();
  274. let menuButtonHtml =
  275. getMenuButtonPrefix() + getCloneList(prefix) + getBrowseList() + getMenuButtonSuffix();
  276. $("div.d-flex.flex-wrap.flex-items-center.wb-break-word.f3.text-normal").append(menuButtonHtml);
  277. }
  278.  
  279. function getMenuButtonPrefix() {
  280. return `
  281. <details class="details-reset details-overlay mr-0 mb-0" id="mirror-menu">
  282. <summary class="btn ml-2 btn-primary" id="menuButtonTitle" data-hotkey="m" title="${message.menuButton.title}" aria-haspopup="menu" role="button">
  283. <span class="css-truncate-target" id="menuButtonName" data-menu-button="">${message.menuButton.name}</span>
  284. <span class="dropdown-caret"></span>
  285. </summary>
  286.  
  287. <details-menu class="SelectMenu SelectMenu--hasFilter" role="menu">
  288. <div class="SelectMenu-modal menuContainer">
  289. <header class="SelectMenu-header">
  290. <span class="SelectMenu-title" id="menuButtonHeader">${message.menuButton.header}</span>
  291. ${settingHtml}
  292. <button class="SelectMenu-closeButton" type="button" data-toggle-for="mirror-menu">
  293. ${icons.closeIcon}
  294. </button>
  295. </header>
  296. <tab-container class="d-flex flex-column js-branches-tags-tabs" style="min-height: 0;">
  297. <div role="tabpanel" class="d-flex flex-column flex-auto" tabindex="0">
  298. <div class="btn-block flash-error menuBlock" id="menuButtonBlock" role="alert">
  299. ${message.menuButton.block}
  300. </div>
  301. <div class="SelectMenu-list" data-filter-list="">`;
  302. }
  303.  
  304. function getSettingHtml() {
  305. const clone = GM_getValue("clone");
  306. const depth = GM_getValue("depth");
  307. const lang = GM_getValue("lang");
  308. const cloneStatus = clone && clone != "undefined" ? " checked" : "";
  309. const depthStatus = depth && depth != "undefined" ? " checked" : "";
  310. const langStatus = lang == "en" ? " checked" : "";
  311. return `
  312. <label class="menuButtonLabel"><input id="menuButtonClone" class="menuButtonCheck" type="checkbox"${cloneStatus}>Clone</input></label>
  313. <label class="menuButtonLabel"><input id="menuButtonDepth" class="menuButtonCheck" type="checkbox"${depthStatus}>Depth</input></label>
  314. <label class="menuButtonLabel"><input id="menuButtonLang" class="menuButtonCheck" type="checkbox"${langStatus}>English</input></label>
  315. `;
  316. }
  317.  
  318. /**
  319. * Clone Checkbox event.
  320. */
  321. $("#menuButtonClone").change(function () {
  322. const status = $("#menuButtonClone").is(":checked");
  323. GM_setValue("clone", status);
  324. if (!status) {
  325. document.getElementById("menuButtonDepth").checked = false;
  326. depthChanged(status);
  327. }
  328. cloneChanged(status);
  329. });
  330.  
  331. /**
  332. * Depth Checkbox event.
  333. */
  334. $("#menuButtonDepth").change(function () {
  335. const status = $("#menuButtonDepth").is(":checked");
  336. depthChanged(status);
  337. console.log(status);
  338. let cloneStatus = $("#menuButtonClone").is(":checked");
  339. if (status && !cloneStatus) {
  340. cloneStatus = true;
  341. document.getElementById("menuButtonClone").checked = true;
  342. GM_setValue("clone", cloneStatus);
  343. cloneChanged(cloneStatus);
  344. }
  345. });
  346.  
  347. /**
  348. * Language Checkbox event.
  349. */
  350. $("#menuButtonLang").change(function () {
  351. const status = $("#menuButtonLang").is(":checked");
  352. const value = status ? "en" : "zh";
  353. GM_setValue("lang", value);
  354. message = getMessage();
  355. updateMessage();
  356. });
  357.  
  358. function cloneChanged(status) {
  359. const inputs = $("input.clone");
  360. for (let i = 0; i < inputs.length; i++) {
  361. let value = inputs[i].value;
  362. if (status) {
  363. value = clonePrefix + value;
  364. } else {
  365. value = value.replace(clonePrefix, "");
  366. }
  367. inputs[i].value = value;
  368. $(inputs[i]).next().children().attr("value", value);
  369. }
  370. }
  371.  
  372. function depthChanged(status) {
  373. GM_setValue("depth", status);
  374. const inputs = $(".form-control.input-monospace.input-sm.clone");
  375. const index = clonePrefix.length;
  376. for (let i = 0; i < inputs.length; i++) {
  377. let value = inputs[i].value;
  378. if (status) {
  379. const length = value.length;
  380. if (value.startsWith(clonePrefix)) {
  381. value = value.slice(0, index) + depthPrefix + value.slice(index, length);
  382. } else {
  383. value = depthPrefix + value;
  384. }
  385. } else {
  386. value = value.replace(depthPrefix, "");
  387. }
  388. inputs[i].value = value;
  389. $(inputs[i]).next().children().attr("value", value);
  390. }
  391. }
  392.  
  393. /**
  394. * Update message by target language.
  395. */
  396. function updateMessage() {
  397. $("#menuButtonTitle").attr("title", message.menuButton.title);
  398. $("#menuButtonName").html(message.menuButton.name);
  399. $("#menuButtonHeader").html(message.menuButton.header);
  400. $("#menuButtonBlock").html(message.menuButton.block);
  401. }
  402.  
  403. function addHttpsClone(prefix) {
  404. let httpsGroup = document.querySelector('[role="tabpanel"]:nth-child(2) div.input-group');
  405. if (!httpsGroup) {
  406. return;
  407. }
  408. let inputs = httpsGroup.querySelectorAll("input.clone");
  409. if (inputs.length > 0) {
  410. return;
  411. }
  412. updateDefaultClone(prefix, httpsGroup);
  413. httpsGroup.insertAdjacentHTML("afterend", getCloneList(prefix));
  414. }
  415.  
  416. /**
  417. * Get the clone list.
  418. */
  419. function getCloneList(prefix) {
  420. const href = window.location.href.split("/");
  421. const git = href[3] + "/" + href[4] + ".git";
  422. let menuButtonHtml = "";
  423. cloneSet.forEach(id => {
  424. menuButtonHtml += getCloneHtml(prefix + mirrors[id]["url"] + "/" + git, mirrors[id]["name"]);
  425. });
  426. return menuButtonHtml;
  427. }
  428.  
  429. function updateDefaultClone(prefix, parent) {
  430. let input = parent.querySelector("input");
  431. gitStr = input.value;
  432. gitNew = prefix + gitStr;
  433. let button = parent.querySelector("clipboard-copy");
  434. input.setAttribute("value", gitNew);
  435. input.setAttribute("aria-label", gitNew);
  436. input.className += " clone";
  437. button.setAttribute("value", gitNew);
  438. }
  439.  
  440. function getMenuButtonSuffix() {
  441. return `</div></div></tab-container></div></details-menu></details>`;
  442. }
  443.  
  444. function addSSHClone(prefix) {
  445. let sshGroup = document.querySelector('[role="tabpanel"]:nth-child(3) div.input-group');
  446. if (!sshGroup) {
  447. return;
  448. }
  449.  
  450. let inputs = sshGroup.querySelectorAll("input.clone");
  451. if (inputs.length > 0) {
  452. return;
  453. }
  454.  
  455. let defaultSsh = sshGroup.firstElementChild;
  456. const sshStr = defaultSsh.value;
  457. let hrefSplit = sshStr.split(":");
  458. let groupHtml = "";
  459.  
  460. if (hrefSplit[0] != "git@github.com") {
  461. return;
  462. }
  463. defaultSsh.value = prefix + sshStr;
  464. defaultSsh.setAttribute("aria-label", prefix + sshStr);
  465. defaultSsh.className += " clone";
  466.  
  467. let button = sshGroup.querySelector("clipboard-copy");
  468. button.setAttribute("value", prefix + sshStr);
  469.  
  470. sshSet.forEach(id => {
  471. groupHtml += getCloneHtml(prefix + mirrors[id]["url"] + hrefSplit[1], mirrors[id]["name"]);
  472. });
  473. sshGroup.insertAdjacentHTML("afterend", groupHtml);
  474. }
  475.  
  476. /**
  477. * Get the clone command prefix.
  478. */
  479. function getClonePrefix() {
  480. let prefix = "";
  481. let clone = GM_getValue("clone");
  482. let depth = GM_getValue("depth");
  483. if (clone) {
  484. prefix += "git clone ";
  485. }
  486. if (depth) {
  487. prefix += "--depth=1 ";
  488. }
  489. return prefix;
  490. }
  491.  
  492. /**
  493. * Get the clone button html string.
  494. * @param {String} url url.
  495. * @param {tip} tip tip.
  496. */
  497. function getCloneHtml(url, tip) {
  498. return `
  499. <div class="input-group" style="margin-top: 4px;" title="${tip}">
  500. <input type="text" class="clone form-control input-monospace input-sm color-bg-subtle" data-autoselect="" value="${url}" aria-label="${url}" readonly="">
  501. <div class="input-group-button">
  502. <clipboard-copy value="${url}" class="btn btn-sm js-clipboard-copy tooltipped-no-delay ClipboardButton" tabindex="0" role="button">
  503. ${icons.copyIcon}${icons.copiedIcon}
  504. </clipboard-copy>
  505. </div>
  506. </div>`;
  507. }
  508.  
  509. /**
  510. * Get the browse list.
  511. */
  512. function getBrowseList() {
  513. let menuButtonHtml = ``;
  514. const href = window.location.href.split("/");
  515. const path = window.location.pathname;
  516. browseSet.forEach(id => {
  517. menuButtonHtml += getBrowseHtml(
  518. mirrors[id]["url"] + path,
  519. mirrors[id]["name"],
  520. mirrors[id]["description"]
  521. );
  522. });
  523. if (href.length == 5 || path.includes("/tree/") || path.includes("/blob/")) {
  524. var html = mirrors[5]["url"] + path.replace("/tree/", "@").replace("/blob/", "@");
  525. if (!path.includes("/blob/")) {
  526. html += "/";
  527. }
  528. menuButtonHtml += getBrowseHtml(html, mirrors[5]["name"], mirrors[5]["description"]);
  529. }
  530. if (location.hostname != "github.com") {
  531. menuButtonHtml += getBrowseHtml(`https://github.com${path}`, "返回GitHub");
  532. }
  533. return menuButtonHtml;
  534. }
  535.  
  536. /**
  537. * Get browse html string.
  538. * @param {String} url url.
  539. * @param {String} name name.
  540. * @param {String} tip tip.
  541. * @returns
  542. */
  543. function getBrowseHtml(url, name, tip = "") {
  544. return `
  545. <a class="SelectMenu-item" href="${url}" target="_blank" title="${tip}" role="menuitemradio" aria-checked="false" rel="nofollow">
  546. <span class="css-truncate css-truncate-overflow" style="width: 520px; overflow: hidden; word-break:keep-all; white-space:nowrap; text-overflow:ellipsis;">${url}</span>
  547. <span class="css-truncate css-truncate-overflow" style="width: 80px; text-align: right;">${name}</span>
  548. </a>`;
  549. }
  550.  
  551. /**
  552. * Add Release list.
  553. */
  554. function addReleasesList() {
  555. $(".Box--condensed")
  556. .find("[href]")
  557. .each(function () {
  558. if ($(this).parent().parent().find(".download-box").length == 0) {
  559. const href = $(this).attr("href");
  560. $(this)
  561. .parent()
  562. .after(`<div class="Box-body download-box" >` + getReleaseDownloadHtml(href) + `</div>`);
  563. $(this).parent().removeClass("Box-body");
  564. }
  565. });
  566. }
  567.  
  568. /**
  569. * Get Release download button html string.
  570. * @param {String} href href.
  571. * @returns html.
  572. */
  573. function getReleaseDownloadHtml(href) {
  574. let html = "";
  575. downloadSet.forEach(id => {
  576. html += `<a class="flex-1 btn btn-outline get-repo-btn" rel="nofollow" href="${mirrors[id]["url"] + href
  577. }" title="${mirrors[id]["description"]}">${mirrors[id]["name"]}</a>`;
  578. });
  579. return html;
  580. }
  581.  
  582. /**
  583. * Add download zip button.
  584. */
  585. function addDownloadZip() {
  586. $("a[data-open-app='link']").each(function () {
  587. var li = $(`<li class="Box-row p-0"></li>`);
  588. const downloadHref = $(this).attr("href");
  589. var aElement = $(this)
  590. .clone()
  591. .removeAttr("data-hydro-click data-hydro-click-hmac data-ga-click");
  592. aElement.addClass("Box-row Box-row--hover-gray");
  593. downloadSet.forEach(id => {
  594. let tempA = aElement.clone();
  595. tempA.attr({
  596. href: mirrors[id]["url"] + downloadHref,
  597. title: mirrors[id]["description"],
  598. });
  599. tempA.html(tempA.html().replace("Download ZIP", `Download ZIP(${mirrors[id]["name"]})`));
  600. li = li.clone().append(tempA);
  601. });
  602. $(this).parent().after(li);
  603. });
  604. }
  605.  
  606. /**
  607. * Add Raw list.
  608. */
  609. function addRawList() {
  610. let rawButton = $('#raw-url, a[data-testid="raw-button"]');
  611. if (rawButton.length == 0) {
  612. return;
  613. }
  614. const href = rawButton.attr("href");
  615. rawSet.forEach(id => {
  616. if (id == 3 || id == 10) {
  617. addRawButton(id, mirrors[id]["url"] + href.replace("/raw", ""), rawButton);
  618. } else if (id == 6) {
  619. addRawButton(id, mirrors[id]["url"] + href.replace("/raw/", "@"), rawButton);
  620. } else {
  621. addRawButton(id, mirrors[id]["url"] + href, rawButton);
  622. }
  623. });
  624. }
  625.  
  626. /**
  627. * Add the Raw Button.
  628. * @param {Number} id id of mirrors.
  629. * @param {String} url url.
  630. * @param {Object} rawButton the raw button.
  631. */
  632. function addRawButton(id, url, rawButton) {
  633. var span = rawButton.clone().removeAttr("id");
  634. span.attr({
  635. href: url,
  636. title: mirrors[id]["description"],
  637. target: "_blank",
  638. });
  639. span.text(mirrors[id]["name"]);
  640. rawButton.before(span);
  641. }
  642.  
  643. /**
  644. * Get message by setting.
  645. */
  646. function getMessage() {
  647. return "zh" == GM_getValue("lang") ? messages.zh : messages.en;
  648. }
  649.  
  650. /**
  651. * Log the title and version at the front of the console.
  652. * @param {String} title title.
  653. * @param {String} version script version.
  654. */
  655. function logInfo(title, version) {
  656. const titleStyle = "color:white;background-color:#606060";
  657. const versionStyle = "color:white;background-color:#1475b2";
  658. const logTitle = " " + title + " ";
  659. const logVersion = " " + version + " ";
  660. console.log("%c" + logTitle + "%c" + logVersion, titleStyle, versionStyle);
  661. }
  662.  
  663. /**
  664. * Check if the visitor is PC.
  665. */
  666. function isPC() {
  667. var userAgentInfo = navigator.userAgent;
  668. var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
  669. var isPC = true;
  670. const len = agents.length;
  671. for (var v = 0; v < len; v++) {
  672. if (userAgentInfo.indexOf(agents[v]) > 0) {
  673. isPC = false;
  674. break;
  675. }
  676. }
  677. return isPC;
  678. }
  679. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址