// ==UserScript==
// @name Roblox Charts Search
// @namespace https://github.com/CountMyBands/Userscripts
// @version 1.1
// @description Adds a search bar to filter games on the Roblox Top Earning charts page
// @author CountMyBands
// @homepageURL https://github.com/CountMyBands/Userscripts
// @icon https://www.roblox.com/favicon.ico
// @match https://www.roblox.com/*
// @grant none
// @run-at document-idle
// @downloadURL https://raw.githubusercontent.com/CountMyBands/Userscripts/main/search.user.js
// @updateURL https://raw.githubusercontent.com/CountMyBands/Userscripts/main/search.user.js
// ==/UserScript==
(function() {
'use strict';
console.log("Search started.");
// ===========================
// 1. Inject Custom CSS for Search Bar
// ===========================
const style = document.createElement('style');
style.innerHTML = `
.roblox-top-earning-search-container {
margin: 10px 0; /* Space above and below the search bar */
padding-left: 10px;
text-align: left; /* Align contents to the left */
width: 100%; /* Ensure the container spans the full width */
box-sizing: border-box; /* Include padding in the element's total width */
}
.roblox-top-earning-search-input {
width: 300px;
max-width: 80%;
padding: 8px 12px;
font-size: 16px;
border: 2px solid #ccc;
border-radius: 4px;
outline: none;
transition: border-color 0.3s;
}
.roblox-top-earning-search-input:focus {
border-color: white; /* Highlight border on focus */
}
`;
document.head.appendChild(style);
// ===========================
// 2. Create and Insert Search Bar
// ===========================
function createSearchBar() {
// Check if search bar already exists
if (document.querySelector('.roblox-top-earning-search-container')) {
console.log("Search bar already exists. Skipping creation.");
return;
}
// Create container
const searchContainer = document.createElement('div');
searchContainer.className = 'roblox-top-earning-search-container';
// Create input
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Search games...';
searchInput.className = 'roblox-top-earning-search-input';
// Append input to container
searchContainer.appendChild(searchInput);
// Insert the search container below the
Top Earning
const heading = document.querySelector('h1');
if (heading && heading.parentNode) {
heading.parentNode.insertBefore(searchContainer, heading.nextSibling);
console.log("Search bar inserted successfully.");
} else {
console.log("Heading not found. Retrying to insert search bar.");
setTimeout(createSearchBar, 1000); // Retry after a short delay if the heading isn't found yet
return;
}
// Add event listener for search input with debouncing
let debounceTimeout;
searchInput.addEventListener('input', function() {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() => {
const query = this.value.trim().toLowerCase();
filterGameCards(query);
}, 300); // 300ms delay
});
}
// ===========================
// 3. Function to Filter Game Cards
// ===========================
function filterGameCards(query) {
const gameGrid = document.querySelector('div[data-testid="game-grid"]');
if (!gameGrid) {
console.log("Game grid not found for filtering.");
return;
}
const gameCards = gameGrid.querySelectorAll('div[data-testid="game-tile"]');
gameCards.forEach(card => {
const gameTitleElem = card.querySelector('.game-card-name.game-name-title');
if (gameTitleElem) {
const title = gameTitleElem.textContent.toLowerCase();
if (title.includes(query)) {
card.style.display = ''; // Show the card
} else {
card.style.display = 'none'; // Hide the card
}
}
});
}
// ===========================
// 4. Observe Game Grid for Dynamic Changes
// ===========================
function observeGameGrid() {
const gameGrid = document.querySelector('div[data-testid="game-grid"]');
if (!gameGrid) {
console.log("Game grid not found for observation.");
return;
}
const observer = new MutationObserver(() => {
console.log("Detected changes in the game grid. Reapplying filters.");
const searchInput = document.querySelector('.roblox-top-earning-search-input');
if (searchInput) {
const query = searchInput.value.trim().toLowerCase();
filterGameCards(query);
}
});
observer.observe(gameGrid, { childList: true, subtree: false });
console.log("MutationObserver set up on game grid.");
}
// ===========================
// 5. Initialize Search Bar
// ===========================
function initialize() {
console.log("Initializing Search script...");
const observer = new MutationObserver(() => {
if (window.location.href.includes('/charts')) {
const heading = document.querySelector('h1');
const gameGrid = document.querySelector('div[data-testid="game-grid"]');
// Check if search bar is already there
const existingSearchBar = document.querySelector('.roblox-top-earning-search-container');
if (heading && gameGrid && !existingSearchBar) {
console.log("Required elements found. Inserting search bar.");
createSearchBar();
observeGameGrid();
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
// Initial check
if (window.location.href.includes('/charts')) {
const heading = document.querySelector('h1');
const gameGrid = document.querySelector('div[data-testid="game-grid"]');
const existingSearchBar = document.querySelector('.roblox-top-earning-search-container');
if (heading && gameGrid && !existingSearchBar) {
console.log("Required elements found immediately.");
createSearchBar();
observeGameGrid();
}
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
initialize();
}
})();