- // ==UserScript==
- // @name ScriptHub - 🧲 高效查找当前网站可用油猴脚本 🔍
- // @name:zh ScriptHub - 🧲 高效查找当前网站可用油猴脚本 🔍
- // @name:en ScriptHub - Available Scripts Finder
- // @name:ja ScriptHub - 🧲 現在のサイトで利用可能なユーザースクリプトを検索 🔍
- // @name:ru ScriptHub - 🧲 Поиск доступных скриптов для текущего сайта 🔍
- // @name:ko ScriptHub - 🧲 현재 웹사이트에서 사용 가능한 유저스크립트 찾기 🔍
- // @namespace http://tampermonkey.net/
- // @version 1.4
- // @description 在页面右下角显示当前网站可用的油猴脚本, Shows available userscripts for the current website from Greasy Fork镜像
- // @description:zh 在页面右下角显示当前网站可用的油猴脚本数量,点击查看详情
- // @description:en Shows available userscripts for the current website from Greasy Fork镜像
- // @description:ja 現在のウェブサイトで利用可能なユーザースクリプトの数を右下に表示し、クリックで詳細を確認できます
- // @description:ru Показывает количество доступных пользовательских скриптов для текущего сайта в правом нижнем углу
- // @description:ko 현재 웹사이트에서 사용 가능한 유저스크립트 수를 우측 하단에 표시하고 클릭하여 자세히 보기
- // @author Musk
- // @keywords userscript, tampermonkey, greasyfork, script finder, utilities, productivity
- // @keywords:zh 用户脚本, 油猴脚本, 脚本查找器, 实用工具, 效率工具, 油猴
- // @keywords:en userscript, tampermonkey, greasyfork, script finder, utilities, productivity
- // @keywords:ja ユーザースクリプト, タンパーモンキー, スクリプト検索, ユーティリティ, 生産性向上
- // @keywords:ru пользовательский скрипт, тампермонки, поиск скриптов, утилиты, продуктивность
- // @keywords:ko 유저스크립트, 템퍼멍키, 스크립트 파인더, 유틸리티, 생산성
- // @match *://*/*
- // @grant GM_addStyle
- // @grant GM_xmlhttpRequest
- // @grant GM_getResourceText
- // @resource SITE_DATA https://gf.qytechs.cn/scripts/by-site.json
- // @connect gf.qytechs.cn
- // @connect www.gf.qytechs.cn
- // @connect *
- // @run-at document-end
- // @noframes
- // @license MIT
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- GM_addStyle(`
- .script-hub-button {
- position: fixed;
- right: 20px;
- bottom: 20px;
- padding: 6px 12px;
- background: rgba(255, 255, 255, 0.15);
- border-radius: 20px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- cursor: move;
- user-select: none;
- z-index: 9999;
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 12px;
- transition: all 0.2s ease;
- }
-
- .script-hub-button:hover {
- background: rgba(255, 255, 255, 0.7);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- }
-
- .script-hub-button .close {
- margin-left: 4px;
- cursor: pointer;
- opacity: 0.6;
- font-size: 12px;
- padding: 2px;
- }
-
- .script-hub-button .close:hover {
- opacity: 1;
- }
-
- .script-hub-sidebar {
- position: fixed;
- top: 0;
- right: -400px;
- width: 400px;
- height: 100vh;
- background: #f8f9fa;
- box-shadow: -2px 0 5px rgba(0,0,0,0.1);
- transition: right 0.3s ease;
- z-index: 10000;
- overflow-y: auto;
- }
-
- .script-hub-sidebar.active {
- right: 0;
- }
-
- .script-list {
- padding: 12px;
- }
-
- .script-item {
- margin: 0 0 12px;
- padding: 12px;
- border-radius: 8px;
- background: #fff;
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
- transition: all 0.2s ease;
- }
-
- .script-item:hover {
- box-shadow: 0 2px 8px rgba(0,0,0,0.15);
- transform: translateY(-1px);
- }
-
- .script-item h3 {
- margin: 0 0 6px 0;
- font-size: 16px;
- }
-
- .script-item h3 a {
- color: #1a73e8;
- text-decoration: none;
- }
-
- .script-item h3 a:hover {
- text-decoration: underline;
- }
-
- .script-description {
- color: #666;
- font-size: 0.9em;
- line-height: 1.4;
- margin: 6px 0;
- }
-
- .script-meta {
- display: flex;
- justify-content: flex-start;
- align-items: center;
- padding: 6px 0 0;
- color: #666;
- font-size: 0.9em;
- border-top: 1px solid #eee;
- margin-top: 6px;
- gap: 16px;
- white-space: nowrap;
- overflow: hidden;
- }
-
- .script-meta span {
- flex-shrink: 0;
- display: flex;
- align-items: center;
- gap: 4px;
- background: #f5f7fa;
- padding: 2px 8px;
- border-radius: 4px;
- font-size: 0.9em;
- }
-
- .script-meta .author {
- flex-shrink: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- min-width: 0;
- }
-
- .script-meta .author a {
- color: inherit;
- text-decoration: none;
- overflow: hidden;
- text-overflow: ellipsis;
- display: block;
- }
-
- .script-meta .author a:hover {
- text-decoration: underline;
- color: #1a73e8;
- }
-
- .sidebar-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 12px 15px;
- border-bottom: 1px solid #eee;
- background: #f8f9fa;
- }
-
- .sidebar-header-tools {
- display: flex;
- align-items: center;
- gap: 12px;
- font-size: 14px;
- color: #666;
- }
-
- .sidebar-header-tools a {
- color: #666;
- text-decoration: none;
- display: flex;
- align-items: center;
- gap: 4px;
- padding: 4px 8px;
- border-radius: 4px;
- transition: all 0.2s ease;
- white-space: nowrap;
- }
-
- .sidebar-header-tools a:hover {
- background: #f5f7fa;
- color: #1a73e8;
- }
-
- .close-button {
- cursor: pointer;
- padding: 4px 8px;
- color: #666;
- font-size: 16px;
- border-radius: 4px;
- transition: all 0.2s ease;
- }
-
- .close-button:hover {
- background: #f5f7fa;
- color: #1a73e8;
- }
-
- .loading, .error, .no-scripts {
- padding: 20px;
- text-align: center;
- color: #666;
- }
-
- .error {
- color: #f44336;
- }
- `);
-
- function extractTLD(domain) {
- domain = domain.replace(/^(https?:\/\/)?(www\.)?/, '').split('/')[0];
- const parts = domain.split('.');
- if (parts.length >= 2) {
- return parts.slice(-2).join('.').toLowerCase();
- }
- return domain.toLowerCase();
- }
-
- function formatDate(dateString) {
- const date = new Date(dateString);
- const now = new Date();
- const month = (date.getMonth() + 1).toString().padStart(2, '0');
- const day = date.getDate().toString().padStart(2, '0');
-
- if (date.getFullYear() === now.getFullYear()) {
- return `${month}月${day}日`;
- } else {
- return `${date.getFullYear().toString().slice(-2)}年${month}月${day}日`;
- }
- }
-
- function createUI() {
- const button = document.createElement('div');
- button.className = 'script-hub-button';
-
- const text = document.createElement('span');
- text.textContent = '0';
-
- const close = document.createElement('span');
- close.className = 'close';
- close.textContent = '×';
- close.onclick = (e) => {
- e.stopPropagation();
- const currentDomain = window.location.hostname;
- if (typeof GM_getValue !== 'undefined' && typeof GM_setValue !== 'undefined') {
- const excludedDomains = GM_getValue('excludedDomains', []);
- if (!excludedDomains.includes(currentDomain)) {
- excludedDomains.push(currentDomain);
- GM_setValue('excludedDomains', excludedDomains);
- }
- }
- button.remove();
- };
-
- button.appendChild(text);
- button.appendChild(close);
-
- let isDragging = false;
- let startX = 0;
- let startY = 0;
- let startLeft = 0;
- let startTop = 0;
-
- function handleMouseDown(e) {
- if (e.target === close) return;
-
- isDragging = true;
- startX = e.clientX;
- startY = e.clientY;
-
- const rect = button.getBoundingClientRect();
- startLeft = rect.left;
- startTop = rect.top;
-
- button.style.transition = 'none';
- button.style.cursor = 'grabbing';
- }
-
- function handleMouseMove(e) {
- if (!isDragging) return;
-
- const deltaX = e.clientX - startX;
- const deltaY = e.clientY - startY;
-
- const newLeft = startLeft + deltaX;
- const newTop = startTop + deltaY;
-
- const buttonWidth = button.offsetWidth;
- const buttonHeight = button.offsetHeight;
- const viewportWidth = window.innerWidth;
- const viewportHeight = window.innerHeight;
-
- const finalLeft = Math.min(Math.max(0, newLeft), viewportWidth - buttonWidth);
- const finalTop = Math.min(Math.max(0, newTop), viewportHeight - buttonHeight);
-
- button.style.left = finalLeft + 'px';
- button.style.top = finalTop + 'px';
- button.style.right = 'auto';
- button.style.bottom = 'auto';
- }
-
- function handleMouseUp() {
- if (!isDragging) return;
-
- isDragging = false;
- button.style.transition = '';
- button.style.cursor = 'move';
- }
-
- button.addEventListener('mousedown', handleMouseDown);
- document.addEventListener('mousemove', handleMouseMove);
- document.addEventListener('mouseup', handleMouseUp);
-
- document.body.appendChild(button);
-
- const sidebar = document.createElement('div');
- sidebar.className = 'script-hub-sidebar';
- document.body.appendChild(sidebar);
-
- document.addEventListener('click', (e) => {
- if (!sidebar.contains(e.target) && !button.contains(e.target) && sidebar.classList.contains('show')) {
- sidebar.classList.remove('show');
- button.classList.remove('active');
- }
- });
-
- sidebar.addEventListener('click', (e) => {
- e.stopPropagation();
- });
-
- button.addEventListener('click', (e) => {
- if (e.target === close) return;
- sidebar.classList.toggle('show');
- button.classList.toggle('active');
- e.stopPropagation();
- });
-
- sidebar.innerHTML = `
- <div class="sidebar-header">
- <div>
- <div class="sidebar-header-tools">
- <a href="https://chromewebstore.google.com/detail/jdopbpkjbknppilnpjmceinnpkaigaem" target="_blank">
- ScriptHub插件
- </a>
- <a href="https://likofree.pages.dev/projects/" target="_blank">
- <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
- <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"/>
- </svg>
- 更多工具
- </a>
- <a href="https://x.com/liko2049" target="_blank">
- <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
- <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"/>
- </svg>
- 作者
- </a>
- </div>
- </div>
- <div class="close-button">✕</div>
- </div>
- <div class="script-list"></div>
- `;
- document.body.appendChild(sidebar);
-
- button.addEventListener('click', async () => {
- sidebar.classList.add('active');
- const scriptList = sidebar.querySelector('.script-list');
-
- if (!scriptList.children.length) {
- const rawDomain = document.location.hostname;
- const domain = extractTLD(rawDomain);
- await loadScriptDetails(domain, scriptList);
- }
- });
-
- sidebar.querySelector('.close-button').addEventListener('click', () => {
- sidebar.classList.remove('active');
- });
-
- return { button, sidebar };
- }
-
- async function loadScriptDetails(domain, container, retryCount = 0) {
- container.innerHTML = '<div class="loading">Loading scripts...</div>';
-
- try {
- const encodedDomain = encodeURIComponent(domain);
- const apiUrl = `https://gf.qytechs.cn/scripts/by-site/${domain}?filter_locale=0&page=1`;
- const response = await fetch(apiUrl);
- const html = await response.text();
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, "text/html");
- const scripts = doc.querySelector("#browse-script-list")?.querySelectorAll('[data-script-id]');
- let scriptsInfo = [];
-
- if (!scripts) {
- scriptsInfo = errorMessage;
- } else {
- scripts.forEach(script => {
- scriptsInfo.push({
- id: script.getAttribute('data-script-id'),
- name: script.getAttribute('data-script-name'),
- author: script.querySelector("dd.script-list-author")?.textContent || '',
- description: script.querySelector(".script-description")?.textContent || '',
- version: script.getAttribute('data-script-version'),
- url: 'https://gf.qytechs.cn/scripts/' + script.getAttribute('data-script-id'),
- createDate: script.getAttribute('data-script-created-date'),
- updateDate: script.getAttribute('data-script-updated-date'),
- installs: script.getAttribute('data-script-total-installs'),
- dailyInstalls: script.getAttribute('data-script-daily-installs'),
- ratingScore: script.getAttribute('data-script-rating-score')
- });
- });
- }
-
- container.innerHTML = '';
-
- if (!scriptsInfo.length) {
- container.innerHTML = '<div class="no-scripts">No scripts found</div>';
- return;
- }
-
- scriptsInfo.forEach(script => {
- const scriptElement = document.createElement('div');
- scriptElement.className = 'script-item';
-
- scriptElement.innerHTML = `
- <h3><a href="${script.url}" target="_blank">${script.name}</a></h3>
- <div class="script-description">${script.description}</div>
- <div class="script-meta">
- <span title="总安装量">📥 ${script.installs || 0}</span>
- <span title="日安装量">📈 ${script.dailyInstalls || 0}</span>
- <span title="更新时间">🕐 ${formatDate(script.updateDate)}</span>
- <span class="author" title="${script.author || 'Unknown'}">
- <a href="${script.url}" target="_blank">
- 👨💻 ${script.author || 'Unknown'}
- </a>
- </span>
- </div>
- `;
- container.appendChild(scriptElement);
- });
- } catch (error) {
- if (retryCount < 3) {
- setTimeout(() => {
- loadScriptDetails(domain, container, retryCount + 1);
- }, 1000 * (retryCount + 1));
- } else {
- container.innerHTML = `
- <div class="error">
- Failed to load scripts. Please try again later.<br>
- <small>Error: ${error.message}</small>
- </div>
- `;
- }
- }
- }
-
- async function init() {
- const { button, sidebar } = createUI();
-
- const rawDomain = document.location.hostname;
- const domain = extractTLD(rawDomain);
-
- try {
- const siteData = JSON.parse(GM_getResourceText('SITE_DATA'));
- const count = siteData[domain] || 0;
-
- if (count === 0) {
- button.style.display = 'none';
- return;
- }
-
- const text = button.querySelector('span:nth-child(1)');
- text.textContent = count.toString();
- } catch (error) {
- button.style.display = 'none';
- }
- }
-
- init();
- })();