您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a compact, open-by-default dropdown menu for search buttons on Otomi Games pages, with grouped buttons in a modern, pink-gradient design matching the site's logo. Product ID buttons are greyed out and unclickable if no valid DLsite ID (RJ/VJ format) is available.
// ==UserScript== // @name Multi-Platform Search Buttons for Otomi Games // @namespace http://tampermonkey.net/ // @version 2.2 // @description Adds a compact, open-by-default dropdown menu for search buttons on Otomi Games pages, with grouped buttons in a modern, pink-gradient design matching the site's logo. Product ID buttons are greyed out and unclickable if no valid DLsite ID (RJ/VJ format) is available. // @author FunkyJustin // @match https://otomi-games.com/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // Inject CSS styles const style = document.createElement('style'); style.innerHTML = ` :root { --primary-pink-start: #FF6B9A; /* Pink from logo */ --primary-pink-end: #FF8C61; /* Orange-pink gradient */ --accent-teal: #4FD1C5; /* Otomi Games teal */ --bg-light: #F7FAFC; /* Light background */ --shadow-color: rgba(0, 0, 0, 0.1); --glass-bg: rgba(247, 250, 252, 0.9); --glass-border: rgba(255, 107, 154, 0.3); /* Pink-tinted border */ } .search-btn { display: inline-flex; align-items: center; padding: 5px 10px; margin: 2px; background: linear-gradient(135deg, var(--primary-pink-start), var(--primary-pink-end)); color: #1A202C; /* Black text to match page */ text-decoration: none; border-radius: 6px; border: 1px solid var(--glass-border); box-shadow: 0 2px 4px var(--shadow-color), inset 0 0 2px var(--primary-pink-start); font-family: Poppins, Roboto, Arial, sans-serif; font-size: 16px; font-weight: 600; backdrop-filter: blur(5px); background-color: var(--glass-bg); transition: transform 0.2s, box-shadow 0.3s, opacity 0.3s; } .search-btn:hover { transform: scale(1.05); box-shadow: 0 4px 8px var(--shadow-color), 0 0 10px var(--accent-teal); opacity: 0.95; } .search-btn.disabled { opacity: 0.5; pointer-events: none; cursor: not-allowed; background: #E0E7FF; color: #1A202C; /* Black text for disabled state */ } .search-btn img { width: 16px; height: 16px; margin-right: 6px; } .dropdown-container { max-width: 600px; margin: 10px 0; } .dropdown-toggle { display: flex; align-items: center; padding: 6px 12px; background: linear-gradient(135deg, var(--primary-pink-start), var(--primary-pink-end)); color: #1A202C; /* Black text to match page */ border: none; border-radius: 6px; font-family: Poppins, Roboto, Arial, sans-serif; font-size: 16px; font-weight: 500; /* Slightly thinner than buttons (600) */ cursor: pointer; transition: transform 0.2s, background 0.3s; box-shadow: 0 2px 4px var(--shadow-color); } .dropdown-toggle:hover { transform: scale(1.03); background: linear-gradient(135deg, #FF8C61, #FF6B9A); } .dropdown-toggle:active, .dropdown-toggle:focus { background: linear-gradient(135deg, var(--primary-pink-start), var(--primary-pink-end)); color: #1A202C; /* Maintain black text */ outline: none; box-shadow: 0 2px 4px var(--shadow-color), inset 0 0 4px var(--primary-pink-start); } .dropdown-toggle::after { content: '▼'; margin-left: 6px; transition: transform 0.3s ease; } .dropdown-toggle.collapsed::after { transform: rotate(180deg); } .dropdown-content { display: block; /* Open by default */ background: var(--glass-bg); border-radius: 6px; box-shadow: 0 4px 12px var(--shadow-color); padding: 10px; margin-top: 5px; backdrop-filter: blur(5px); border: 1px solid var(--glass-border); } .dropdown-content.hidden { display: none; } .button-group { margin-bottom: 8px; } .group-label { font-family: Poppins, Roboto, Arial, sans-serif; font-size: 15px; font-weight: 600; color: #2D3748; margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.5px; } .button-group-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, auto)); gap: 4px; } .circle-btn-wrapper { display: inline-flex; flex-wrap: nowrap; align-items: center; margin-left: 8px; margin-bottom: 10px; margin-right: 8px; } .circle-btn-wrapper .search-btn { color: #1A202C !important; /* Enforce black text for developer buttons */ } `; document.head.appendChild(style); // Icon URLs for each website const icons = { f95zone: 'https://f95zone.to/favicon.ico', ryuugames: 'https://www.ryuugames.com/wp-content/uploads/2020/05/cropped-ryuugames_logo-1-32x32.png', otomi: 'https://otomi-games.com/favicon.ico' }; // Function to create a styled button function createButton(text, url, iconUrl, isDisabled = false) { const a = document.createElement('a'); a.className = `search-btn ${isDisabled ? 'disabled' : ''}`; a.href = url; a.target = '_blank'; a.title = text; if (iconUrl) { const img = document.createElement('img'); img.src = iconUrl; img.alt = ''; a.appendChild(img); } a.appendChild(document.createTextNode(text)); return a; } // Main function to add buttons function addSearchButtons() { // Extract game title const titleEl = document.querySelector('h1.post-title.entry-title a'); if (!titleEl) { console.warn('Game title element not found.'); return; } let gameTitle = titleEl.innerText.trim(); let productId = ''; // Extract product ID from title (e.g., [RJ01321968] or [VJ013026]) const idMatch = gameTitle.match(/\[([RV]J\d{6,8})\]/); // Match RJ/VJ followed by 6 or 8 digits if (idMatch) { productId = idMatch[1]; // e.g., RJ01321968 or VJ013026 gameTitle = gameTitle.replace(/\[([RV]J\d{6,8})\]/, '').replace(/\[.*?\]/g, '').trim(); // Clean title } else { // If no ID in title, check URL slug for a valid DLsite ID const urlMatch = window.location.href.match(/\/([^\/]+)\/$/); if (urlMatch) { const slug = urlMatch[1]; // e.g., oceanlust // Check if the slug is a valid DLsite ID if (slug.match(/^[RV]J\d{6,8}$/)) { productId = slug; // e.g., RJ01321968 } } gameTitle = gameTitle.replace(/\[.*?\]/g, '').trim(); // Remove [Final], etc. } // If no valid DLsite ID was found, ensure productId is empty if (!productId.match(/^[RV]J\d{6,8}$/)) { productId = ''; // Invalidate if not a proper DLsite ID } // Extract developer (more flexible selector) const developerLi = Array.from(document.querySelectorAll('ul.wp-block-list li')).find(li => li.textContent.includes('Publisher:') || li.textContent.includes('Developer:') ); const developerName = developerLi ? developerLi.textContent.replace(/^(Publisher|Developer):/, '').trim() : ''; if (!developerName) { console.warn('Developer element not found.'); return; } // Define search URLs const urls = { f95zoneGame: `https://f95zone.to/sam/latest_alpha/#/cat=games/page=1/search=${encodeURIComponent(gameTitle)}`, f95zoneForum: `https://f95zone.to/search/?q=${encodeURIComponent(gameTitle)}&t=post&c[child_nodes]=1&c[nodes][0]=2&c[title_only]=1&o=relevance`, f95zonePID: `https://f95zone.to/search/?q=${encodeURIComponent(productId)}`, otomiGame: `https://otomi-games.com/?s=${encodeURIComponent(gameTitle)}`, otomiPID: `https://otomi-games.com/?s=${encodeURIComponent(productId)}`, // Updated URL format ryuuGame: `https://www.ryuugames.com/?s=${encodeURIComponent(gameTitle)}`, ryuuPID: `https://www.ryuugames.com/?s=${encodeURIComponent(productId)}`, f95zoneDev: `https://f95zone.to/sam/latest_alpha/#/cat=games/page=1/creator=${encodeURIComponent(developerName)}`, ryuuDev: `https://www.ryuugames.com/?s=${encodeURIComponent(developerName)}`, otomiDev: `https://otomi-games.com/?s=${encodeURIComponent(developerName)}` }; // Create buttons, disabling product ID buttons if no valid ID const hasProductId = !!productId; const buttons = { f95Game: createButton('Search F95Zone', urls.f95zoneGame, icons.f95zone), f95Forum: createButton('F95 Forums', urls.f95zoneForum, icons.f95zone), f95PID: createButton('F95 Product ID', urls.f95zonePID, icons.f95zone, !hasProductId), otomiGame: createButton('Search Otomi', urls.otomiGame, icons.otomi), otomiPID: createButton('Otomi Product ID', urls.otomiPID, icons.otomi, !hasProductId), ryuuGame: createButton('Search Ryuugames', urls.ryuuGame, icons.ryuugames), ryuuPID: createButton('Ryuu Product ID', urls.ryuuPID, icons.ryuugames, !hasProductId), f95Dev: createButton('Search Developer on F95Zone', urls.f95zoneDev, icons.f95zone), ryuuDev: createButton('Search Developer on Ryuugames', urls.ryuuDev, icons.ryuugames), otomiDev: createButton('Search Developer on OtomiGames', urls.otomiDev, icons.otomi) }; // Append buttons in a dropdown menu const titleContainer = document.querySelector('header.entry-header'); if (titleContainer) { const dropdownContainer = document.createElement('div'); dropdownContainer.className = 'dropdown-container'; // Create toggle button const toggleButton = document.createElement('button'); toggleButton.className = 'dropdown-toggle'; toggleButton.textContent = 'Search Options'; toggleButton.addEventListener('click', () => { dropdownContent.classList.toggle('hidden'); toggleButton.classList.toggle('collapsed'); }); // Create dropdown content const dropdownContent = document.createElement('div'); dropdownContent.className = 'dropdown-content'; // F95Zone group const f95Group = document.createElement('div'); f95Group.className = 'button-group'; const f95Label = document.createElement('div'); f95Label.className = 'group-label'; f95Label.textContent = 'F95Zone'; const f95Grid = document.createElement('div'); f95Grid.className = 'button-group-grid'; [buttons.f95Game, buttons.f95Forum, buttons.f95PID].forEach(btn => f95Grid.appendChild(btn)); f95Group.appendChild(f95Label); f95Group.appendChild(f95Grid); // OtomiGames group const otomiGroup = document.createElement('div'); otomiGroup.className = 'button-group'; const otomiLabel = document.createElement('div'); otomiLabel.className = 'group-label'; otomiLabel.textContent = 'OtomiGames'; const otomiGrid = document.createElement('div'); otomiGrid.className = 'button-group-grid'; [buttons.otomiGame, buttons.otomiPID].forEach(btn => otomiGrid.appendChild(btn)); otomiGroup.appendChild(otomiLabel); otomiGroup.appendChild(otomiGrid); // Ryuugames group const ryuuGroup = document.createElement('div'); ryuuGroup.className = 'button-group'; const ryuuLabel = document.createElement('div'); ryuuLabel.className = 'group-label'; ryuuLabel.textContent = 'Ryuugames'; const ryuuGrid = document.createElement('div'); ryuuGrid.className = 'button-group-grid'; [buttons.ryuuGame, buttons.ryuuPID].forEach(btn => ryuuGrid.appendChild(btn)); ryuuGroup.appendChild(ryuuLabel); ryuuGroup.appendChild(ryuuGrid); // Append groups to dropdown content dropdownContent.appendChild(f95Group); dropdownContent.appendChild(otomiGroup); dropdownContent.appendChild(ryuuGroup); // Append toggle button and content to container dropdownContainer.appendChild(toggleButton); dropdownContainer.appendChild(dropdownContent); titleContainer.appendChild(dropdownContainer); } // Append developer buttons if (developerLi) { const devWrapper = document.createElement('span'); devWrapper.className = 'circle-btn-wrapper'; [buttons.f95Dev, buttons.ryuuDev, buttons.otomiDev].forEach(btn => devWrapper.appendChild(btn)); developerLi.insertAdjacentElement('afterend', devWrapper); } } // Use MutationObserver to detect when required elements are available const observer = new MutationObserver((mutations, obs) => { const titleEl = document.querySelector('h1.post-title.entry-title a'); const developerLi = Array.from(document.querySelectorAll('ul.wp-block-list li')).find(li => li.textContent.includes('Publisher:') || li.textContent.includes('Developer:') ); if (titleEl && developerLi) { addSearchButtons(); obs.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); // Fallback to run after a short delay for both old and new pages setTimeout(() => { const titleEl = document.querySelector('h1.post-title.entry-title a'); const developerLi = Array.from(document.querySelectorAll('ul.wp-block-list li')).find(li => li.textContent.includes('Publisher:') || li.textContent.includes('Developer:') ); if (titleEl && developerLi && !document.querySelector('.dropdown-container')) { addSearchButtons(); } }, 3000); // 3-second delay for reliability })();