|
// ==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(); |
|
})(); |