Files
rewards-farmer/src/main.py
T

125 lines
4.3 KiB
Python

import logging
import os
import sys
import log_utils
import accounts
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",
)
logger = logging.getLogger(__name__)
def build_options(account: accounts.Account) -> 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}")
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
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))
return False
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()
try:
configured = accounts.configured()
except ValueError as exc:
logger.error("[FAIL] %s", exc)
return 2
started = 0
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),
)
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...")
return 0 if started else 1
if __name__ == "__main__":
sys.exit(main())