From a47b86e74d04c8985f46ef8c62e83ce2a86b8ec1 Mon Sep 17 00:00:00 2001 From: HMCDAT <90164248+hmcdat@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:26:52 +0700 Subject: [PATCH] Support new quest to get points (#138) * support passwordless auth (using Authenticator app) * update readme * feat: added mobile app tasks (daily check in + read to earn) * fix some stuff * make ReadToEarn use the delay config * fix daily reward search per week * reorder mobile tasks * fix message * Search fixes, reformatting and types --------- Co-authored-by: TheNetsky <56271887+TheNetsky@users.noreply.github.com> --- README.md | 2 + package.json | 2 +- src/browser/BrowserFunc.ts | 58 +++++- src/config.json | 4 +- src/functions/Activities.ts | 12 ++ src/functions/Login.ts | 54 ++++++ src/functions/Workers.ts | 2 +- src/functions/activities/DailyCheckIn.ts | 48 +++++ src/functions/activities/ReadToEarn.ts | 73 ++++++++ src/functions/activities/Search.ts | 49 +++-- src/index.ts | 65 ++++--- src/interface/AppUserData.ts | 226 +++++++++++++++++++++++ src/interface/Config.ts | 2 + src/interface/OAuth.ts | 9 + src/updateConfig.js | 58 +++--- 15 files changed, 584 insertions(+), 80 deletions(-) create mode 100644 src/functions/activities/DailyCheckIn.ts create mode 100644 src/functions/activities/ReadToEarn.ts create mode 100644 src/interface/AppUserData.ts create mode 100644 src/interface/OAuth.ts diff --git a/README.md b/README.md index 6a4f278..6c0a0d6 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ Under development, however mainly for personal use! - [x] Completing Punchcards - [x] Solving This Or That Quiz (Random) - [x] Solving ABC Quiz +- [x] Completing Daily Check In +- [x] Completing Read To Earn - [ ] Completing Shopping Game - [ ] Completing Gaming Tab - [x] Clustering Support diff --git a/package.json b/package.json index d6e3131..6ddda68 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "typescript": "^5.5.4" }, "dependencies": { - "axios": "^1.7.4", + "axios": "^1.7.5", "cheerio": "^1.0.0", "fingerprint-generator": "^2.1.54", "fingerprint-injector": "^2.1.54", diff --git a/src/browser/BrowserFunc.ts b/src/browser/BrowserFunc.ts index c5c7759..31b62f8 100644 --- a/src/browser/BrowserFunc.ts +++ b/src/browser/BrowserFunc.ts @@ -1,10 +1,12 @@ import { Page } from 'playwright' import { CheerioAPI, load } from 'cheerio' +import axios, { AxiosRequestConfig } from 'axios' import { MicrosoftRewardsBot } from '../index' import { Counters, DashboardData, MorePromotion, PromotionalItem } from './../interface/DashboardData' import { QuizData } from './../interface/QuizData' +import { AppUserData } from '../interface/AppUserData' export default class BrowserFunc { @@ -131,10 +133,10 @@ export default class BrowserFunc { } /** - * Get total earnable points + * Get total earnable points with web browser * @returns {number} Total earnable points */ - async getEarnablePoints(): Promise { + async getBrowserEarnablePoints(): Promise { try { const data = await this.getDashboardData() @@ -166,7 +168,57 @@ export default class BrowserFunc { return totalEarnablePoints } catch (error) { - throw this.bot.log('GET-EARNABLE-POINTS', 'An error occurred:' + error, 'error') + throw this.bot.log('GET-BROWSER-EARNABLE-POINTS', 'An error occurred:' + error, 'error') + } + } + + /** + * Get total earnable points with mobile app + * @returns {number} Total earnable points + */ + async getAppEarnablePoints(accessToken: string): Promise { + try { + const eligibleOffers = [ + 'ENUS_readarticle3_30points', + 'Gamification_Sapphire_DailyCheckIn' + ] + let totalEarnablePoints = 0 + + const data = await this.getDashboardData() + let geoLocale = data.userProfile.attributes.country + geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toLowerCase() : 'us' + + const userDataRequest: AxiosRequestConfig = { + url: 'https://prod.rewardsplatform.microsoft.com/dapi/me?channel=SAAndroid&options=613', + method: 'GET', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'X-Rewards-Country': geoLocale, + 'X-Rewards-Language': 'en' + } + } + + const userDataResponse: AppUserData = (await axios(userDataRequest)).data + const userData = userDataResponse.response + const eligibleActivities = userData.promotions.filter((x) => eligibleOffers.includes(x.attributes.offerid ?? '')) + + for (const item of eligibleActivities) { + if (item.attributes.type === 'msnreadearn') { + totalEarnablePoints += parseInt(item.attributes.pointmax ?? '') - parseInt(item.attributes.pointprogress ?? '') + break + } else if (item.attributes.type === 'checkin') { + const checkInDay = parseInt(item.attributes.progress ?? '') % 7 + + if (checkInDay < 6 && (new Date()).getDate() != (new Date(item.attributes.last_updated ?? '')).getDate()) { + totalEarnablePoints += parseInt(item.attributes['day_' + (checkInDay + 1) + '_points'] ?? '') + } + break + } + } + + return totalEarnablePoints + } catch (error) { + throw this.bot.log('GET-APP-EARNABLE-POINTS', 'An error occurred:' + error, 'error') } } diff --git a/src/config.json b/src/config.json index c5d1509..aa0a65b 100644 --- a/src/config.json +++ b/src/config.json @@ -10,7 +10,9 @@ "doMorePromotions": true, "doPunchCards": true, "doDesktopSearch": true, - "doMobileSearch": true + "doMobileSearch": true, + "doDailyCheckIn": true, + "doReadToEarn": true }, "globalTimeout": 30000, "searchSettings": { diff --git a/src/functions/Activities.ts b/src/functions/Activities.ts index ffa30bb..3cf723a 100644 --- a/src/functions/Activities.ts +++ b/src/functions/Activities.ts @@ -8,6 +8,8 @@ import { Poll } from './activities/Poll' import { Quiz } from './activities/Quiz' import { ThisOrThat } from './activities/ThisOrThat' import { UrlReward } from './activities/UrlReward' +import { ReadToEarn } from './activities/ReadToEarn' +import { DailyCheckIn } from './activities/DailyCheckIn' import { DashboardData } from '../interface/DashboardData' @@ -49,4 +51,14 @@ export default class Activities { await urlReward.doUrlReward(page) } + doReadToEarn = async (accessToken: string, data: DashboardData): Promise => { + const readToEarn = new ReadToEarn(this.bot) + await readToEarn.doReadToEarn(accessToken, data) + } + + doDailyCheckIn = async (accessToken: string, data: DashboardData): Promise => { + const dailyCheckIn = new DailyCheckIn(this.bot) + await dailyCheckIn.doDailyCheckIn(accessToken, data) + } + } \ No newline at end of file diff --git a/src/functions/Login.ts b/src/functions/Login.ts index aaab206..66e75e2 100644 --- a/src/functions/Login.ts +++ b/src/functions/Login.ts @@ -3,6 +3,9 @@ import readline from 'readline' import { MicrosoftRewardsBot } from '../index' import { saveSessionData } from '../util/Load' +import axios from 'axios' +import { OAuth } from '../interface/OAuth' +import * as crypto from 'crypto' const rl = readline.createInterface({ input: process.stdin, @@ -12,6 +15,11 @@ const rl = readline.createInterface({ export class Login { private bot: MicrosoftRewardsBot + private clientId: string = '0000000040170455' + private authBaseUrl: string = 'https://login.live.com/oauth20_authorize.srf' + private redirectUrl: string = 'https://login.live.com/oauth20_desktop.srf' + private tokenUrl: string = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token' + private scope: string = 'service::prod.rewardsplatform.microsoft.com::MBI_SSL' constructor(bot: MicrosoftRewardsBot) { this.bot = bot @@ -200,4 +208,50 @@ export class Login { } } + async getMobileAccessToken(page: Page, email: string) { + const authorizeUrl = new URL(this.authBaseUrl) + + authorizeUrl.searchParams.append('response_type', 'code') + authorizeUrl.searchParams.append('client_id', this.clientId) + authorizeUrl.searchParams.append('redirect_uri', this.redirectUrl) + authorizeUrl.searchParams.append('scope', this.scope) + authorizeUrl.searchParams.append('state', crypto.randomBytes(16).toString('hex')) + authorizeUrl.searchParams.append('access_type', 'offline_access') + authorizeUrl.searchParams.append('login_hint', email) + + await page.goto(authorizeUrl.href) + + const currentUrl = new URL(page.url()) + let code: string + + // eslint-disable-next-line no-constant-condition + while (true) { + this.bot.log('LOGIN-APP', 'Waiting for authorization') + if (currentUrl.hostname === 'login.live.com' && currentUrl.pathname === '/oauth20_desktop.srf') { + code = currentUrl.searchParams.get('code')! + break + } + } + + const body = new URLSearchParams() + body.append('grant_type', 'authorization_code') + body.append('client_id', this.clientId) + body.append('code', code) + body.append('redirect_uri', this.redirectUrl) + + const tokenRequest = { + url: this.tokenUrl, + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + data: body.toString() + } + + const tokenResponse = await axios(tokenRequest) + const tokenData: OAuth = await tokenResponse.data + + this.bot.log('LOGIN-APP', 'Successfully authorized') + return tokenData.access_token + } } \ No newline at end of file diff --git a/src/functions/Workers.ts b/src/functions/Workers.ts index 9b5b543..2951267 100644 --- a/src/functions/Workers.ts +++ b/src/functions/Workers.ts @@ -58,7 +58,7 @@ export class Workers { await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL }) // Wait for new page to load, max 10 seconds, however try regardless in case of error - await page.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => { }) + await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => { }) await this.solveActivities(page, activitiesUncompleted, punchCard) diff --git a/src/functions/activities/DailyCheckIn.ts b/src/functions/activities/DailyCheckIn.ts new file mode 100644 index 0000000..232a9cd --- /dev/null +++ b/src/functions/activities/DailyCheckIn.ts @@ -0,0 +1,48 @@ +import axios from 'axios' +import { randomBytes } from 'crypto' + +import { Workers } from '../Workers' + +import { DashboardData } from '../../interface/DashboardData' + + +export class DailyCheckIn extends Workers { + public async doDailyCheckIn(accessToken: string, data: DashboardData) { + this.bot.log('DAILY-CHECK-IN', 'Starting Daily Check In') + + try { + let geoLocale = data.userProfile.attributes.country + geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toLowerCase() : 'us' + + const jsonData = { + amount: 1, + country: geoLocale, + id: randomBytes(64).toString('hex'), + type: 101, + attributes: { + offerid: 'Gamification_Sapphire_DailyCheckIn' + } + } + + const claimRequest = { + url: 'https://prod.rewardsplatform.microsoft.com/dapi/me/activities', + method: 'POST', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + 'X-Rewards-Country': geoLocale, + 'X-Rewards-Language': 'en' + }, + data: JSON.stringify(jsonData) + } + + const claimResponse = await axios(claimRequest) + const claimedPoint = parseInt((await claimResponse.data).response.activity.p) + + this.bot.log('DAILY-CHECK-IN', claimedPoint > 0 ? `Claimed ${claimedPoint} points` : 'Already claimed today') + } catch (error) { + this.bot.log('DAILY-CHECK-IN', 'An error occurred:' + error, 'error') + } + } + +} \ No newline at end of file diff --git a/src/functions/activities/ReadToEarn.ts b/src/functions/activities/ReadToEarn.ts new file mode 100644 index 0000000..02c0041 --- /dev/null +++ b/src/functions/activities/ReadToEarn.ts @@ -0,0 +1,73 @@ +import axios from 'axios' +import { randomBytes } from 'crypto' + +import { Workers } from '../Workers' + +import { DashboardData } from '../../interface/DashboardData' + + +export class ReadToEarn extends Workers { + public async doReadToEarn(accessToken: string, data: DashboardData) { + this.bot.log('READ-TO-EARN', 'Starting Read to Earn') + + try { + + let geoLocale = data.userProfile.attributes.country + geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toLowerCase() : 'us' + + const userDataRequest = { + url: 'https://prod.rewardsplatform.microsoft.com/dapi/me', + method: 'GET', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'X-Rewards-Country': geoLocale, + 'X-Rewards-Language': 'en' + } + } + const userDataResponse = await axios(userDataRequest) + const userData = (await userDataResponse.data).response + let balance: number = userData.balance + + const jsonData = { + amount: 1, + country: geoLocale, + id: '1', + type: 101, + attributes: { + offerid: 'ENUS_readarticle3_30points' + } + } + + for (let i = 0; i < 10; ++i) { + jsonData.id = randomBytes(64).toString('hex') + const claimRequest = { + url: 'https://prod.rewardsplatform.microsoft.com/dapi/me/activities', + method: 'POST', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + 'X-Rewards-Country': geoLocale, + 'X-Rewards-Language': 'en' + }, + data: JSON.stringify(jsonData) + } + + const claimResponse = await axios(claimRequest) + const newBalance = (await claimResponse.data).response.balance + + if (newBalance == balance) { + this.bot.log('READ-TO-EARN', 'Read all available articles') + break + } else { + balance = newBalance + this.bot.log('READ-TO-EARN', `Read article ${i + 1}`) + await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.config.searchSettings.searchDelay.min, this.bot.config.searchSettings.searchDelay.max))) + } + } + + this.bot.log('READ-TO-EARN', 'Completed Read to Earn') + } catch (error) { + this.bot.log('READ-TO-EARN', 'An error occurred:' + error, 'error') + } + } +} \ No newline at end of file diff --git a/src/functions/activities/Search.ts b/src/functions/activities/Search.ts index f018115..eb146a5 100644 --- a/src/functions/activities/Search.ts +++ b/src/functions/activities/Search.ts @@ -133,15 +133,19 @@ export class Search extends Workers { private async bingSearch(searchPage: Page, query: string) { const platformControlKey = platform() === 'darwin' ? 'Meta' : 'Control' + + // Try a max of 5 times for (let i = 0; i < 5; i++) { try { - // Go to top of the page await searchPage.evaluate(() => { window.scrollTo(0, 0) }) + // Set it since params get added after visiting + this.searchPageURL = searchPage.url() + await this.bot.utils.wait(500) const searchBar = '#sb_form_q' @@ -155,14 +159,19 @@ export class Search extends Workers { await searchPage.keyboard.type(query) await searchPage.keyboard.press('Enter') + await this.bot.utils.wait(1000) + + // Bing.com in Chrome opens a new tab when searching + const resultPage = await this.bot.browser.utils.getLatestTab(searchPage) + if (this.bot.config.searchSettings.scrollRandomResults) { await this.bot.utils.wait(2000) - await this.randomScroll(searchPage) + await this.randomScroll(resultPage) } if (this.bot.config.searchSettings.clickRandomResults) { await this.bot.utils.wait(2000) - await this.clickRandomLink(searchPage) + await this.clickRandomLink(resultPage) } // Delay between searches @@ -181,7 +190,7 @@ export class Search extends Workers { // Reset the tabs const lastTab = await this.bot.browser.utils.getLatestTab(searchPage) - await this.closeTabs(lastTab, this.searchPageURL) + await this.closeTabs(lastTab) await this.bot.utils.wait(4000) } @@ -262,11 +271,13 @@ export class Search extends Workers { private async randomScroll(page: Page) { try { - const scrollAmount = this.bot.utils.randomNumber(5, 5000) + const viewportHeight = await page.evaluate(() => window.innerHeight) + const totalHeight = await page.evaluate(() => document.body.scrollHeight) + const randomScrollPosition = Math.floor(Math.random() * (totalHeight - viewportHeight)) - await page.evaluate((scrollAmount) => { - window.scrollBy(0, scrollAmount) - }, scrollAmount) + await page.evaluate((scrollPos) => { + window.scrollTo(0, scrollPos) + }, randomScrollPosition) } catch (error) { this.bot.log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error') @@ -275,23 +286,22 @@ export class Search extends Workers { private async clickRandomLink(page: Page) { try { - const searchListingURL = new URL(page.url()) // Get searchPage info before clicking - await page.click('#b_results .b_algo h2').catch(() => { }) // Since we don't really care if it did it or not + await page.click('#b_results .b_algo h2', { timeout: 2000 }).catch(() => { }) // Since we don't really care if it did it or not // Will get current tab if no new one is created let lastTab = await this.bot.browser.utils.getLatestTab(page) - // Let website load, if it doesn't load within 5 sec. exit regardless - await this.bot.utils.wait(5000) + // Stay for 10 seconds + await this.bot.utils.wait(10_000) let lastTabURL = new URL(lastTab.url()) // Get new tab info // Check if the URL is different from the original one, don't loop more than 5 times. let i = 0 - while (lastTabURL.href !== searchListingURL.href && i < 5) { + while (lastTabURL.href !== this.searchPageURL && i < 5) { - await this.closeTabs(lastTab, searchListingURL.href) + await this.closeTabs(lastTab) // End of loop, refresh lastPage lastTab = await this.bot.browser.utils.getLatestTab(page) // Finally update the lastTab var again @@ -304,26 +314,25 @@ export class Search extends Workers { } } - private async closeTabs(lastTab: Page, url: string) { + private async closeTabs(lastTab: Page) { const browser = lastTab.context() const tabs = browser.pages() - // If more than 3 tabs are open, close the last tab + // If more than 2 tabs are open, close the last tab if (tabs.length > 2) { await lastTab.close() // If only 1 tab is open, open a new one to search in } else if (tabs.length === 1) { const newPage = await browser.newPage() - await newPage.goto(url) + await newPage.goto(this.searchPageURL) - // Else go back one page + // Else go back one page, this means the correct amount is open } else { - await lastTab.goBack() + await lastTab.goBack().catch(() => { }) } } - private calculatePoints(counters: Counters) { const mobileData = counters.mobileSearch?.[0] // Mobile searches const genericData = counters.pcSearch?.[0] // Normal searches diff --git a/src/index.ts b/src/index.ts index df33302..1924d5a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,6 +34,7 @@ export class MicrosoftRewardsBot { private accounts: Account[] private workers: Workers private login = new Login(this) + private accessToken: string = '' constructor() { this.log = log @@ -134,14 +135,19 @@ export class MicrosoftRewardsBot { // Login into MS Rewards, then go to rewards homepage await this.login.login(this.homePage, account.email, account.password) + this.accessToken = await this.login.getMobileAccessToken(this.homePage, account.email) + await this.browser.func.goHome(this.homePage) const data = await this.browser.func.getDashboardData() log('MAIN-POINTS', `Current point count: ${data.userStatus.availablePoints}`) - const earnablePoints = await this.browser.func.getEarnablePoints() + const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints() + const appEarnablePoints = await this.browser.func.getAppEarnablePoints(this.accessToken) + + const earnablePoints = browserEnarablePoints + appEarnablePoints this.collectedPoints = earnablePoints - log('MAIN-POINTS', `You can earn ${earnablePoints} points today`) + log('MAIN-POINTS', `You can earn ${earnablePoints} points today (Browser: ${browserEnarablePoints} points, App: ${appEarnablePoints} points)`) // If runOnZeroPoints is false and 0 points to earn, don't continue if (!this.config.runOnZeroPoints && this.collectedPoints === 0) { @@ -200,42 +206,49 @@ export class MicrosoftRewardsBot { const data = await this.browser.func.getDashboardData() - // If no mobile searches data found, stop (Does not exist on new accounts) - if (!data.userStatus.counters.mobileSearch) { - log('MAIN', 'No mobile searches found, stopping!') - - // Close mobile browser - return await this.closeBrowser(browser, account.email) + // Do daily check in + if (this.config.workers.doDailyCheckIn) { + await this.activities.doDailyCheckIn(this.accessToken, data) } - // Open a new tab to where the tasks are going to be completed - const workerPage = await browser.newPage() + // Do read to earn + if (this.config.workers.doReadToEarn) { + await this.activities.doReadToEarn(this.accessToken, data) + } - // Go to homepage on worker page - await this.browser.func.goHome(workerPage) + // If no mobile searches data found, stop (Does not exist on new accounts) + if (data.userStatus.counters.mobileSearch) { + // Open a new tab to where the tasks are going to be completed + const workerPage = await browser.newPage() - // Do mobile searches - if (this.config.workers.doMobileSearch) { - await this.activities.doSearch(workerPage, data) + // Go to homepage on worker page + await this.browser.func.goHome(workerPage) - // Fetch current search points - const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0] + // Do mobile searches + if (this.config.workers.doMobileSearch) { + await this.activities.doSearch(workerPage, data) - // If the remaining mobile points does not equal 0, restart and assume the generated UA is invalid - // Retry until all points are gathered when (retryMobileSearch is enabled) - if (this.config.searchSettings.retryMobileSearch && mobileSearchPoints && ((mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0)) { - log('MAIN', 'Unable to complete mobile searches, bad User-Agent? Retrying...') + // Fetch current search points + const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0] - // Close mobile browser - await this.closeBrowser(browser, account.email) + // If the remaining mobile points does not equal 0, restart and assume the generated UA is invalid + // Retry until all points are gathered when (retryMobileSearch is enabled) + if (this.config.searchSettings.retryMobileSearch && mobileSearchPoints && ((mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0)) { + log('MAIN', 'Unable to complete mobile searches, bad User-Agent? Retrying...') - // Retry - await this.Mobile(account) + // Close mobile browser + await this.closeBrowser(browser, account.email) + + // Retry + await this.Mobile(account) + } } + } else { + log('MAIN', 'No mobile searches found!') } // Fetch new points - const earnablePoints = await this.browser.func.getEarnablePoints() + const earnablePoints = await this.browser.func.getBrowserEarnablePoints() + await this.browser.func.getAppEarnablePoints(this.accessToken) // If the new earnable is 0, means we got all the points, else retract this.collectedPoints = earnablePoints === 0 ? this.collectedPoints : (this.collectedPoints - earnablePoints) diff --git a/src/interface/AppUserData.ts b/src/interface/AppUserData.ts new file mode 100644 index 0000000..f4f2b0e --- /dev/null +++ b/src/interface/AppUserData.ts @@ -0,0 +1,226 @@ +export interface AppUserData { + response: Response; + correlationId: string; + code: number; +} + +export interface Response { + profile: Profile; + balance: number; + counters: null; + promotions: Promotion[]; + catalog: null; + goal_item: GoalItem; + activities: null; + cashback: null; + orders: Order[]; + rebateProfile: null; + rebatePayouts: null; + giveProfile: GiveProfile; + autoRedeemProfile: null; + autoRedeemItem: null; + thirdPartyProfile: null; + notifications: null; + waitlist: null; + autoOpenFlyout: null; + coupons: null; + recommendedAffordableCatalog: null; +} + +export interface GiveProfile { + give_user: string; + give_organization: { [key: string]: GiveOrganization | null }; + first_give_optin: string; + last_give_optout: string; + give_lifetime_balance: string; + give_lifetime_donation_balance: string; + give_balance: string; + form: null; +} + +export interface GiveOrganization { + give_organization_donation_points: number; + give_organization_donation_point_to_currency_ratio: number; + give_organization_donation_currency: number; +} + +export interface GoalItem { + name: string; + provider: string; + price: number; + attributes: GoalItemAttributes; + config: GoalItemConfig; +} + +export interface GoalItemAttributes { + category: string; + CategoryDescription: string; + 'desc.group_text': string; + 'desc.legal_text'?: string; + 'desc.sc_description': string; + 'desc.sc_title': string; + display_order: string; + ExtraLargeImage: string; + group: string; + group_image: string; + group_sc_image: string; + group_title: string; + hidden?: string; + large_image: string; + large_sc_image: string; + medium_image: string; + MobileImage: string; + original_price: string; + Remarks?: string; + ShortText?: string; + showcase?: string; + small_image: string; + title: string; + cimsid: string; + user_defined_goal?: string; + disable_bot_redemptions?: string; + 'desc.large_text'?: string; + english_title?: string; + etid?: string; + sku?: string; + coupon_discount?: string; +} + +export interface GoalItemConfig { + amount: string; + currencyCode: string; + isHidden: string; + PointToCurrencyConversionRatio: string; +} + +export interface Order { + id: string; + t: Date; + sku: string; + item_snapshot: ItemSnapshot; + p: number; + s: S; + a: A; + child_redemption: null; + third_party_partner: null; + log: Log[]; +} + +export interface A { + form?: string; + OrderId: string; + CorrelationId: string; + Channel: string; + Language: string; + Country: string; + EvaluationId: string; + provider?: string; + referenceOrderID?: string; + externalRefID?: string; + denomination?: string; + rewardName?: string; + sendEmail?: string; + status?: string; + createdAt?: Date; + bal_before_deduct?: string; + bal_after_deduct?: string; +} + +export interface ItemSnapshot { + name: string; + provider: string; + price: number; + attributes: GoalItemAttributes; + config: ItemSnapshotConfig; +} + +export interface ItemSnapshotConfig { + amount: string; + countryCode: string; + currencyCode: string; + sku: string; +} + +export interface Log { + time: Date; + from: From; + to: S; + reason: string; +} + +export enum From { + Created = 'Created', + RiskApproved = 'RiskApproved', + RiskReview = 'RiskReview' +} + +export enum S { + Cancelled = 'Cancelled', + RiskApproved = 'RiskApproved', + RiskReview = 'RiskReview', + Shipped = 'Shipped' +} + +export interface Profile { + ruid: string; + attributes: ProfileAttributes; + offline_attributes: OfflineAttributes; +} + +export interface ProfileAttributes { + publisher: string; + publisher_upd: Date; + creative: string; + creative_upd: Date; + program: string; + program_upd: Date; + country: string; + country_upd: Date; + referrerhash: string; + referrerhash_upd: Date; + optout_upd: Date; + language: string; + language_upd: Date; + target: string; + target_upd: Date; + created: Date; + created_upd: Date; + epuid: string; + epuid_upd: Date; + goal: string; + goal_upd: Date; + waitlistattributes: string; + waitlistattributes_upd: Date; + serpbotscore_upd: Date; + iscashbackeligible: string; + cbedc: string; + rlscpct_upd: Date; + give_user: string; + rebcpc_upd: Date; + SerpBotScore_upd: Date; + AdsBotScore_upd: Date; + dbs_upd: Date; + rbs: string; + rbs_upd: Date; + iris_segmentation: string; + iris_segmentation_upd: Date; +} + +export interface OfflineAttributes { +} + +export interface Promotion { + name: string; + priority: number; + attributes: { [key: string]: string }; + tags: Tag[]; +} + +export enum Tag { + AllowTrialUser = 'allow_trial_user', + ExcludeGivePcparent = 'exclude_give_pcparent', + ExcludeGlobalConfig = 'exclude_global_config', + ExcludeHidden = 'exclude_hidden', + LOCString = 'locString', + NonGlobalConfig = 'non_global_config' +} diff --git a/src/interface/Config.ts b/src/interface/Config.ts index 515d96d..05c4104 100644 --- a/src/interface/Config.ts +++ b/src/interface/Config.ts @@ -35,4 +35,6 @@ export interface Workers { doPunchCards: boolean; doDesktopSearch: boolean; doMobileSearch: boolean; + doDailyCheckIn: boolean; + doReadToEarn: boolean; } diff --git a/src/interface/OAuth.ts b/src/interface/OAuth.ts new file mode 100644 index 0000000..6f1d669 --- /dev/null +++ b/src/interface/OAuth.ts @@ -0,0 +1,9 @@ +export interface OAuth { + access_token: string; + refresh_token: string; + scope: string; + expires_in: number; + ext_expires_in: number; + foci: string; + token_type: string; +} \ No newline at end of file diff --git a/src/updateConfig.js b/src/updateConfig.js index 571866e..f1d3dd3 100755 --- a/src/updateConfig.js +++ b/src/updateConfig.js @@ -1,41 +1,43 @@ -const fs = require('fs'); -const path = require('path'); +const fs = require('fs') +const path = require('path') -const configPath = path.join(__dirname, '../dist/config.json'); +const configPath = path.join(__dirname, '../dist/config.json') // Read the existing config file -const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); +const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) // Update the config with environment variables if they are set -config.baseURL = process.env.BASE_URL || config.baseURL; -config.sessionPath = process.env.SESSION_PATH || config.sessionPath; -config.headless = process.env.HEADLESS ? process.env.HEADLESS === 'true' : config.headless; -config.runOnZeroPoints = process.env.RUN_ON_ZERO_POINTS ? process.env.RUN_ON_ZERO_POINTS === 'true' : config.runOnZeroPoints; -config.clusters = process.env.CLUSTERS ? parseInt(process.env.CLUSTERS, 10) : config.clusters; -config.saveFingerprint = process.env.SAVE_FINGERPRINT ? process.env.SAVE_FINGERPRINT === 'true' : config.saveFingerprint; -config.globalTimeout = process.env.GLOBAL_TIMEOUT ? parseInt(process.env.GLOBAL_TIMEOUT, 10) : config.globalTimeout; +config.baseURL = process.env.BASE_URL || config.baseURL +config.sessionPath = process.env.SESSION_PATH || config.sessionPath +config.headless = process.env.HEADLESS ? process.env.HEADLESS === 'true' : config.headless +config.runOnZeroPoints = process.env.RUN_ON_ZERO_POINTS ? process.env.RUN_ON_ZERO_POINTS === 'true' : config.runOnZeroPoints +config.clusters = process.env.CLUSTERS ? parseInt(process.env.CLUSTERS, 10) : config.clusters +config.saveFingerprint = process.env.SAVE_FINGERPRINT ? process.env.SAVE_FINGERPRINT === 'true' : config.saveFingerprint +config.globalTimeout = process.env.GLOBAL_TIMEOUT ? parseInt(process.env.GLOBAL_TIMEOUT, 10) : config.globalTimeout -config.workers.doDailySet = process.env.DO_DAILY_SET ? process.env.DO_DAILY_SET === 'true' : config.workers.doDailySet; -config.workers.doMorePromotions = process.env.DO_MORE_PROMOTIONS ? process.env.DO_MORE_PROMOTIONS === 'true' : config.workers.doMorePromotions; -config.workers.doPunchCards = process.env.DO_PUNCH_CARDS ? process.env.DO_PUNCH_CARDS === 'true' : config.workers.doPunchCards; -config.workers.doDesktopSearch = process.env.DO_DESKTOP_SEARCH ? process.env.DO_DESKTOP_SEARCH === 'true' : config.workers.doDesktopSearch; -config.workers.doMobileSearch = process.env.DO_MOBILE_SEARCH ? process.env.DO_MOBILE_SEARCH === 'true' : config.workers.doMobileSearch; +config.workers.doDailySet = process.env.DO_DAILY_SET ? process.env.DO_DAILY_SET === 'true' : config.workers.doDailySet +config.workers.doMorePromotions = process.env.DO_MORE_PROMOTIONS ? process.env.DO_MORE_PROMOTIONS === 'true' : config.workers.doMorePromotions +config.workers.doPunchCards = process.env.DO_PUNCH_CARDS ? process.env.DO_PUNCH_CARDS === 'true' : config.workers.doPunchCards +config.workers.doDesktopSearch = process.env.DO_DESKTOP_SEARCH ? process.env.DO_DESKTOP_SEARCH === 'true' : config.workers.doDesktopSearch +config.workers.doMobileSearch = process.env.DO_MOBILE_SEARCH ? process.env.DO_MOBILE_SEARCH === 'true' : config.workers.doMobileSearch +config.workers.doDailyCheckIn = process.env.DO_DAILY_CHECK_IN ? process.env.DO_DAILY_CHECK_IN === 'true' : config.workers.doDailyCheckIn +config.workers.doReadToEarn = process.env.DO_READ_TO_EARN ? process.env.DO_READ_TO_EARN === 'true' : config.workers.doReadToEarn -config.searchSettings.useGeoLocaleQueries = process.env.USE_GEO_LOCALE_QUERIES ? process.env.USE_GEO_LOCALE_QUERIES === 'true' : config.searchSettings.useGeoLocaleQueries; -config.searchSettings.scrollRandomResults = process.env.SCROLL_RANDOM_RESULTS ? process.env.SCROLL_RANDOM_RESULTS === 'true' : config.searchSettings.scrollRandomResults; -config.searchSettings.clickRandomResults = process.env.CLICK_RANDOM_RESULTS ? process.env.CLICK_RANDOM_RESULTS === 'true' : config.searchSettings.clickRandomResults; -config.searchSettings.searchDelay.min = process.env.SEARCH_DELAY_MIN ? parseInt(process.env.SEARCH_DELAY_MIN, 10) : config.searchSettings.searchDelay.min; -config.searchSettings.searchDelay.max = process.env.SEARCH_DELAY_MAX ? parseInt(process.env.SEARCH_DELAY_MAX, 10) : config.searchSettings.searchDelay.max; -config.searchSettings.retryMobileSearch = process.env.RETRY_MOBILE_SEARCH ? process.env.RETRY_MOBILE_SEARCH === 'true' : config.searchSettings.retryMobileSearch; +config.searchSettings.useGeoLocaleQueries = process.env.USE_GEO_LOCALE_QUERIES ? process.env.USE_GEO_LOCALE_QUERIES === 'true' : config.searchSettings.useGeoLocaleQueries +config.searchSettings.scrollRandomResults = process.env.SCROLL_RANDOM_RESULTS ? process.env.SCROLL_RANDOM_RESULTS === 'true' : config.searchSettings.scrollRandomResults +config.searchSettings.clickRandomResults = process.env.CLICK_RANDOM_RESULTS ? process.env.CLICK_RANDOM_RESULTS === 'true' : config.searchSettings.clickRandomResults +config.searchSettings.searchDelay.min = process.env.SEARCH_DELAY_MIN ? parseInt(process.env.SEARCH_DELAY_MIN, 10) : config.searchSettings.searchDelay.min +config.searchSettings.searchDelay.max = process.env.SEARCH_DELAY_MAX ? parseInt(process.env.SEARCH_DELAY_MAX, 10) : config.searchSettings.searchDelay.max +config.searchSettings.retryMobileSearch = process.env.RETRY_MOBILE_SEARCH ? process.env.RETRY_MOBILE_SEARCH === 'true' : config.searchSettings.retryMobileSearch -config.webhook.enabled = process.env.WEBHOOK_ENABLED ? process.env.WEBHOOK_ENABLED === 'true' : config.webhook.enabled; -config.webhook.url = process.env.WEBHOOK_URL || config.webhook.url; +config.webhook.enabled = process.env.WEBHOOK_ENABLED ? process.env.WEBHOOK_ENABLED === 'true' : config.webhook.enabled +config.webhook.url = process.env.WEBHOOK_URL || config.webhook.url // Write the updated config back to the file try { - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); - console.log('Config file updated with environment variables'); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)) + console.log('Config file updated with environment variables') } catch (error) { - console.error(`Failed to write updated config file to ${configPath}:`, error); - process.exit(1); + console.error(`Failed to write updated config file to ${configPath}:`, error) + process.exit(1) } \ No newline at end of file