14 Commits

Author SHA1 Message Date
TheNetsky
d5fd06d229 1.5.3
Fix issue with pop-up dismissal
2025-04-30 16:20:56 +02:00
TheNetsky
0ddc964878 1.5.2
- Updated packages
- Cleanup
- Improved message dismissal
- Improved login functionality

With help from @LightZirconite
2025-04-24 14:50:22 +02:00
CNOCM
325bf65b30 Update README (#264)
* Update Search.ts

* Update README.md
2025-03-03 10:59:06 +01:00
HMCDAT
6f19bd4b0e Update Dockerfile (#262) 2025-02-28 12:34:47 +01:00
AariaX
caf6a42a38 Make shuffleArray more random? (#254) 2025-02-25 16:14:51 +01:00
TheNetsky
352d47229b Merge branch 'main' of https://github.com/TheNetsky/Microsoft-Rewards-Script 2025-02-24 17:59:28 +01:00
TheNetsky
9a12ee1ec8 Fix geoLocale not being uppercase 2025-02-24 17:59:20 +01:00
Netsky
b630c3ddda Update README.md 2025-02-24 15:52:53 +01:00
Netsky
287e3897da Update README.md 2025-02-24 15:51:08 +01:00
Netsky
fcf6aba446 Bump version 2025-02-24 15:49:45 +01:00
AariaX
1102f2ca94 Google API Update (1.5.1) (#247)
* switch to google internal api

* A bit of tidying up

* A bit more of tidying up

* A bit more of tidying up

* Pre 1.5.1

- Add proxy exclusions
- Update ReadMe
- Update config Interface

---------

Co-authored-by: TheNetsky <56271887+TheNetsky@users.noreply.github.com>
2025-02-24 15:49:19 +01:00
1OSA
82a896e83f added webhooklogexcludefunc (#240) 2025-02-20 23:21:46 +01:00
Netsky
b0bd1f52c4 Update queries.json 2025-02-19 19:32:45 +01:00
Netsky
7e4121e01b Update run_daily.sh 2025-02-19 15:29:51 +01:00
20 changed files with 363 additions and 129 deletions

View File

@@ -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 && \

View File

@@ -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!

View File

@@ -1,6 +1,6 @@
{
"name": "microsoft-rewards-script",
"version": "1.5.0",
"version": "1.5.3",
"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",

View File

@@ -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)

View File

@@ -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

View File

@@ -11,8 +11,9 @@ export default class BrowserUtil {
this.bot = bot
}
async tryDismissAllMessages(page: Page): Promise<boolean> {
async tryDismissAllMessages(page: Page): Promise<void> {
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,27 +24,23 @@ 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) => {
for (const button of buttons) {
try {
const element = page.locator(button.selector)
if (await element.first().isVisible({ timeout: 1000 })) {
await element.first().click({ timeout: 1000 })
this.bot.log(this.bot.isMobile, 'DISMISS-ALL-MESSAGES', `Dismissed: ${button.label}`)
return true
}
} catch (error) {
// Ignore errors and continue
}
return false
})
const element = button.isXPath ? page.locator(`xpath=${button.selector}`) : page.locator(button.selector)
await element.first().click({ timeout: 500 })
await page.waitForTimeout(500)
const results = await Promise.allSettled(dismissTasks)
return results.some(result => result.status === 'fulfilled' && result.value === true)
this.bot.log(this.bot.isMobile, 'DISMISS-ALL-MESSAGES', `Dismissed: ${button.label}`)
} catch (error) {
// Silent fail
}
}
}
async getLatestTab(page: Page): Promise<Page> {

View File

@@ -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": ""

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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é"
]
}
]
]

View File

@@ -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;

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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
}
}
}

View File

@@ -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 {