ZOYKUHot Board

user0332/rewards-farmerDocumentation

Automation for MS Rewards based on https://youtu.be/4qdPcMNaioA

Quick Install
pip install rewards-farmer

User0332/rewards-farmer

Automation for MS Rewards based on https://youtu.be/4qdPcMNaioA.

A Windows desktop app built on this farmer, with setup, run history and a schedule, lives in mardausdennis/rewards-farmer-app.

Table of Contents

Core Setup & Running Instructions

Important

Use at your own risk. Microsoft may take action against your account for using automated scripts to gain rewards points. The YouTube video contains more details about the techniques implemented to avoid detection of this script.

Clone the repository.

git clone https://github.com/User0332/rewards-farmer
cd rewards-farmer

Installing Dependencies

Activate the virtual environment & install dependencies (you may have to use python -m poetry instead of poetry). You must have Python 3.12+ and Poetry installed.

If iex (poetry env activate) fails with "Cannot bind argument to parameter 'Command' because it is null", poetry install did not create an environment. Run python --version first: an older Python leaves poetry with nothing to activate, and the message explaining that goes to stderr rather than into iex.

Windows (PowerShell)

poetry install
iex (poetry env activate)

*nix (Bash)

poetry install
eval $(poetry env activate)

You must also have a webdriver for Microsoft Edge installed. If you already have the Edge Browser installed, you probably have this component as well.

Configuration Script

At this point you can run the config script using python src/init-config.py, which will walk you through the available configuration options. However, it is still recommended that you continue reading the rest of this README before doing so to fully understand each option.

Running init-config.py with the --manage-accounts flag (shown below) will allow you to add, remove, and sign in to accounts without reconfiguring the entire project.

py .\src\init-config.py --manage-accounts

For signing into multiple accounts properly, see the relevant notice.

Where search queries come from

The bot needs short strings to type into Bing. Two backends produce them, set with QUERY_SOURCE:

QUERY_SOURCE Needs Notes
llm (default) OpenRouter or Ollama account + model llm via Ollama is the current behaviour, unchanged
trends nothing Google Trends, Wikipedia and Bing autosuggest
QUERY_SOURCE=trends python src/main.py          # bash
$env:QUERY_SOURCE="trends"; python src/main.py  # PowerShell

trends needs no account, no API key and no model download, so the LLM setup below is optional if you use it. If every feed is unreachable it falls back to nouns.txt rather than failing the run.

If you would like to use LLMs, you should also configure an LLM provider through a .env file in the project root. The script now talks to either OpenRouter or a local OpenAI-compatible LLM endpoint depending on LLM_PROVIDER.

A sample nouns.txt file is included in the project root and can be modified by the user to contain seed words for an LLM to complete 20 searches. The wordlist should be separated by newline.

Example .env values:

LLM_PROVIDER=openrouter
OPENROUTER_API_KEY=your_key_here
OPENROUTER_MODEL=openai/gpt-4o-mini

# Or use a local endpoint instead
# LLM_PROVIDER=local
# LOCAL_LLM_BASE_URL=http://localhost:11434/v1
# LOCAL_LLM_MODEL=gemma3:4b

For OpenRouter, the code uses the OpenAI-compatible chat completions API at https://openrouter.ai/api/v1/chat/completions. For local models, the endpoint must also be OpenAI-compatible. More configuration options can be found in .env.example.

If the configuration options are not provided, they default to LLM_PROVIDER=local, LOCAL_LLM_BASE_URL=http://localhost:11434/v1, LOCAL_LLM_MODEL=gemma4:cloud, and OPENROUTER_MODEL=openrouter/free.

You must also provide an image for the script to upload to complete the visual search task. A helper script is included at src/random_image_for_visual_search.py that will download an image from Wikipedia named visual_search.jpg into the project root for you. You may also provide an image of your own, just ensure that the absolute path of the image is placed in the VISUAL_SEARCH_IMAGE_PATH constant at the top of rewards_tasks.py.

Single Profile Setup

Tip

It is recommended to configure REWARDS_ACCOUNTS rather than run the script using the default configuration from this section, even if you are running only one account. This allows each profile to be isolated by a named data directory and makes it easier to add another account later.

You can do this using the configuration script or by manual configuration. This method will create entirely new profiles separate from the default profile discussed here, so you will have to complete sign in again.

By default, the script runs a single account using the Default profile in the base data directory. To sign in to this profile, you can do one of two things to launch the browser:

  1. Run main.py (python src/main.py; paths are resolved from the repository, so it can be started from any directory), wait for the page to launch, and then CTRL-C to quit the application immediately.
  2. Run Edge directly through msedge --user-data-dir="<USER_DATA_DIR>" --profile-directory=Default https://rewards.bing.com. Note that the default user data directory is <repo>\data-dir.

Once the browser launches, sign in to the created profile with your Microsoft account on both Bing and rewards.bing.com. Additionally, see the relevant notice on signing in properly.

