From 16a05dc4a4209d1490b4f88367dd33550647f2a7 Mon Sep 17 00:00:00 2001 From: Rippenkneifer <147978010+Rippenkneifer@users.noreply.github.com> Date: Sat, 11 Jan 2025 14:01:31 +0100 Subject: [PATCH 1/4] Update main.py pause - configurable --- main.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index cfc57c83..132b47a2 100644 --- a/main.py +++ b/main.py @@ -234,6 +234,13 @@ def executeBot(currentAccount: Account, args: argparse.Namespace): with Browser(mobile=False, account=currentAccount, args=args) as desktopBrowser: utils = desktopBrowser.utils Login(desktopBrowser, args).login() + logging.info("[LOGIN] Successfully logged in.") + + # Check if the script should pause after login + if CONFIG.get("default").get("pause_after_login", False): + logging.info("[PAUSE] Pausing after login. Press Enter to continue...") + input() # Wait for user input to continue + startingPoints = utils.getAccountPoints() logging.info( f"[POINTS] You have {formatNumber(startingPoints)} points on your account" @@ -257,6 +264,13 @@ def executeBot(currentAccount: Account, args: argparse.Namespace): with Browser(mobile=True, account=currentAccount, args=args) as mobileBrowser: utils = mobileBrowser.utils Login(mobileBrowser, args).login() + logging.info("[LOGIN] Successfully logged in (mobile).") + + # Check if the script should pause after login + if CONFIG.get("default").get("pause_after_login", False): + logging.info("[PAUSE] Pausing after login. Press Enter to continue...") + input() # Wait for user input to continue + if startingPoints is None: startingPoints = utils.getAccountPoints() ReadToEarn(mobileBrowser).completeReadToEarn() @@ -272,16 +286,16 @@ def executeBot(currentAccount: Account, args: argparse.Namespace): accountPoints = utils.getAccountPoints() logging.info( - f"[POINTS] You have earned {formatNumber(accountPoints - startingPoints)} points this run !" + f"[POINTS] You have earned {formatNumber(accountPoints - startingPoints)} points this run!" ) - logging.info(f"[POINTS] You are now at {formatNumber(accountPoints)} points !") + logging.info(f"[POINTS] You are now at {formatNumber(accountPoints)} points!") appriseSummary = AppriseSummary[CONFIG.get("apprise").get("summary")] if appriseSummary == AppriseSummary.ALWAYS: goalStatus = "" if goalPoints > 0: logging.info( f"[POINTS] You are now at {(formatNumber((accountPoints / goalPoints) * 100))}% of your " - f"goal ({goalTitle}) !" + f"goal ({goalTitle})!" ) goalStatus = ( f"🎯 Goal reached: {(formatNumber((accountPoints / goalPoints) * 100))}%" From 11c6b7846c0fb1cc0f7514122174753b133c41b4 Mon Sep 17 00:00:00 2001 From: Rippenkneifer <147978010+Rippenkneifer@users.noreply.github.com> Date: Sat, 11 Jan 2025 14:02:33 +0100 Subject: [PATCH 2/4] Update config.yaml --- config.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/config.yaml b/config.yaml index d02856b6..19dfb876 100644 --- a/config.yaml +++ b/config.yaml @@ -10,13 +10,14 @@ apprise: enabled: True # True or False login-code: enabled: True # True or False - summary: ON_ERROR + summary: NEVER default: - geolocation: US # Replace with your country code https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 - language: en # Replace with your language code https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + pause_after_login: False # True, pause Script after Login + geolocation: DE # Replace with your country code https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + language: de # Replace with your language code https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes logging: level: INFO # See https://docs.python.org/3/library/logging.html#logging-levels retries: - base_delay_in_seconds: 120 # base_delay_in_seconds * 2^max = 14.0625 * 2^6 = 900 = 15 minutes - max: 4 + base_delay_in_seconds: 2 # base_delay_in_seconds * 2^max = 14.0625 * 2^6 = 900 = 15 minutes + max: 1 strategy: EXPONENTIAL From 8dc036205731019befd5cf9d5f07d23531e3539b Mon Sep 17 00:00:00 2001 From: Rippenkneifer <147978010+Rippenkneifer@users.noreply.github.com> Date: Sat, 11 Jan 2025 14:04:09 +0100 Subject: [PATCH 3/4] Update activities.py Changed something for the Daily-Set idk if it has done quizzes before in the Daily-Set so keep care here it will not do it, since i dont have Quizzes or anything related to that, cant test it. goodluck. --- src/activities.py | 158 ++++++++++++++++++++++++++++------------------ 1 file changed, 97 insertions(+), 61 deletions(-) diff --git a/src/activities.py b/src/activities.py index 91a36a70..9fe589f5 100644 --- a/src/activities.py +++ b/src/activities.py @@ -3,9 +3,13 @@ from random import randint from time import sleep -from selenium.common import TimeoutException +from selenium.common import TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC from src.browser import Browser from src.constants import REWARDS_URL @@ -44,14 +48,46 @@ def __init__(self, browser: Browser): self.webdriver = browser.webdriver def openDailySetActivity(self, cardId: int): - # Open the Daily Set activity for the given cardId - cardId += 1 - element = self.webdriver.find_element( - By.XPATH, - f'//*[@id="daily-sets"]/mee-card-group[1]/div/mee-card[{cardId}]/div/card-content/mee-rewards-daily-set-item-content/div/a', - ) - self.browser.utils.click(element) - self.browser.utils.switchToNewTab() + # Count total number of cards + total_cards = len(self.webdriver.find_elements(By.XPATH, '//*[@id="daily-sets"]/mee-card-group[1]/div/mee-card')) + logging.info(f"Total number of Daily Set activity cards: {total_cards}.") + + # Open the Daily Set activity for the given cardId + cardId += 1 + element = self.webdriver.find_element( + By.XPATH, + f'//*[@id="daily-sets"]/mee-card-group[1]/div/mee-card[{cardId}]/div/card-content/mee-rewards-daily-set-item-content/div/a', + ) + + logging.info(f"Attempting to click on Daily Set activity card {cardId}.") + # Perform Ctrl + Click (Strg+Linksklick) + ActionChains(self.webdriver).key_down(Keys.CONTROL).click(element).key_up(Keys.CONTROL).perform() + logging.info(f"Clicked on Daily Set activity card {cardId}.") + + # Mark activity as complete after the click + logging.info(f"Marking card {cardId} as complete.") + self.webdriver.execute_script("arguments[0].setAttribute('complete', 'true');", element) + + # Wait for new tab to open + original_window = self.webdriver.current_window_handle + all_windows = self.webdriver.window_handles + + # Check if a new tab was opened + if len(all_windows) > 1: + logging.info(f"New tab detected after clicking card {cardId}.") + # Switch to the new tab + self.webdriver.switch_to.window(all_windows[-1]) + logging.info(f"Switched to the new tab for card {cardId}.") + + # Close the new tab + self.webdriver.close() + logging.info(f"Closed the new tab for card {cardId}.") + + # Switch back to the original tab + self.webdriver.switch_to.window(original_window) + logging.info(f"Switched back to the original tab after handling card {cardId}.") + else: + logging.info(f"No new tab was opened for card {cardId}.") def openMorePromotionsActivity(self, cardId: int): cardId += 1 @@ -69,8 +105,13 @@ def completeSearch(self): def completeSurvey(self): # Simulate completing a survey activity - # noinspection SpellCheckingInspection - self.webdriver.find_element(By.ID, f"btoption{randint(0, 1)}").click() + try: + survey_option = WebDriverWait(self.webdriver, 10).until( + EC.element_to_be_clickable((By.ID, f"btoption{randint(0, 1)}")) + ) + survey_option.click() + except TimeoutException: + logging.error("Survey option not found or not clickable.") def completeQuiz(self): # Simulate completing a quiz activity @@ -111,11 +152,8 @@ def completeQuiz(self): ).get_attribute("data-option") == correctOption ): - element = self.webdriver.find_element( - By.ID, f"rqAnswerOption{i}" - ) + element = self.webdriver.find_element(By.ID, f"rqAnswerOption{i}") self.browser.utils.click(element) - self.browser.utils.waitUntilQuestionRefresh() break @@ -149,11 +187,8 @@ def completeThisOrThat(self): ) answer1, answer1Code = self.getAnswerAndCode("rqAnswerOption0") answer2, answer2Code = self.getAnswerAndCode("rqAnswerOption1") - answerToClick: WebElement - if answer1Code == correctAnswerCode: - answerToClick = answer1 - elif answer2Code == correctAnswerCode: - answerToClick = answer2 + + answerToClick = answer1 if answer1Code == correctAnswerCode else answer2 self.browser.utils.click(answerToClick) sleep(randint(10, 15)) @@ -171,58 +206,59 @@ def getAnswerAndCode(self, answerId: str) -> tuple[WebElement, str]: def doActivity(self, activity: dict, activities: list[dict]) -> None: try: activityTitle = cleanupActivityTitle(activity["title"]) - logging.debug(f"activityTitle={activityTitle}") + logging.info(f"Processing activity: {activityTitle}") + if activity["complete"] is True or activity["pointProgressMax"] == 0: - logging.debug("Already done, returning") + logging.info("Activity already completed. Skipping.") return - if activityTitle in CONFIG.get("apprise").get("notify").get( - "incomplete-activity" - ).get("ignore"): - logging.debug(f"Ignoring {activityTitle}") + + if activityTitle in CONFIG.get("apprise").get("notify").get("incomplete-activity").get("ignore"): + logging.info(f"Ignoring activity: {activityTitle}") return - # Open the activity for the activity + cardId = activities.index(activity) - isDailySet = ( - "daily_set_date" in activity["attributes"] - and activity["attributes"]["daily_set_date"] - ) + isDailySet = "daily_set_date" in activity["attributes"] and activity["attributes"]["daily_set_date"] + if isDailySet: self.openDailySetActivity(cardId) else: self.openMorePromotionsActivity(cardId) - with contextlib.suppress(TimeoutException): - searchbar = self.browser.utils.waitUntilClickable(By.ID, "sb_form_q") - self.browser.utils.click(searchbar) - if activityTitle in ACTIVITY_TITLE_TO_SEARCH: - searchbar.send_keys(ACTIVITY_TITLE_TO_SEARCH[activityTitle]) - sleep(2) - searchbar.submit() - elif "poll" in activityTitle: - logging.info(f"[ACTIVITY] Completing poll of card {cardId}") - # Complete survey for a specific scenario - self.completeSurvey() - elif activity["promotionType"] == "urlreward": - # Complete search for URL reward - self.completeSearch() - elif activity["promotionType"] == "quiz": - # Complete different types of quizzes based on point progress max - if activity["pointProgressMax"] == 10: - self.completeABC() - elif activity["pointProgressMax"] in [30, 40]: - self.completeQuiz() - elif activity["pointProgressMax"] == 50: - self.completeThisOrThat() - else: - # Default to completing search - self.completeSearch() - except Exception: - logging.error(f"[ACTIVITY] Error doing {activityTitle}", exc_info=True) - # todo Make configurable - sleep(randint(300, 600)) + + # Handle specific activity types + try: + if activityTitle in ACTIVITY_TITLE_TO_SEARCH: + searchbar = self.browser.utils.waitUntilClickable(By.ID, "sb_form_q", timeout=10) + searchbar.send_keys(ACTIVITY_TITLE_TO_SEARCH[activityTitle]) + sleep(2) + searchbar.submit() + elif "poll" in activityTitle: + logging.info(f"[ACTIVITY] Completing poll for card {cardId}") + self.completeSurvey() + elif activity["promotionType"] == "urlreward": + self.completeSearch() + elif activity["promotionType"] == "quiz": + if activity["pointProgressMax"] == 10: + self.completeABC() + elif activity["pointProgressMax"] in [30, 40]: + self.completeQuiz() + elif activity["pointProgressMax"] == 50: + self.completeThisOrThat() + else: + self.completeSearch() + except Exception as e: + logging.error(f"Error while handling activity {activityTitle}: {e}") + + except Exception as e: + logging.error(f"[ACTIVITY] Error processing activity {activityTitle}: {e}", exc_info=True) + + # Shorten sleep time or remove it entirely + sleep(randint(2, 5)) + logging.info("Resetting tabs.") self.browser.utils.resetTabs() + logging.info("Finished processing activity.") def completeActivities(self): - logging.info("[DAILY SET] " + "Trying to complete the Daily Set...") + logging.info("[DAILY SET] " + "Trying to complete the Daily Set...[acitivties.py]") dailySetPromotions = self.browser.utils.getDailySetPromotions() self.browser.utils.goToRewards() for activity in dailySetPromotions: From 1f75f94209f02392de69962d48ef9046480a1abc Mon Sep 17 00:00:00 2001 From: Rippenkneifer <147978010+Rippenkneifer@users.noreply.github.com> Date: Sat, 11 Jan 2025 15:47:13 +0100 Subject: [PATCH 4/4] Update searches.py Searches --- src/searches.py | 199 +++++++++++++++++++++++++----------------------- 1 file changed, 104 insertions(+), 95 deletions(-) diff --git a/src/searches.py b/src/searches.py index 0b2cdf00..c08d3ea0 100644 --- a/src/searches.py +++ b/src/searches.py @@ -11,43 +11,28 @@ import requests from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException from src.browser import Browser from src.utils import CONFIG, makeRequestsSession, getProjectRoot - class RetriesStrategy(Enum): - """ - method to use when retrying - """ - EXPONENTIAL = auto() - """ - an exponentially increasing `base_delay_in_seconds` between attempts - """ CONSTANT = auto() - """ - the default; a constant `base_delay_in_seconds` between attempts - """ - class Searches: maxRetries: Final[int] = CONFIG.get("retries").get("max") - """ - the max amount of retries to attempt - """ baseDelay: Final[float] = CONFIG.get("retries").get("base_delay_in_seconds") - """ - how many seconds to delay - """ - # retriesStrategy = Final[ # todo Figure why doesn't work with equality below retriesStrategy = RetriesStrategy[CONFIG.get("retries").get("strategy")] def __init__(self, browser: Browser): self.browser = browser self.webdriver = browser.webdriver - dumbDbm = dbm.dumb.open((getProjectRoot() / "google_trends").__str__()) + db_path = getProjectRoot() / "google_trends" + dumbDbm = dbm.dumb.open(str(db_path)) self.googleTrendsShelf: shelve.Shelf = shelve.Shelf(dumbDbm) def __enter__(self): @@ -57,102 +42,116 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.googleTrendsShelf.__exit__(None, None, None) def getGoogleTrends(self, wordsCount: int) -> list[str]: - # Function to retrieve Google Trends search terms - searchTerms: list[str] = [] - i = 0 + logging.info("Fetching Google Trends data...") + searchTerms = [] session = makeRequestsSession() - while len(searchTerms) < wordsCount: - i += 1 - # Fetching daily trends from Google Trends API + + for i in range(1, wordsCount + 1): r = session.get( - f"https://trends.google.com/trends/api/dailytrends?hl={self.browser.localeLang}" + f"https://trends.google.com/trends/api/dailytrends?hl={self.browser.localeLang}" \ f'&ed={(date.today() - timedelta(days=i)).strftime("%Y%m%d")}&geo={self.browser.localeGeo}&ns=15' ) - assert ( - r.status_code == requests.codes.ok - ), "Adjust retry config in src.utils.Utils.makeRequestsSession" + + if r.status_code != requests.codes.ok: + logging.error("Failed to fetch Google Trends data, retry config may need adjustment.") + continue + trends = json.loads(r.text[6:]) - for topic in trends["default"]["trendingSearchesDays"][0][ - "trendingSearches" - ]: + for topic in trends["default"]["trendingSearchesDays"][0]["trendingSearches"]: searchTerms.append(topic["title"]["query"].lower()) searchTerms.extend( - relatedTopic["query"].lower() - for relatedTopic in topic["relatedQueries"] + relatedTopic["query"].lower() for relatedTopic in topic["relatedQueries"] ) + searchTerms = list(set(searchTerms)) - del searchTerms[wordsCount : (len(searchTerms) + 1)] - return searchTerms + + if len(searchTerms) >= wordsCount: + break + + logging.info(f"Retrieved {len(searchTerms)} Google Trends terms.") + return searchTerms[:wordsCount] def getRelatedTerms(self, term: str) -> list[str]: - # Function to retrieve related terms from Bing API - relatedTerms: list[str] = ( - makeRequestsSession() - .get( - f"https://api.bing.com/osjson.aspx?query={term}", - headers={"User-agent": self.browser.userAgent}, - ) - .json()[1] - ) # todo Wrap if failed, or assert response? - if not relatedTerms: + logging.debug(f"Fetching related terms for: {term}") + response = makeRequestsSession().get( + f"https://api.bing.com/osjson.aspx?query={term}", + headers={"User-agent": self.browser.userAgent}, + ) + + if response.status_code != requests.codes.ok: + logging.error(f"Failed to fetch related terms for: {term}") return [term] - return relatedTerms + + relatedTerms = response.json()[1] + logging.debug(f"Related terms for {term}: {relatedTerms}") + return relatedTerms if relatedTerms else [term] def bingSearches(self) -> None: - # Function to perform Bing searches - logging.info( - f"[BING] Starting {self.browser.browserType.capitalize()} Edge Bing searches..." - ) + logging.info(f"Starting {self.browser.browserType.capitalize()} Bing searches...") + + # Initialize rewards.bing.com tab + rewards_tab = None + for handle in self.webdriver.window_handles: + self.webdriver.switch_to.window(handle) + if self.webdriver.current_url.startswith("https://rewards.bing.com"): + rewards_tab = handle + break - self.browser.utils.goToSearch() + if not rewards_tab: + logging.info("Opening rewards.bing.com in a new tab...") + self.webdriver.execute_script("window.open('https://rewards.bing.com');") + rewards_tab = self.webdriver.window_handles[-1] + + # Initialize bing.com tab + search_tab = None + for handle in self.webdriver.window_handles: + self.webdriver.switch_to.window(handle) + if self.webdriver.current_url.startswith("https://www.bing.com"): + search_tab = handle + break + + if not search_tab: + logging.info("Opening bing.com in a new tab...") + self.webdriver.execute_script("window.open('https://www.bing.com');") + search_tab = self.webdriver.window_handles[-1] while True: - desktopAndMobileRemaining = self.browser.getRemainingSearches( - desktopAndMobile=True - ) - logging.info(f"[BING] Remaining searches={desktopAndMobileRemaining}") - if ( - self.browser.browserType == "desktop" - and desktopAndMobileRemaining.desktop == 0 - ) or ( - self.browser.browserType == "mobile" - and desktopAndMobileRemaining.mobile == 0 - ): + # Switch to rewards.bing.com tab to check remaining searches + self.webdriver.switch_to.window(rewards_tab) + remainingSearches = self.browser.getRemainingSearches(desktopAndMobile=True) + logging.info(f"Remaining searches: {remainingSearches}") + + if (self.browser.browserType == "desktop" and remainingSearches.desktop == 0) or \ + (self.browser.browserType == "mobile" and remainingSearches.mobile == 0): break - if desktopAndMobileRemaining.getTotal() > len(self.googleTrendsShelf): - # self.googleTrendsShelf.clear() # Maybe needed? - logging.debug( - f"google_trends before load = {list(self.googleTrendsShelf.items())}" - ) - trends = self.getGoogleTrends(desktopAndMobileRemaining.getTotal()) + # Ensure we have enough Google Trends terms + if remainingSearches.getTotal() > len(self.googleTrendsShelf): + trends = self.getGoogleTrends(remainingSearches.getTotal()) shuffle(trends) for trend in trends: self.googleTrendsShelf[trend] = None - logging.debug( - f"google_trends after load = {list(self.googleTrendsShelf.items())}" - ) - self.bingSearch() + # Perform a search in the Bing tab + self.webdriver.switch_to.window(search_tab) + self.bingSearch(rewards_tab) + + # Remove used term from Google Trends shelf del self.googleTrendsShelf[list(self.googleTrendsShelf.keys())[0]] sleep(randint(10, 15)) - logging.info( - f"[BING] Finished {self.browser.browserType.capitalize()} Edge Bing searches !" - ) + logging.info(f"Finished {self.browser.browserType.capitalize()} Bing searches.") - def bingSearch(self) -> None: - # Function to perform a single Bing search + def bingSearch(self, rewards_tab) -> None: pointsBefore = self.browser.utils.getAccountPoints() rootTerm = list(self.googleTrendsShelf.keys())[0] terms = self.getRelatedTerms(rootTerm) - logging.debug(f"terms={terms}") + logging.info(f"Using root term: {rootTerm}") termsCycle: cycle[str] = cycle(terms) + baseDelay = Searches.baseDelay - logging.debug(f"rootTerm={rootTerm}") - # todo If first 3 searches of day, don't retry since points register differently, will be a bit quicker for i in range(self.maxRetries + 1): if i != 0: sleepTime: float @@ -163,31 +162,41 @@ def bingSearch(self) -> None: else: raise AssertionError sleepTime += baseDelay * random() # Add jitter - logging.debug( - f"[BING] Search attempt not counted {i}/{Searches.maxRetries}, sleeping {sleepTime}" - f" seconds..." + logging.warning( + f"Retry {i}/{self.maxRetries}. Sleeping for {sleepTime:.2f} seconds..." ) sleep(sleepTime) - searchbar = self.browser.utils.waitUntilClickable( - By.ID, "sb_form_q", timeToWait=40 - ) + try: + # Ensure the Bing search page is active or on a search result page + if not self.webdriver.current_url.startswith("https://www.bing.com"): + logging.warning("[BING] Current tab is not Bing. Redirecting to Bing search page.") + self.webdriver.get("https://www.bing.com") + + searchbar = self.browser.utils.waitUntilClickable( + By.ID, "sb_form_q", timeToWait=60 + ) + except TimeoutException: + logging.error("[BING] Search bar not found or clickable. Retrying...") + continue + searchbar.clear() term = next(termsCycle) - logging.debug(f"term={term}") + logging.info(f"Searching for term: {term}") sleep(1) searchbar.send_keys(term) sleep(1) searchbar.submit() + sleep(5) # Wait for points to reflect + + # Switch back to rewards.bing.com tab to update points + self.webdriver.switch_to.window(rewards_tab) pointsAfter = self.browser.utils.getAccountPoints() if pointsBefore < pointsAfter: - # todo Make configurable - sleep(randint(300, 600)) + logging.info( + f"Points increased. Current points: {pointsAfter}" + ) return - # todo - # if i == (maxRetries / 2): - # logging.info("[BING] " + "TIMED OUT GETTING NEW PROXY") - # self.webdriver.proxy = self.browser.giveMeProxy() logging.error("[BING] Reached max search attempt retries")