From afccf81f8f8e4f88a6628c0f9169b637d3c50969 Mon Sep 17 00:00:00 2001 From: Mally Dang Date: Wed, 2 Sep 2026 12:54:14 +0700 Subject: [PATCH] feat: spoof the rewards app headers --- src/main.py | 171 +++++++++++++++++++++++++++++----------------------- 1 file changed, 94 insertions(+), 77 deletions(-) diff --git a/src/main.py b/src/main.py index 1b9e362..fbae9f3 100644 --- a/src/main.py +++ b/src/main.py @@ -8,108 +8,125 @@ import rewards_tasks from selenium import webdriver from selenium.common.exceptions import SessionNotCreatedException -HEADLESS = os.environ.get("REWARDS_HEADLESS", "").strip().lower() in ("1", "true", "yes") +HEADLESS = os.environ.get("REWARDS_HEADLESS", "").strip().lower() in ( + "1", + "true", + "yes", +) logger = logging.getLogger(__name__) def build_options(account: accounts.Account) -> webdriver.EdgeOptions: - options = webdriver.EdgeOptions() + options = webdriver.EdgeOptions() - options.add_experimental_option("excludeSwitches", ["enable-automation"]) - options.add_experimental_option('useAutomationExtension', False) - options.add_argument("--disable-blink-features=AutomationControlled") - options.add_argument(f"--user-data-dir={account.user_data_dir}") - options.add_argument(f"--profile-directory={account.profile_name}") + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option("useAutomationExtension", False) + options.add_argument("--disable-blink-features=AutomationControlled") + options.add_argument(f"--user-data-dir={account.user_data_dir}") + options.add_argument(f"--profile-directory={account.profile_name}") - if HEADLESS: - # A container has no display. The window size is set explicitly because - # the pointer code works in viewport coordinates, and the default - # headless window is small enough to put cards out of reach. - options.add_argument("--headless=new") - options.add_argument("--window-size=1920,1080") - options.add_argument("--no-sandbox") - options.add_argument("--disable-dev-shm-usage") + if HEADLESS: + # A container has no display. The window size is set explicitly because + # the pointer code works in viewport coordinates, and the default + # headless window is small enough to put cards out of reach. + options.add_argument("--headless=new") + options.add_argument("--window-size=1920,1080") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") - return options + return options def run_account(account: accounts.Account) -> bool: - """Work one account. Returns whether the browser started.""" - try: - driver = webdriver.Edge(options=build_options(account)) - except SessionNotCreatedException as exc: - # Chromium allows one process per user data directory. When the profile - # is already open the driver's copy exits during startup, and selenium - # reports it as the browser crashing with a message that names neither - # the profile nor the other window. - logger.error("[FAIL] %s: could not start Edge with this profile.", account.name) - logger.error(" profile directory: %s", account.user_data_dir) - logger.error(" The usual cause is that this profile is already open in another") - logger.error(" Edge window, including one left over from a previous run.") - logger.error(" driver said: %s", log_utils.exception_summary(exc)) + """Work one account. Returns whether the browser started.""" + try: + driver = webdriver.Edge(options=build_options(account)) + # Set headers to spoof the rewards app for the rewards only quests + driver.execute_cdp_cmd("Network.enable", {}) + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36 Edg/151.0.0.0 MSRewards/Desktop/1.1.0", + "X-Rewards-Source": "msrewards-desktop", + } - return False + driver.execute_cdp_cmd("Network.setExtraHTTPHeaders", {"headers": headers}) + except SessionNotCreatedException as exc: + # Chromium allows one process per user data directory. When the profile + # is already open the driver's copy exits during startup, and selenium + # reports it as the browser crashing with a message that names neither + # the profile nor the other window. + logger.error("[FAIL] %s: could not start Edge with this profile.", account.name) + logger.error(" profile directory: %s", account.user_data_dir) + logger.error( + " The usual cause is that this profile is already open in another" + ) + logger.error(" Edge window, including one left over from a previous run.") + logger.error(" driver said: %s", log_utils.exception_summary(exc)) - try: - rewards = rewards_tasks.RewardsTaskUtils(driver) - rewards.complete_all_tasks() - finally: - try: - driver.quit() - except Exception as exc: - # quit() raises when the browser is already gone. Letting it out - # here would replace whatever actually went wrong with the tidy-up's - # own error, and the process it is meant to end is dead anyway. - logger.warning( - "%s: the driver did not shut down cleanly: %s", - account.name, log_utils.exception_summary(exc) - ) + return False - return True + try: + rewards = rewards_tasks.RewardsTaskUtils(driver) + rewards.complete_all_tasks() + finally: + try: + driver.quit() + except Exception as exc: + # quit() raises when the browser is already gone. Letting it out + # here would replace whatever actually went wrong with the tidy-up's + # own error, and the process it is meant to end is dead anyway. + logger.warning( + "%s: the driver did not shut down cleanly: %s", + account.name, + log_utils.exception_summary(exc), + ) + + return True def main() -> int: - log_utils.setup_logging() + log_utils.setup_logging() - try: - configured = accounts.configured() - except ValueError as exc: - logger.error("[FAIL] %s", exc) + try: + configured = accounts.configured() + except ValueError as exc: + logger.error("[FAIL] %s", exc) - return 2 + return 2 - started = 0 + started = 0 - for account in configured: - if len(configured) > 1: - logger.info("=== account: %s ===", account.name) + for account in configured: + if len(configured) > 1: + logger.info("=== account: %s ===", account.name) - # One account must not be able to end the batch. complete_all_tasks - # already contains a task that fails, and run_account names the profile - # that is already open, but everything else - a driver that will not - # start for some other reason, the browser dying mid-run, a page that - # never loads - reached here and took the remaining accounts with it. - # KeyboardInterrupt is deliberately not caught: Ctrl-C means stop. - try: - if run_account(account): - started += 1 - except Exception as exc: - logger.error( - "[FAIL] %s: %s: %s", - account.name, type(exc).__name__, log_utils.exception_summary(exc), - exc_info=logger.isEnabledFor(logging.DEBUG) - ) + # One account must not be able to end the batch. complete_all_tasks + # already contains a task that fails, and run_account names the profile + # that is already open, but everything else - a driver that will not + # start for some other reason, the browser dying mid-run, a page that + # never loads - reached here and took the remaining accounts with it. + # KeyboardInterrupt is deliberately not caught: Ctrl-C means stop. + try: + if run_account(account): + started += 1 + except Exception as exc: + logger.error( + "[FAIL] %s: %s: %s", + account.name, + type(exc).__name__, + log_utils.exception_summary(exc), + exc_info=logger.isEnabledFor(logging.DEBUG), + ) - if len(configured) > 1: - logger.info("%s/%s accounts ran", started, len(configured)) + if len(configured) > 1: + logger.info("%s/%s accounts ran", started, len(configured)) - # Nothing is watching a container, and stdin is not a terminal there. - if not HEADLESS: - input("Press Enter to exit...") + # Nothing is watching a container, and stdin is not a terminal there. + if not HEADLESS: + input("Press Enter to exit...") - return 0 if started else 1 + return 0 if started else 1 if __name__ == "__main__": - sys.exit(main()) + sys.exit(main())