您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Extension to fix Ryuugames titles and highlight links
// ==UserScript== // @name Ryuugames Fixer // @namespace http://tampermonkey.net/ // @version 0.1 // @description Extension to fix Ryuugames titles and highlight links // @author Olexandro // @icon data:image/png;base64, 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 // @match https://www.ryuugames.com/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; function log(message) { console.log(message); } // Function to check if a version looks like a date function isDateLikeVersion(version) { // Remove any prefix like 'v', 'ver', 'version', etc. (case-insensitive) let versionNum = version; // Check for 'v' prefix (case-insensitive) if (versionNum.toLowerCase().startsWith('v')) { versionNum = versionNum.substring(1); } // Check for other prefixes like 'ver', 'version', etc. else if (versionNum.toLowerCase().match(/^(ver|version)/)) { // Find the index where the numbers start const numIndex = versionNum.search(/\d/); if (numIndex > 0) { versionNum = versionNum.substring(numIndex); } } // Skip further checks if versionNum doesn't contain numbers or dots if (!/[\d.]/.test(versionNum)) { return false; } // Check if version contains spaces, which might indicate a complex version number if (versionNum.includes(' ')) { return false; } // FIRST check for common software version patterns and exclude them const parts = versionNum.split('.'); // Common software version patterns: // 1. X.Y.Z format where all are single digits (e.g., 1.7.1) // 2. X.Y format with only 2 parts // Check for X.Y.Z format with single digits in Y and Z if (parts.length === 3) { // Pattern like 1.7.1 or 2.0.3 (common for software) if (parts[1].length === 1 && parts[2].length === 1) { return false; } // If any part is larger than 31, it can't be a day in a date if (parts.some(part => parseInt(part, 10) > 31)) { return false; } } // Only 2 parts is almost certainly a version, not a date (1.7, 2.0) if (parts.length === 2) { return false; } // NOW check for date patterns // Function to check if a string looks like a valid day, month or year const isValidDatePart = (part, type) => { const num = parseInt(part, 10); if (isNaN(num)) return false; if (type === 'day') { return num >= 1 && num <= 31; } else if (type === 'month') { return num >= 1 && num <= 12; } else if (type === 'year') { // Years can be 2-digit (like 22) or 4-digit (like 2022) return (num >= 0 && num <= 99) || (num >= 1900 && num <= 2099); } return false; }; // If exactly 3 parts, check if they look like a date if (parts.length === 3) { // For specific patterns like YY.MM.DD with 2-digit parts, strongly favor date if (parts.every(part => part.length === 2 && /^\d{2}$/.test(part))) { // Check for valid date parts const p1 = parseInt(parts[0], 10); const p2 = parseInt(parts[1], 10); const p3 = parseInt(parts[2], 10); // If middle part is 1-12 (month) and the other parts are reasonable if (p2 >= 1 && p2 <= 12 && p1 >= 1 && p1 <= 31 && p3 >= 0 && p3 <= 99) { return true; } } // For versions like 25.04.06 (very likely a date) if (parts[0].length === 2 && parts[1].length === 2 && parts[2].length === 2) { // Check if middle part looks like a month (01-12) const month = parseInt(parts[1], 10); if (month >= 1 && month <= 12) { return true; } } } return false; } // Function to clean the final title string function cleanTitle(title) { // Replace colons with tildes let cleanedTitle = title.replace(/:/g, ' ~ '); // Replace multiple spaces with a single space cleanedTitle = cleanedTitle.replace(/\s+/g, ' '); // Replace "Uncensored" with "UNC" cleanedTitle = cleanedTitle.replace(/Uncensored/gi, 'UNC'); return cleanedTitle.trim(); } // Function to modify the title element function modifyTitle() { // Find the title element const titleElement = document.querySelector('h1.entry-title'); if (titleElement) { let originalText = titleElement.textContent; // Extract parts of the title let titleName = originalText; let rjCode = ""; let versions = []; let languageTag = ""; // Extract the language tag [ENG] const langTagMatch = titleName.match(/\[ENG\]/i); if (langTagMatch) { languageTag = langTagMatch[0]; titleName = titleName.replace(langTagMatch[0], '').trim(); } // Extract the RJ code const rjCodeMatch = titleName.match(/\(RJ\d+(?:\/RJ\d+)*\)/); if (rjCodeMatch) { rjCode = rjCodeMatch[0]; // Replace slashes with plus signs in RJ codes rjCode = rjCode.replace(/\/RJ/g, '+RJ'); titleName = titleName.replace(rjCodeMatch[0], '').trim(); } // Extract all versions - improved regex to catch versions with letters (e.g., 1.0.0a) // Look for patterns like (v1.0), (v1.0a), (ver1.0), (Ver1.0.0a), (V1.0), etc. const versionRegex = /\((v|ver|Ver|V|VER|VERSION|Version)?\.?\s*(\d+(?:\.\d+)*(?:[a-z]+)?(?:\s+\d+(?:\.\d+)*(?:[a-z]+)?)*)\)/gi; // Find all version patterns let match; while ((match = titleName.match(versionRegex)) !== null) { if (!match || match.length === 0) break; const fullMatch = match[0]; // Extract the version number with possible letter suffix const versionNumMatch = fullMatch.match(/\((?:(v|ver|Ver|V|VER|VERSION|Version))?\.?\s*([\d\s\.\w]+)\)/i); if (versionNumMatch) { const prefixFromMatch = versionNumMatch[1] || ''; const versionNum = versionNumMatch[2]; // Set the prefix based on what was found let prefix = "v"; // Default prefix if (prefixFromMatch) { // If it's a single letter prefix, standardize to lowercase 'v' if (prefixFromMatch.toLowerCase() === 'v') { prefix = "v"; } else { // For longer prefixes like 'ver', 'version', keep them as is prefix = prefixFromMatch; } } // Replace spaces with underscores in version numbers const versionNumFormatted = versionNum.replace(/\s+/g, '_'); // Add version to versions array with appropriate prefix const formattedVersion = `${prefix}${versionNumFormatted}`; // Add to versions array without additional parentheses versions.push(formattedVersion); // Remove this version from title name titleName = titleName.replace(fullMatch, '').trim(); } } // Check for "Uncensored" text in title and move it to versions list const uncensoredMatch = titleName.match(/\(Uncensored\)|[^\(]Uncensored[^\)]/i); let hasUncensored = false; if (uncensoredMatch) { // Удаляем Uncensored вместе со скобками, если они есть if (uncensoredMatch[0].includes('(Uncensored)')) { titleName = titleName.replace(/\(Uncensored\)/i, '').trim(); } else { titleName = titleName.replace(/Uncensored/i, '').trim(); } hasUncensored = true; } // Separate versions into regular versions and date-like versions const regularVersions = []; const dateLikeVersions = []; versions.forEach(version => { const isDate = isDateLikeVersion(version); if (isDate) { dateLikeVersions.push(version); } else { regularVersions.push(version); } }); // Construct the new title in the required format let newTitle = ""; // Add language tag at the very beginning if it exists if (languageTag) { newTitle += `${languageTag} `; } // Add RJ code next if (rjCode) { newTitle += `${rjCode} `; } // Add the title name newTitle += titleName; // Add UNC tag after title but before versions if (hasUncensored) { newTitle += ` UNC`; } // Add regular versions first if (regularVersions.length > 0) { newTitle += ` ${regularVersions.join(' ')}`; } // Add date-like versions at the very end if (dateLikeVersions.length > 0) { newTitle += ` ${dateLikeVersions.join(' ')}`; } // Clean the title (replace colons with tildes, remove multiple spaces) newTitle = cleanTitle(newTitle); // Update the title titleElement.textContent = newTitle; } } // Function to highlight non-ryuugames links function highlightNonRyuugamesLinks(container) { // Add styles for highlighted links const style = document.createElement('style'); style.textContent = ` .non-ryuugames-link { background-color: #ffeb3b; padding: 2px 4px; border-radius: 3px; border: 1px solid #fbc02d; } `; document.head.appendChild(style); // Find all links in the container const allLinks = container.querySelectorAll('a'); allLinks.forEach(link => { const href = link.href.toLowerCase(); if (!href.includes('ryuugames.com')) { link.classList.add('non-ryuugames-link'); } }); } // Function to highlight ryuugames links function highlightRyuugamesLinks() { // First find the div with dir="ltr" const ltrDiv = document.querySelector('div[dir="ltr"]'); if (!ltrDiv) { return; } // Find all links to ryuugames.com within this div const ryuugamesLinks = ltrDiv.querySelectorAll('a[href*="www.ryuugames.com"]'); if (!ryuugamesLinks || ryuugamesLinks.length === 0) { return; } // Get the highest parent div of the first found link let currentElement = ryuugamesLinks[0]; let highestDiv = null; // Traverse up the DOM tree until we reach the ltr div while (currentElement && currentElement !== ltrDiv) { if (currentElement.tagName === 'DIV') { // Store the first div we find (it will be the highest one) if (!highestDiv) { highestDiv = currentElement; } } currentElement = currentElement.parentElement; } // Highlight non-ryuugames links in the found div if (highestDiv) { highlightNonRyuugamesLinks(highestDiv); } } // Function to convert text URLs to clickable links function convertTextToLinks() { // Target the main content area instead of a specific container // This will find all possible paragraphs containing URLs const containers = document.querySelectorAll('.td-post-content'); if (!containers || containers.length === 0) { return; } // Process each container containers.forEach((container, containerIndex) => { // Find all paragraph elements and div elements (which might contain text with URLs) const elements = container.querySelectorAll('p, div'); // Regular expression to match URLs but avoid HTML tags // This looks for http/https/www URLs but stops matching at HTML tag characters const urlRegex = /(https?:\/\/[^<>\s]+)|(www\.[^<>\s]+\.[^<>\s]+)/gi; // Process each element elements.forEach((element, index) => { // Function to process text nodes only const processTextNodes = (node) => { if (node.nodeType === Node.TEXT_NODE) { // Only process text nodes for URL replacement const textContent = node.textContent; // Skip empty or whitespace-only text nodes if (!textContent.trim()) { return false; } // Check if the text contains a URL if (!urlRegex.test(textContent)) { urlRegex.lastIndex = 0; // Reset regex state return false; } // Reset regex state urlRegex.lastIndex = 0; // Replace URLs in text const newContent = textContent.replace(urlRegex, (match) => { let href = match; // Add https:// to URLs starting with www. if (match.toLowerCase().startsWith('www.')) { href = 'https://' + match; } // Remove .html from dlsite URLs if (href.toLowerCase().includes('dlsite') && href.toLowerCase().endsWith('.html')) { href = href.substring(0, href.length - 5); // Remove .html } return `<a href="${href}" target="_blank">${match}</a>`; }); if (textContent !== newContent) { // Create a temporary container const fragment = document.createElement('div'); fragment.innerHTML = newContent; // Replace the text node with the new content const parent = node.parentNode; while (fragment.firstChild) { parent.insertBefore(fragment.firstChild, node); } parent.removeChild(node); return true; // Changes were made } } else if (node.nodeType === Node.ELEMENT_NODE) { // Skip if this is already a link or an element inside a link let parent = node; while (parent) { if (parent.nodeName === 'A') { return false; } parent = parent.parentNode; } // Recursively process child nodes let changed = false; Array.from(node.childNodes).forEach(child => { changed = processTextNodes(child) || changed; }); return changed; } return false; }; // Create a clone to work with const elementClone = element.cloneNode(true); // Process all text nodes in the element const changed = processTextNodes(elementClone); // If changes were made, update the original element if (changed) { element.innerHTML = elementClone.innerHTML; } }); }); } // Main execution function function main() { modifyTitle(); convertTextToLinks(); highlightRyuugamesLinks(); } // Run the modification when the page is loaded document.addEventListener('DOMContentLoaded', main); // Sometimes DOMContentLoaded might have already fired, so run immediately as well if (document.readyState === 'loading') { // Document is still loading, wait for DOMContentLoaded document.addEventListener('DOMContentLoaded', main); } else { // Document has already loaded, run immediately main(); } })();