EU Users: you may have to accept a consent banner once on rewards.bing.com and on the Bing search page, bing.com. Once you consent, your choice will be saved for future runs using the same profile, so you will not need to interact with the banner during automated runs.

The bot finds buttons by their English labels, so it starts Edge with --accept-lang=en-US and Rewards and Bing load in English whatever language Edge is set to. Edge saves this setting in the profile, so Bing and Rewards also show up in English when you open that profile yourself.

Close all webdriver browser instances. Run main.py again; the automation should start working.

If Edge will not start

When the browser fails to start, the log names the likely cause from the driver's own message, and falls back to printing that message as is. Three optional environment variables help when it does not:

Variable Effect
MSEDGEDRIVER_PATH Full path to msedgedriver to use, instead of letting selenium look for one. Try this first on Unable to obtain driver for MicrosoftEdge.
EDGE_BINARY Full path to the Edge executable, for an install selenium does not find on its own.
REWARDS_DRIVER_LOG Path to write a verbose msedgedriver log to. On Chrome instance exited this log holds Edge's actual reason; attach it to a bug report.
$env:REWARDS_DRIVER_LOG="msedgedriver.log"; python src/main.py   # PowerShell
REWARDS_DRIVER_LOG=msedgedriver.log python src/main.py          # bash

src/check_selectors.py starts Edge the same way and reads the same variables.

Running more than one account

Rewards is per Microsoft account and the browser profile holds the sign-in, so an account here is a profile directory. REWARDS_ACCOUNTS takes a comma separated list, and each name gets its own directory under data-dir:

REWARDS_ACCOUNTS=personal,spare python src/main.py

Each is signed in once by hand, the same way as the single profile, using its own directory:

msedge --user-data-dir="<USER_DATA_DIR>\personal" --profile-directory=Default https://rewards.bing.com

Note that the default user data directory is <repo>\data-dir.

Note

When signing in to an account, whether through the above command or through the accounts manager in the configuration script, you must ensure that you sign in to the Edge profile, rewards.bing.com, and bing.com so that the script never accidentally uses a global account associated with your Windows account. To do this, when you launch the sign-in window either via the above command or the accounts manager, look for the profile icon in the top right hand corner of the window and click on it. If the browser does not display the desired account, click on "Profile settings" and select "Sign out" on the page that appears. The Edge window will briefly refresh. Then, click on the profile icon in the top right again. There should be a dropdown menu of accounts to choose from. Choose the correct account and sign in. Then, head to rewards.bing.com and bing.com to make sure they are both using the desired account. If they are not, sign out from those sites and then click sign in.

They run one after another, and an account that fails is reported and skipped rather than ending the run, whether it fails to start or dies partway through. Leave REWARDS_ACCOUNTS unset and everything behaves exactly as before, using the single profile in data-dir.

Docker

Runs the bot without installing Edge, a driver or Python on the host.

docker compose build
docker compose run --rm rewards-farmer

The container defaults to QUERY_SOURCE=trends, so it needs no Ollama account and no model. Set QUERY_SOURCE=llm and OLLAMA_HOST to a reachable address to use a model instead.

Sign in first. The profile in data-dir starts logged out. Sign in from inside the container, which opens the Rewards page in a browser there and puts that browser on your screen as a web page:

docker compose run --rm --service-ports signin

Open http://localhost:6080, sign in, then close the Edge window on that screen. The container exits on its own and the profile is ready. Nothing is installed on the host, and the host operating system stops mattering, because the profile is written inside the container rather than on the host. The screen is an ordinary web page, so whatever browser you already have will do.

--service-ports is not optional. docker compose run publishes no ports without it, and the page then never loads.

One account at a time, since it is one browser window:

REWARDS_ACCOUNTS=personal docker compose run --rm --service-ports signin

The port is published on 127.0.0.1 only, so it is not reachable from the network. While the service is up it is showing a live Microsoft sign-in page.

Signing in signs the browser in, not just the website, so Edge may sync bookmarks and autofill into the profile it just created. data-dir is a bot profile living in the project directory rather than your everyday browser profile, and it is gitignored, but it is worth knowing what ends up there.

Using a local LLM with Docker

Point the base URL to the Docker host gateway. Example .env for Ollama Llama 3.2:

QUERY_SOURCE=llm
LLM_PROVIDER=local
LOCAL_LLM_BASE_URL=http://host.docker.internal:11434/v1
LOCAL_LLM_MODEL=llama3.2

If your model provider listens on localhost only, you must configure it to also listen on Docker's network bridge. Example for Ollama: OLLAMA_HOST=0.0.0.0:11434 ollama serve

Why sign-in has to happen inside the container

Chromium encrypts cookie values with a key it gets from the operating system, and the container has to be able to unwrap that key to read the profile.

