HF 镜像跳转

在 Hugging Face 仓库页面添加镜像跳转按钮

  1. // ==UserScript==
  2. // @name HF 镜像跳转
  3. // @name:en HF Mirror Redirect
  4. // @namespace https://github.com/zhzLuke96/hf-links
  5. // @version v1.3
  6. // @description 在 Hugging Face 仓库页面添加镜像跳转按钮
  7. // @description:en Add mirror redirect buttons on Hugging Face repository pages.
  8. // @author zhzluke96
  9. // @match https://huggingface.co/*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=huggingface.co
  11. // @grant none
  12. // @license MIT
  13. // @supportURL https://github.com/zhzLuke96/hf-links/issues
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. "use strict";
  18.  
  19. const exit = (message, code = 0) =>
  20. console.log(`[hf-links|${code}] ${message}`);
  21.  
  22. /**
  23. * @type {typeof document.querySelector}
  24. */
  25. const $ = document.querySelector.bind(document);
  26.  
  27. const $header = $(
  28. "body > div > main > div.SVELTE_HYDRATER.contents > header > div > h1"
  29. );
  30. if (!$header) {
  31. exit("没找到header");
  32. return;
  33. }
  34.  
  35. const frag = document.createDocumentFragment();
  36. const div = document.createElement("div");
  37. frag.appendChild(div);
  38.  
  39. const build = (html) => {
  40. div.innerHTML = html;
  41. const elem = div.firstElementChild;
  42. div.innerHTML = "";
  43. return elem;
  44. };
  45.  
  46. const Button = ({ text, icon, onClick }) => {
  47. const elem = build(`
  48. <div
  49. class="inline-flex items-center overflow-hidden whitespace-nowrap rounded-md border bg-white text-sm leading-none text-gray-500 mr-2"
  50. >
  51. <button
  52. class="relative flex items-center overflow-hidden from-red-50 to-transparent dark:from-red-900 px-1.5 py-1 hover:bg-gradient-to-t focus:outline-none"
  53. title="${text}"
  54. >
  55. <small
  56. class="left-1.5 absolute"
  57. >${icon}</small>
  58. <span class="ml-4 pl-0.5">${text}</span>
  59. </button>
  60. </div>
  61. `);
  62. onClick && elem.addEventListener("click", onClick);
  63. return elem;
  64. };
  65.  
  66. const parse_hf_repo = () => {
  67. {
  68. // 有可能是 datasets
  69. const match = location.pathname.match(/^\/datasets\/([^/]+)\/([^/]+)/);
  70. if (match) {
  71. return {
  72. kind: "datasets",
  73. repo_owner: match[1],
  74. repo_name: match[2],
  75. pathname: `/datasets/${match[1]}/${match[2]}`,
  76. };
  77. }
  78. }
  79.  
  80. // 从 URL 中解析仓库名和所有者
  81. const match = location.pathname.match(/^\/([^/]+)\/([^/]+)/);
  82. if (match) {
  83. return {
  84. kind: "models",
  85. repo_owner: match[1],
  86. repo_name: match[2],
  87. pathname: `/${match[1]}/${match[2]}`,
  88. };
  89. }
  90. return {};
  91. };
  92.  
  93. const { pathname, repo_name } = parse_hf_repo();
  94.  
  95. if (!pathname || !repo_name) {
  96. exit("解析repo名字失败");
  97. return;
  98. }
  99.  
  100. const buttons = [
  101. {
  102. href: `https://hf-mirror.com${pathname}`,
  103. label: "hf-mirror",
  104. icon: "🤗",
  105. },
  106. {
  107. // href: `https://modelscope.cn/search?search=${encodeURIComponent(
  108. // `${repo_owner}/${repo_name}`
  109. // )}`,
  110.  
  111. // NOTE: 只搜索 repo name 因为一般都是搬运, owner 不一样
  112. href: `https://modelscope.cn/search?search=${encodeURIComponent(
  113. `${repo_name}`
  114. )}`,
  115. label: "model-scope",
  116. icon: "👾",
  117. },
  118. ];
  119.  
  120. for (const button of buttons) {
  121. const node = Button({
  122. link: button.href,
  123. text: button.label,
  124. icon: button.icon,
  125. onClick: () => {
  126. const nw = window.open(button.href, "_blank", "noopener,noreferrer");
  127. if (nw) nw.opener = null;
  128. },
  129. });
  130. $header.appendChild(node);
  131.  
  132. // NOTE: header 不知道为啥 rerender... 所以要检测一下,最多检测 10 次
  133. // NOTE: 用 MutationObserver 可能好点,但是卡死了... 所以用 interval
  134.  
  135. let check_times = 0;
  136. const timer = setInterval(() => {
  137. if (check_times >= 10) {
  138. clearInterval(timer);
  139. return;
  140. }
  141. check_times++;
  142. if ($header.innerHTML.includes(button.href)) {
  143. return;
  144. }
  145. $header.appendChild(node);
  146. }, 500);
  147. }
  148.  
  149. exit("按钮添加成功");
  150. })();

QingJ © 2025

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