🏠 Home 

AutoGrind: Intelligent Bing Rewards Auto-Grinder

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.


Install this script?
// ==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);
}