On Linux with no keyring running it falls back to a fixed key, which is true both on a plain Linux host and inside the image, so a profile signed in on such a host does carry straight in.

On Windows the key is wrapped with DPAPI and tied to the Windows account that wrote it, and the container has no DPAPI. A profile signed in with a normal Windows Edge window reported 73 cookies on disk, of which Edge in the container could read 19 — the ones it had just set itself — while .MSA.Auth and ANON, the ones the sign-in actually rests on, came back absent. The container starts, looks healthy and behaves as though it were logged out. macOS wraps the key with the login Keychain, which the container cannot reach either.

Signing in through the container sidesteps all of this: the profile is written by the same Edge that later reads it, so the two never disagree about the key.

You can still sign in with a host browser if you prefer, and on a Linux host it works. Close every window of that profile afterwards, and close them rather than killing them: Chromium allows one process per profile directory, and a browser that was killed leaves a SingletonLock naming the machine that wrote it, which the container reads as the profile being open somewhere else.

Provide the visual search image on the host too. visual_search.jpg is not in the repository and is not built into the image, so create it once in the project root and the compose file mounts it in:

python src/random_image_for_visual_search.py

Without it every other task still runs; only the visual search one fails.

Multiple accounts work the same way in the container. Sign each profile in once, one at a time, then run them together:

REWARDS_ACCOUNTS=personal docker compose run --rm --service-ports signin
REWARDS_ACCOUNTS=spare    docker compose run --rm --service-ports signin

REWARDS_ACCOUNTS=personal,spare docker compose run --rm rewards-farmer

REWARDS_HEADLESS=1 is set in the image. It also works on the host if you want a run with no visible window; the pointer code needs an explicit window size in that mode, which main.py sets.

Logging

The script logs to the console. Two optional environment variables change that:

Variable Default Effect
REWARDS_FARMER_LOG_LEVEL INFO Set to DEBUG to also attach the full stack trace to every [FAIL] line.
REWARDS_FARMER_LOG_FILE unset Path to also write the log to, useful for unattended runs.

Windows (PowerShell)

$env:REWARDS_FARMER_LOG_LEVEL="DEBUG"; $env:REWARDS_FARMER_LOG_FILE="run.log"; python src/main.py

*nix (Bash)

REWARDS_FARMER_LOG_LEVEL=DEBUG REWARDS_FARMER_LOG_FILE=run.log python src/main.py

If you are opening an issue about a crash, running with REWARDS_FARMER_LOG_LEVEL=DEBUG and attaching the log is the most useful thing you can include.

Conditional runs and records

--read-state records open work before and after farming. --check-only reads it without starting tasks; it still opens Edge to inspect the Rewards page

--only-if-open reads first and runs when at least 15 points are known to be open, or a quest step is unlocked. Change the threshold with --min-points; when only a quest qualifies, only quests run. An unreadable page is skipped unless you also pass --run-if-unreadable. --unattended exits without waiting for Enter

Windows (PowerShell)

python src/main.py --only-if-open --min-points 20 --unattended

*nix (Bash)

python src/main.py --check-only --unattended

Runs write timestamped JSONL records under runs/, with account and task outcomes and any requested state reads. runs/quest-steps-today.json tracks quest links already clicked today so an unchanged link does not trigger another conditional run. Without new flags, console output stays unchanged

A lock under USER_DATA_DIR (by default data-dir) prevents two farmer runs from using the same profile root at once. A second run exits with code 3 before opening Edge

Windows Virtual Desktop (Windows only)

To run the browser on a separate Windows Virtual Desktop so searches run in the background without interrupting your current workspace:

Variable Default Effect
USE_VIRTUAL_DESKTOP false When true, automatically creates a new Windows Virtual Desktop via Win+Ctrl+D and launches the browser there. Windows only.
SWITCH_BACK_TO_MAIN_DESKTOP true When true (and USE_VIRTUAL_DESKTOP is enabled), automatically switches back to your starting desktop after launching Edge.
SWITCH_BACK_DELAY_SECONDS 1.5 Delay in seconds to wait before switching back, giving Edge time to attach its window to the new desktop.
CLEANUP_VIRTUAL_DESKTOP true When true, automatically closes the created worker virtual desktop via Win+Ctrl+F4 after completing all profiles and pressing Enter, returning focus to your main desktop.

Set these in your .env file or provide them as environment variables:

Windows (PowerShell)

$env:USE_VIRTUAL_DESKTOP="true"; python src/main.py

Note

The script automatically detects which virtual desktop you started from and calculates the exact number of navigation hops so it returns directly to your starting desktop. When CLEANUP_VIRTUAL_DESKTOP=true, the worker desktop is safely closed after you press Enter on exit, returning you to your main desktop.

Please open up a GitHub issue if you run into any difficulties.

Document mirror archived from official upstream repository · Visit upstream GitHub
Snapshot date: 2026-11-09