google-search-plus.user.js

5 min read Original article ↗
// ==UserScript== // @name Google Search + // @namespace http://tampermonkey.net/ // @version 1.9 // @description Extend Google Search with additional options. // @author overflowy // @match https://www.google.com/search* // @match https://www.google.*/search* // @run-at document-start // @grant none // ==/UserScript== (function() { 'use strict'; // Function to extract search query from URL function getSearchQuery() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('q') || ''; } // Function to create button container function createButtonContainer() { if (document.getElementById('search-buttons-container')) { return document.getElementById('search-buttons-container'); } const container = document.createElement('div'); container.id = 'search-buttons-container'; container.style.cssText = ` display: flex; gap: 8px; margin-bottom: 16px; align-items: center; `; return container; } // Universal function to create buttons (both single and split) function createButton(config) { // Check if button already exists if (document.getElementById(config.id)) return null; // Handle split button if (config.split) { const buttonContainer = document.createElement('div'); buttonContainer.id = config.id; buttonContainer.style.cssText = ` display: inline-flex; border-radius: 3px; overflow: hidden; box-shadow: 0 1px 2px rgba(0,0,0,0.1); transition: all 0.2s ease; `; config.split.forEach((splitConfig, index) => { const splitButton = document.createElement('button'); splitButton.innerHTML = splitConfig.text; splitButton.style.cssText = ` background-color: ${config.backgroundColor}; color: white; border: none; padding: 6px 10px; cursor: pointer; font-size: 12px; display: inline-flex; align-items: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; line-height: 1; ${index < config.split.length - 1 ? 'border-right: 1px solid rgba(255,255,255,0.2);' : ''} `; // Add hover effects splitButton.addEventListener('mouseenter', () => { splitButton.style.backgroundColor = config.hoverColor; buttonContainer.style.transform = 'translateY(-1px)'; buttonContainer.style.boxShadow = '0 2px 4px rgba(0,0,0,0.15)'; }); splitButton.addEventListener('mouseleave', () => { splitButton.style.backgroundColor = config.backgroundColor; buttonContainer.style.transform = 'translateY(0)'; buttonContainer.style.boxShadow = '0 1px 2px rgba(0,0,0,0.1)'; }); // Add click handler splitButton.addEventListener('click', splitConfig.onClick); buttonContainer.appendChild(splitButton); }); return buttonContainer; } else { // Handle single button const button = document.createElement('button'); button.id = config.id; button.innerHTML = config.text; button.style.cssText = ` background-color: ${config.backgroundColor}; color: white; border: none; padding: 6px 12px; border-radius: 3px; cursor: pointer; font-size: 12px; display: inline-flex; align-items: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; transition: all 0.2s ease; box-shadow: 0 1px 2px rgba(0,0,0,0.1); line-height: 1; white-space: nowrap; `; // Add hover effects button.addEventListener('mouseenter', () => { button.style.backgroundColor = config.hoverColor; button.style.transform = 'translateY(-1px)'; button.style.boxShadow = '0 2px 4px rgba(0,0,0,0.15)'; }); button.addEventListener('mouseleave', () => { button.style.backgroundColor = config.backgroundColor; button.style.transform = 'translateY(0)'; button.style.boxShadow = '0 1px 2px rgba(0,0,0,0.1)'; }); // Add click handler button.addEventListener('click', config.onClick); return button; } } // Function to add all buttons function addSearchButtons() { const query = getSearchQuery(); if (!query) return; // Create button container const container = createButtonContainer(); // Button configurations const buttonConfigs = [ { id: 'reddit-button', text: 'Reddit', backgroundColor: '#ff4500', hoverColor: '#e63d00', onClick: () => { const currentQuery = getSearchQuery(); let redditQuery; // Check if site:reddit.com is already in the query if (currentQuery.toLowerCase().includes('site:reddit.com')) { redditQuery = currentQuery; } else { redditQuery = `${currentQuery} site:reddit.com`; } const googleUrl = `${window.location.origin}/search?q=${encodeURIComponent(redditQuery)}`; window.location.href = googleUrl; } }, { id: 'perplexity-button', text: 'Perplexity', backgroundColor: '#32b8c6', hoverColor: '#2a9da8', onClick: () => { const perplexityUrl = `https://www.perplexity.ai/search?q=${encodeURIComponent(query)}`; window.open(perplexityUrl, '_blank'); } }, { id: 'hn-button-container', backgroundColor: '#ff6600', hoverColor: '#e55a00', split: [ { text: 'HN Posts', onClick: () => { const algoliaUrl = `https://hn.algolia.com/?dateRange=all&page=0&prefix=false&query=${encodeURIComponent(query)}&sort=byPopularity&type=story`; window.open(algoliaUrl, '_blank'); } }, { text: 'Ask HN', onClick: () => { const hackerSearchUrl = `https://hackersearch.net/ask?q=${encodeURIComponent(query)}`; window.open(hackerSearchUrl, '_blank'); } } ] }, { id: 'youtube-button', text: 'YouTube', backgroundColor: '#ff0000', hoverColor: '#cc0000', onClick: () => { const youtubeUrl = `https://www.youtube.com/results?search_query=${encodeURIComponent(query)}`; window.open(youtubeUrl, '_blank'); } }, { id: 'maps-button', text: 'Maps', backgroundColor: '#34a853', hoverColor: '#2d8f47', onClick: () => { const mapsUrl = `https://www.google.com/maps/search/${encodeURIComponent(query)}`; window.open(mapsUrl, '_blank'); } }, { id: 'annas-archive-button', text: "Anna's Archive", backgroundColor: '#8b5a3c', hoverColor: '#744a33', onClick: () => { const annasArchiveUrl = `https://annas-archive.org/search?q=${encodeURIComponent(query)}`; window.open(annasArchiveUrl, '_blank'); } } ]; // Create and add all buttons buttonConfigs.forEach(config => { const button = createButton(config); if (button) { container.appendChild(button); } }); // Only add container if it has buttons and isn't already in DOM if (container.children.length > 0 && !document.getElementById('search-buttons-container')) { // Find a suitable place to insert the container const searchContainer = document.querySelector('#search') || document.querySelector('#main') || document.querySelector('#center_col'); if (searchContainer) { // Try to place it near the search results info const resultStats = document.querySelector('#result-stats'); if (resultStats) { resultStats.parentNode.insertBefore(container, resultStats.nextSibling); } else { // Fallback: insert at the beginning of the search container searchContainer.insertBefore(container, searchContainer.firstChild); } } } } // Enhanced initialization with multiple strategies function initializeScript() { // Try immediate execution if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', addSearchButtons); } else { addSearchButtons(); } // Also try when page fully loads if (document.readyState !== 'complete') { window.addEventListener('load', addSearchButtons); } // Enhanced mutation observer for faster detection const observer = new MutationObserver((mutations) => { let shouldCheck = false; for (const mutation of mutations) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.nodeType === 1 && // Element node (node.id === 'search' || node.id === 'main' || node.querySelector && node.querySelector('#search, #main'))) { shouldCheck = true; break; } } } if (shouldCheck) break; } if (shouldCheck) { setTimeout(addSearchButtons, 10); } }); observer.observe(document.documentElement, { childList: true, subtree: true }); // URL change detection (for SPA behavior) let lastUrl = location.href; const urlObserver = new MutationObserver(() => { const url = location.href; if (url !== lastUrl) { lastUrl = url; setTimeout(addSearchButtons, 50); } }); urlObserver.observe(document, {subtree: true, childList: true}); } // Start immediately initializeScript(); })();