mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-12 11:26:18 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ddc964878 | ||
|
|
325bf65b30 | ||
|
|
6f19bd4b0e | ||
|
|
caf6a42a38 | ||
|
|
352d47229b | ||
|
|
9a12ee1ec8 | ||
|
|
b630c3ddda | ||
|
|
287e3897da | ||
|
|
fcf6aba446 | ||
|
|
1102f2ca94 | ||
|
|
82a896e83f | ||
|
|
b0bd1f52c4 | ||
|
|
7e4121e01b |
@@ -37,6 +37,7 @@ RUN touch /var/log/cron.log
|
||||
|
||||
# Define the command to run your application with cron optionally
|
||||
CMD ["sh", "-c", "echo \"$TZ\" > /etc/timezone && \
|
||||
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
|
||||
dpkg-reconfigure -f noninteractive tzdata && \
|
||||
envsubst < /etc/cron.d/microsoft-rewards-cron.template > /etc/cron.d/microsoft-rewards-cron && \
|
||||
chmod 0644 /etc/cron.d/microsoft-rewards-cron && \
|
||||
|
||||
20
README.md
20
README.md
@@ -57,9 +57,11 @@ A basic docker `compose.yaml` is provided. Follow these steps to configure and r
|
||||
| baseURL | MS Rewards page | `https://rewards.bing.com` |
|
||||
| sessionPath | Path to where you want sessions/fingerprints to be stored | `sessions` (In ./browser/sessions) |
|
||||
| headless | If the browser window should be visible be ran in the background | `false` (Browser is visible) |
|
||||
| parallel | If you want mobile and desktop tasks to run parallel or sequential| `true` |
|
||||
| runOnZeroPoints | Run the rest of the script if 0 points can be earned | `false` (Will not run on 0 points) |
|
||||
| clusters | Amount of instances ran on launch, 1 per account | `1` (Will run 1 account at the time) |
|
||||
| saveFingerprint | Re-use the same fingerprint each time | `false` (Will generate a new fingerprint each time) |
|
||||
| saveFingerprint.mobile | Re-use the same fingerprint each time | `false` (Will generate a new fingerprint each time) |
|
||||
| saveFingerprint.desktop | Re-use the same fingerprint each time | `false` (Will generate a new fingerprint each time) |
|
||||
| workers.doDailySet | Complete daily set items | `true` |
|
||||
| workers.doMorePromotions | Complete promotional items | `true` |
|
||||
| workers.doPunchCards | Complete punchcards | `true` |
|
||||
@@ -67,17 +69,19 @@ A basic docker `compose.yaml` is provided. Follow these steps to configure and r
|
||||
| workers.doMobileSearch | Complete daily mobile searches | `true` |
|
||||
| workers.doDailyCheckIn | Complete daily check-in activity | `true` |
|
||||
| workers.doReadToEarn | Complete read to earn activity | `true` |
|
||||
| searchOnBingLocalQueries | Complete the activity "search on Bing" using the `queries.json` or fetched from this repo | `false` (Will fetch from this repo) |
|
||||
| globalTimeout | The length before the action gets timeout | `30s` |
|
||||
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `true` (Uses EN-US generated queries) |
|
||||
| scrollRandomResults | Scroll randomly in search results | `true` |
|
||||
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `false` (Uses EN-US generated queries) |
|
||||
| searchSettings.scrollRandomResults | Scroll randomly in search results | `true` |
|
||||
| searchSettings.clickRandomResults | Visit random website from search result| `true` |
|
||||
| searchSettings.searchDelay | Minimum and maximum time in miliseconds between search queries | `min: 1min` `max: 2min` |
|
||||
| searchSettings.retryMobileSearchAmount | Keep retrying mobile searches for specified amount | `3` |
|
||||
| searchSettings.searchDelay | Minimum and maximum time in milliseconds between search queries | `min: 3min` `max: 5min` |
|
||||
| searchSettings.retryMobileSearchAmount | Keep retrying mobile searches for specified amount | `2` |
|
||||
| logExcludeFunc | Functions to exclude out of the logs and webhooks | `SEARCH-CLOSE-TABS` |
|
||||
| webhookLogExcludeFunc | Functions to exclude out of the webhooks log | `SEARCH-CLOSE-TABS` |
|
||||
| proxy.proxyGoogleTrends | Enable or disable proxying the request via set proxy | `true` (will be proxied) |
|
||||
| proxy.proxyBingTerms | Enable or disable proxying the request via set proxy | `true` (will be proxied) |
|
||||
| webhook.enabled | Enable or disable your set webhook | `false` |
|
||||
| webhook.url | Your Discord webhook URL | `null` |
|
||||
| cronStartTime | Scheduled script run-time, *only available for docker implementation* | `0 5,11 * * *` (5:00 am, 11:00 am daily) |
|
||||
| | Run the script immediately when the Docker container starts | `true` |
|
||||
|
||||
## Features ##
|
||||
- [x] Multi-Account Support
|
||||
@@ -112,4 +116,4 @@ A basic docker `compose.yaml` is provided. Follow these steps to configure and r
|
||||
## Disclaimer ##
|
||||
Your account may be at risk of getting banned or suspended using this script, you've been warned!
|
||||
<br />
|
||||
Use this script at your own risk!
|
||||
Use this script at your own risk!
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "microsoft-rewards-script",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.2",
|
||||
"description": "Automatically do tasks for Microsoft Rewards but in TS!",
|
||||
"main": "index.js",
|
||||
"engines": {
|
||||
@@ -35,11 +35,11 @@
|
||||
"typescript": "^5.5.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"axios": "^1.8.4",
|
||||
"chalk": "^4.1.2",
|
||||
"cheerio": "^1.0.0",
|
||||
"fingerprint-generator": "^2.1.62",
|
||||
"fingerprint-injector": "^2.1.62",
|
||||
"fingerprint-generator": "^2.1.66",
|
||||
"fingerprint-injector": "^2.1.66",
|
||||
"http-proxy-agent": "^7.0.2",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"ms": "^2.1.3",
|
||||
|
||||
@@ -46,7 +46,7 @@ class Browser {
|
||||
const context = await newInjectedContext(browser as any, { fingerprint: fingerprint })
|
||||
|
||||
// Set timeout to preferred amount
|
||||
context.setDefaultTimeout(this.bot.utils.stringToMs(this.bot.config?.globalTimeout ?? 30_000))
|
||||
context.setDefaultTimeout(this.bot.utils.stringToMs(this.bot.config?.globalTimeout ?? 30000))
|
||||
|
||||
await context.addCookies(sessionData.cookies)
|
||||
|
||||
|
||||
@@ -298,7 +298,7 @@ export default class BrowserFunc {
|
||||
|
||||
async waitForQuizRefresh(page: Page): Promise<boolean> {
|
||||
try {
|
||||
await page.waitForSelector('span.rqMCredits', { state: 'visible', timeout: 10_000 })
|
||||
await page.waitForSelector('span.rqMCredits', { state: 'visible', timeout: 10000 })
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
return true
|
||||
|
||||
@@ -13,6 +13,7 @@ export default class BrowserUtil {
|
||||
|
||||
async tryDismissAllMessages(page: Page): Promise<boolean> {
|
||||
const buttons = [
|
||||
{ selector: 'button[type="submit"]', label: 'Submit Button' },
|
||||
{ selector: '#acceptButton', label: 'AcceptButton' },
|
||||
{ selector: '.ext-secondary.ext-button', label: '"Skip for now" Button' },
|
||||
{ selector: '#iLandingViewAction', label: 'iLandingViewAction' },
|
||||
@@ -23,21 +24,25 @@ export default class BrowserUtil {
|
||||
{ selector: '.ms-Button.ms-Button--primary', label: 'Primary Button' },
|
||||
{ selector: '.c-glyph.glyph-cancel', label: 'Mobile Welcome Button' },
|
||||
{ selector: '.maybe-later', label: 'Mobile Rewards App Banner' },
|
||||
{ selector: '//div[@id="cookieConsentContainer"]//button[contains(text(), "Accept")]', label: 'Accept Cookie Consent Container' },
|
||||
{ selector: '//div[@id="cookieConsentContainer"]//button[contains(text(), "Accept")]', label: 'Accept Cookie Consent Container', isXPath: true },
|
||||
{ selector: '#bnp_btn_accept', label: 'Bing Cookie Banner' },
|
||||
{ selector: '#reward_pivot_earn', label: 'Reward Coupon Accept' }
|
||||
]
|
||||
|
||||
const dismissTasks = buttons.map(async (button) => {
|
||||
try {
|
||||
const element = page.locator(button.selector)
|
||||
const element = button.isXPath
|
||||
? page.locator(`xpath=${button.selector}`)
|
||||
: page.locator(button.selector)
|
||||
|
||||
if (await element.first().isVisible({ timeout: 1000 })) {
|
||||
await element.first().click({ timeout: 1000 })
|
||||
await page.waitForTimeout(500)
|
||||
this.bot.log(this.bot.isMobile, 'DISMISS-ALL-MESSAGES', `Dismissed: ${button.label}`)
|
||||
return true
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore errors and continue
|
||||
} catch {
|
||||
// Silent fail
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"baseURL": "https://rewards.bing.com",
|
||||
"sessionPath": "sessions",
|
||||
"headless": true,
|
||||
"headless": false,
|
||||
"parallel": true,
|
||||
"runOnZeroPoints": false,
|
||||
"clusters": 1,
|
||||
@@ -33,6 +33,13 @@
|
||||
"logExcludeFunc": [
|
||||
"SEARCH-CLOSE-TABS"
|
||||
],
|
||||
"webhookLogExcludeFunc": [
|
||||
"SEARCH-CLOSE-TABS"
|
||||
],
|
||||
"proxy": {
|
||||
"proxyGoogleTrends": true,
|
||||
"proxyBingTerms": true
|
||||
},
|
||||
"webhook": {
|
||||
"enabled": false,
|
||||
"url": ""
|
||||
|
||||
@@ -39,7 +39,7 @@ export class Login {
|
||||
// Check if account is locked
|
||||
await this.checkAccountLocked(page)
|
||||
|
||||
const isLoggedIn = await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10_000 }).then(() => true).catch(() => false)
|
||||
const isLoggedIn = await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10000 }).then(() => true).catch(() => false)
|
||||
|
||||
if (!isLoggedIn) {
|
||||
await this.execLogin(page, email, password)
|
||||
@@ -86,30 +86,80 @@ export class Login {
|
||||
}
|
||||
|
||||
private async enterEmail(page: Page, email: string) {
|
||||
const emailPrefilled = await page.waitForSelector('#userDisplayName', { timeout: 2_000 }).catch(() => null)
|
||||
if (emailPrefilled) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email already prefilled by Microsoft')
|
||||
return
|
||||
}
|
||||
const emailInputSelector = 'input[type="email"]'
|
||||
|
||||
await page.fill('#i0116', email)
|
||||
await page.click('#idSIButton9')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email entered successfully')
|
||||
try {
|
||||
// Wait for email field
|
||||
const emailField = await page.waitForSelector(emailInputSelector, { state: 'visible', timeout: 2000 }).catch(() => null)
|
||||
if (!emailField) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email field not found', 'warn')
|
||||
return
|
||||
}
|
||||
|
||||
await this.bot.utils.wait(1000)
|
||||
|
||||
// Check if email is prefilled
|
||||
const emailPrefilled = await page.waitForSelector('#userDisplayName', { timeout: 5000 }).catch(() => null)
|
||||
if (emailPrefilled) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email already prefilled by Microsoft')
|
||||
} else {
|
||||
// Else clear and fill email
|
||||
await page.fill(emailInputSelector, '')
|
||||
await this.bot.utils.wait(500)
|
||||
await page.fill(emailInputSelector, email)
|
||||
await this.bot.utils.wait(1000)
|
||||
}
|
||||
|
||||
const nextButton = await page.waitForSelector('button[type="submit"]', { timeout: 2000 }).catch(() => null)
|
||||
if (nextButton) {
|
||||
await nextButton.click()
|
||||
await this.bot.utils.wait(2000)
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email entered successfully')
|
||||
} else {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Next button not found after email entry', 'warn')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', `Email entry failed: ${error}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
private async enterPassword(page: Page, password: string) {
|
||||
const passwordInputSelector = 'input[type="password"]'
|
||||
|
||||
try {
|
||||
await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 })
|
||||
await this.bot.utils.wait(2000)
|
||||
await page.fill('#i0118', password)
|
||||
await page.click('#idSIButton9')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entered successfully')
|
||||
} catch {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entry failed or 2FA required')
|
||||
// Wait for password field
|
||||
const passwordField = await page.waitForSelector(passwordInputSelector, { state: 'visible', timeout: 5000 }).catch(() => null)
|
||||
if (!passwordField) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Password field not found, possibly 2FA required', 'warn')
|
||||
await this.handle2FA(page)
|
||||
return
|
||||
}
|
||||
|
||||
await this.bot.utils.wait(1000)
|
||||
|
||||
// Clear and fill password
|
||||
await page.fill(passwordInputSelector, '')
|
||||
await this.bot.utils.wait(500)
|
||||
await page.fill(passwordInputSelector, password)
|
||||
await this.bot.utils.wait(1000)
|
||||
|
||||
const nextButton = await page.waitForSelector('button[type="submit"]', { timeout: 2000 }).catch(() => null)
|
||||
if (nextButton) {
|
||||
await nextButton.click()
|
||||
await this.bot.utils.wait(2000)
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entered successfully')
|
||||
} else {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Next button not found after password entry', 'warn')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', `Password entry failed: ${error}`, 'error')
|
||||
await this.handle2FA(page)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async handle2FA(page: Page) {
|
||||
try {
|
||||
const numberToPress = await this.get2FACode(page)
|
||||
@@ -138,7 +188,7 @@ export class Login {
|
||||
while (true) {
|
||||
const button = await page.waitForSelector('button[aria-describedby="pushNotificationsTitle errorDescription"]', { state: 'visible', timeout: 2000 }).catch(() => null)
|
||||
if (button) {
|
||||
await this.bot.utils.wait(60_000)
|
||||
await this.bot.utils.wait(60000)
|
||||
await button.click()
|
||||
|
||||
continue
|
||||
@@ -148,7 +198,7 @@ export class Login {
|
||||
}
|
||||
}
|
||||
|
||||
await page.click('button[aria-describedby="confirmSendTitle"]').catch(() => {})
|
||||
await page.click('button[aria-describedby="confirmSendTitle"]').catch(() => { })
|
||||
await this.bot.utils.wait(2000)
|
||||
const element = await page.waitForSelector('#displaySign', { state: 'visible', timeout: 2000 })
|
||||
return await element.textContent()
|
||||
@@ -162,7 +212,7 @@ export class Login {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', `Press the number ${numberToPress} on your Authenticator app to approve the login`)
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'If you press the wrong number or the "DENY" button, try again in 60 seconds')
|
||||
|
||||
await page.waitForSelector('#i0281', { state: 'detached', timeout: 60_000 })
|
||||
await page.waitForSelector('#i0281', { state: 'detached', timeout: 60000 })
|
||||
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Login successfully approved!')
|
||||
break
|
||||
@@ -203,7 +253,7 @@ export class Login {
|
||||
}
|
||||
|
||||
// Wait for login to complete
|
||||
await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10_000 })
|
||||
await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10000 })
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Successfully logged into the rewards portal')
|
||||
}
|
||||
|
||||
@@ -272,7 +322,7 @@ export class Login {
|
||||
currentUrl = new URL(page.url())
|
||||
await this.bot.utils.wait(5000)
|
||||
}
|
||||
|
||||
|
||||
const body = new URLSearchParams()
|
||||
body.append('grant_type', 'authorization_code')
|
||||
body.append('client_id', this.clientId)
|
||||
|
||||
@@ -153,7 +153,7 @@ export class Workers {
|
||||
Due to common false timeout on this function, we're ignoring the error regardless, if it worked then it's faster,
|
||||
if it didn't then it gave enough time for the page to load.
|
||||
*/
|
||||
await activityPage.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => { })
|
||||
await activityPage.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { })
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
switch (activity.promotionType) {
|
||||
|
||||
@@ -15,18 +15,18 @@ export class ABC extends Workers {
|
||||
const maxIterations = 15
|
||||
let i
|
||||
for (i = 0; i < maxIterations && !$('span.rw_icon').length; i++) {
|
||||
await page.waitForSelector('.wk_OptionClickClass', { state: 'visible', timeout: 10_000 })
|
||||
await page.waitForSelector('.wk_OptionClickClass', { state: 'visible', timeout: 10000 })
|
||||
|
||||
const answers = $('.wk_OptionClickClass')
|
||||
const answer = answers[this.bot.utils.randomNumber(0, 2)]?.attribs['id']
|
||||
|
||||
await page.waitForSelector(`#${answer}`, { state: 'visible', timeout: 10_000 })
|
||||
await page.waitForSelector(`#${answer}`, { state: 'visible', timeout: 10000 })
|
||||
|
||||
await this.bot.utils.wait(2000)
|
||||
await page.click(`#${answer}`) // Click answer
|
||||
|
||||
await this.bot.utils.wait(4000)
|
||||
await page.waitForSelector('div.wk_button', { state: 'visible', timeout: 10_000 })
|
||||
await page.waitForSelector('div.wk_button', { state: 'visible', timeout: 10000 })
|
||||
await page.click('div.wk_button') // Click next question button
|
||||
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
@@ -11,7 +11,7 @@ export class Poll extends Workers {
|
||||
try {
|
||||
const buttonId = `#btoption${Math.floor(this.bot.utils.randomNumber(0, 1))}`
|
||||
|
||||
await page.waitForSelector(buttonId, { state: 'visible', timeout: 10_000 }).catch(() => { }) // We're gonna click regardless or not
|
||||
await page.waitForSelector(buttonId, { state: 'visible', timeout: 10000 }).catch(() => { }) // We're gonna click regardless or not
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
await page.click(buttonId)
|
||||
|
||||
@@ -29,7 +29,7 @@ export class Quiz extends Workers {
|
||||
const answers: string[] = []
|
||||
|
||||
for (let i = 0; i < quizData.numberOfOptions; i++) {
|
||||
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10_000 })
|
||||
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10000 })
|
||||
const answerAttribute = await answerSelector?.evaluate(el => el.getAttribute('iscorrectoption'))
|
||||
|
||||
if (answerAttribute && answerAttribute.toLowerCase() === 'true') {
|
||||
@@ -59,7 +59,7 @@ export class Quiz extends Workers {
|
||||
|
||||
for (let i = 0; i < quizData.numberOfOptions; i++) {
|
||||
|
||||
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10_000 })
|
||||
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10000 })
|
||||
const dataOption = await answerSelector?.evaluate(el => el.getAttribute('data-option'))
|
||||
|
||||
if (dataOption === correctOption) {
|
||||
|
||||
@@ -4,9 +4,17 @@ import { platform } from 'os'
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
import { Counters, DashboardData } from '../../interface/DashboardData'
|
||||
import { GoogleTrends } from '../../interface/GoogleDailyTrends'
|
||||
import { GoogleSearch } from '../../interface/Search'
|
||||
import { AxiosRequestConfig } from 'axios'
|
||||
|
||||
type GoogleTrendsResponse = [
|
||||
string,
|
||||
[
|
||||
string,
|
||||
...null[],
|
||||
[string, ...string[]]
|
||||
][]
|
||||
];
|
||||
|
||||
export class Search extends Workers {
|
||||
private bingHome = 'https://bing.com'
|
||||
@@ -26,7 +34,7 @@ export class Search extends Workers {
|
||||
}
|
||||
|
||||
// Generate search queries
|
||||
let googleSearchQueries = await this.getGoogleTrends(data.userProfile.attributes.country, missingPoints)
|
||||
let googleSearchQueries = await this.getGoogleTrends(this.bot.config.searchSettings.useGeoLocaleQueries ? data.userProfile.attributes.country : 'US')
|
||||
googleSearchQueries = this.bot.utils.shuffleArray(googleSearchQueries)
|
||||
|
||||
// Deduplicate the search terms
|
||||
@@ -39,7 +47,7 @@ export class Search extends Workers {
|
||||
|
||||
await this.bot.browser.utils.tryDismissAllMessages(page)
|
||||
|
||||
let maxLoop = 0 // If the loop hits 10 this when not gaining any points, we're assuming it's stuck. If it ddoesn't continue after 5 more searches with alternative queries, abort search
|
||||
let maxLoop = 0 // If the loop hits 10 this when not gaining any points, we're assuming it's stuck. If it doesn't continue after 5 more searches with alternative queries, abort search
|
||||
|
||||
const queries: string[] = []
|
||||
// Mobile search doesn't seem to like related queries?
|
||||
@@ -148,7 +156,7 @@ export class Search extends Workers {
|
||||
await this.bot.utils.wait(500)
|
||||
|
||||
const searchBar = '#sb_form_q'
|
||||
await searchPage.waitForSelector(searchBar, { state: 'visible', timeout: 10_000 })
|
||||
await searchPage.waitForSelector(searchBar, { state: 'visible', timeout: 10000 })
|
||||
await searchPage.click(searchBar) // Focus on the textarea
|
||||
await this.bot.utils.wait(500)
|
||||
await searchPage.keyboard.down(platformControlKey)
|
||||
@@ -203,49 +211,64 @@ export class Search extends Workers {
|
||||
return await this.bot.browser.func.getSearchPoints()
|
||||
}
|
||||
|
||||
private async getGoogleTrends(geoLocale: string, queryCount: number): Promise<GoogleSearch[]> {
|
||||
private async getGoogleTrends(geoLocale: string = 'US'): Promise<GoogleSearch[]> {
|
||||
const queryTerms: GoogleSearch[] = []
|
||||
let i = 0
|
||||
|
||||
geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toUpperCase() : 'US'
|
||||
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', `Generating search queries, can take a while! | GeoLocale: ${geoLocale}`)
|
||||
|
||||
while (queryCount > queryTerms.length) {
|
||||
i += 1
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate() - i)
|
||||
const formattedDate = this.formatDate(date)
|
||||
|
||||
try {
|
||||
const request = {
|
||||
url: `https://trends.google.com/trends/api/dailytrends?geo=${geoLocale}&hl=en&ed=${formattedDate}&ns=15`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
const response = await this.bot.axios.request(request)
|
||||
|
||||
const data: GoogleTrends = JSON.parse((await response.data).slice(5))
|
||||
|
||||
for (const topic of data.default.trendingSearchesDays[0]?.trendingSearches ?? []) {
|
||||
queryTerms.push({
|
||||
topic: topic.title.query.toLowerCase(),
|
||||
related: topic.relatedQueries.map(x => x.query.toLowerCase())
|
||||
})
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'An error occurred:' + error, 'error')
|
||||
break
|
||||
try {
|
||||
const request: AxiosRequestConfig = {
|
||||
url: 'https://trends.google.com/_/TrendsUi/data/batchexecute',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
|
||||
},
|
||||
data: `f.req=[[[i0OFE,"[null, null, \\"${geoLocale.toUpperCase()}\\", 0, null, 48]"]]]`
|
||||
}
|
||||
|
||||
const response = await this.bot.axios.request(request, this.bot.config.proxy.proxyGoogleTrends)
|
||||
const rawText = response.data
|
||||
|
||||
const trendsData = this.extractJsonFromResponse(rawText)
|
||||
if (!trendsData) {
|
||||
throw this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'Failed to parse Google Trends response', 'error')
|
||||
}
|
||||
|
||||
const mappedTrendsData = trendsData.map(query => [query[0], query[9]!.slice(1)])
|
||||
if (mappedTrendsData.length < 90) {
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'Insufficient search queries, falling back to US', 'warn')
|
||||
return this.getGoogleTrends()
|
||||
}
|
||||
|
||||
for (const [topic, relatedQueries] of mappedTrendsData) {
|
||||
queryTerms.push({
|
||||
topic: topic as string,
|
||||
related: relatedQueries as string[]
|
||||
})
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
|
||||
return queryTerms
|
||||
}
|
||||
|
||||
private extractJsonFromResponse(text: string): GoogleTrendsResponse[1] | null {
|
||||
const lines = text.split('\n')
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim()
|
||||
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
||||
try {
|
||||
return JSON.parse(JSON.parse(trimmed)[0][2])[1]
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private async getRelatedTerms(term: string): Promise<string[]> {
|
||||
try {
|
||||
const request = {
|
||||
@@ -256,7 +279,7 @@ export class Search extends Workers {
|
||||
}
|
||||
}
|
||||
|
||||
const response = await this.bot.axios.request(request)
|
||||
const response = await this.bot.axios.request(request, this.bot.config.proxy.proxyBingTerms)
|
||||
|
||||
return response.data[1] as string[]
|
||||
} catch (error) {
|
||||
@@ -266,14 +289,6 @@ export class Search extends Workers {
|
||||
return []
|
||||
}
|
||||
|
||||
private formatDate(date: Date): string {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
|
||||
return `${year}${month}${day}`
|
||||
}
|
||||
|
||||
private async randomScroll(page: Page) {
|
||||
try {
|
||||
const viewportHeight = await page.evaluate(() => window.innerHeight)
|
||||
@@ -297,7 +312,7 @@ export class Search extends Workers {
|
||||
await this.closeContinuePopup(page)
|
||||
|
||||
// Stay for 10 seconds for page to load and "visit"
|
||||
await this.bot.utils.wait(10_000)
|
||||
await this.bot.utils.wait(10000)
|
||||
|
||||
// Will get current tab if no new one is created, this will always be the visited site or the result page if it failed to click
|
||||
let lastTab = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
@@ -20,7 +20,7 @@ export class SearchOnBing extends Workers {
|
||||
const query = await this.getSearchQuery(activity.title)
|
||||
|
||||
const searchBar = '#sb_form_q'
|
||||
await page.waitForSelector(searchBar, { state: 'visible', timeout: 10_000 })
|
||||
await page.waitForSelector(searchBar, { state: 'visible', timeout: 10000 })
|
||||
await page.click(searchBar)
|
||||
await this.bot.utils.wait(500)
|
||||
await page.keyboard.type(query)
|
||||
|
||||
@@ -134,5 +134,156 @@
|
||||
"Japan time",
|
||||
"New York time"
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"title": "Maisons près de chez vous",
|
||||
"queries": [
|
||||
"Maisons près de chez moi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Vous ressentez des symptômes ?",
|
||||
"queries": [
|
||||
"Éruption cutanée sur l'avant-bras",
|
||||
"Nez bouché",
|
||||
"Toux chatouilleuse"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Faites vos achats plus vite",
|
||||
"queries": [
|
||||
"Acheter une PS5",
|
||||
"Acheter une Xbox",
|
||||
"Offres sur les chaises"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Traduisez tout !",
|
||||
"queries": [
|
||||
"Traduction bienvenue à la maison en coréen",
|
||||
"Traduction bienvenue à la maison en japonais",
|
||||
"Traduction au revoir en japonais"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Rechercher paroles de chanson",
|
||||
"queries": [
|
||||
"Paroles de Debarge rhythm of the night"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Et si nous regardions ce film une nouvelle fois?",
|
||||
"queries": [
|
||||
"Alien film",
|
||||
"Film Aliens",
|
||||
"Film Alien 3",
|
||||
"Film Predator"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Planifiez une petite escapade",
|
||||
"queries": [
|
||||
"Vols Amsterdam-Tokyo",
|
||||
"Vols New York-Tokyo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Consulter postes à pourvoir",
|
||||
"queries": [
|
||||
"emplois chez Microsoft",
|
||||
"Offres d'emploi Microsoft",
|
||||
"Emplois près de chez moi",
|
||||
"emplois chez Boeing"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Vous pouvez suivre votre colis",
|
||||
"queries": [
|
||||
"Suivi Chronopost"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Trouver un endroit à découvrir",
|
||||
"queries": [
|
||||
"Itinéraire vers Berlin",
|
||||
"Itinéraire vers Tokyo",
|
||||
"Itinéraire vers New York"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Trop fatigué pour cuisiner ce soir ?",
|
||||
"queries": [
|
||||
"KFC près de chez moi",
|
||||
"Burger King près de chez moi",
|
||||
"McDonalds près de chez moi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Convertissez rapidement votre argent",
|
||||
"queries": [
|
||||
"convertir 250 EUR en yen",
|
||||
"convertir 500 EUR en yen"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Apprenez à cuisiner une nouvelle recette",
|
||||
"queries": [
|
||||
"Comment faire cuire la ratatouille",
|
||||
"Comment faire cuire les lasagnes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Trouvez des emplacements pour rester!",
|
||||
"queries": [
|
||||
"Hôtels Berlin Allemagne",
|
||||
"Hôtels Amsterdam Pays-Bas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Comment se porte l'économie ?",
|
||||
"queries": [
|
||||
"CAC 40"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Qui a gagné ?",
|
||||
"queries": [
|
||||
"score du Paris Saint-Germain"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Temps de jeu",
|
||||
"queries": [
|
||||
"Jeu vidéo Overwatch",
|
||||
"Jeu vidéo Call of Duty"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Enrichissez votre vocabulaire",
|
||||
"queries": [
|
||||
"definition definition"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Quelle heure est-il ?",
|
||||
"queries": [
|
||||
"Heure du Japon",
|
||||
"Heure de New York"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Vérifier la météo",
|
||||
"queries": [
|
||||
"Météo de Paris",
|
||||
"Météo de la France"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Tenez-vous informé des sujets d'actualité",
|
||||
"queries": [
|
||||
"Augmentation Impots",
|
||||
"Mort célébrité"
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -5,13 +5,15 @@ export interface Config {
|
||||
parallel: boolean;
|
||||
runOnZeroPoints: boolean;
|
||||
clusters: number;
|
||||
workers: Workers;
|
||||
saveFingerprint: ConfigSaveFingerprint;
|
||||
workers: ConfigWorkers;
|
||||
searchOnBingLocalQueries: boolean;
|
||||
globalTimeout: number | string;
|
||||
searchSettings: SearchSettings;
|
||||
webhook: Webhook;
|
||||
searchSettings: ConfigSearchSettings;
|
||||
logExcludeFunc: string[];
|
||||
saveFingerprint: ConfigSaveFingerprint;
|
||||
webhookLogExcludeFunc: string[];
|
||||
proxy: ConfigProxy;
|
||||
webhook: ConfigWebhook;
|
||||
}
|
||||
|
||||
export interface ConfigSaveFingerprint {
|
||||
@@ -19,25 +21,30 @@ export interface ConfigSaveFingerprint {
|
||||
desktop: boolean;
|
||||
}
|
||||
|
||||
export interface SearchSettings {
|
||||
export interface ConfigSearchSettings {
|
||||
useGeoLocaleQueries: boolean;
|
||||
scrollRandomResults: boolean;
|
||||
clickRandomResults: boolean;
|
||||
searchDelay: SearchDelay;
|
||||
searchDelay: ConfigSearchDelay;
|
||||
retryMobileSearchAmount: number;
|
||||
}
|
||||
|
||||
export interface SearchDelay {
|
||||
export interface ConfigSearchDelay {
|
||||
min: number | string;
|
||||
max: number | string;
|
||||
}
|
||||
|
||||
export interface Webhook {
|
||||
export interface ConfigWebhook {
|
||||
enabled: boolean;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface Workers {
|
||||
export interface ConfigProxy {
|
||||
proxyGoogleTrends: boolean;
|
||||
proxyBingTerms: boolean;
|
||||
}
|
||||
|
||||
export interface ConfigWorkers {
|
||||
doDailySet: boolean;
|
||||
doMorePromotions: boolean;
|
||||
doPunchCards: boolean;
|
||||
|
||||
@@ -28,8 +28,5 @@ sleep $SLEEPTIME
|
||||
# Log the start of the script
|
||||
echo "Starting script..."
|
||||
|
||||
# Update config with environment variables before running the script
|
||||
node src/updateConfig.js
|
||||
|
||||
# Execute the Node.js script directly
|
||||
npm run start
|
||||
|
||||
@@ -36,7 +36,12 @@ class AxiosClient {
|
||||
}
|
||||
|
||||
// Generic method to make any Axios request
|
||||
public async request(config: AxiosRequestConfig): Promise<AxiosResponse> {
|
||||
public async request(config: AxiosRequestConfig, bypassProxy = false): Promise<AxiosResponse> {
|
||||
if (bypassProxy) {
|
||||
const bypassInstance = axios.create()
|
||||
return bypassInstance.request(config)
|
||||
}
|
||||
|
||||
return this.instance.request(config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ export function log(isMobile: boolean | 'main', title: string, message: string,
|
||||
const cleanStr = `[${currentTime}] [PID: ${process.pid}] [${type.toUpperCase()}] ${platformText} [${title}] ${message}`
|
||||
|
||||
// Send the clean string to the Webhook
|
||||
Webhook(configData, cleanStr)
|
||||
if (!configData.webhookLogExcludeFunc.some(x => x.toLowerCase() === title.toLowerCase())) {
|
||||
Webhook(configData, cleanStr)
|
||||
}
|
||||
|
||||
// Formatted string with chalk for terminal logging
|
||||
const str = `[${currentTime}] [PID: ${process.pid}] [${type.toUpperCase()}] ${chalkedPlatform} [${title}] ${message}`
|
||||
@@ -40,4 +42,4 @@ export function log(isMobile: boolean | 'main', title: string, message: string,
|
||||
applyChalk ? console.log(applyChalk(str)) : console.log(str)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,9 @@ export default class Util {
|
||||
}
|
||||
|
||||
shuffleArray<T>(array: T[]): T[] {
|
||||
const shuffledArray = array.slice()
|
||||
|
||||
shuffledArray.sort(() => Math.random() - 0.5)
|
||||
|
||||
return shuffledArray
|
||||
return array.map(value => ({ value, sort: Math.random() }))
|
||||
.sort((a, b) => a.sort - b.sort)
|
||||
.map(({ value }) => value)
|
||||
}
|
||||
|
||||
randomNumber(min: number, max: number): number {
|
||||
|
||||
Reference in New Issue
Block a user