This user script automatically finds random words from the current search r###lts and searches Bing with them. Additionally, it auto clicks the unclaimed daily points from your rewards dashboard too.
// ==UserScript== // @name AutoGrind: Intelligent Bing Rewards Auto-Grinder // @namespace https://github.com/jeryjs/ // @version 5.2.4 // @description This user script automatically finds random words from the current search r###lts and searches Bing with them. Additionally, it auto clicks the unclaimed daily points from your rewards dashboard too. // @icon https://www.bing.com/favicon.ico // @author Jery // @match https://www.bing.com/search* // @match https://rewards.bing.com/* // @grant GM_getValue // @grant GM_setValue // @license MIT // ==/UserScript== /*=============================================*\ |* CONFIGURATION *| \*=============================================*/ // Constants var MAX_SEARCHES = GM_getValue("max-searches", 33); // Maximum number of words to search var TIMEOUT_RANGE = GM_getValue("timeout-range", [5, 10]); // Randomize the time to wait between searches var COOLDOWN_TIMEOUT = GM_getValue("cooldown-timeout", 15); // Cooldown_Timeout between searches var UNDER_COOLDOWN = GM_getValue("under-cooldown", false); // Workaround for cooldown restriction var OPEN_RANDOM_LINKS = GM_getValue("open-random-links", true); // Simulate real human searcg by opening links var COLLECT_DAILY_ACTIVITY = GM_getValue("collect-daily-activity", false); // Automatically collect daily activity points from bingo rewards dashboard page var AUTO_CLOSE_TABS = GM_getValue("auto-close-tabs", true); // Automatically close any tabs/windows opened by the script var TIMEOUT = (Math.floor(Math.random() * (TIMEOUT_RANGE[1] - TIMEOUT_RANGE[0]) * 1000) + TIMEOUT_RANGE[0] * 1000); // Randomize the timeout with given range // Configuration options for the user script const configurations = [ { id: "max-searches", name: "Max Searches", type: "slider", value: MAX_SEARCHES, range: [3, 50], description: "The maximum number of searches to make.<br>Default: 33", }, { id: "timeout-range", name: "Random Timeout Range", type: "range", value: TIMEOUT_RANGE, range: [1, 60], description: "Randomise the time to wait between searches (in seconds).<br>Example: 5-10 makes the script wait between 5 to 10 seconds before going to next search.<br>Setting it below 5 is not advised as bing limits how often points are awarded searches.<br>Default: 5-10", }, { id: "under-cooldown", name: "Under Cooldown Workaround", type: "checkbox", value: UNDER_COOLDOWN, description: "Enable this option if you are facing the 15 min cooldown restriction.<br>For some accounts, Bing restricts the points earned to 9-12 points every 15 minutes. Enabling this option makes the script wait 15 mins after every 4 searches..<br>Default: False", }, { id: "cooldown-timeout", name: "Cooldown Timeout", type: "slider", value: COOLDOWN_TIMEOUT, range: [3, 30], disabled: !UNDER_COOLDOWN, description: "The Cooldown timeout between every 4th search (in seconds).<br> Under Cooldown must be enabled for this option to become active.<br>Default: 15", }, { id: "open-random-links", name: "Open Random Links", type: "checkbox", value: OPEN_RANDOM_LINKS, description: "Enable this option to open any random link from the page in an iframe after every search. It has been observed that doing this removes the 15-point restriction after a while / reduces chances of getting the restriction.<br>Default: True", }, { id: "collect-daily-activity", name: "Daily Activity Points", type: "checkbox", value: COLLECT_DAILY_ACTIVITY, description: "Open rewards page and auto-collect daily activity points from the Bing rewards dashboard page. This option loads the activities into an iframe to earn the points.<br>Default: False", }, { id: "auto-close-tabs", name: "Auto Close Tabs", type: "checkbox", value: AUTO_CLOSE_TABS, description: "Automatically close any tabs/windows opened by the script. This applies to the search page and any rewards page that were opened as well.<br>Default: True", } ]; // Load previous searches from local storage or initialize an empty array var searches = GM_getValue("searches", []); // store the list of window handles or urls to close them later var tabsToClose = GM_getValue("tabsToClose", []); // Adjust timeout if under cooldown and within search limit if (UNDER_COOLDOWN && searches.length % 4 == 0 && searches.length <= MAX_SEARCHES - 4) { TIMEOUT = COOLDOWN_TIMEOUT * 60000; // mins * 60 secs } // Check if the current page is Bing search page const isSearchPage = window.location.href.startsWith("https://www.bing.com/search"); // Check if the current page is Bing rewards page const isRewardPage = window.location.href.startsWith("https://rewards.bing.com"); // Check whether current device is a mobile or not const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); /*=============================================*\ |* MAIN UI *| \*=============================================*/ /** * Create a container for the auto-search icon and settings icon. * The auto-search icon starts the search process, and the settings icon opens the settings overlay. */ const autoSearchContainer = document.createElement("div"); autoSearchContainer.classList.add("auto-search-container"); const searchIcon = document.createElement("div"); searchIcon.classList.add("search-icon"); if(!isRewardPage) searchIcon.innerHTML = `<a style="font-size: 25px">🔍</a><span>Auto-Search</span>`; searchIcon.title = "Start Auto-Search!!"; searchIcon.addEventListener("click", startSearch); const settingsIcon = document.createElement("div"); settingsIcon.classList.add("settings-icon"); if(!isRewardPage) settingsIcon.innerHTML = `<a style="font-size: 20px;">⚙️</a><span>Configure</span>`; autoSearchContainer.appendChild(searchIcon); autoSearchContainer.appendChild(settingsIcon); setTimeout(() => { if (searchIcon.textContent.includes("Auto-Search")) searchIcon.classList.add("shrink") }, 3000); /** * Create a settings overlay to configure the user script. * The settings overlay contains a list of configuration options that can be adjusted by the user. * The settings are stored in the local storage and are used to update the script's behavior. */ const settingsOverlay = document.createElement("div"); settingsOverlay.classList.add("settings-overlay"); settingsOverlay.style = `position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: none; justify-content: center; align-items: center; z-index: 1000;`; const settingsContent = document.createElement("div"); settingsContent.style = `background-color: white; padding: 20px; border-radius: 10px; display: flex; flex-direction: column;`; configurations.forEach(config => { const settingItem = document.createElement("div"); settingItem.classList.add("settings-item"); const name = document.createElement("div"); name.classList.add("settings-item-name"); name.textContent = config.name; const inputContainer = document.createElement("div"); inputContainer.classList.add("settings-item-input"); let input; if (config.type == "slider") { input = document.createElement("input"); input.type = "range"; input.min = config.range[0]; input.max = config.range[1]; input.value = GM_getValue(config.id, config.value); input.disabled = config.disabled; } else if (config.type == "range") { input = document.createElement("div"); input.classList.add("range-slider"); input.value = GM_getValue(config.id, config.value); input.valueText = input.value.join("-"); for (let i = 0; i < 2; i++) { let rangeInput = document.createElement("input"); Object.assign(rangeInput, { type: "range", value: config.value[i], min: config.range[0], max: config.range[1], style: `height: 1px;` }); input.appendChild(rangeInput); rangeInput.addEventListener("input", () => { if (parseInt(input.children[0].value) > parseInt(input.children[1].value)) input.children[i].value = input.children[1 - i].value; // Ensure min <= max input.value = [input.children[0].value, input.children[1].value]; input.valueText = input.value.join("-"); }); } } else if (config.type == "checkbox") { input = document.createElement("input"); input.type = "checkbox"; input.checked = GM_getValue(config.id, config.value); input.oninput = () => input.valueText = input.checked ? "Enabled" : "Disabled"; } input.id = config.id; input.dispatchEvent(new Event("input")); // Trigger input event to initialize `input.valueText` const currentValue = document.createElement("div"); currentValue.classList.add("settings-item-value"); currentValue.textContent = input.valueText??input.value; input.addEventListener("input", () => { GM_setValue(config.id, input.type == "checkbox" ? input.checked : input.value); currentValue.textContent = input.valueText??input.value; updateConfigVariable(config.id, input.type == "checkbox" ? input.checked : input.value); }); inputContainer.appendChild(input); const description = document.createElement("div"); description.classList.add("settings-item-description"); description.innerHTML = config.description; settingItem.appendChild(name); settingItem.appendChild(inputContainer); settingItem.appendChild(currentValue); settingItem.appendChild(description); settingsContent.appendChild(settingItem); }); const closeButton = document.createElement("button"); closeButton.textContent = "Close"; closeButton.style = `align-self: center; margin-top: 10px; padding: 5px 10px; border-radius: 5px; background-color: lightgray;`; closeButton.addEventListener("click", () => { settingsOverlay.style.display = "none"; }); settingsContent.appendChild(closeButton); settingsOverlay.appendChild(settingsContent); document.body.appendChild(settingsOverlay); settingsIcon.addEventListener("click", () => { settingsOverlay.style.display = "flex"; }); // Add logic to enable/disable the cooldown-timeout input document.getElementById("under-cooldown").addEventListener("change", (event) => { const cooldownInput = document.getElementById("cooldown-timeout"); cooldownInput.disabled = !event.target.checked; }); /** * This function updates the icon's appearance with the specified content and classlist. * @param {string} content - The content to display in the icon. * @param {string} classlist - The classlist to apply to the icon. */ function updateIcon(content, classlist="searching") { searchIcon.classList.add(classlist); settingsIcon.classList.add(classlist); searchIcon.querySelector("span").textContent = content; } /** * This function updates the configuration variables based on the user's input in the settings overlay. * This is required only for configurations that require immediate changes before reloading the tab like the `Max Searches` option. * @param {string} id - The id of the configuration variable to update. * @param {string} value - The new value of the configuration variable. */ function updateConfigVariable(id, value) { if (id === "max-searches") MAX_SEARCHES = parseInt(value); else if (id === "cooldown-timeout") COOLDOWN_TIMEOUT = parseInt(value); else if (id === "under-cooldown") UNDER_COOLDOWN = value == "true"; } /*=============================================*\ |* HELPER FUNCTIONS *| \*=============================================*/ /** * Perform a search for dynamically extracted random words from descriptions. * This function finds all the r###lt elements on the page and extracts random words from their text content. * The extracted words are stored in the searches array, which is then used to perform searches on Bing. * The number of searches is limited to [MAX_SEARCHES]. * If [COLLECT_DAILY_ACTIVITY] is enabled, the Bing rewards page is opened in a new tab to collect daily activity points. * A search term is opened in the current tab to start the search process. */ function startSearch() { searches = []; GM_setValue("searches", searches); tabsToClose = []; GM_setValue("tabsToClose", tabsToClose); const r###ltElements = document.querySelectorAll(".b_caption p"); while (searches.length < MAX_SEARCHES) { r###ltElements.forEach((element) => { const text = element.textContent.trim().split(/\s+/); for (let i = 0; i < text.length; i += Math.floor(Math.random() * 5) + 1) { const chunk = text.slice(i, i + Math.floor(Math.random() * 5) + 1).join(" "); if (chunk && chunk !== "...") searches.push(chunk); if (searches.length >= MAX_SEARCHES) break; } if (searches.length >= MAX_SEARCHES) return; }); } searches = [...new Set(searches)]; // Remove duplicates searches = searches.slice(0, MAX_SEARCHES); // Extract up to MAX_SEARCHES console.log(searches); GM_setValue("searches", searches); if (COLLECT_DAILY_ACTIVITY) window.open(`https://rewards.bing.com/?ref=rewardspanel`, "_blank"); if (AUTO_CLOSE_TABS) addTabToClose("https://rewards.bing.com/?ref=rewardspanel"); const nextSearchTerm = searches.pop(); GM_setValue("searches", searches); addTabToClose(`https://www.bing.com/search?go=Search&q=${encodeURI(searches[0])}&qs=ds&form=QBRE`); window.open(`https://www.bing.com/search?go=Search&q=${nextSearchTerm}&qs=ds&form=QBRE`, "_self"); } /** * Wait for elements to appear on the page and execute a callback function when they are found. * This function repeatedly checks for the presence of the specified selectors on the page. * Once any of the selectors is found, the callback function is called with the selector as a parameter. * @param {Array} selectors - The selectors to wait for. * @param {Function} callback - The callback function to execute when the selectors are found. */ function waitForElements(selectors, callback) { if (selectors == null) { callback(null); return; } for (let selector of selectors) { if (document.querySelector(selector)) { callback(selector); return; } } setTimeout(() => waitForElements(selectors, callback), 500); } /** * Add a tab to the list of tabs to close after a specified timeout. * This function adds the tab to the [tabsToClose] array and sets a timeout to close the tab. * The tabs to close are stored in the local storage and are closed after the specified timeout. * @param {Window} tab - The tab to close. * @param {number} timeout - The timeout in milliseconds to close the tab. * @example addTabToClose(window.open("https://rewards.bing.com/?ref=rewardspanel", "_blank"), 5000); */ function addTabToClose(tab, timeout=5000) { tabsToClose.push( {"url": tab, "timeout": timeout} ); GM_setValue("tabsToClose", tabsToClose); } /*=============================================*\ |* MAIN SCRIPT *| \*=============================================*/ /** * Wait for the page to load the points element first. * For android, this step is skipped. * Bing seems to have 2 different selectors for the points element * based on which browser is being used, so the possible selectors are * put inside [pointsElem] * In case of mobile, the script skips searching for the element. */ try { if (isSearchPage) { // Add the auto-search icons to the top left corner of the page document.body.appendChild(autoSearchContainer); let pointsElems = isMobile ? null : ["#id_rc", ".points-container"]; waitForElements(pointsElems, function (pointsElem) { /** * If the current URL contains the "&form=STARTSCRIPT" parameter, * the script automatically extracts words from the page and starts the search. * This is a workaround for automating the script locally, without the need for * clicking the start icon. */ if (window.location.href.includes("&form=STARTSCRIPT")) { startSearch(); } /** * If the current URL contains the "&qs=ds&form=QBRE" parameter (which I've noticed that bing sets for all searches), * and the [searches] array is not empty, the script automatically waits * for a few secs (on mobile, it waits fixed time and on desktop it waits until points element is updated), * and then proceeds to the next search in the current tab. */ else if (searches.length > 0 && window.location.href.includes("&qs=ds&form=QBRE")) { updateIcon(`${searches.length} left`); let targetNode = document.querySelector(pointsElem); const observerTimeout = 4000; let observerOptions = {characterData: true, childList: true, subtree: true}; if (pointsElem != null) { let oldTextContent = targetNode.textContent.trim(); let observer = new MutationObserver((mutationsList, observer) => { for (let mutation of mutationsList) { if (mutation.type == "childList" || mutation.type == "characterData") { let newTextContent = targetNode.textContent.trim(); if (newTextContent != oldTextContent) { gotoNextSearch(); observer.disconnect(); clearTimeout(timeoutId); break; } } } }); observer.observe(targetNode, observerOptions); } // Store the timeout ID so it can be cleared by the observer let timeoutId = setTimeout(() => { gotoNextSearch(); }, observerTimeout); /** * Go to the next search after a timeout. * This function updates the icon's appearance with a countdown timer. * After the timeout, it opens the next search in the current tab and updates the searches array in local storage. * If [OPEN_RANDOM_LINKS] is enabled, it also opens a random link from the search r###lts in an iframe. * It's been observed that some links like britannica.com refuse to open in an iframe and end up opening in current window. * As a workaround, such domains are excluded from being opened in an iframe. */ function gotoNextSearch() { countdownTimer(TIMEOUT / 1000); if (OPEN_RANDOM_LINKS) { try { let searchLinks = isMobile ? document.querySelectorAll(".b_algoheader > a") : document.querySelectorAll("li.b_algo h2 a"); // workaround for the britannica bug. const excludeDomains = ["britannica.com", "sunshineseeker.com"]; searchLinks = Array.from(searchLinks).filter(link => !excludeDomains.some(domain => link.closest(".b_algo").querySelector(".b_tpcn div.tpmeta").innerText.includes(domain))); let randLink = searchLinks[Math.floor(Math.random() * searchLinks.length)]; let iframe = document.createElement("iframe"); iframe.name = "randLinkFrame"; iframe.style.width = "100%"; iframe.style.height = "600px"; randLink.parentElement.appendChild(iframe); randLink.target = "randLinkFrame"; randLink.click(); } catch (e) { console.error(e); } } setTimeout(() => { window.open(`https://www.bing.com/search?go=Search&q=${encodeURI(searches.pop())}&qs=ds&form=QBRE`, "_self"); // document.querySelector("textarea.b_searchbox").value = searches.pop(); // document.querySelector("input.b_searchboxSubmit").click(); GM_setValue("searches", searches); }, TIMEOUT); } /** * Start a countdown timer and update the icon's appearance. * This function updates the icon's appearance every second with the remaining time in the countdown. * @param {number} count - The duration of the countdown in seconds. */ function countdownTimer(count) { let c = parseInt(count); const intervalId = setInterval(() => { updateIcon(c, "counting"); if (c == 0) { clearInterval(intervalId); } c--; }, 1000); } } }); } /** * If the current page is the Bing rewards page, the script automatically clicks the unclaimed daily points. * To prevent opening new tabs, points are opened into an iframe inside each point card. */ if (isRewardPage) { if (COLLECT_DAILY_ACTIVITY) { // Wait for the page to load the point cards first window.onload = () => document.querySelectorAll("a.ds-card-sec:has(span.mee-icon-AddMedium)").forEach((card) => { addTabToClose(card.href); card.click(); }); } } /** * Close the current tab if it has been registered for auto-closing. * Waits for the specified timeout before closing the tab. * If the tab cant be closed, then use a workaround for modern browser's limitation in closing tabs that werent opened by the script. * Tip: This workaround still might not work, so you can use an external tool to automate closing windows * by checking for the title of the window (Close this window). */ if (AUTO_CLOSE_TABS) { const tabToClose = tabsToClose.find(tab => window.location.href.includes(tab.url)); if (tabToClose) { tabsToClose = tabsToClose.filter(tab => tab.url != tabToClose.url); GM_setValue("tabsToClose", tabsToClose); setTimeout(() => { window.close() // IF the tab still hasnt closed, take the user to close-this-window page window.open(`https://jeryjs.github.io/Userscripts/Bing-AutoGrind/close-this-window.html?bing-autogrind=true`, "_self"); }, tabToClose.timeout); } } /*=============================================*\ |* CSS STYLES *| \*=============================================*/ const stylesheet = Object.assign(document.createElement("style"), {textContent: ` .auto-search-container { position: fixed; top: 90px; left: 20px; display: inline-grid; align-items: flex-start; z-index: 1000; } .auto-search-container span { margin-left: 5px; } .search-icon, .settings-icon { display: flex; align-items: center; overflow: hidden; cursor: pointer; border-radius: 20px; background-color: lightgray; padding: 2px 10px 2px 0px; transition: all 0.5s ease; } .b_drk .search-icon, .b_drk .settings-icon, .b_dark .search-icon, .b_dark .settings-icon { background-color: #333; } .search-icon.shrink { width: 27px; transition: width 0.5s; } .search-icon.shrink:hover { width: 100%; } .settings-icon { opacity: 0; width: 23px; transition: all 0.5s ease; } .auto-search-container:hover .settings-icon, .auto-search-container .settings-icon.searching { opacity: 1; } .settings-icon:hover { width: 100%; } .search-icon.searching { background-color: lightblue !important; } .b_drk .search-icon.searching, .b_dark .search-icon.searching { background-color: midnightblue !important; } .search-icon.counting { background-color: lightgreen !important; } .b_drk .search-icon.counting, .b_dark .search-icon.counting { background-color: green !important; } /******** Settings Overlay *********/ .b_drk .settings-overlay > div, .b_dark .settings-overlay > div { background-color: black !important; } .settings-item { display: flex; justify-content: space-between; align-items: center; margin: 10px 0; } .settings-item:hover { border: ivory groove 1px; border-radius: 10px; } .settings-item-name { flex-basis: 20%; text-align: left; } .settings-item-input { flex-basis: 10%; text-align: center; } .settings-item-value { flex-basis: 20%; text-align: center; } .settings-item-description { flex-basis: 40%; height: 0; width: 50vw; padding: 5px 10px 20px 10px; border: grey solid 1px; border-radius: 10px; overflow: hidden; transition: height 0.3s ease; } .settings-item:hover .settings-item-description { height: 60px; overflow-y: scroll; place-content: center; text-align: center; padding: 20px; } `}) if(!isRewardPage) document.head.appendChild(stylesheet); } catch (e) { alert("AutoGrind ran into an error. Check the console for more information.\n"+e); console.error("AutoGrind ran into an error. Check the console for more information.\n"+e); }