ScriptHub - 🧲 高效查找当前网站可用油猴脚本 🔍

在页面右下角显示当前网站可用的油猴脚本, Shows available userscripts for the current website from Greasy Fork镜像

  1. // ==UserScript==
  2. // @name ScriptHub - 🧲 高效查找当前网站可用油猴脚本 🔍
  3. // @name:zh ScriptHub - 🧲 高效查找当前网站可用油猴脚本 🔍
  4. // @name:en ScriptHub - Available Scripts Finder
  5. // @name:ja ScriptHub - 🧲 現在のサイトで利用可能なユーザースクリプトを検索 🔍
  6. // @name:ru ScriptHub - 🧲 Поиск доступных скриптов для текущего сайта 🔍
  7. // @name:ko ScriptHub - 🧲 현재 웹사이트에서 사용 가능한 유저스크립트 찾기 🔍
  8. // @namespace http://tampermonkey.net/
  9. // @version 1.4
  10. // @description 在页面右下角显示当前网站可用的油猴脚本, Shows available userscripts for the current website from Greasy Fork镜像
  11. // @description:zh 在页面右下角显示当前网站可用的油猴脚本数量,点击查看详情
  12. // @description:en Shows available userscripts for the current website from Greasy Fork镜像
  13. // @description:ja 現在のウェブサイトで利用可能なユーザースクリプトの数を右下に表示し、クリックで詳細を確認できます
  14. // @description:ru Показывает количество доступных пользовательских скриптов для текущего сайта в правом нижнем углу
  15. // @description:ko 현재 웹사이트에서 사용 가능한 유저스크립트 수를 우측 하단에 표시하고 클릭하여 자세히 보기
  16. // @author Musk
  17. // @keywords userscript, tampermonkey, greasyfork, script finder, utilities, productivity
  18. // @keywords:zh 用户脚本, 油猴脚本, 脚本查找器, 实用工具, 效率工具, 油猴
  19. // @keywords:en userscript, tampermonkey, greasyfork, script finder, utilities, productivity
  20. // @keywords:ja ユーザースクリプト, タンパーモンキー, スクリプト検索, ユーティリティ, 生産性向上
  21. // @keywords:ru пользовательский скрипт, тампермонки, поиск скриптов, утилиты, продуктивность
  22. // @keywords:ko 유저스크립트, 템퍼멍키, 스크립트 파인더, 유틸리티, 생산성
  23. // @match *://*/*
  24. // @grant GM_addStyle
  25. // @grant GM_xmlhttpRequest
  26. // @grant GM_getResourceText
  27. // @resource SITE_DATA https://gf.qytechs.cn/scripts/by-site.json
  28. // @connect gf.qytechs.cn
  29. // @connect www.gf.qytechs.cn
  30. // @connect *
  31. // @run-at document-end
  32. // @noframes
  33. // @license MIT
  34. // ==/UserScript==
  35.  
  36. (function() {
  37. 'use strict';
  38.  
  39. GM_addStyle(`
  40. .script-hub-button {
  41. position: fixed;
  42. right: 20px;
  43. bottom: 20px;
  44. padding: 6px 12px;
  45. background: rgba(255, 255, 255, 0.15);
  46. border-radius: 20px;
  47. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  48. cursor: move;
  49. user-select: none;
  50. z-index: 9999;
  51. display: flex;
  52. align-items: center;
  53. gap: 8px;
  54. font-size: 12px;
  55. transition: all 0.2s ease;
  56. }
  57.  
  58. .script-hub-button:hover {
  59. background: rgba(255, 255, 255, 0.7);
  60. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  61. }
  62.  
  63. .script-hub-button .close {
  64. margin-left: 4px;
  65. cursor: pointer;
  66. opacity: 0.6;
  67. font-size: 12px;
  68. padding: 2px;
  69. }
  70.  
  71. .script-hub-button .close:hover {
  72. opacity: 1;
  73. }
  74.  
  75. .script-hub-sidebar {
  76. position: fixed;
  77. top: 0;
  78. right: -400px;
  79. width: 400px;
  80. height: 100vh;
  81. background: #f8f9fa;
  82. box-shadow: -2px 0 5px rgba(0,0,0,0.1);
  83. transition: right 0.3s ease;
  84. z-index: 10000;
  85. overflow-y: auto;
  86. }
  87.  
  88. .script-hub-sidebar.active {
  89. right: 0;
  90. }
  91.  
  92. .script-list {
  93. padding: 12px;
  94. }
  95.  
  96. .script-item {
  97. margin: 0 0 12px;
  98. padding: 12px;
  99. border-radius: 8px;
  100. background: #fff;
  101. box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  102. transition: all 0.2s ease;
  103. }
  104.  
  105. .script-item:hover {
  106. box-shadow: 0 2px 8px rgba(0,0,0,0.15);
  107. transform: translateY(-1px);
  108. }
  109.  
  110. .script-item h3 {
  111. margin: 0 0 6px 0;
  112. font-size: 16px;
  113. }
  114.  
  115. .script-item h3 a {
  116. color: #1a73e8;
  117. text-decoration: none;
  118. }
  119.  
  120. .script-item h3 a:hover {
  121. text-decoration: underline;
  122. }
  123.  
  124. .script-description {
  125. color: #666;
  126. font-size: 0.9em;
  127. line-height: 1.4;
  128. margin: 6px 0;
  129. }
  130.  
  131. .script-meta {
  132. display: flex;
  133. justify-content: flex-start;
  134. align-items: center;
  135. padding: 6px 0 0;
  136. color: #666;
  137. font-size: 0.9em;
  138. border-top: 1px solid #eee;
  139. margin-top: 6px;
  140. gap: 16px;
  141. white-space: nowrap;
  142. overflow: hidden;
  143. }
  144.  
  145. .script-meta span {
  146. flex-shrink: 0;
  147. display: flex;
  148. align-items: center;
  149. gap: 4px;
  150. background: #f5f7fa;
  151. padding: 2px 8px;
  152. border-radius: 4px;
  153. font-size: 0.9em;
  154. }
  155.  
  156. .script-meta .author {
  157. flex-shrink: 1;
  158. overflow: hidden;
  159. text-overflow: ellipsis;
  160. min-width: 0;
  161. }
  162.  
  163. .script-meta .author a {
  164. color: inherit;
  165. text-decoration: none;
  166. overflow: hidden;
  167. text-overflow: ellipsis;
  168. display: block;
  169. }
  170.  
  171. .script-meta .author a:hover {
  172. text-decoration: underline;
  173. color: #1a73e8;
  174. }
  175.  
  176. .sidebar-header {
  177. display: flex;
  178. justify-content: space-between;
  179. align-items: center;
  180. padding: 12px 15px;
  181. border-bottom: 1px solid #eee;
  182. background: #f8f9fa;
  183. }
  184.  
  185. .sidebar-header-tools {
  186. display: flex;
  187. align-items: center;
  188. gap: 12px;
  189. font-size: 14px;
  190. color: #666;
  191. }
  192.  
  193. .sidebar-header-tools a {
  194. color: #666;
  195. text-decoration: none;
  196. display: flex;
  197. align-items: center;
  198. gap: 4px;
  199. padding: 4px 8px;
  200. border-radius: 4px;
  201. transition: all 0.2s ease;
  202. white-space: nowrap;
  203. }
  204.  
  205. .sidebar-header-tools a:hover {
  206. background: #f5f7fa;
  207. color: #1a73e8;
  208. }
  209.  
  210. .close-button {
  211. cursor: pointer;
  212. padding: 4px 8px;
  213. color: #666;
  214. font-size: 16px;
  215. border-radius: 4px;
  216. transition: all 0.2s ease;
  217. }
  218.  
  219. .close-button:hover {
  220. background: #f5f7fa;
  221. color: #1a73e8;
  222. }
  223.  
  224. .loading, .error, .no-scripts {
  225. padding: 20px;
  226. text-align: center;
  227. color: #666;
  228. }
  229.  
  230. .error {
  231. color: #f44336;
  232. }
  233. `);
  234.  
  235. function extractTLD(domain) {
  236. domain = domain.replace(/^(https?:\/\/)?(www\.)?/, '').split('/')[0];
  237. const parts = domain.split('.');
  238. if (parts.length >= 2) {
  239. return parts.slice(-2).join('.').toLowerCase();
  240. }
  241. return domain.toLowerCase();
  242. }
  243.  
  244. function formatDate(dateString) {
  245. const date = new Date(dateString);
  246. const now = new Date();
  247. const month = (date.getMonth() + 1).toString().padStart(2, '0');
  248. const day = date.getDate().toString().padStart(2, '0');
  249. if (date.getFullYear() === now.getFullYear()) {
  250. return `${month}月${day}日`;
  251. } else {
  252. return `${date.getFullYear().toString().slice(-2)}年${month}月${day}日`;
  253. }
  254. }
  255.  
  256. function createUI() {
  257. const button = document.createElement('div');
  258. button.className = 'script-hub-button';
  259. const text = document.createElement('span');
  260. text.textContent = '0';
  261. const close = document.createElement('span');
  262. close.className = 'close';
  263. close.textContent = '×';
  264. close.onclick = (e) => {
  265. e.stopPropagation();
  266. const currentDomain = window.location.hostname;
  267. if (typeof GM_getValue !== 'undefined' && typeof GM_setValue !== 'undefined') {
  268. const excludedDomains = GM_getValue('excludedDomains', []);
  269. if (!excludedDomains.includes(currentDomain)) {
  270. excludedDomains.push(currentDomain);
  271. GM_setValue('excludedDomains', excludedDomains);
  272. }
  273. }
  274. button.remove();
  275. };
  276. button.appendChild(text);
  277. button.appendChild(close);
  278. let isDragging = false;
  279. let startX = 0;
  280. let startY = 0;
  281. let startLeft = 0;
  282. let startTop = 0;
  283.  
  284. function handleMouseDown(e) {
  285. if (e.target === close) return;
  286. isDragging = true;
  287. startX = e.clientX;
  288. startY = e.clientY;
  289. const rect = button.getBoundingClientRect();
  290. startLeft = rect.left;
  291. startTop = rect.top;
  292. button.style.transition = 'none';
  293. button.style.cursor = 'grabbing';
  294. }
  295.  
  296. function handleMouseMove(e) {
  297. if (!isDragging) return;
  298. const deltaX = e.clientX - startX;
  299. const deltaY = e.clientY - startY;
  300. const newLeft = startLeft + deltaX;
  301. const newTop = startTop + deltaY;
  302. const buttonWidth = button.offsetWidth;
  303. const buttonHeight = button.offsetHeight;
  304. const viewportWidth = window.innerWidth;
  305. const viewportHeight = window.innerHeight;
  306. const finalLeft = Math.min(Math.max(0, newLeft), viewportWidth - buttonWidth);
  307. const finalTop = Math.min(Math.max(0, newTop), viewportHeight - buttonHeight);
  308. button.style.left = finalLeft + 'px';
  309. button.style.top = finalTop + 'px';
  310. button.style.right = 'auto';
  311. button.style.bottom = 'auto';
  312. }
  313.  
  314. function handleMouseUp() {
  315. if (!isDragging) return;
  316. isDragging = false;
  317. button.style.transition = '';
  318. button.style.cursor = 'move';
  319. }
  320.  
  321. button.addEventListener('mousedown', handleMouseDown);
  322. document.addEventListener('mousemove', handleMouseMove);
  323. document.addEventListener('mouseup', handleMouseUp);
  324. document.body.appendChild(button);
  325.  
  326. const sidebar = document.createElement('div');
  327. sidebar.className = 'script-hub-sidebar';
  328. document.body.appendChild(sidebar);
  329.  
  330. document.addEventListener('click', (e) => {
  331. if (!sidebar.contains(e.target) && !button.contains(e.target) && sidebar.classList.contains('show')) {
  332. sidebar.classList.remove('show');
  333. button.classList.remove('active');
  334. }
  335. });
  336.  
  337. sidebar.addEventListener('click', (e) => {
  338. e.stopPropagation();
  339. });
  340.  
  341. button.addEventListener('click', (e) => {
  342. if (e.target === close) return;
  343. sidebar.classList.toggle('show');
  344. button.classList.toggle('active');
  345. e.stopPropagation();
  346. });
  347.  
  348. sidebar.innerHTML = `
  349. <div class="sidebar-header">
  350. <div>
  351. <div class="sidebar-header-tools">
  352. <a href="https://chromewebstore.google.com/detail/jdopbpkjbknppilnpjmceinnpkaigaem" target="_blank">
  353. ScriptHub插件
  354. </a>
  355. <a href="https://likofree.pages.dev/projects/" target="_blank">
  356. <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
  357. <path d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z"/>
  358. </svg>
  359. 更多工具
  360. </a>
  361. <a href="https://x.com/liko2049" target="_blank">
  362. <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
  363. <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
  364. </svg>
  365. 作者
  366. </a>
  367. </div>
  368. </div>
  369. <div class="close-button">✕</div>
  370. </div>
  371. <div class="script-list"></div>
  372. `;
  373. document.body.appendChild(sidebar);
  374.  
  375. button.addEventListener('click', async () => {
  376. sidebar.classList.add('active');
  377. const scriptList = sidebar.querySelector('.script-list');
  378. if (!scriptList.children.length) {
  379. const rawDomain = document.location.hostname;
  380. const domain = extractTLD(rawDomain);
  381. await loadScriptDetails(domain, scriptList);
  382. }
  383. });
  384.  
  385. sidebar.querySelector('.close-button').addEventListener('click', () => {
  386. sidebar.classList.remove('active');
  387. });
  388.  
  389. return { button, sidebar };
  390. }
  391.  
  392. async function loadScriptDetails(domain, container, retryCount = 0) {
  393. container.innerHTML = '<div class="loading">Loading scripts...</div>';
  394.  
  395. try {
  396. const encodedDomain = encodeURIComponent(domain);
  397. const apiUrl = `https://gf.qytechs.cn/scripts/by-site/${domain}?filter_locale=0&page=1`;
  398. const response = await fetch(apiUrl);
  399. const html = await response.text();
  400. const parser = new DOMParser();
  401. const doc = parser.parseFromString(html, "text/html");
  402. const scripts = doc.querySelector("#browse-script-list")?.querySelectorAll('[data-script-id]');
  403. let scriptsInfo = [];
  404. if (!scripts) {
  405. scriptsInfo = errorMessage;
  406. } else {
  407. scripts.forEach(script => {
  408. scriptsInfo.push({
  409. id: script.getAttribute('data-script-id'),
  410. name: script.getAttribute('data-script-name'),
  411. author: script.querySelector("dd.script-list-author")?.textContent || '',
  412. description: script.querySelector(".script-description")?.textContent || '',
  413. version: script.getAttribute('data-script-version'),
  414. url: 'https://gf.qytechs.cn/scripts/' + script.getAttribute('data-script-id'),
  415. createDate: script.getAttribute('data-script-created-date'),
  416. updateDate: script.getAttribute('data-script-updated-date'),
  417. installs: script.getAttribute('data-script-total-installs'),
  418. dailyInstalls: script.getAttribute('data-script-daily-installs'),
  419. ratingScore: script.getAttribute('data-script-rating-score')
  420. });
  421. });
  422. }
  423.  
  424. container.innerHTML = '';
  425. if (!scriptsInfo.length) {
  426. container.innerHTML = '<div class="no-scripts">No scripts found</div>';
  427. return;
  428. }
  429.  
  430. scriptsInfo.forEach(script => {
  431. const scriptElement = document.createElement('div');
  432. scriptElement.className = 'script-item';
  433. scriptElement.innerHTML = `
  434. <h3><a href="${script.url}" target="_blank">${script.name}</a></h3>
  435. <div class="script-description">${script.description}</div>
  436. <div class="script-meta">
  437. <span title="总安装量">📥 ${script.installs || 0}</span>
  438. <span title="日安装量">📈 ${script.dailyInstalls || 0}</span>
  439. <span title="更新时间">🕐 ${formatDate(script.updateDate)}</span>
  440. <span class="author" title="${script.author || 'Unknown'}">
  441. <a href="${script.url}" target="_blank">
  442. 👨‍💻 ${script.author || 'Unknown'}
  443. </a>
  444. </span>
  445. </div>
  446. `;
  447. container.appendChild(scriptElement);
  448. });
  449. } catch (error) {
  450. if (retryCount < 3) {
  451. setTimeout(() => {
  452. loadScriptDetails(domain, container, retryCount + 1);
  453. }, 1000 * (retryCount + 1));
  454. } else {
  455. container.innerHTML = `
  456. <div class="error">
  457. Failed to load scripts. Please try again later.<br>
  458. <small>Error: ${error.message}</small>
  459. </div>
  460. `;
  461. }
  462. }
  463. }
  464.  
  465. async function init() {
  466. const { button, sidebar } = createUI();
  467.  
  468. const rawDomain = document.location.hostname;
  469. const domain = extractTLD(rawDomain);
  470. try {
  471. const siteData = JSON.parse(GM_getResourceText('SITE_DATA'));
  472. const count = siteData[domain] || 0;
  473.  
  474. if (count === 0) {
  475. button.style.display = 'none';
  476. return;
  477. }
  478.  
  479. const text = button.querySelector('span:nth-child(1)');
  480. text.textContent = count.toString();
  481. } catch (error) {
  482. button.style.display = 'none';
  483. }
  484. }
  485.  
  486. init();
  487. })();

QingJ © 2025

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