├── .circleci └── config.yml ├── .github └── FUNDING.yml ├── README.md ├── screenshots ├── popup.png ├── wiki-join-rewards-program.png ├── wiki-recommended-settings-1.png └── wiki-recommended-settings-2.png └── src ├── background-scripts ├── messages.js ├── prefs.js ├── queries.js ├── reminder.js ├── schedule.js ├── search.js └── spoof.js ├── chrome-utils.js ├── constants.js ├── content-scripts ├── main.js ├── open-reward-tasks.js ├── quiz-answer-hash-function.js ├── script-injector.js ├── window-open-injection │ ├── injector.js │ └── main.js └── window-variable-grabber │ ├── injector.js │ └── main.js ├── data.js ├── icons ├── icon128.png ├── icon16.png └── icon48.png ├── manifest.json ├── options.css ├── options.html ├── options.js ├── popup.css ├── popup.html ├── popup.js ├── query-templates.js └── utils.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:12.14.1 6 | environment: 7 | - CHROME_APP_ID: ipbgaooglppjombmbgebgmaehjkfabme 8 | - FF_APP_ID: "{15130aad-56df-4f03-9fbd-f91ca1cc658c}" 9 | steps: 10 | - checkout 11 | - run: 12 | name: "Install Dependencies" 13 | command: sudo npm i -g web-ext 14 | - run: 15 | name: "Package Extension" 16 | command: git archive -o package.zip HEAD:src 17 | - run: 18 | name: "Upload & Publish Chrome Extension" 19 | command: | 20 | if [ "${CIRCLE_BRANCH}" == "master" ]; then 21 | ACCESS_TOKEN=$(curl "https://accounts.google.com/o/oauth2/token" -d "client_id=${CHROME_CLIENT_ID}&client_secret=${CHROME_CLIENT_SECRET}&refresh_token=${CHROME_REFRESH_TOKEN}&grant_type=refresh_token&redirect_uri=urn:ietf:wg:oauth:2.0:oob" | jq -r .access_token) 22 | curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -X PUT -T package.zip -v "https://www.googleapis.com/upload/chromewebstore/v1.1/items/${CHROME_APP_ID}" 23 | curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -H "Content-Length: 0" -X POST -v "https://www.googleapis.com/chromewebstore/v1.1/items/${CHROME_APP_ID}/publish" 24 | fi 25 | - run: 26 | name: "Sign & Upload Firefox Addon" 27 | # Use || true so that even when the signing fails for listed addons, the build will not fail 28 | # (signing will always fail since the addon will always be pending review). 29 | # 30 | # And this is a hacky solution to remove "incognito": "split" from the manifest, but 31 | # this is better than maintaining two separate manifests which differ by only this line 32 | # (Firefox does not support "incognito": "split"). 33 | command: | 34 | if [ "${CIRCLE_BRANCH}" == "master" ]; then 35 | cd src 36 | sed -i '/"incognito": "split",/d' ./manifest.json 37 | web-ext sign --id="${FF_APP_ID}" --api-key="${FF_JWT_ISSUER}" --api-secret="${FF_JWT_SECRET}" || true 38 | fi 39 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://www.buymeacoffee.com/mikeyaworski # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ABS 2 | Chrome Extension and Firefox Addon to automatically perform a number of daily searches and collect bonus reward points. 3 | 4 | New version 1.2.31.5 2/12/22 5 | 6 | ![](/screenshots/popup.png) 7 | 8 | Chrome & Firefox extensions removed, only available via Git now. This version is specifically to counter the accounts that end up at the URL with /?redref=amc at the end. As far as I can tell this will also work for accounts that don't have this problem as it's still a valid rewards URL so this should be universal. 9 | 10 | To Install this extension in Chrome go in to Extensions, turn on Developer mode with the toggle in top right corner and then in the left corner click on load unpacked and point it to the src folder. As for Firefox you'll need to make a developer account with Firefox developer here - https://addons.mozilla.org/en-GB/firefox/ and follow the guide here - https://extensionworkshop.com/documentation/publish/submitting-an-add-on/, upload the files to get it signed (make sure to select for personal use, on your own only and DON'T publish it for all to download else it'll probably end up getting banned again) and in a short while you'll receive an email stating it's been created and you can return back to developer menu to download the .xpi file to install it manually in Firefox as normal. 11 | 12 | NOTE 08/03/22 :- Delay now changed to 3 minutes instead of 1 to allow more time for searches to complete before rewards tasks start running. 13 | 14 | NOTE 07/03/22 :- Rewards tasks will now run 60 seconds after searches start because searches were taking over all open tabs and not completing some of the rewards tasks, mainly the 30 pointer ones! If 60 seconds isn't long enough for you then just change the 60000 on line 16 of the file called open-rewards-tasks.js which you can find in the src/content-scripts folder, just add an extra 1000 to the 60000 for each second you need to add to the initial 60 seconds delay. 15 | 16 | NOTE 12/11/21 :- If any issues regarding Rewards tabs not opening but searches work still then first try uninstalling the add-on and re-installing it again before opening a ticket. I do maintain it when and as needed and use it daily myself so am usually aware of any issue that breaks it and will be looking to get it working again ASAP. Thanks 17 | 18 | NOTE 2/12/22 :- Updated rewards URL so that rewards start working again. 19 | 20 | Buy Me A Coffee 21 | 22 | 23 | ## Upcoming Features 24 | 25 | Nothing currently planned. Please open an issue if you have any ideas! 26 | -------------------------------------------------------------------------------- /screenshots/popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h4x0rm1k3/ABS/dd7a83dd38a61de65fe835f40f8761ae238a1ed7/screenshots/popup.png -------------------------------------------------------------------------------- /screenshots/wiki-join-rewards-program.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h4x0rm1k3/ABS/dd7a83dd38a61de65fe835f40f8761ae238a1ed7/screenshots/wiki-join-rewards-program.png -------------------------------------------------------------------------------- /screenshots/wiki-recommended-settings-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h4x0rm1k3/ABS/dd7a83dd38a61de65fe835f40f8761ae238a1ed7/screenshots/wiki-recommended-settings-1.png -------------------------------------------------------------------------------- /screenshots/wiki-recommended-settings-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h4x0rm1k3/ABS/dd7a83dd38a61de65fe835f40f8761ae238a1ed7/screenshots/wiki-recommended-settings-2.png -------------------------------------------------------------------------------- /src/background-scripts/messages.js: -------------------------------------------------------------------------------- 1 | let activePort = null; 2 | 3 | chrome.runtime.onConnect.addListener(port => { 4 | activePort = port; 5 | port.onMessage.addListener(async msg => { 6 | switch (msg.type) { 7 | case constants.MESSAGE_TYPES.START_SEARCH: { 8 | const tab = await getCurrentTab(); 9 | if (tab) startSearches(tab.id); 10 | break; 11 | } 12 | case constants.MESSAGE_TYPES.STOP_SEARCH: { 13 | stopSearches(); 14 | break; 15 | } 16 | default: { 17 | break; 18 | } 19 | } 20 | }); 21 | port.onDisconnect.addListener(() => { 22 | activePort = null; 23 | }); 24 | }); 25 | 26 | chrome.runtime.onMessage.addListener((msg, sender, cb) => { 27 | switch(msg.type) { 28 | case constants.MESSAGE_TYPES.GET_SEARCH_COUNTS: { 29 | if (setSearchCounts) setSearchCounts(); 30 | break; 31 | } 32 | case constants.MESSAGE_TYPES.OPEN_URL_IN_BACKGROUND: { 33 | chrome.tabs.create({ 34 | url: msg.url, 35 | active: false, 36 | }); 37 | break; 38 | } 39 | default: break; 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /src/background-scripts/prefs.js: -------------------------------------------------------------------------------- 1 | const prefKeys = Object.keys(constants.DEFAULT_PREFERENCES); 2 | const prefs = { ...constants.DEFAULT_PREFERENCES }; 3 | hookStorage(prefKeys, prefs); 4 | // other scripts which depends on the global prefs object can use 5 | // "await prefsLoaded" so that the prefs are guaranteed to be loaded before use 6 | const prefsLoaded = getStorage(prefKeys, prefs); 7 | -------------------------------------------------------------------------------- /src/background-scripts/queries.js: -------------------------------------------------------------------------------- 1 | let dailyTrendQueries = []; 2 | // the is the set of available queries which can be used future queries 3 | // and this set should be reset upon it becoming empty (reuse past queries) 4 | let availableQueries = []; 5 | // store usedQueries as a JSON hash map instead of Set or Array for efficiency 6 | // and because chrome.storage will not serialize a Set object 7 | let usedQueries = {}; 8 | 9 | function addToAvailableQueries(queries) { 10 | availableQueries = removeDuplicates(availableQueries.concat(queries)); 11 | } 12 | 13 | function getCustomQueriesArray(queries) { 14 | return queries.trim().split('\n').filter(q => q); 15 | } 16 | 17 | async function updateQueries() { 18 | await prefsLoaded; 19 | 20 | availableQueries = []; 21 | if (prefs.searchWithCustomQueries) { 22 | addToAvailableQueries(getCustomQueriesArray(prefs.customQueries)); 23 | } 24 | if (prefs.searchWithDailyTrends) { 25 | addToAvailableQueries(dailyTrendQueries); 26 | } 27 | 28 | remove(availableQueries, q => usedQueries[q]); 29 | } 30 | 31 | async function fetchDailyTrendQueries() { 32 | try { 33 | function handleResult(result) { 34 | // 6 is to remove the malformed `)]}', ` at the beginning of the response 35 | const json = JSON.parse(result.substring(6)); 36 | return json.default.trendingSearchesDays.map(day => { 37 | return day.trendingSearches.map(search => search.title.query.toLowerCase()); 38 | }).flat().filter(q => q); 39 | } 40 | 41 | // fetch the last X days to get a fair number of queries to pull from 42 | const results = await Promise.all( 43 | new Array(constants.NUM_DAILY_TREND_FETCHES).fill().map((_, i) => { 44 | // don't worry about timezone shifts here. shouldn't matter if we're off by a day on the API calls. 45 | let date = new Date(Date.now() - i * constants.ONE_DAY_MILLIS); 46 | date = date.toISOString(); 47 | date = date.substring(0, date.indexOf('T')).replace(/-/g, ''); 48 | return fetch(`${constants.DAILY_TRENDS_API}&ed=${date}`) 49 | .then(r => { 50 | if (!r.ok) throw new Error('Fetching daily queries failed'); 51 | return r.text(); 52 | }) 53 | }), 54 | ); 55 | dailyTrendQueries = results.map(handleResult).flat(); 56 | setStorage('lastDailyTrendFetch', Date.now()); 57 | setStorage('dailyTrendQueries', dailyTrendQueries); 58 | // fetch again in 1 day 59 | chrome.alarms.create(constants.ALARMS.FETCH_DAILY_TRENDS, { 60 | periodInMinutes: constants.ONE_DAY_MINS, 61 | }); 62 | } catch (err) { 63 | // log the error, but do nothing and default to the hardcoded queries 64 | console.error(err); 65 | } 66 | } 67 | 68 | const queriesAreAvailable = getStorage(['lastDailyTrendFetch', 'dailyTrendQueries', 'usedQueries']).then(async res => { 69 | usedQueries = res.usedQueries || {}; 70 | if (!res.lastDailyTrendFetch || Date.now() - res.lastDailyTrendFetch > constants.ONE_DAY_MILLIS) { 71 | await fetchDailyTrendQueries(); 72 | } else { 73 | dailyTrendQueries = res.dailyTrendQueries; 74 | } 75 | await updateQueries(); 76 | }); 77 | 78 | async function addUsedQuery(query) { 79 | await queriesAreAvailable; 80 | 81 | usedQueries[query] = true; 82 | setStorage('usedQueries', usedQueries); 83 | 84 | remove(availableQueries, q => q === query); 85 | // reset the available queries when empty 86 | if (availableQueries.length === 0) { 87 | usedQueries = {}; 88 | setStorage('usedQueries', usedQueries); 89 | updateQueries(); 90 | } 91 | } 92 | 93 | function isQueryUsed(query) { 94 | return usedQueries && usedQueries[query]; 95 | } 96 | 97 | function getRandomLetters() { 98 | return Math.random().toString(36).substr(2); 99 | } 100 | 101 | async function getSearchQuery() { 102 | await prefsLoaded; 103 | 104 | if (prefs.randomLettersSearch) return getRandomLetters(); 105 | 106 | // try using an available query. if there are none, just fallback to the hardcoded queries 107 | if (availableQueries.length) { 108 | const query = getRandomElement(availableQueries); 109 | addUsedQuery(query); 110 | return query; 111 | } 112 | 113 | if (!prefs.searchWithTemplates) return getRandomLetters(); 114 | 115 | const queryTemplate = getRandomElement(queryTemplates); 116 | const variables = queryTemplate.template.match(/(\$\d+)/g); // variables are $1, $2, ... where the digit is the ID of the variable 117 | const query = variables.reduce((acc, variable, i) => { 118 | const type = queryTemplate.types[i]; 119 | const value = getRandomElement(types[type]); 120 | return acc.replace(variable, value); 121 | }, queryTemplate.template); 122 | 123 | return query; 124 | } 125 | 126 | hookStorage(['customQueries', 'searchWithCustomQueries', 'searchWithDailyTrends', 'searchWithTemplates'].map(key => ({ 127 | key, 128 | cb: updateQueries, 129 | }))); 130 | 131 | chrome.alarms.onAlarm.addListener(async alarm => { 132 | if (alarm.name === constants.ALARMS.FETCH_DAILY_TRENDS) { 133 | await fetchDailyTrendQueries(); 134 | updateQueries(); 135 | } 136 | }); 137 | -------------------------------------------------------------------------------- /src/background-scripts/reminder.js: -------------------------------------------------------------------------------- 1 | function setBadgeReminder() { 2 | chrome.browserAction.setBadgeText({ text: constants.BADGE_REMINDER_TEXT }); 3 | chrome.browserAction.setBadgeBackgroundColor({ color: constants.BADGE_COLORS.REMINDER }); 4 | } 5 | 6 | /** 7 | * Meant to be called after every search (hooked on lastSearch storage value). 8 | */ 9 | function updateReminderTimeout(lastSearch) { 10 | const timeSinceLastSearch = Date.now() - lastSearch; 11 | if (!lastSearch || timeSinceLastSearch > constants.ONE_DAY_MILLIS) { 12 | setBadgeReminder(); 13 | chrome.alarms.clear(constants.ALARMS.REMINDER); 14 | return; 15 | } 16 | clearBadge(); 17 | // will overwrite existing alarm 18 | chrome.alarms.create(constants.ALARMS.REMINDER, { 19 | when: lastSearch + constants.ONE_DAY_MILLIS, 20 | }); 21 | } 22 | 23 | hookStorage([{ 24 | key: 'lastSearch', 25 | cb: updateReminderTimeout, 26 | }]); 27 | 28 | chrome.alarms.onAlarm.addListener(alarm => { 29 | if (alarm.name === constants.ALARMS.REMINDER) setBadgeReminder(); 30 | }); 31 | 32 | function initiateReminderScript() { 33 | getStorage([{ 34 | key: 'lastSearch', 35 | cb: updateReminderTimeout, 36 | }]); 37 | } 38 | 39 | // don't need to fetch the storage every time the background page becomes inactive and then active again. 40 | // just fetch it on startup, do the initial check for the reminder, 41 | // and then hook into the storage value for subsequent reminder updates. 42 | chrome.runtime.onInstalled.addListener(initiateReminderScript); 43 | chrome.runtime.onStartup.addListener(initiateReminderScript); 44 | -------------------------------------------------------------------------------- /src/background-scripts/schedule.js: -------------------------------------------------------------------------------- 1 | function startSearchesInNewTab() { 2 | chrome.tabs.create({ active: false }, tab => { 3 | startSearches(tab.id); 4 | }); 5 | getStorage(['scheduledTimeOpensRewardTasks']).then(({ scheduledTimeOpensRewardTasks }) => { 6 | if (!scheduledTimeOpensRewardTasks) return; 7 | chrome.tabs.create({ url: constants.REWARDS_URL, active: false }, newTab => { 8 | function listener(updatedTabId, info, updatedTab) { 9 | if (newTab.id === updatedTabId && info.status === 'complete' && updatedTab.url.includes(constants.REWARDS_URL)) { 10 | chrome.tabs.executeScript(newTab.id, { 11 | file: '/content-scripts/open-reward-tasks.js', 12 | }, () => { 13 | chrome.tabs.onUpdated.removeListener(listener); 14 | }); 15 | } 16 | } 17 | chrome.tabs.onUpdated.addListener(listener); 18 | }); 19 | }) 20 | } 21 | 22 | /** 23 | * Find the next scheduled search and then from then on, schedule every 24 hours. 24 | */ 25 | async function attemptScheduling() { 26 | const { 27 | scheduleSearches, 28 | scheduledTime, 29 | lastSearch, 30 | } = await getStorage(['scheduleSearches', 'scheduledTime', 'lastSearch']); 31 | 32 | if (!scheduleSearches) { 33 | chrome.alarms.clear(constants.ALARMS.SCHEDULED_SEARCH); 34 | return; 35 | } 36 | 37 | const timeUntilTodaysScheduledHour = getDateFromTime(scheduledTime); 38 | let when = timeUntilTodaysScheduledHour.getTime(); 39 | // there has already been a search completed today, so do the search tomorrow instead 40 | if (lastSearch > getMidnightDate()) { 41 | when += constants.ONE_DAY_MILLIS; 42 | } 43 | chrome.alarms.create(constants.ALARMS.SCHEDULED_SEARCH, { 44 | when, 45 | periodInMinutes: constants.ONE_DAY_MINS, 46 | }); 47 | } 48 | 49 | // whenever any of these storage values change, clear the old schedule and attempt to schedule searches in the future 50 | hookStorage(['scheduleSearches', 'scheduledTime'].map(key => ({ 51 | key, 52 | cb: attemptScheduling, 53 | }))); 54 | 55 | chrome.alarms.onAlarm.addListener(alarm => { 56 | if (alarm.name === constants.ALARMS.SCHEDULED_SEARCH) { 57 | startSearchesInNewTab(); 58 | } 59 | }); 60 | 61 | chrome.runtime.onInstalled.addListener(attemptScheduling); 62 | chrome.runtime.onStartup.addListener(attemptScheduling); 63 | -------------------------------------------------------------------------------- /src/background-scripts/search.js: -------------------------------------------------------------------------------- 1 | function sendMessage(msg) { 2 | if (activePort) activePort.postMessage(msg); 3 | } 4 | 5 | function setBadgeReminderWithCount(count) { 6 | chrome.browserAction.setBadgeText({ text: count.toString() }); // must be a string type 7 | chrome.browserAction.setBadgeBackgroundColor({ color: constants.BADGE_COLORS.COUNT }); 8 | } 9 | 10 | function updateLastSearch() { 11 | setStorage('lastSearch', Date.now()); 12 | } 13 | 14 | // store it in an object because eventually, we will be storing this information in local storage 15 | // and once that happens, it will be an object as well 16 | let currentSearchSettings = { 17 | // currentSearchingTabId, 18 | // platformSpoofing, 19 | // desktopIterations, 20 | // mobileIterations, 21 | // overallCount, 22 | // desktopCount, 23 | // mobileCount, 24 | }; 25 | let searchTimeout; 26 | 27 | function stopSearches() { 28 | currentSearchSettings = {}; 29 | clearTimeout(searchTimeout); 30 | clearBadge(); 31 | sendMessage({ type: constants.MESSAGE_TYPES.CLEAR_SEARCH_COUNTS }); 32 | spoof(false); 33 | mobileSpoof(false); 34 | } 35 | 36 | function setSearchCounts() { 37 | const { 38 | currentSearchingTabId, 39 | overallCount, 40 | desktopCount, 41 | mobileCount, 42 | desktopIterations, 43 | mobileIterations, 44 | platformSpoofing, 45 | } = currentSearchSettings; 46 | if (!currentSearchingTabId) { 47 | sendMessage({ type: constants.MESSAGE_TYPES.CLEAR_SEARCH_COUNTS }); 48 | return; 49 | } 50 | 51 | const containsDesktop = platformSpoofing.includes('desktop'); 52 | const containsMobile = platformSpoofing.includes('mobile'); 53 | const desktopRemaining = desktopIterations - desktopCount; 54 | const mobileRemaining = mobileIterations - mobileCount; 55 | 56 | sendMessage({ 57 | type: constants.MESSAGE_TYPES.UPDATE_SEARCH_COUNTS, 58 | numIterations: desktopIterations + mobileIterations, 59 | overallCount, 60 | containsDesktop, 61 | containsMobile, 62 | desktopRemaining, 63 | mobileRemaining, 64 | }); 65 | } 66 | 67 | /** 68 | * Actually redirects to perform search query. 69 | */ 70 | async function search(isMobile) { 71 | await prefsLoaded; 72 | if (!currentSearchSettings) return; 73 | 74 | const { 75 | desktopCount, 76 | mobileCount, 77 | currentSearchingTabId, 78 | } = currentSearchSettings; 79 | 80 | if (isMobile && mobileCount === 0) mobileSpoof(true); 81 | 82 | return new Promise(async (resolve, reject) => { 83 | const query = await getSearchQuery(); 84 | chrome.tabs.update(currentSearchingTabId, { 85 | url: `https://bing.com/search?q=${query}`, 86 | }, () => { 87 | // we expect an error if there is the tab is closed, for example 88 | if (chrome.runtime.lastError) return reject(chrome.runtime.lastError); 89 | 90 | if (prefs.blitzSearch) { 91 | // arbitrarily wait 500ms on the last mobile search before resolving 92 | // so that there is a delay before disabling the mobile spoofing 93 | // (otherwise the last search will occur after the spoofing is disabled) 94 | const delay = (mobileCount === desktopCount && desktopCount > 0) ? 500 : 0; 95 | setTimeout(resolve, delay); 96 | return; 97 | } 98 | 99 | function listener(updatedTabId, info) { 100 | if (currentSearchingTabId === updatedTabId && info.status === 'complete') { 101 | resolve(); 102 | chrome.tabs.onUpdated.removeListener(listener); 103 | } 104 | } 105 | chrome.tabs.onUpdated.addListener(listener); 106 | }); 107 | }); 108 | } 109 | 110 | /** 111 | * Computes the data we need to make our searches, invokes the search function, 112 | * and schedules another search in the future (after some delay). 113 | */ 114 | async function searchLoop(currentSearchingTabId) { 115 | await prefsLoaded; 116 | 117 | let { 118 | platformSpoofing, 119 | desktopIterations, 120 | mobileIterations, 121 | overallCount, 122 | desktopCount, 123 | mobileCount, 124 | } = currentSearchSettings; 125 | 126 | let currentDelay = Number(prefs.delay); 127 | if (prefs.randomSearch) { 128 | const minDelay = Number(prefs.randomSearchDelayMin); 129 | const maxDelay = Number(prefs.randomSearchDelayMax); 130 | currentDelay = random(minDelay, maxDelay); 131 | } 132 | 133 | try { 134 | const isMobile = platformSpoofing === 'mobile-only' || (platformSpoofing === 'desktop-and-mobile' && overallCount >= desktopIterations); 135 | await search(isMobile); 136 | 137 | // This is to address the issue where you stop the searches while the page is loading (or start searches in another tab) 138 | // and we are awaiting the search to complete. The timeout function is async, so even if the timeout has been cleared, 139 | // once the promise finishes, it will invoke another search. So, we check after done waiting for if the search has completed. 140 | if (currentSearchSettings.currentSearchingTabId !== currentSearchingTabId) return; 141 | 142 | overallCount++; 143 | if (isMobile) mobileCount++; 144 | else desktopCount++; 145 | Object.assign(currentSearchSettings, { 146 | overallCount, 147 | desktopCount, 148 | mobileCount, 149 | }); 150 | 151 | setSearchCounts(); 152 | setBadgeReminderWithCount(desktopIterations + mobileIterations - overallCount); 153 | 154 | if (overallCount >= desktopIterations + mobileIterations) { 155 | stopSearches(); 156 | } else { 157 | // cannot use chrome.alarms since an alarm will fire, at most, every one minute 158 | searchTimeout = setTimeout(() => searchLoop(currentSearchingTabId), currentDelay); 159 | } 160 | } catch (err) { 161 | console.error(err.message); 162 | stopSearches(); 163 | } 164 | } 165 | 166 | /** 167 | * Computes/stores the search settings which will be used, then invokes the first search loop. 168 | */ 169 | async function startSearches(tabId) { 170 | stopSearches(); 171 | 172 | await prefsLoaded; 173 | updateLastSearch(); 174 | 175 | const { platformSpoofing } = prefs; 176 | const minInterations = Number(prefs.randomSearchIterationsMin); 177 | const maxIterations = Number(prefs.randomSearchIterationsMax); 178 | let desktopIterations = prefs.randomSearch ? random(minInterations, maxIterations) : Number(prefs.desktopIterations); 179 | let mobileIterations = prefs.randomSearch ? random(minInterations, maxIterations) : Number(prefs.mobileIterations); 180 | 181 | // send messages over the port to initiate spoofing based on the preference 182 | if (platformSpoofing === 'none' || !platformSpoofing) { 183 | spoof(false); 184 | mobileSpoof(false); 185 | mobileIterations = 0; 186 | } else if (platformSpoofing === 'desktop-and-mobile') { 187 | spoof(true); 188 | mobileSpoof(false); 189 | } else if (platformSpoofing === 'desktop-only') { 190 | spoof(true); 191 | mobileSpoof(false); 192 | mobileIterations = 0; 193 | } else if (platformSpoofing === 'mobile-only') { 194 | spoof(true); 195 | mobileSpoof(true); 196 | desktopIterations = 0; 197 | } 198 | 199 | Object.assign(currentSearchSettings, { 200 | currentSearchingTabId: tabId, 201 | platformSpoofing, 202 | desktopIterations, 203 | mobileIterations, 204 | overallCount: 0, 205 | desktopCount: 0, 206 | mobileCount: 0, 207 | }); 208 | 209 | setSearchCounts(); 210 | searchLoop(tabId); 211 | } 212 | -------------------------------------------------------------------------------- /src/background-scripts/spoof.js: -------------------------------------------------------------------------------- 1 | const mobileUserAgent = getRandomElement(constants.MOBILE_USER_AGENTS); 2 | const edgeUserAgent = constants.EDGE_USER_AGENT; 3 | 4 | let spoofUserAgent = false; 5 | let doMobileSearches = false; 6 | 7 | // TODO: have these functions set storage values, instead of local variables, once we 8 | // rewrite this to be a non-persisted script (currently persisted due to blocking web requests) 9 | function spoof(value) { 10 | spoofUserAgent = value; 11 | } 12 | function mobileSpoof(value) { 13 | doMobileSearches = value; 14 | } 15 | 16 | chrome.webRequest.onBeforeSendHeaders.addListener(details => { 17 | const { requestHeaders } = details; 18 | requestHeaders.forEach(header => { 19 | if (header.name === 'User-Agent' && spoofUserAgent) { 20 | if (doMobileSearches) header.value = mobileUserAgent; 21 | else header.value = edgeUserAgent; 22 | } 23 | }); 24 | return { requestHeaders }; 25 | }, { 26 | urls: ['https://*.bing.com/*'], 27 | }, ['blocking', 'requestHeaders']); 28 | -------------------------------------------------------------------------------- /src/chrome-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adds a listener to changed storage values. `storage` 3 | * will be updated or the associated callback function (`cb`) 4 | * will be called whenever a change occurs to the storage values. 5 | * The storage values change when a user interacts with the extension's popup. 6 | * 7 | * @param {Array} storageKeys Array of storage "keys" which are hooked. If the element is a string, it is a storage key. 8 | * If the element is an object, it has a `key` field (string) and a `cb` field (function). 9 | * `cb` will be called with the new value and `storage` will not be set for this element (that is up to the caller). 10 | * @param {Object} storage Object of storage values 11 | */ 12 | function hookStorage(storageKeys, storage) { 13 | chrome.storage.onChanged.addListener(res => { 14 | if (chrome.runtime.lastError) { 15 | return; 16 | } 17 | storageKeys.forEach(storageKey => { 18 | // it's either a string or an object of the form { key, cb } 19 | if (typeof storageKey === 'string') { 20 | if (res[storageKey] !== undefined) storage[storageKey] = res[storageKey].newValue; 21 | } else { 22 | const { key, cb } = storageKey; 23 | if (res[key] !== undefined) cb(res[key].newValue); 24 | } 25 | }); 26 | }); 27 | } 28 | 29 | /** 30 | * Similar to hookStorage, except the storage values are retrieved immediately and only once. 31 | * Also returns a Promise for convenience. 32 | * 33 | * @param {Array} storageKeys 34 | * @param {Object?} storage 35 | * 36 | * @returns {Promise} Promise which resolves to a mapping of storage key to the value 37 | */ 38 | async function getStorage(storageKeys, storage) { 39 | return new Promise((resolve, reject) => { 40 | chrome.storage.local.get(storageKeys.map(key => (typeof key === 'string' ? key : key.key)), res => { 41 | if (chrome.runtime.lastError) { 42 | reject(chrome.runtime.lastError); 43 | return; 44 | } 45 | resolve(storageKeys.reduce((acc, storageKey) => { 46 | // it's either a string or an object of the form { key, cb } 47 | if (typeof storageKey === 'string') { 48 | if (!storage) return { ...acc, [storageKey]: res[storageKey] }; 49 | if (res[storageKey] !== undefined) storage[storageKey] = res[storageKey]; 50 | else storage[storageKey] = constants.DEFAULT_PREFERENCES[storageKey]; 51 | return { ...acc, [storageKey]: res[storageKey] }; 52 | } 53 | const { key, cb } = storageKey; 54 | cb(res[key]); 55 | return { ...acc, [key]: res[key] }; 56 | }, {})); 57 | }); 58 | }); 59 | } 60 | 61 | /** 62 | * Sets a local storage value or set of values. 63 | * @param {string | Object} keyOrMap Either a string (representing a key, in which case val must be provided) 64 | * or an object with key-value-pair mappings for storing. 65 | * @param {any?} val The value associated with key, iff key is a string. 66 | */ 67 | async function setStorage(keyOrMap, val) { 68 | return new Promise((resolve, reject) => { 69 | const options = typeof keyOrMap === 'string' 70 | ? { [keyOrMap]: val } 71 | : keyOrMap; 72 | chrome.storage.local.set(options, () => { 73 | if (chrome.runtime.lastError) { 74 | reject(chrome.runtime.lastError); 75 | return; 76 | } 77 | resolve(); 78 | }); 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | const constants = Object.freeze({ 2 | ONE_DAY_MINS: 24 * 60, 3 | ONE_DAY_MILLIS: 24 * 60 * 60 * 1000, 4 | BADGE_COLORS: Object.freeze({ 5 | REMINDER: '#F41A22', 6 | COUNT: '#2196F3', 7 | }), 8 | BADGE_REMINDER_TEXT: '!', 9 | ALARMS: Object.freeze({ 10 | REMINDER: 'reminder-alarm', 11 | SCHEDULED_SEARCH: 'scheduled-search-alarm', 12 | FETCH_DAILY_TRENDS: 'fetch-daily-trends-alarm', 13 | }), 14 | CLICK_DELAY: 500, 15 | DEFAULT_PREFERENCES: Object.freeze({ 16 | desktopIterations: 35, 17 | mobileIterations: 25, 18 | delay: 1000, 19 | autoClick: true, 20 | randomGuesses: true, 21 | randomSearch: false, 22 | randomSearchDelayMin: 1200, 23 | randomSearchDelayMax: 2500, 24 | randomSearchIterationsMin: 35, 25 | randomSearchIterationsMax: 42, 26 | randomLettersSearch: false, 27 | blitzSearch: false, 28 | platformSpoofing: 'desktop-and-mobile', 29 | customQueries: '', 30 | searchWithCustomQueries: false, 31 | searchWithDailyTrends: true, 32 | searchWithTemplates: true, 33 | scheduleSearches: true, 34 | scheduledTime: '02:00', 35 | scheduledTimeOpensRewardTasks: true, 36 | }), 37 | MESSAGE_TYPES: Object.freeze({ 38 | START_SEARCH: 0, // popup => background script 39 | STOP_SEARCH: 1, // popup => background script 40 | GET_SEARCH_COUNTS: 2, // popup => background script 41 | UPDATE_SEARCH_COUNTS: 3, // background script => popup 42 | CLEAR_SEARCH_COUNTS: 4, // background script => popup 43 | CORRECT_ANSWER_RECEIVED: 5, // window-variable-grabber script => content script 44 | OPEN_URL_IN_BACKGROUND: 6, // window-variable-grabber script => content script => background script 45 | }), 46 | REWARDS_URL: 'https://rewards.bing.com/?redref=amc', 47 | DAILY_TRENDS_API: 'https://trends.google.com/trends/api/dailytrends?geo=US', 48 | NUM_DAILY_TREND_FETCHES: 4, 49 | // TODO: add more mobile user agents 50 | MOBILE_USER_AGENTS: Object.freeze([ 51 | 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Mobile Safari/537.36 Edg/86.0.622.51', 52 | ]), 53 | EDGE_USER_AGENT: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36 Edg/86.0.622.51', 54 | }); 55 | -------------------------------------------------------------------------------- /src/content-scripts/main.js: -------------------------------------------------------------------------------- 1 | let correctAnswer; 2 | let answerHashIV; 3 | document.addEventListener(constants.MESSAGE_TYPES.CORRECT_ANSWER_RECEIVED, e => { 4 | correctAnswer = e.detail.correctAnswer; 5 | answerHashIV = e.detail.answerHashIV; 6 | }); 7 | 8 | const prefs = { ...constants.DEFAULT_PREFERENCES }; 9 | 10 | function clickElement(e, checkVisibility = true) { 11 | if (!e) return; 12 | // e.offsetParent checks that the element (and its parents) do not have the style property 'display: none' 13 | // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent 14 | // this will break if e has style property 'position: fixed', but that shouldn't happen 15 | if (!checkVisibility || e.offsetParent) e.click(); 16 | } 17 | 18 | function clickOption(selector, parent = document) { 19 | const e = parent.querySelector(selector); 20 | if (e && e.getAttribute('data-serpquery')) clickElement(e, true); 21 | } 22 | 23 | function click(selector, parent = document) { 24 | const e = parent.querySelector(selector); 25 | clickElement(e); 26 | } 27 | 28 | function clickHidden(selector, parent = document) { 29 | const e = parent.querySelector(selector); 30 | clickElement(e, false); 31 | } 32 | 33 | function clickLoop() { 34 | if (prefs.autoClick) { 35 | click('#rqStartQuiz'); 36 | clickOption('#currentQuestionContainer .b_cards[iscorrectoption=True]:not(.btsel)'); 37 | clickOption(`#currentQuestionContainer .rqOption:not(.optionDisable)[data-option="${correctAnswer}"]`); 38 | clickOption('.bt_poll .btOption'); 39 | 40 | // click the hidden element here since options are only available while the background is hidden. 41 | // once the background is not hidden, that means the results are being shown. 42 | clickHidden('#OptionBackground00.b_hide'); 43 | 44 | // does not apply to this/that quizzes since the correct answer is hashed for that 45 | clickOption(`#currentQuestionContainer .btOptionCard[data-option="${correctAnswer}"]`); 46 | 47 | // for this/that quizzes: 48 | // either guess randomly or attempt the correct guess (only works on mobile view) 49 | // but at least give the option to guess randomly instead of forcing it to be correct 50 | // since this is a risky thing to get 100% correct 51 | if (prefs.randomGuesses) { 52 | // not actually random - just picks the first one 53 | clickOption('#currentQuestionContainer .btOptionCard'); 54 | } else { 55 | // only works on mobile view 56 | click('.bt_correctOp'); 57 | // for desktop view, compare the hashes of options to the hash of the correct answer we got from the window object 58 | const options = [...document.querySelectorAll('#currentQuestionContainer .btOptionCard')]; 59 | const correctOption = options.find(option => correctAnswer && correctAnswer === quizAnswerHashFunction(answerHashIV, option.dataset.option)); 60 | clickElement(correctOption); 61 | } 62 | 63 | click('#ListOfQuestionAndAnswerPanes div[id^=QuestionPane]:not(.wk_hideCompulsary) .wk_choicesInstLink'); 64 | 65 | // this actually might not be necessary, but we can leave it in anyway 66 | click('#ListOfQuestionAndAnswerPanes div[id^=AnswerPane]:not(.b_hide) input[type=submit]'); 67 | } 68 | } 69 | 70 | getStorage([ 71 | 'autoClick', 72 | 'randomGuesses', 73 | ], prefs).then(() => { 74 | setInterval(clickLoop, constants.CLICK_DELAY); 75 | }); 76 | 77 | hookStorage([ 78 | 'autoClick', 79 | 'randomGuesses', 80 | ], prefs); 81 | -------------------------------------------------------------------------------- /src/content-scripts/open-reward-tasks.js: -------------------------------------------------------------------------------- 1 | function clickElement(e, checkVisibility = true) { 2 | if (!e) return; 3 | // e.offsetParent checks that the element (and its parents) do not have the style property 'display: none' 4 | // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent 5 | // this will break if e has style property 'position: fixed', but that shouldn't happen 6 | if (!checkVisibility || e.offsetParent) e.click(); 7 | } 8 | 9 | function clickAll(selector, parent = document) { 10 | const elements = [...parent.querySelectorAll(selector)]; 11 | elements.forEach(e => 12 | // wait 60 seconds between each "click", to ensure that the site correctly registers that it has been completed 13 | // (it seems that quizzes and polls running at the same time do not get registered as completed) 14 | setTimeout(function() { 15 | clickElement(e, true); 16 | }, 180000) 17 | ); 18 | } 19 | 20 | // scope this in a function so we can inject this script multiple times 21 | (() => { 22 | // This timeout lets us queue our clicks until after the page has had time to inject window.open. 23 | // This is only reproducible in Firefox as far as I'm aware. 24 | setTimeout(() => { 25 | const cards = [...document.querySelectorAll('mee-card')]; 26 | if (cards.length) { 27 | cards.forEach(card => { 28 | if (card.querySelector('.mee-icon-AddMedium')) { 29 | clickAll('a.ds-card-sec', card); 30 | } 31 | }); 32 | } 33 | }, 0); 34 | })(); 35 | -------------------------------------------------------------------------------- /src/content-scripts/quiz-answer-hash-function.js: -------------------------------------------------------------------------------- 1 | function quizAnswerHashFunction(IV, input) { 2 | if (!input || !IV) return null; 3 | const nonce = parseInt(IV.substr(IV.length - 2), 16); 4 | const t = input.split('').reduce((acc, char) => acc + char.charCodeAt(0), nonce); 5 | return t.toString(); 6 | } 7 | -------------------------------------------------------------------------------- /src/content-scripts/script-injector.js: -------------------------------------------------------------------------------- 1 | function injectScript(url) { 2 | const s = document.createElement('script'); 3 | s.src = chrome.runtime.getURL(url); 4 | s.onload = function() { 5 | this.remove(); 6 | }; 7 | (document.head || document.documentElement).appendChild(s); 8 | } 9 | 10 | injectScript('constants.js'); 11 | -------------------------------------------------------------------------------- /src/content-scripts/window-open-injection/injector.js: -------------------------------------------------------------------------------- 1 | document.addEventListener(constants.MESSAGE_TYPES.OPEN_URL_IN_BACKGROUND, e => { 2 | chrome.runtime.sendMessage({ 3 | type: constants.MESSAGE_TYPES.OPEN_URL_IN_BACKGROUND, 4 | url: e.detail, 5 | }); 6 | }); 7 | 8 | injectScript('/content-scripts/window-open-injection/main.js'); 9 | -------------------------------------------------------------------------------- /src/content-scripts/window-open-injection/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This injection is useful for forcing the opened reward tasks to be in the background. 3 | */ 4 | 5 | function parseUrl(url) { 6 | if (url.startsWith('/')) { 7 | return `${window.location.origin}${url}`; 8 | } 9 | return url; 10 | } 11 | 12 | const windowOpen = window.open; 13 | window.open = (url, windowName, windowFeatures) => { 14 | if (windowName === '_blank') { 15 | document.dispatchEvent(new CustomEvent(constants.MESSAGE_TYPES.OPEN_URL_IN_BACKGROUND, { 16 | detail: parseUrl(url), 17 | })); 18 | } else { 19 | windowOpen(url, windowName, windowFeatures); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/content-scripts/window-variable-grabber/injector.js: -------------------------------------------------------------------------------- 1 | injectScript('/content-scripts/window-variable-grabber/main.js'); 2 | -------------------------------------------------------------------------------- /src/content-scripts/window-variable-grabber/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Lightweight, similar to Lodash's _.get, but without most of the safety conditions 3 | * 4 | * @param {Object} obj 5 | * @param {Array} path 6 | */ 7 | function get(obj, path) { 8 | let result = obj; 9 | path.forEach(junction => { 10 | if (!result) return; 11 | result = result[junction]; 12 | }); 13 | return result; 14 | } 15 | 16 | // grab the correct answer from the window object 17 | setInterval(() => { 18 | document.dispatchEvent(new CustomEvent(constants.MESSAGE_TYPES.CORRECT_ANSWER_RECEIVED, { 19 | detail: { 20 | correctAnswer: get(window, ['rewardsQuizRenderInfo', 'correctAnswer']), 21 | answerHashIV: get(window, ['_G', 'IG']), 22 | }, 23 | })); 24 | }, 500); 25 | -------------------------------------------------------------------------------- /src/data.js: -------------------------------------------------------------------------------- 1 | const socialMedias = ["instagram", "twitter", "youtube", "tiktok", "twitch"]; 2 | 3 | const people = ["gabourey sidibe","gavin rossdale","gayatri joshi","gemma arterton","gemma ward","genelia d’souza","george clooney","george michael","gerard butler","geri halliwell","ghada kunash","ghaith al ghaith","gillian anderson","ginnifer goodwin","giorgio armani","girls aloud","gisele bündchen","gisele bundchen","giuliana rancic","goldie hawn","gordon ramsay","govind nihalani","govinda","gul panag","gulshan kavarana","guy ritchie","gwen stefani","gwyneth paltrow","halle berry","haneef al raisani","hansika motwani","harman baweja","harper seven beckham","harrison ford","hassan habib","hassan shehreyar yasin","hayden christensen","hayden panettiere","haylie duff","heath ledger","heather graham","heather locklear","heidi klum","heidi montag","helen frith powell","helen mirren","helena bonham carter","hema malini","hilary duff","hilary swank","himesh reshammiya","hind abdulrazak","hrishitaa bhatt","hrithik roshan","hugh grant","hugh jackman","hulk hogan","j k rowling","j.c butler","jack black","jack nicholson","jackie shroff","jada pinkett smith","jade goody","jaden smith","jake gyllenhaal","jalal chahda","james franco","james hogan","james marsden","james purefoy","jamie cullum","jamie foxx","jamie lee curtis","jamie lynn spears","jamie oliver","jane krakowski","jane lynch","janet jackson","janice dickinson","january jones","jason biggs","javed akhtar","javed jaffri","javier bardem","jay manuel","jay z","jaya bachchan","jaya prada","jc and sim","jean-paul gaultier","jeetendra","jennifer aniston","jennifer connelly","jennifer garner","jennifer hudson","jennifer lawrence","jennifer lopez","jennifer love hewitt","jenson button","jeremy clarkson","jeremy piven","jeremy renner","jerry bruckheimer","jerry seinfeld","jesse eisenberg","jesse james","jesse mccartney","jessica alba","jessica biel","jessica michibata","jessica simpson","jessica szohr","jessie shrimali","jiah khan","jim carrey","jimmy choo","jimmy fallon","jimmy shergill","joaquin phoenix","jodhi may","jodie kidd","joe jonas","joe manganiello","joel madden","john abraham","john galiano","john mayer","john travolta","johnny depp","johnny lever","jon bon jovi","jonathan rhys meyers","jordan","joseph gordon-levitt","josh duhamel","josh hartnett","josh kelley","joshua jackson","joss stone","jude law","jugal hansraj","juhi chawla","julia dempster","julia ormond","julia roberts","julia stiles","julian mcmahon","julianne moore","juliette lewis","justin bieber","justin long","justin timberlake","kal penn","kamal haasan","kangana ranaut","kanye west","karan johar","kareena kapoor","karl wolf","kat von d","kate beckinsale","kate bosworth","kate dalheimer & henry dyer","kate hudson","kate middleton","kate moss","kate winslet","katharine mcphee","katherine heigl","katie holmes","katie price","katrina kaif","katy perry","keanu reeves","keira knightley","keith urban","kelis","kellan lutz","kelly brook","kelly clarkson","kelly lundberg","kelly osbourne","kelly rowland","kelly rutherford","ken paves","kendall jenner","kendra wilkinson","kerry katona","kesha","kevin connolly","kevin federline","kevin spacey","khalid alsuwaidi","khloe kardashian","kim cattrall","kim kardashian","kim sharma","kimberley walsh","kimberly stewart","kimora lee simmons","kirron kher","kirsten dunst","kirstie alley","klaus ehrenbrandtner","koena mitra","konkona sen sharma","kourtney kardashian","kristen bell","kristen renton","kristen scott thomas","kristen stewart","kristin cavallari","kristin davis","kristin scott thomas","kt tunstall","kumar gaurav","kumar sanu","kunal khemu","kunal kohli","kurt russell","kylie minogue","lady gaga","lamar odom","lance van breda","lara dutta","lara stone","larry king","lars narfeldt","lata mangeshkar","laura murtauno","lauren conrad","lea michele","leighton meester","leona lewis","leonardo dicaprio","lewis hamilton","liam gallagher","liam hemsworth","liam neeson","lil' kim","lily allen","lily cole","lindsay lohan","lindsay price","lisa kudrow","liv tyler","liza de jimenez de luna","lo bosworth","lola lopez","lourdes leon","lucky ali","lucy liu","lujain omran","luke wilson","lupe roldan and martin valent","macaulay culkin","maddox jolie-pitt","madonna","maggie gyllenhaal","mahima chaudhry","maitha el abbar","malaika arora-khan","malin akerman","malliha tabari","mallika sherawat","mandira bedi","mandy moore","mani ratnam","manish malhotra","manisha koirala","manjari phadnis","manoj bajpai","marc anthony","marc jacobs","marcia cross","maria conceicao","maria dowling","mariah carey","marion cotillard","marisa tomei","mariya kassam","mark ronson","mark wahlberg","markus thesleff","martin lawrence","mary j blige","mary-kate olsen","matt damon","matt dillon","matt goss","matt leblanc","matthew fox","matthew marsden","matthew mcconaughey","matthew morrison","matthew perry","mauizo and edwina viel","meat loaf","meg ryan","megan fox","mel b","mel gibson","melanie chisholm","melissa george","meryl streep","michael douglas","michael jackson","michael keaton","michelle monaghan","michelle obama","michelle pfeiffer","michelle rodriguez","michelle williams","mick jagger","mike myers","mila kunis","miley cyrus","milind soman","milla jovovich","millie tsai","minka kelly","miranda kerr","mischa barton","mithoon","mithun chakraborty","mo'nique","mohammed al habtoor","mohnish behl","molly sims","monica cruz","morgan freeman","mugdha godse","nagarjuna","nagesh kukunoor","nana patekar","nandita das","naomi campbell","naomi watts","nariman zaydan","naseeruddin shah","natalie carney", "kapoor wazir","natalie imbruglia","natalie portman","natascha mcelhone","natasha bedingfield","natassia scarlett","nazia husein","ne-yo","neetu chandra","neha dhupia","neil nitin mukesh","nelly furtado","neve campbell","new kids on the block","nicholas hoult","nick cannon","nick carter","nick jonas","nicki minaj","nicky hilton","nicolas cage","nicole kidman","nicole richie","nicole scherzinger","nicollette sheridan","p diddy","padma lakshmi","paloma faith","pamela anderson","pankaj kapur","paresh rawal","paris hilton","patricia arquette","patrick dempsey","paul mccartney","paula abdul","peaches geldof","penelope cruz","penn badgley","perez hilton","perizaad zorabian","pete wentz","peter andre","peter jackson","phil collins","pierce brosnan","pierre cardin","piers morgan","pink","pink floyd","pixie geldof","pixie lott","pooja bedi","preity zinta","prince harry","prince william","priyanka chopra","priyanshu chatterjee","r kelly","rachel bilson","rachel mcadams","rachel weisz","rachel zoe","rahul bose","rahul khanna","rahul mahajan","rahul roy","raima sen","raj babbar","rajat kapoor","rajesh khanna","rajiv khandelwal","rakesh roshan","rakeysh omprakash mehra","rakhi sawant","ralph fiennes","ram gopal varma","ramesh sippy","ranbir kapoor","randeep hooda","rani mukerji","ranvir shorey","rati agnihotri","raveena tandon","rebecca de courcy-ireland","rebecca demornay","rebecca hall","rebecca romijn","reese witherspoon","renee zellweger","richard branson","ricky gervais","ricky martin","rihanna","rima and dina zahran","rimi sen","rishi kapoor","rita ora","rob lowe","robbie williams","robert buckley","robert carlyle","robert de niro","robert downey jr","robert pattinson","robin williams","rod stewart","rohini gehani","roisin murphy","ronan keating","rosario dawson","rose byrne","roselyn sanchez","rosie hayes","rosie huntington-whiteley","ross gardiner","rumer willis","rupert friend","rupert grint","russ kientch","russell brand","russell crowe","ruth bradley","ryan gosling","ryan philippe","ryan reynolds","ryan seacrest","sacha baron cohen","sacha jafri","saddruddin hashwani","sadie frost","saeed saif hamarain","saif ali khan","salina handa","salma hayek","sam mendes","sam niell","sam worthington","samantha ronson","samuel l jackson","sandra bullock","sania mirza","sanjay suri","sarah belhasa","sarah chalke","sarah ferguson","sarah jessica parker","sarah michelle gellar","sarah wynter","scarlett johansson","scott disick","seal","sean paul","sean penn","seann william scott","sebastian noat","selena gomez","selma blair","seth green","sex and the city","shaan","shabana azmi","shae brotherton","shah rukh khan","shahid kapur","shahriar khodjasteh","shakira","shakti kapoor","shamita shetty","shane warne","shankar, ehsaan and loy","sharon osbourne","sharon stone","shashi kapoor","shekhar kapur","shekhar suman","sheryl crow","shia labeouf","shiloh nouvel jolie-pitt","shilpa shetty","shiney ahuja","shreyas talpade","sienna miller","sigourney weaver","simon cowell","simon le bon","simone heng","snooki","sofia vergara","soha and javier nashaat","sohail khan","solange knowles","sonali bendre","sonam kapoor","sonu nigam","sophia loren","sophie dahl","sophie ellis-bextor","sophie mir","sophie monk","spencer pratt","sridevi","stefano gabbana","stella mccartney","stephanie pratt","steve carell","steve martin","steve zahn","stevie wonder","sting","subhash ghai","sunidhi chauhan","suniel shetty","suri cruise","susan sarandon","sushmita sen","sarah larson","tahmina nargis memon","tamara mellon","tanushree dutta","taylor lautner","taylor momsen","taylor swift","teri hatcher","terrence howard","thandie newton","the pussycat dolls","theresa gobara","thomas lundgren","thomas oveson","tiffany thornton","tiger woods","tilda swinton","tina fey","tinsley mortimer","tobey maguire","tom and dan","tom cruise","tom ford","tom hanks","tom jones","tom sturridge","tom welling","tommy hilfiger","tori spelling","twinkle khanna","tyra banks","vanessa hudgens","vanessa paradis","vanessa williams","venus williams","vera farmiga","victoria beckham","victoria silvstedt","vidya balan","vidya malvade","vin diesel","vinay pathak","vince vaughn","vinod khanna","virginie & olivier dolz","virginie maillard","arnold schwarzenegger","emma watson","daniel radcliffe","brad pitt","charles chaplin","sylvester stallone","will smith","clint eastwood","cameron diaz","steven spielberg","al pacino","robert downey jr.","sean connery","orlando bloom","dwayne johnson","jackie chan","angelina jolie","adam sandler","anne hathaway","edward norton","bradley cooper","will ferrell","daniel craig","ian mckellen","bruce willis","samuel l. jackson","ben stiller","tommy lee jones","antonio banderas","denzel washington","tim allen","jean-claude van damme","zach galifianakis","owen wilson","christian bale","bruce lee","drew barrymore","bill murray","jason statham","jet li","rowan atkinson","marlon brando","channing tatum","ben affleck","emma stone","chris hemsworth","james mcavoy","james cameron","amitabh bachchan","brendan fraser","tom hiddleston","aamir khan","rajinikanth","wayne gretzky","eddie murphy","michael caine","gary oldman","stellan skarsgard","anthony daniels","stanley tucci","robert deniro","andy serkis","don cheadle","woody harrelson","cate blanchett","elizabeth banks","jonah hill","idris elba","dustin hoffman","philip seymour hoffman","chris evans","50 cent","a trak","aarti chhabria","aarya babbar","abbie cornish","abhay deol","abhishek bachchan","adel ali","adele","bar refaeli","barkha shewakramani","barney york and lindsay steven","barry kirsch","bebhinn kelly","benji madden","beyonce knowles","bhoomika chawla","caitlin wilson","camilla chaudry","carey mulligan","carla bruni","carly ray jepsen","carmen electra","carrie underwood","casey affleck","cash warren","dakota fanning","dalia dogmoch soubra","dannii minogue","danny denzongpa","dariush zandi","darsheel safary","david arquette","david beckham","ed westwick","eileen wallis","elizabeth hurley","elizabeth jagger","elle macpherson","ellen degeneres","ellen page","ellen pompeo","fadi daroub","farah khan","fardeen khan","farhan akhtar","fearne cotton","felicity huffman","fergie","feroz khan","fitriana hay","frankie dettori","imran khan","imran saeed chaudury","ingie chalhoub","irrfan khan","isaac hayes","isabel lucas","isabelle de la bruyere","isha koppikar","isla fisher","ivanka trump","oded fehr","oj simpson","olivia palermo","olivia wilde","om puri","oprah winfrey","ozzy osbourne","queen latifah","quentin tarantino","uday chopra","uma thurman","upen patel","urmila matondkar","usher","wayne rooney","wentworth miller","westlife","whitney houston","whitney port","willie garsob","willow smith","winona ryder","will.i.am","xavier samuel","yana gupta","yash chopra","yvonne zima","zac efron","zaeem jamal","zain mustafa","zayan gardour","zayed khan","zeenat aman","zeina sultani","zoe saldana","zooey deschanel","adhyayan suman","aditi govitrikar","aditya narayan","aditya pancholi","adrian grenier","aerosmith","agyness deyn","aiisha ramadan","aimee queenborough smith","ajay devgan","akshay kumar","akshaye khanna","alanis morissette","alec baldwin","alex rodriguez","alexa chung","alexander mcqueen","alexander skarsgard","ali f mostafa","ali larter","ali lohan","alicia keys","alka yagnik","amal alamuddin","amanda seyfried","america ferrera","amisha patel","amrita arora","amrita rao","amy adams","amy winehouse","anastasia griffith","andrew garfield","anil kapoor","anna wintour","annalynne mccord","annette bening","annie lennox","anupam kher","anushka sharma","arbaaz khan","archana puran singh","asha bhosle","ashlee simpson","ashley greene","ashley olsen","ashley tisdale","ashmit patel","ashton kutcher","ashutosh gowarikar","asin","asmaa al-shabibi","atif aslam","audrina patridge","avril lavigne","ayesha and dipesh dipala","ayesha takia","billy ray cyrus","bipasha basu","bjork","blake lively","bobby deol","bobby kamani","boman irani","bonnie somerville","bonnie wright","brian austin green","britney spears","brittany snow","brody jenner","brooke shields","brooklyn decker","bruce dickinson","bryan adams","cat deeley","catboy and geordiebird","catherine zeta-jones","celina jaitley","celine dion","chace crawford","chad michael murray","charlie sheen","charlize theron","chelsea clinton","chelsy davy","cher","cheryl cole","chloe sevigny","chris brown","chris martin","christian louboutin","christina aguilera","christina applegate","christina ricci","chunky pandey","claire danes","claudia schiffer","coleen rooney","colin farrell","colin firth","corrine bailey rae","courteney cox","cristiano ronaldo dos santos aveiro","cruz beckham","cynthia nixon","david hasselhoff","david letterman","dayanara torres","deepika padukone","demi lovato","demi moore","denise richards","desert heat","dev patel","dharmendra","diane kruger","dilip kumar","dina abdulhadi daryani","dina lohan","dino morea","dita von teese","dj bliss and brothers","domenico dolce","dominic cooper","donatella versace","doug reinhardt","elton john","emily blunt","emily deschanel","emma bunton","emma roberts","emmanuel sabater","enrique iglesias","eric bana","erin o`connor","esha deol","esther canadas","esther tognozzi","eva longoria","eva mendes","ewan mcgregor","freida pinto"]; 4 | 5 | const animals = ["canidae","felidae","cat","cattle","dog","donkey","goat","guinea pig","horse","pig","rabbit","laboratory rat strains","sheep breeds","water buffalo breeds","chicken breeds","duck breeds","goose breeds","pigeon breeds","turkey breeds","aardvark","aardwolf","african buffalo","african elephant","african leopard","albatross","alligator","alpaca","american buffalo","american robin","amphibian","anaconda","angelfish","anglerfish","ant","anteater","antelope","antlion","ape","aphid","arabian leopard","arctic fox","arctic wolf","armadillo","arrow crab","asp","baboon","badger","bald eagle","bandicoot","barnacle","barracuda","basilisk","bass","bat","beaked whale","bear","beaver","bedbug","bee","beetle","bird","bison","blackbird","black panther","black widow spider","blue bird","blue jay","blue whale","boa","boar","bobcat","bobolink","bonobo","box jellyfish","bovid","buffalo,african","bug","butterfly","buzzard","camel","canid","cape buffalo","capybara","cardinal","caribou","carp","catshark","caterpillar","catfish","centipede","cephalopod","chameleon","cheetah","chickadee","chicken","chimpanzee","chinchilla","chipmunk","clam","clownfish","cobra","cockroach","cod","condor","constrictor","coral","cougar","cow","coyote","crab","crane","crane fly","crawdad","crayfish","cricket","crocodile","crow","cuckoo","cicada","damselfly","deer","dingo","dinosaur","dolphin","dormouse","dove","dragonfly","duck","dung beetle","eagle","earthworm","earwig","echidna","eel","egret","elephant","elephant seal","elk","emu","ermine","falcon","ferret","finch","firefly","fish","flamingo","flea","fly","flyingfish","fowl","fox","frog","fruit bat","gamefowl","galliform","gazelle","gecko","gerbil","giant panda","giant squid","gibbon","gila monster","giraffe","goldfish","goose","gopher","gorilla","grasshopper","great blue heron","great white shark","grizzly bear","ground shark","ground sloth","grouse","guan","guanaco","guineafowl","gull","guppy","haddock","halibut","hammerhead shark","hamster","hare","harrier","hawk","hedgehog","hermit crab","heron","herring","hippopotamus","hookworm","hornet","hoverfly","hummingbird","humpback whale","hyena","iguana","impala","irukandji jellyfish","jackal","jaguar","jay","jellyfish","junglefowl","kangaroo","kangaroo mouse","kangaroo rat","kingfisher","kite","koala","koi","komodo dragon","krill","ladybug","lamprey","landfowl","land snail","lark","leech","lemming","lemur","leopard","leopon","limpet","lion","lizard","llama","lobster","locust","loon","louse","lungfish","lynx","macaw","mackerel","magpie","mammal","manatee","mandrill","manta ray","marlin","marmoset","marmot","marsupial","marten","mastodon","meadowlark","meerkat","mink","minnow","mite","mockingbird","mole","mollusk","mongoose","monitor lizard","monkey","moose","mosquito","moth","mountain goat","mouse","mule","muskox","narwhal","newt","nightingale","ocelot","octopus","opossum","orangutan","orca","ostrich","otter","owl","ox","panda","panther","panthera hybrid","parakeet","parrot","parrotfish","partridge","peacock","peafowl","pelican","penguin","perch","peregrine falcon","pheasant","pigeon","pike","pilot whale","pinniped","piranha","planarian","platypus","polar bear","pony","porcupine","porpoise","possum","prairie dog","prawn","praying mantis","primate","ptarmigan","puffin","puma","python","quail","quelea","quokka","raccoon","rainbow trout","rat","rattlesnake","raven","batoidea","rajiformes","red panda","reindeer","reptile","rhinoceros","right whale","roadrunner","rodent","rook","rooster","roundworm","saber-toothed cat","sailfish","salamander","salmon","sawfish","scale insect","scallop","scorpion","seahorse","sea lion","sea slug","sea snail","shark","sheep","shrew","shrimp","silkworm","silverfish","skink","skunk","sloth","slug","smelt","snail","snake","snipe","snow leopard","sockeye salmon","sole","sparrow","sperm whale","spider","spider monkey","spoonbill","squid","squirrel","starfish","star-nosed mole","steelhead trout","stingray","stoat","stork","sturgeon","sugar glider","swallow","swan","swift","swordfish","swordtail","tahr","takin","tapir","tarantula","tarsier","tasmanian devil","termite","tern","thrush","tick","tiger","tiger shark","tiglon","toad","tortoise","toucan","trapdoor spider","tree frog","trout","tuna","turkey","turtle","tyrannosaurus","urial","vampire bat","vampire squid","vicuna","viper","vole","vulture","wallaby","walrus","wasp","warbler","water boa","water buffalo","weasel","whale","whippet","whitefish","whooping crane","wildcat","wildebeest","wildfowl","wolf","wolverine","wombat","woodpecker","worm","wren","xerinae","x-ray fish","yak","yellow perch","zebra","zebra finch","animals by number of neurons","animals by size","common household pests","common names of poisonous animals","bali cattle","domestic bactrian camel","domestic canary","domestic dromedary camel","domestic duck","domestic goat","domestic goose","domestic guineafowl","domestic hedgehog","domestic pig","domestic pigeon","domestic rabbit","domestic silkmoth","domestic silver fox","domestic turkey","lab rat","gayal","ringneck dove","siamese fighting fish","society finch"]; 6 | 7 | const food = ["aloo pie","apple crispapple crumble","apple pie","australian and new zealand meat pie","bacon and egg pie","bakewell tart","banana cream pie","banoffee pie","bean pie","bedfordshire clanger","bisteeyapastilla etc","blackberry pie","black bottom pie","black bun","blueberry pie","bob andy pie","bougatsa","boysenberry pie","bridie","buko pie","bumbleberry pie","bundevara","bndner nusstorte","burek","butter pie","butter tart","buttermilk pie","canel","cantaloupe pie","caramel tart","cashew pie","cheesecake","cheese pie","cherry pie","chess pie","chestnut pie","chicken and mushroom pie","chiffon pie","chinese piept chinois","cobbler","coconut cream pie","cookie cake pie","corned beef pie","coulibiac","cumberland pie","curry pie","curry puff","custard tartflans ptissier","derby pie","egg tart","empanada","fish piefishermans pie","flan","flan chino ","flapper pie","fleischkuekle","flipper pie","fried pie","gibanica","green grape pie","homity pie","hornazo","jamaican patty","kalakukko","karelian pasties","key lime pie","khachapuri","killie pie","knish","kuchen","kurnik","lanttusupikas","lemon ice box pie","lemon meringue pie","manchester tart","meat and potato pie","meat pie","melktert","melton mowbray pork pie","millionaire pie","mince pie","mississippi mud pie","moravian chicken pie","mustard tart","natchitoches meat pie","neenish tart","pomaqechpochmak","pasc","pastafrola","pastel de nata","pasty","peach pie","peanut pie","pear tart","pecan pie","pie la mode","pirog","pirozhkipirozhok piroshki","pork pie","pot pie","pumpkin pie","quiche","qumeshtore me pete","raisin pie","rappie pie","raspberry pie","razzleberry pie","rhubarb pie","samosasingara sambusac samsa","saskatoonberry pie","sausage roll","scotch pie","seapiecipaille","sfiha","shaker lemon pie","shepherds pie","shoofly pie","soparnik","southern tomato pie","spanakopitaspinach pie","stargazy pie","steak and kidney pie","steak pie","strawberry pie","strawberry rhubarb pie","sugar pie","sweet potato pie","tamale pie","tarta capuchina ","tarta de santiago","tarte conversation","tiropitagreek cheese pie","tocinillo de cielo ","tourtire","treacle tart","vlaai","vsterbotten pie","walnut pie","watalappam","whe","woolton pie","whoopie pie","acar","afghan salad","ambrosia","arab salad","asinan","banana salad ","bean salad","bok lhongbok lahong","caesar salad","cappon magro","celery victor","cheese slaw","chef salad","chicken salad","chilean salad","chinese chicken salad","oban salatas","cobb salad","coleslaw","cookie salad","crab louie","curtido","dressed herring","egg salad","fattoush","fiambre","fruit salad","gadogado","garden salad","glasswort salad","glorified rice","golbaengi muchim","greek salad","ham salad","insalata caprese","israeli salad","jello salad","kachumbari","kachumber","karedok","khao yam","kinilnat","ksr","kosambari","lalab","larb","lyutika","macaroni salad","macedonia salad","mallung","matbucha","mesclun","michigan salad","mimosa salad","mushroom salad","naem khluk","nioise salad","olivier saladrussian salad","panzanella","pao cai","pasembur","pasta salad","pecel","perigourdine","phla mu","piyaz","poke salad","potato salad","raheb","rojak","sevenlayer salad","sabich salad","salat avocado","serbian salad","shepherds salad","shopska salad","shirazi salad","singju","snickers salad","som tamsom tum","szaot","tabbouleh","taco salad","green papaya salad","gi nhch","tam mu yo","tam phonlamai ruam","taramosalata","tuna salad","urap","urnebes","vinegret","waldorf salad","american sub","bacon","bagel toast","baked bean","baloney","barbecue","barros jarpa","barros luco","bauru","beef on weck","beirute","blt","bocadillo","bologna","bosna","bratwurst","breakfast roll","breakfast","british rail","butifarra ","broodje kroket","bun kebab","butterbrot","caprese","carrozza","caviar","cemita","chacarero","cheese","cheese dream","cheese and pickle","cheesesteak","chicken","chicken schnitzel","chickpea salad","chili burger","chimichurris","chip butty","chipped beef","chivito","chocolate","chopped cheese","choripn","chow mein sandwich","churrasco","club","completo","corned beef","crisp","cuban","cucumber","cudighi","grilled cottage cheese sandwich","cutlet sandwich italian","dagwood","deli","denver","doner kebab","donkey burger","doubles","doughnut sandwich","dynamite","dyrlgens natmad","elvis","egg","fairy bread","falafel","farroupilha","fischbrtchen","fish finger","fluffernutter","fools gold loaf","francesinha","francesinha poveira","french dip","fried brain","fruit","ftira","gatsby","gerber","gua bao","guajolota","gudille","grillade","gyro","hagelslag or vlokken","ham","ham and pickle sandwich","ham and cheese","ham and egg bun","hamburger","hamglizzy","har cheong gai burger","horseshoe","hot brown","hot dog","hot chicken","hot turkey","ice cream","indian taco","italian beef","italian","jam","jambonbeurre","jibarito","jucy lucy","kanapka","katsu sando","kabuli burger","kaisers jagdproviant","khao jee pt","kokoretsi","kottenbutter","leberkse","lettuce","limburger","lobster roll","lox","luther burger","mallorca de jamn y queso","marmalade","marmite","martino","meatball","medianoche","melt","mettbrtchen","mitraillette","mollete","montadito","monte cristo","montrealstyle smoked meat","mortadella","motherinlaw","muffuletta","naan","obloen chlebky","openfaced","pambazo","panbagnat","panini","pastrami on rye","patty melt","peameal bacon sandwich","peanut butter and jelly","peanut butter banana and bacon sandwich","pebete","pepito","pepper and egg","pepper and egg italian","pilgrim","pimento cheese","pistolette","pljeskavica","po boy","polish boy","porchetta","porilainen","pork chop bun","pork roll sandwich","pork tenderloin","prawn roll","prego","primanti","prosperity sandwich","pudgy pie","pulled pork","queen alexandras sandwich","rachel","reuben","roast beef","roti bakar","roti john","rou jia mo","ruisleip","runza","sabich","sailor","sndwich de milanesa","sandwich loaf","de miga","salt beef bagel","sausage","schmitter","sealed crustless","shawarma","shooters sandwich","shuco","slider","sloppy joe","sloppy joe ","smrgstrta","smrrebrd","sol over gudhjem","souvlaki","spaghetti","specials deli sandwiches","spiedie","st paul","steak bomb","steak burger","steak","submarinesubbaguette","tavern","tea","toast","toast hawaii","toastie","tofu","tongue toast","torta","torta ahogada","tramezzino","trancapecho","tripleta","tuna","turkey devonshire","vada pav","vegemite","vegetable","veggie burger","bitterballen","bonda","cereal ","cokodok","cracker nuts","crpe","croquette","doughnut ","gulha","khanom buang","pakora","pancakes","parippu vada","pazham pori","pizza ","poffertjes","pretzel soft","telebhaja ","uzhunnu vada","waffle","brittle","imli candy","bubblegum","candy","chocolate ","chocolate bar ","chocolate rugelach","chocolate truffle","fudge","geplak","grass jelly","marshmallow","marzipan","nougat","panforte","pudding ","rice krispie treats","smores","toffee","turkish delight","arrowroot","chebakia","chocolate chip cookie","cookie ","ginger snaps","graham crackers","oatmeal cookie","peanut butter cookie","gansito","jaffa cakes","snack cake","churros","fruit bun","pastry ","scones","snack pie ","toaster pastry ","amazake","atole","coffee","colada morada","energy drinks ","flavored milk","horchata","juice","kefir","malted milk","milkshake","root beer","root beer float","sikhye","soft drinks","smoothie ","sports drinks ","tamagozake","tejuino","frozen custard","ice cream ","ice pop","achappam","ada","dolma","french fries","hummus","instant soup ","khandvi ","kuzhalappam","meze","nian gao","onion rings ","piattos","sesame sticks","sev mamra","spring roll ","tahini","tapas","tempura","unniyappamkuzhiyappam","yogurt ","energy bar ","flapjack","granola bar ","bagel ","bread ","buterbrod","croissant ","croutons ","fried bake","guabao","houska ","lavash","open sandwich ","pasta","sandwich ","tea sandwich","tortilla","totopo","american cheese","cheese ","cream cheese","korbiky","oaxaca cheese","obatzda","parmesan cheese","processed cheese","string cheese","banana chip","cheese puff","chifle","corn chips","corn nuts ","fish and shrimp chips","multigrain snacks ","nachos","pita chips","pork rind ","potato chips ","pretzel hard ","snack mix","tortilla chips ","animal cracker","arare","bagel chips","crackers ","hardtack","knckebrd","oyster cracker","rice cracker ","senbei ","soda cracker","water biscuit","i kfte","corn dog","dried cuttlefish","dried fish ","dried squid ","fish such as fried fish ","hot dogs","jerky ","kibbeh nayyeh","omelet","oysters ","pickled herring ","soused herring","bombay mix","cup noodles","instant noodles ","ramen","aguadito","ajiaco","aorda","acquacotta","amish preaching soup","anal kzl soup","ashe doogh","avgolemono","avocado soup","bacon soup","bak kut teh","bakso","barley","beef noodle soup","beer soup","bergen fish soup","binignit","birds nest soup","borscht","bouillabaisse","bouillon ","bouroubourou","bread soup","brenebon","brown veal","brown windsor soup","bun bo hue","buridda","butajiru","cabbage soup kapusniak kapustnica zelnacka","caldillo de congrio","caldillo de perro","caldo verde","callaloo","canh chua","canja de galinha","carp soup ","carrot soup","cazuela","chestnut bisque","chicken noodle soup","chicken soup","chicken vegetable soup","chupe","chupe andino","cioppino","cockaleekie","cold borscht altibariai","consomm","corn chowder","crab bisque","cream of apple soup","cream of asparagus","cream of broccoli","cream of celery","cream of chicken","cream of potato","cream of tomato","cream of crab","cream of mushroom","crme ninon","cucumber soup","cullen skink","curry mee","dalithoy","dashi","dillegrout","duck soup noodles","egg drop soup","egusi soup","ezogelin soup","fabada asturiana","fanesca","fish soup bee hoon","fish","fishermans soup","french onion soup","frittatensuppe","fruktsoppa","fufu and egusi soup","fumet","garmugia","gazpacho","ginataan","ginestrata","goat meat pepper soup","gogi guksu","golden mushroom","gomguk","goulash soup","ground nut soup","kimchi guk","gumbo","harira","hot and sour soup","slensk kjtspa","joumou","kharcho","kusksu","kwti","laksa","lagman","leek soup","lettuce soup","lentil soup","lobster stew","lobster bisque","loglog","lohikeitto","lung fung soup","lyvzha","maccu","manhattan clam chowder","maryland crab soup","matzah ball soup","melon soup","minestrone","miso soup","miyeok guk","mohinga","mote de queso","mulligan stew","mulligatawny","naengmyeon","nettle soup","new england clam chowder","nikujaga","okra soup","okroshka","oxtail soup","palm nut soup","panada","panadelsuppe","pasta fagioli","yellow pea soup","peanut soup","philadelphia pepper pot","ph","pickle soup","pork blood soup","pozole","psarosoupa ","pumpkin","rasam","rassolnik","rawon","rishtay rqaq o adas","rumfords soup","saimin","salmorejo","sambar","samgyetang","sayur asem","sayur lodeh","scotch broth","shark fin soup","shchav sorrel soup green borscht green shchi","shchi","seafood chowder","shecrab soup","shrimp bisque","sliced fish soup","snert","solyanka","sop saudara","sopa de gato","soto","soto ayam","soup alla canavese","soup no 5","sour cherry soup","sour rye soup white borscht ur","sour soup ","spinach soup","split pea","squash bisque","stone soup","sup kambing","stracciatella","swedish fruit soup","taco soup","talbina","tng fn","tng min","tapado","tarator","tarhana","tekwan","tinola","tom yum","tomato bisque","tomato soup","tongseng","tortilla soup","tteokguk","turkey soup","ukha or yushka","vegetable soup","vichyssoise","vori vori","waterzooi","wedding soup","white beef","white veal","wine soup","winter melon","aj de gallina","alicot","andrajos","asam pedas","balbacua","bamia","beef bourguignon","beef stroganoff","bicol express","bigos","birnen bohnen und speck","birria","blanquette de veau","booyah","bosnian pot","brongkos","brudet","brunswick stew","buseca","buddha jumps over the wall","buu kebab","burgoo","cabbage stew","cacciucco","cachupa","caldeirada","caldereta de cordero","caldo av","caldo gallego","callos","caparrones","capra e fagioli","carbonade flamande","carne mechada","cassoulet","cawl","chairo","chakapuli","chapea","chicken mull","chili con carne","cholent","ciambotta","cincinnati chili","cocido lebaniego","cocido madrileo","cocido montas","compote","corn stew","coq au vin","cotriade","cozidococido","daube","dimlama","dinuguan","drokpa katsa","escudella i carn dolla","touffe","fabes con almejas","fahsa","frikl","fasole cu crnai","feijoada","fesenjn","flaki","fzelk","fricot","gaisburger marsch","galinhada","garbure","ghapama","ghormeh sabzi","goat water","goulash","guatitas","gve","guyana pepperpot","hachee","hasenpfeffer","hochepot","hoosh","hot pot","irish stew","islim or patlcan kebab","istrian stew","kadhi","kaldereta","kamounia","kapuska","karekare","karelian hot pot","kig ha farz","kuru fasulye","lancashire hotpot","lskisoosi","lecs","locro","lunggoi katsa","maafe","maconochie","mechado","mjave lobio","moambe","mocot","molagoottal","moqueca","navarin","ndol","oil down","olla podrida","ollada","ostrich stew","ostropel","oyster stew","paila marina","palaver sauce","paomo","pasulj ","pichelsteiner","pinangat","piperade","pisto","prklt","potaufeu","potjiekos","pottage","puchero","qoiri","rabbit stew","ragout","ratatouille","red cooked pork","rssypottu","rogan josh","rubaboo","sagamite","saksang","saltah","scouse","seco","sekba","semur","shiro","sinigang","skirts and kidneys","sonofabitch stew","stew peas","sulu kfte","tajine","tas kebap","tatws pum munud","tharid","tocan","tomato bredie","tombet","tuna pot","carpaccio","ceviche","crudo","eia ota","esqueixada","gravlax","gohu ikan","hinava","hoe","kelaguen","kilawin","koi pla","kokoda","kuai","lakerda","lap palarb pla","namer","ota ika","poke","sashimi","soused herring ","stroganina","tiradito","tuna tartare","umai","xato","yusheng","angels on horseback","antipasto","bakwan","batagor","batata vada","barbajuan","blooming onion","bruschetta","buffalo wing","canap","chaat","chicken fingers","chicken lollipop","crab puff","crab rangoon","crostini","crudits","dahi puri","dahi vada","deviled eggs","devils on horseback","eggplant salads and appetizers","fried mushrooms","garlic knot","haggis pakora","jalapeo popper","ketoprak","lumpia","malakoff","martabak","mozzarella sticks","onion ring","paneer tikka","panipuri","papadum","papri chaat","pigs in a blanket","perkedel","pizzetta","potato skins","potato wedges","prawn cocktail","pu pu platter","queso flameado","rocky mountain oysters","rumaki","saganaki","sakinaluchakli","samosa","salmon tartare","serabi","stuffed mushrooms","sushi","tokwat baboy","zakuski","100 grand bar","3 musketeers","3 musketeers truffle crisp","5th avenue","5 star","aero","aero caramel","aero mint","aero orange","after eight","air delight","albeni","alberts ice cubes","almond joy","amul chocolate","baby ruth","balisto","bar none","bar one","big turk","boost","bounty","bournville","breakaway","bros","bros puur","bubu lubu","bun","butterfinger","cadbury dairy milk","cadbury darkmilk","cadbury fruit & nut","cadbury tempo","caramello","caramello koala","caramilk","caravan","carlos v","catch bar","charleston chew","cherry ripe","cherry mash","chokito","chomp","chunky","clark bar","coffee crisp","coffee crisp orange","cow chocolate","crachi","cri-cri","crispy crunch","crunch","crunchie","crunky","cup-o-gold","curly wurly","dagoba chai chocolate","daim","double decker","dove bar","dream","drifter","duncans","eat-more","encore!","excellence","five star caramel","flake","forever yours","freddo","frys chocolate cream","frys turkish delight","galaxy","galaxy caramel","galaxy ripple","glosette","golden rough","goobers","googoo cluster","grand slam","green and blacks","hail","happy hippo","haviland thin mints","haviland wintergreen patty","heath bar","hershey bar","hershey bar with almonds","hershey almond toffee bar","hersheys creamy caramel","hersheys kisses","hersheys special dark","holly bar","idaho spud","intense orange","ivory mountain","jacek","jersey milk","junior caramels","karl fazer milk chocolate","kinderkinder bueno","kit kat","korkunov chocolate bars","krackel","krembanan","kvikk lunsj","la fama","leah bar","lindor","lion bar","lion peanut","lottes ghana","lunch bar","m&s","m-azing","macadamia","macadamia nut","maestro","malagos","maracaibo 65","marathon","marble chocolate","mars bar","mars bar lava","meiji almond","mekupelet","menthe","milk shake","milka","milky bar","milky way","milky way midnight","milo bar","mint krokant","mirage","moka","moro","mounds","mountain bar","mousse","mr. big","mr. goodbar","munchies","neapolitan coconut slice","nestle crunch","nestlé milk chocolate","nestlé triple decker bar","nestle white","nickel lunch","noisette","nougatti","nut goodie","nut lovers","nutrageous","nuts","oh henry","old faithful","pal-o-mine","pb max","peanut slab","penguin","pep","peppermint crisp","perky nana","pesek zman","picnic","pistache","pistachio and rose petal chocolate bar","polly waffle","powerhouse","prince","prince polo","princessa","ragusa","raider","rally bar","reeses crispy crunchy bar","reeses fast break","reeses peanut butter cups","reeses sticks","ritter sport","rocky road","rolo","safari","salted pretzel","sarris chocolate covered pretzels","sasha chocolate goldleaf","skor","sky bar","smooth sailin","snickers","snik snak","spira","starbar","sublime","sweet marie","swirled caramel & salted peanut","take 5","tango","tim tam","time out","tin larin","toak chocolate","toblerone","toffee crisp","toffifee","tonys chocolonely","top deck","topvalu","topic","torino","túró rudi","turtles","twin bing","twirl","twix","u-no bar","vice versas","violet crumble","walnut crush","welchs fudge","whatchamacallit","whip","white knight","whittakers","willocrisp","wispa","wonka bar","wonka mud sludge","woodies","wunderbar","yankie bar","york bar","york peppermint pattie","yorkie","zero bar","acidophiline","amasi","appam","atchara","ayran","bagoong","bagoong monamon","bagoong terong","balao-balao","bánh cuốn","beer","blaand","boza","bread","brem","burong isda","burong mangga","burong talangka","buttermilk","calpis","chass","cheonggukjang","chicha","chinese pickles","cincalok","cocoa","cod liver oil ","crème fraîche","dhokla","doenjang","doogh","dosa","doubanjiang","douchi","douzhi","fermented bean curd","fermented bean paste","fermented fish","fermented milk products","filmjölk","fish sauce","ganjang","garri","garum","gejang","gochujang","gundruk","hákarl","hongeohoe","idli","igunaq","injera","iru ","jeotgal","jogijeot","kapusta kiszona duszona","katsuobushi","kaymak","kenkey","ketchup","khanom chin","kimchi","kiviak","kombucha","kumis","kusaya","kuzhi paniyaram","kvass","lassi","leben ","lufu ","mageu","meigan cai","miso","mixian ","mohnyin tjin","murri ","mursik","myeolchijeot","nata de coco","nata de piña","nattō","nem chua","ngapi","ogi ","ogiri","oncom","palappam","pesaha appam","peuyeum","pickles","podpiwek","poi ","pon ye gyi","portuguese ground red pepper ","pulque","puto","rakfisk","rượu nếp","ryazhenka","saeujeot","salami","sauerkraut","şalgam","shark meat","shiokara","shrimp paste ","sinki ","skyr","smântână","smetana ","som moo","sour cabbage","sour cream","soured milk","sowans","soy sauce","ssamjang","stinky tofu","strained yogurt","suan cai","sumbala","surströmming","taba ng talangka","tabasco sauce","tapai","tempeh","tesgüino","tianjin preserved vegetable","tianmianjiang","tibicos","tsukemono","tương","tungrymbai","viili","vinegar","wine","white sugar sponge cake","worcestershire sauce","yakult","yellow soybean paste","yogurt","yongfeng chili sauce","zha cai","chakuli pitha","enduri pitha","albacore","alewife","amberjack","anchovy","angelfish","ballyhoo","barracuda","atlantic pomfret","bass","bigeye ","blackfish","blacksmith","blue marlin","blueback","bluefish","bluegill","bocaccio","bombay duck","bonefish","bonito","bowfin","bream","brill","broadbill","buffalo fish","butter fish","butterfly fish","cabrilla","calico bass","capelin","carp","carpsucker","cero","channel bass","char","chilean sea bass","chilipepper ","chup","cichlid","cigarfish","cisco","coalfish","cobia, cabio, or black bonito","cod","common snook","corbina, corvina","cottonwick","crappie","creville","croacker","crucian carp","cubbyu","cunner","dab","damselfish","doctorfish","eulachon","flounder","flatfish","fluke","flyingfish","frostfish","gag grouper mycteroperca microlepis","giant kelpfish","gizzard shad","goatfish","gobies","goldeye","goldfish","grayling","graysby","greenling","grouper","grunion","grunt","guavina","haddock","hake","halfbeak","halfmoon","halibut","hamlet ","harvestfish","hawkfish","herring","hind","hogchoker","hogfish","hoki","horse mackerel","jack mackerel","jacks and pompanos","jacksmelt","john dory","kelpfish","kingfish","ladyfish","lafayette","lake herring","largemouth bass","lingcod","lizardfish","lookdown","mackerel","mahimahi","margate","menhaden","menpachii","merluccio","milkfish or awa","mojarras","mooneye","moonfish","mossbunker","mullet","muskellunge","mutton hamlet","muttonfish","needlefish","opaleye","palometa","parrotfish","patagonian toothfish","perch","permit","pickerel","pigfish","pike","pikeperch","pilchard","pinfish","plaice","pollock","pomfret","pompano","porgies and sea bream","porkfish","poutassou","prickleback","queenfish","quillback","redfish","roach","rock bass","rockhind","rockfish","rose fish","rohu", "labeo rohita","rudderfish","sablefish","saithe","salmon","sardine","sargo","sauger","scad","scorpionfish","scup","sea bass","sea chubs","sea perch","sea robin","sea trout","shad","sheepshead","sierra","silverside","skipjack","smallmouth bass","smelts","bluestripe snapper","snappers","sole","spadefish","spanish mackerel","spearing","splittail","spot","sprat","squawfish","squirrelfish","steelhead","striped bass","sucker","sunfish","surfperch","surgeonfish","tarpon","tautog","temperate bass","tench","tenpounder","threadfin","tigerfish","tilapia","tilefish","tomcod","topsmelt","tripletail","trout","turbot","wahoo","walleye","walleye pollock","warmouth","weakfish","white fish","whiting","wrasse","yellowtail","yellowtail snapper","barbine","bavette","bigoli","bucatini","busiate ","capellini","fedelini","ferrazuoli","fettuccine","fileja","linguine","lagane","lasagna","lasagnette","lasagnotte","maccheroni alla molinara","maccheroncini di campofilone","mafalde","matriciani","pappardelle","perciatelli","pici","pillus","rustiche","sagne ncannulate","scialatelli or scialatielli","spaghetti alla chitarra","spaghettini","spaghettoni","stringozzi","su filindeu","tagliatelle","taglierini","trenette","tripoline","vermicelli","ziti","anelli","boccoli","calamarata","campanelle or torchio","cappelli da chef","casarecce","castellane","cavatappi","cavatelli","chifferi","cicioneddos","conchiglie","creste di galli","fagioloni","farfalle","fazzoletti","festoni","fiorentine","fiori","fusilli","fusilli bucati","garganelli","gemelli","gnocchi","gomiti","lanterne","lorighittas","macaroni","maccheroncelli","mafaldine","maltagliati","malloreddus","mandala","marille","mezzani","mezze maniche","mezze penne","mezzi bombardoni","nuvole","paccheri","passatelli","pasta al ceppo","penne","penne ricce","picchiarelli","pipe rigate","pizzoccheri","quadrefiore","radiatori","riccioli","ricciolini","ricciutelle","rigatoncini","rigatoni","rombi","rotelle","sagnette","sagnarelli","sedani","spirali","spiralini ","strapponi","strozzapreti","testaroli","tortiglioni","treccioni","trenne","trofie","tuffoli","vesuvio","cencioni","corzetti","fainelle","foglie dulivo","orecchiette","acini di pepe","anellini","conchigliette","corallini","ditali","egg barley","farfalline","fideos","filini","fregula","funghini","gramigne","grattini","grattoni","midolline","occhi di pernice","orzo","pastina","piombi","ptitim","puntine","quadrettini","sorprese","stelle","stortini","tripolini","alphabet pasta","agnolotti","caccavelle","cannelloni","cappelletti ","caramelle","casoncelli","casunziei","conchiglioni","culurgiones","fagottini","lumache","mezzelune","occhi di lupo","pansotti","ravioli","rotolo ripieno","sacchettoni","tortelli","tortellini","tortelloni","tufoli","canederli","donderet","abiu","apple","atemoya","avocado","banana and plantains","barbados cherry","blackberry","black sapote","blueberry","bunch grape","caimito","canistel","cantaloupes and muskmelons","carambola","chestnut","chinese jujube","coconut palm","eugenia","fig","grape","guava","huckleberry","jackfruit","jojoba","longan","loquat","lychee","mamey sapote","mamoncillo","mango","monstera","muscadine grape","naranjillo","natal plum","nectarine","olive trees","oranges","oriental persimmon","papaya","passion fruit","peach, plum and nectarine production","peanut production","pear","pecan","pejibaye","persimmon","pineapple","pineapple guava","pitaya","plum","pomegranate","raspberry","sapodilla","seagrape","spondias","strawberries","sugar apple","tamarind","watermelon","white sapote"]; 8 | -------------------------------------------------------------------------------- /src/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h4x0rm1k3/ABS/dd7a83dd38a61de65fe835f40f8761ae238a1ed7/src/icons/icon128.png -------------------------------------------------------------------------------- /src/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h4x0rm1k3/ABS/dd7a83dd38a61de65fe835f40f8761ae238a1ed7/src/icons/icon16.png -------------------------------------------------------------------------------- /src/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h4x0rm1k3/ABS/dd7a83dd38a61de65fe835f40f8761ae238a1ed7/src/icons/icon48.png -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ABS", 3 | "description": "Automatically perform daily searches and collect bonus reward points.", 4 | "version": "1.2.31.5", 5 | "manifest_version": 2, 6 | "icons": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" }, 7 | "content_scripts": [{ 8 | "matches": ["https://*.bing.com/*"], 9 | "js": [ 10 | "constants.js", 11 | "utils.js", 12 | "chrome-utils.js", 13 | "content-scripts/script-injector.js", 14 | "content-scripts/window-variable-grabber/injector.js", 15 | "content-scripts/quiz-answer-hash-function.js", 16 | "content-scripts/main.js" 17 | ] 18 | }, { 19 | "matches": ["https://rewards.bing.com/?redref=amc"], 20 | "js": [ 21 | "constants.js", 22 | "content-scripts/script-injector.js", 23 | "content-scripts/window-open-injection/injector.js" 24 | ] 25 | }], 26 | "background": { 27 | "scripts": [ 28 | "constants.js", 29 | "utils.js", 30 | "data.js", 31 | "query-templates.js", 32 | "chrome-utils.js", 33 | "background-scripts/prefs.js", 34 | "background-scripts/queries.js", 35 | "background-scripts/reminder.js", 36 | "background-scripts/spoof.js", 37 | "background-scripts/search.js", 38 | "background-scripts/schedule.js", 39 | "background-scripts/messages.js" 40 | ], 41 | "persistent": true 42 | }, 43 | "content_security_policy": "script-src 'self' https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js; object-src 'self'", 44 | "browser_action": { 45 | "default_popup": "popup.html" 46 | }, 47 | "options_ui": { 48 | "page": "options.html", 49 | "open_in_tab": false 50 | }, 51 | "commands": { 52 | "start-searches": { 53 | "suggested_key": { 54 | "default": "Ctrl+Shift+S", 55 | "mac": "Command+Shift+S" 56 | }, 57 | "description": "Start searches" 58 | } 59 | }, 60 | "web_accessible_resources": [ 61 | "constants.js", 62 | "content-scripts/window-variable-grabber/main.js", 63 | "content-scripts/window-open-injection/main.js" 64 | ], 65 | "permissions": [ 66 | "https://*.bing.com/*", 67 | "https://trends.google.com/*", 68 | "https://rewards.bing.com/*", 69 | "https://rewards.bing.com/", 70 | "webRequest", 71 | "webRequestBlocking", 72 | "tabs", 73 | "alarms", 74 | "storage" 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /src/options.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 340px; 3 | background-color: #424242; 4 | color: white; 5 | padding: 18px; 6 | margin: 0px; 7 | font-size: 16px; 8 | } 9 | 10 | hr { 11 | width: 100%; 12 | margin-left: 0; 13 | } 14 | 15 | textarea { 16 | width: 100%; 17 | background: transparent; 18 | color: white; 19 | } 20 | 21 | textarea:focus, input:focus { 22 | outline: none; 23 | } 24 | 25 | label { 26 | display: flex; 27 | cursor: pointer; 28 | } 29 | 30 | .row { 31 | display: flex; 32 | } 33 | 34 | .option-wrapper { 35 | margin-top: 5px; 36 | display: flex; 37 | } 38 | 39 | .option-wrapper input { 40 | margin-right: 5px; 41 | } 42 | 43 | .text-secondary { 44 | color: #79D6B5; 45 | font-size: 0.9rem; 46 | } 47 | 48 | .btc-code { 49 | background-color: #222222; 50 | width: fit-content; 51 | word-break: break-word; 52 | margin-top: 5px; 53 | padding: 10px; 54 | border-radius: 10px; 55 | text-align: center; 56 | } 57 | -------------------------------------------------------------------------------- /src/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ABS Options 6 | 7 | 8 | 9 | 10 |
3Cc3CmCbyzMu8g8zZSxAdRJydAyURGrWH7
11 |
12 | 13 |
Queries (one per line):
14 | 15 |
16 | Specify which queries to include when searching: 17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 | 25 |
26 |
27 |
28 | 29 |
30 | 31 |
32 |
33 | 34 |
Scheduling
35 |
As long as your browser is open, it will attempt to open a new tab and run searches at the specified time every day.
36 | 37 |
38 | 39 |
40 |
41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/options.js: -------------------------------------------------------------------------------- 1 | // id is HTML id attribute 2 | // elementKey is how to get the value of that element (depends on type of input) 3 | // preferenceKey the is key in chrome storage and constants.DEFAULT_PREFERENCES 4 | const preferenceBindings = [ 5 | { id: 'random-letters-search', elementKey: 'checked', preferenceKey: 'randomLettersSearch' }, 6 | { id: 'custom-queries', elementKey: 'value', preferenceKey: 'customQueries' }, 7 | { id: 'search-with-custom-queries', elementKey: 'checked', preferenceKey: 'searchWithCustomQueries' }, 8 | { id: 'search-with-daily-trends', elementKey: 'checked', preferenceKey: 'searchWithDailyTrends' }, 9 | { id: 'search-with-templates', elementKey: 'checked', preferenceKey: 'searchWithTemplates' }, 10 | { id: 'schedule-daily-searches', elementKey: 'checked', preferenceKey: 'scheduleSearches' }, 11 | { id: 'scheduled-time', elementKey: 'value', preferenceKey: 'scheduledTime' }, 12 | { id: 'scheduled-time-open-reward-tasks', elementKey: 'checked', preferenceKey: 'scheduledTimeOpensRewardTasks' }, 13 | ]; 14 | 15 | // id is HTML id attribute 16 | // eventType is the type of event to listen for 17 | // fn is what to run when the event occurs (defaults to saveChanges) 18 | const changeBindings = [ 19 | { id: 'random-letters-search', eventType: 'change' }, 20 | { id: 'custom-queries', eventType: 'input' }, 21 | { id: 'search-with-custom-queries', eventType: 'change' }, 22 | { id: 'search-with-daily-trends', eventType: 'change' }, 23 | { id: 'search-with-templates', eventType: 'change' }, 24 | { id: 'schedule-daily-searches', eventType: 'change' }, 25 | { id: 'scheduled-time', eventType: 'change' }, 26 | { id: 'scheduled-time-open-reward-tasks', eventType: 'change' }, 27 | ]; 28 | 29 | function saveChanges() { 30 | const newPreferences = preferenceBindings.reduce((acc, binding) => ({ 31 | ...acc, 32 | [binding.preferenceKey]: document.getElementById(binding.id)[binding.elementKey], 33 | }), {}); 34 | setStorage(newPreferences); 35 | } 36 | 37 | getStorage( 38 | preferenceBindings.map(({ id, elementKey, preferenceKey }) => ({ 39 | key: preferenceKey, 40 | cb: value => { 41 | // value could be false, in which case the shortcut || operator 42 | // would evaluate to the default (not intended) 43 | document.getElementById(id)[elementKey] = value === undefined 44 | ? constants.DEFAULT_PREFERENCES[preferenceKey] 45 | : value; 46 | }, 47 | })), 48 | ); 49 | 50 | changeBindings.forEach(({ id, eventType }) => { 51 | document.getElementById(id).addEventListener(eventType, saveChanges); 52 | }); 53 | 54 | -------------------------------------------------------------------------------- /src/popup.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 300px; 3 | background-color: #424242; 4 | color: white; 5 | padding: 18px; 6 | margin: 0px; 7 | font-size: 16px; 8 | } 9 | 10 | h2 { 11 | margin: 0px 0px 10px 0px; 12 | text-align: center; 13 | } 14 | 15 | label { 16 | display: flex; 17 | cursor: pointer; 18 | } 19 | 20 | .row { 21 | display: flex; 22 | } 23 | 24 | .row > * { 25 | margin-right: 5px; 26 | } 27 | 28 | .fifty { 29 | width: 50%; 30 | } 31 | 32 | input[type="checkbox"] { 33 | cursor: pointer; 34 | } 35 | 36 | input { 37 | display: block; 38 | margin: 8px 0px; 39 | color: white; 40 | } 41 | 42 | select { 43 | margin-bottom: 8px; 44 | } 45 | 46 | input:focus, select:focus { 47 | outline: none; 48 | } 49 | 50 | input[type="number"] { 51 | width: 60px; 52 | padding: 4px 2px; 53 | margin-top: 2px; 54 | border: 1px solid #ccc; 55 | border-radius: 4px; 56 | box-sizing: border-box; 57 | background-color: transparent; 58 | border-top: none; 59 | border-left: none; 60 | border-right: none; 61 | border-bottom: 1px solid white; 62 | border-radius: 0px; 63 | } 64 | 65 | input[type="number"]:focus { 66 | transition-duration: 0.2s; 67 | border-bottom: 1px solid #2196f3; 68 | } 69 | 70 | input[type="button"] { 71 | background-color: #4caf50; 72 | border: none; 73 | font-weight: bold; 74 | padding: 10px 16px; 75 | text-align: center; 76 | cursor: pointer; 77 | outline: none; 78 | transition-duration: 0.4s; 79 | } 80 | 81 | input[type="button"]:hover { 82 | background-color: #388e3c; 83 | } 84 | 85 | a:visited { 86 | color: #2196f3; 87 | } 88 | 89 | a { 90 | color: #2196f3; 91 | text-decoration: none; 92 | } 93 | 94 | a:hover { 95 | color: #1976d2; 96 | } 97 | 98 | .option-wrapper { 99 | margin-top: 5px; 100 | display: flex; 101 | } 102 | 103 | .option-wrapper input { 104 | margin-right: 5px; 105 | } 106 | 107 | #static-search-input-wrapper, #random-search-input-wrapper { 108 | /* visibility will be changed based off random search preference */ 109 | display: none; 110 | } 111 | 112 | #iteration-count-wrapper { 113 | /* visibility will be changed during iterations */ 114 | display: none; 115 | } 116 | 117 | #iteration-count-wrapper > div { 118 | display: flex; 119 | flex-direction: column; 120 | align-items: flex-start; 121 | } 122 | -------------------------------------------------------------------------------- /src/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ABS 6 | 7 | 8 | 9 |
10 |
11 |
12 | Desktop searches: 13 | 14 |
15 |
16 | Mobile searches: 17 | 18 |
19 |
20 |
21 |
22 | Delay (ms): 23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | Min num searches: 31 | 32 |
33 |
34 | Max num searches: 35 | 36 |
37 |
38 |
39 |
40 | Min delay (ms): 41 | 42 |
43 |
44 | Max delay (ms): 45 | 46 |
47 |
48 |
49 | 50 |
51 | 52 |
53 | 54 | Platform spoofing (Edge) 55 | 61 | 62 |
63 | Customize Search Settings 64 |
65 | 66 |
67 | 68 | 69 |
70 | 71 | Reset to default values 72 | 73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 | 81 |
82 | 83 |
84 | 85 | 86 | 87 |
88 |
89 | Searches left... 90 |
91 |
92 |
93 |
94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/popup.js: -------------------------------------------------------------------------------- 1 | const port = chrome.runtime.connect(); 2 | 3 | const staticSearchesWrapper = document.getElementById('static-search-input-wrapper'); 4 | const randomSearchesWrapper = document.getElementById('random-search-input-wrapper'); 5 | 6 | const iterationCount1 = document.getElementById('iteration-count-1'); 7 | const iterationCount2 = document.getElementById('iteration-count-2'); 8 | const iterationCountWrapper = document.getElementById('iteration-count-wrapper'); 9 | 10 | // if we are spoofing desktop searches, show a count labelled 'desktop'. same for mobile. 11 | // if we are not spoofing anything, then just display an unlabelled count. 12 | function setCountDisplayText({ 13 | numIterations, 14 | overallCount, 15 | containsDesktop, 16 | containsMobile, 17 | desktopRemaining, 18 | mobileRemaining, 19 | }) { 20 | if (numIterations === overallCount) { 21 | clearCountDisplayText(); 22 | return; 23 | } 24 | iterationCountWrapper.style = 'display: block;'; 25 | 26 | if (containsDesktop) { 27 | iterationCount1.innerText = `${desktopRemaining} (desktop)`; 28 | } 29 | if (containsMobile) { 30 | const el = containsDesktop ? iterationCount2 : iterationCount1; 31 | el.innerText = `${mobileRemaining} (mobile)`; 32 | } 33 | if (!containsDesktop && !containsMobile) { 34 | iterationCount1.innerText = numIterations - overallCount; 35 | } 36 | } 37 | 38 | function clearCountDisplayText() { 39 | iterationCount1.innerText = ''; 40 | iterationCount2.innerText = ''; 41 | iterationCountWrapper.style = 'display: none;'; 42 | } 43 | 44 | port.onMessage.addListener(msg => { 45 | switch(msg.type) { 46 | case constants.MESSAGE_TYPES.UPDATE_SEARCH_COUNTS: { 47 | setCountDisplayText(msg); 48 | break; 49 | } 50 | case constants.MESSAGE_TYPES.CLEAR_SEARCH_COUNTS: { 51 | clearCountDisplayText(); 52 | break; 53 | } 54 | default: break; 55 | } 56 | }); 57 | chrome.runtime.sendMessage({ 58 | type: constants.MESSAGE_TYPES.GET_SEARCH_COUNTS, 59 | }); 60 | 61 | function updateSearchInputsVisibility() { 62 | if (document.getElementById('random-search').checked) { 63 | staticSearchesWrapper.style = 'display: none;'; 64 | randomSearchesWrapper.style = 'display: block;'; 65 | } else { 66 | staticSearchesWrapper.style = 'display: block;'; 67 | randomSearchesWrapper.style = 'display: none;'; 68 | } 69 | } 70 | 71 | // id is HTML id attribute 72 | // elementKey is how to get the value of that element (depends on type of input) 73 | // preferenceKey the is key in chrome storage and constants.DEFAULT_PREFERENCES 74 | const preferenceBindings = [ 75 | { id: 'desktop-iterations', elementKey: 'value', preferenceKey: 'desktopIterations' }, 76 | { id: 'mobile-iterations', elementKey: 'value', preferenceKey: 'mobileIterations' }, 77 | { id: 'delay', elementKey: 'value', preferenceKey: 'delay' }, 78 | { id: 'random-search-iterations-min', elementKey: 'value', preferenceKey: 'randomSearchIterationsMin' }, 79 | { id: 'random-search-iterations-max', elementKey: 'value', preferenceKey: 'randomSearchIterationsMax' }, 80 | { id: 'random-search-delay-min', elementKey: 'value', preferenceKey: 'randomSearchDelayMin' }, 81 | { id: 'random-search-delay-max', elementKey: 'value', preferenceKey: 'randomSearchDelayMax' }, 82 | { id: 'auto-click', elementKey: 'checked', preferenceKey: 'autoClick' }, 83 | { id: 'random-guesses', elementKey: 'checked', preferenceKey: 'randomGuesses' }, 84 | { id: 'platform-spoofing', elementKey: 'value', preferenceKey: 'platformSpoofing' }, 85 | { id: 'random-search', elementKey: 'checked', preferenceKey: 'randomSearch' }, 86 | { id: 'blitz-search', elementKey: 'checked', preferenceKey: 'blitzSearch' }, 87 | ]; 88 | 89 | getStorage( 90 | preferenceBindings.map(({ id, elementKey, preferenceKey }) => ({ 91 | key: preferenceKey, 92 | cb: value => { 93 | // value could be false, in which case the shortcut || operator 94 | // would evaluate to the default (not intended) 95 | document.getElementById(id)[elementKey] = value === undefined 96 | ? constants.DEFAULT_PREFERENCES[preferenceKey] 97 | : value; 98 | }, 99 | })), 100 | ).then(updateSearchInputsVisibility); 101 | 102 | function saveChanges() { 103 | updateSearchInputsVisibility(); 104 | const newPreferences = preferenceBindings.reduce((acc, binding) => ({ 105 | ...acc, 106 | [binding.preferenceKey]: document.getElementById(binding.id)[binding.elementKey], 107 | }), {}); 108 | setStorage(newPreferences); 109 | } 110 | 111 | function reset(e) { 112 | e.preventDefault(); // the reset button is actually a link, so we don't want it to redirect 113 | if (document.getElementById('random-search').checked) { 114 | document.getElementById('random-search-iterations-min').value = constants.DEFAULT_PREFERENCES.randomSearchIterationsMin; 115 | document.getElementById('random-search-iterations-max').value = constants.DEFAULT_PREFERENCES.randomSearchIterationsMax; 116 | document.getElementById('random-search-delay-min').value = constants.DEFAULT_PREFERENCES.randomSearchDelayMin; 117 | document.getElementById('random-search-delay-max').value = constants.DEFAULT_PREFERENCES.randomSearchDelayMax; 118 | } else { 119 | document.getElementById('desktop-iterations').value = constants.DEFAULT_PREFERENCES.desktopIterations; 120 | document.getElementById('mobile-iterations').value = constants.DEFAULT_PREFERENCES.mobileIterations; 121 | document.getElementById('delay').value = constants.DEFAULT_PREFERENCES.delay; 122 | } 123 | saveChanges(); 124 | } 125 | 126 | function openOptions(e) { 127 | e.preventDefault(); // the open-options button is actually a link, so we don't want it to redirect 128 | if (chrome.runtime.openOptionsPage) { 129 | chrome.runtime.openOptionsPage(); 130 | } else { 131 | window.open(chrome.runtime.getURL('options.html')); 132 | } 133 | } 134 | 135 | // id is HTML id attribute 136 | // eventType is the type of event to listen for 137 | // fn is what to run when the event occurs (defaults to saveChanges) 138 | const changeBindings = [ 139 | { id: 'desktop-iterations', eventType: 'input' }, 140 | { id: 'mobile-iterations', eventType: 'input' }, 141 | { id: 'delay', eventType: 'input' }, 142 | { id: 'random-search', eventType: 'change' }, 143 | { id: 'random-search-iterations-min', eventType: 'input' }, 144 | { id: 'random-search-iterations-max', eventType: 'input' }, 145 | { id: 'random-search-delay-min', eventType: 'input' }, 146 | { id: 'random-search-delay-max', eventType: 'input' }, 147 | { id: 'auto-click', eventType: 'change' }, 148 | { id: 'random-guesses', eventType: 'change' }, 149 | { id: 'platform-spoofing', eventType: 'change' }, 150 | { id: 'blitz-search', eventType: 'change' }, 151 | { id: 'reset', eventType: 'click', fn: reset }, 152 | { id: 'open-options', eventType: 'click', fn: openOptions }, 153 | { id: 'stop', eventType: 'click', fn: stopSearches }, 154 | ]; 155 | 156 | changeBindings.forEach(({ id, eventType, fn = saveChanges }) => { 157 | document.getElementById(id).addEventListener(eventType, fn); 158 | }); 159 | 160 | function startSearches() { 161 | port.postMessage({ type: constants.MESSAGE_TYPES.START_SEARCH }); 162 | } 163 | 164 | function stopSearches() { 165 | port.postMessage({ type: constants.MESSAGE_TYPES.STOP_SEARCH }); 166 | } 167 | 168 | chrome.commands.onCommand.addListener(command => { 169 | if (command === 'start-searches') startSearches(); 170 | }); 171 | document.getElementById('search').addEventListener('click', startSearches); 172 | 173 | document.getElementById('open-reward-tasks').addEventListener('click', async () => { 174 | function openRewardTasks() { 175 | return new Promise(resolve => { 176 | chrome.tabs.executeScript({ 177 | file: '/content-scripts/open-reward-tasks.js', 178 | }, resolve); 179 | }); 180 | } 181 | 182 | const tab = await getCurrentTab(); 183 | if (tab && tab.url.includes(constants.REWARDS_URL)) { 184 | openRewardTasks(); 185 | } else { 186 | chrome.tabs.update({ 187 | url: constants.REWARDS_URL, 188 | }, () => { 189 | async function listener(updatedTabId, info, updatedTab) { 190 | if (tab.id === updatedTabId && info.status === 'complete' && updatedTab.url.includes(constants.REWARDS_URL)) { 191 | await openRewardTasks(); 192 | chrome.tabs.onUpdated.removeListener(listener); 193 | } 194 | } 195 | chrome.tabs.onUpdated.addListener(listener); 196 | }); 197 | } 198 | }); 199 | -------------------------------------------------------------------------------- /src/query-templates.js: -------------------------------------------------------------------------------- 1 | const queryTemplates = [ 2 | { 3 | template: 'how fast can a $1 run', 4 | types: ['animal'], 5 | }, 6 | { 7 | template: '$1 net worth', 8 | types: ['person'], 9 | }, 10 | { 11 | template: 'who is $1', 12 | types: ['person'], 13 | }, 14 | { 15 | template: 'weight of average $1', 16 | types: ['animal'], 17 | }, 18 | { 19 | template: 'picture of $1', 20 | types: ['animal'], 21 | }, 22 | { 23 | template: 'picture of $1', 24 | types: ['person'], 25 | }, 26 | { 27 | template: '$1 $2', 28 | types: ['person', 'socialMedia'], 29 | }, 30 | { 31 | template: '$1 calories', 32 | types: ['food'], 33 | }, 34 | { 35 | template: 'recipe with $1', 36 | types: ['food'], 37 | }, 38 | ]; 39 | 40 | const types = { 41 | animal: animals, 42 | person: people, 43 | food: food, 44 | socialMedia: socialMedias, 45 | }; 46 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | function getRandomElement(arr) { 2 | return arr[Math.floor(Math.random() * arr.length)]; 3 | } 4 | 5 | function random(min, max) { 6 | return Math.floor(Math.random() * (max - min)) + min; 7 | } 8 | 9 | /** 10 | * Returns a new array without duplicates. Does not modify original array. 11 | */ 12 | function removeDuplicates(list) { 13 | return list.reduce((acc, element) => { 14 | if (element && !acc.includes(element)) return [...acc, element]; 15 | return acc; 16 | }, []); 17 | } 18 | 19 | /** 20 | * Modifies the original array and whenever predicateFn returns true (given an array element), 21 | * it removes that element from the array 22 | */ 23 | function remove(array, predicateFn) { 24 | for (let i = array.length - 1; i > -1; i--) { 25 | if (predicateFn(array[i])) array.splice(i, 1); 26 | } 27 | } 28 | 29 | function clearBadge() { 30 | chrome.browserAction.setBadgeText({ text: '' }); 31 | } 32 | 33 | function getCurrentTab() { 34 | return new Promise(resolve => { 35 | chrome.tabs.query({ active: true, currentWindow: true }, tabs => { 36 | resolve(tabs[0]); 37 | }); 38 | }); 39 | } 40 | 41 | function getDateFromTime(time) { 42 | const date = new Date(); 43 | const [hourStr, minStr] = time.split(':'); 44 | date.setHours(Number(hourStr), Number(minStr), 0); 45 | return date; 46 | } 47 | 48 | function getMidnightDate() { 49 | const date = new Date(); 50 | date.setHours(0, 0, 0); 51 | return date; 52 | } 53 | --------------------------------------------------------------------------------