Last active
September 3, 2025 08:08
-
-
Save overflowy/628223ea43782c3bd102dfe3b9163a34 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==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(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment