Skip to content
LinCanNerdPublic

About

WG-Gesucht flat-hunting assistant: an LLM scores ads and drafts messages, you approve every one in Telegram

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

wg-agent: your flat-hunting sidekick for WG-Gesucht

A flat-hunting assistant that runs on your own machine. It watches your WG-Gesucht, Kleinanzeigen and ImmoScout24 searches, uses an LLM (on your own machine or a cloud API) to score each new ad and write a personal first message in the ad's language, and sends you a card in Telegram. Nothing is ever sent until you tap ✅.

Disclaimer: this is an unofficial personal tool, not affiliated with WG-Gesucht, Kleinanzeigen or ImmoScout24. Automated access may be against the sites' terms of use. Use it at your own risk, keep the request volume low (the defaults are conservative) and never remove the human approval step.

Features

  • Fast: checks your searches every 2–3.5 minutes. With WG-Gesucht Plus, logged in, you also get Plus's head start on new ads.
  • Three sites: WG-Gesucht, Kleinanzeigen's "Auf Zeit & WG" and "Mietwohnungen" categories, and ImmoScout24's flats and WG rooms. Each site has its own page budget, so one site pushing back doesn't stop the others. Immowelt isn't supported: it shows a captcha right away.
  • ImmoScout24 is read-only: its website blocks automated browsers on the first page, so the agent reads ImmoScout24 through the API its app uses, as Fredy does. It can't send there: you copy the draft, send it from the ImmoScout24 app and tap 📤 I sent it, which logs it in applications.xlsx. Many new private ads take messages only from ImmoScout24 Plus members for their first days; the card shows until when (🔒).
  • Cheap filtering first: paid, partner and company ads, swap offers (Tauschangebote), old ads, ads over budget and ads for the other gender only are dropped from the search page, without opening them.
  • Reads the whole ad: all description tabs (where code words hide), exact coordinates, flatmates, who they're looking for, the advertiser's "member since" date and, with Plus, the number of applicants.
  • Real commute times by bike and public transport to the places you choose, via the free Transitous routing service (Google Distance Matrix is optional). Ads that are too far away are dropped or flagged.
  • No double messages: skips reposts of the same flat, the same flat advertised on the other site (same text, or same street, size and rent), and advertisers you've already written to.
  • LLM drafting: scores the ad 0–10, spots scam signals and code words ("start your message with Banane"), answers questions the ad asks, and writes the message in German or English: casual "du" for WGs, polite "Sie" for studios. By default it sends your own fixed text and only adds a sentence about the ad. German drafts come with an English translation for you.
  • Trap check: a plain text search (not the model) finds tests hidden in the ad ("schreib das Wort Brezel in deine Nachricht", "write X in the subject line") and shows them on the card, so a test the model overlooks still reaches you. The code word goes into a natural sentence; if it must come first, the message really starts with it, a greeting-style one ("Moin Moin") becomes the greeting, and a subject-line one goes alone on the first line. The card checks all of this again before you send.
  • Telegram approval: photo album + card with commute, flatmates, score, warnings, notes and the draft. Buttons: ✅ Send ❌ Skip ✏️ Edit 🔁 Rewrite 🔗 Ad 🗺 Map 🚲 Route (📤 I sent it instead of ✅ Send on ImmoScout24).
  • Safe sending: goes straight to the contact form, refuses if you already wrote to that ad or person, and confirms the message actually went out. Sending on Kleinanzeigen stays a dry run until you switch it on (sites.kleinanzeigen.dry_run), because it hasn't been verified on the live site yet.
  • Reply tracking: checks your WG-Gesucht and Kleinanzeigen inboxes, pings you in Telegram when someone answers, and keeps an applications.xlsx log of everything you sent (/excel).
  • For friends too: run one agent per person on the same machine, each with their own profile, searches and Telegram bot. See Several people on one machine.

How it works

search page ──► card filter ──► ad page ──► filters, repost & same-person check
                                                   │
          Telegram card ◄── LLM score + draft ◄── commute (Transitous)
                │
          you tap ✅ ──► the site's contact form ──► applications.xlsx ◄── inbox replies
File What it does
bot.py Telegram bot, main loop, filters, SQLite storage
sites.py What all sites share: one persistent, logged-in Chromium (Playwright) and the Site interface
wg.py WG-Gesucht: search, ad page, contact form and inbox selectors
kleinanzeigen.py Kleinanzeigen: search, ad page (both 2026 layouts), contact form and inbox
immoscout.py ImmoScout24: search and ad details through the app's API (read-only)
llm.py Prompt, scoring, drafting, scam signals (any OpenAI-compatible API)
commute.py Bike and public transport times via Transitous / Nominatim (or Google)
guard.py Request budget and block cooldowns per site, saved across restarts
tracker.py applications.xlsx export and matching inbox replies to sent ads

Requirements

  • An always-on computer with Python 3.10+ (Linux or macOS): a home server, a Raspberry Pi-class board or a small VPS. It runs the Telegram bot and a headless Chromium; no GPU is needed if you use a cloud LLM.
  • An LLM: a local one or a cloud API key. See Choosing an LLM.
  • A Telegram bot token (from @BotFather) and your Telegram user id (from @userinfobot).
  • A WG-Gesucht account (Plus is optional but helps), and a Kleinanzeigen account if you search there. ImmoScout24 needs no login here: you send from its app, with your own account.

Setup

git clone <this repo> wg-agent && cd wg-agent
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
playwright install --with-deps chromium     # on ARM64, try without --with-deps if it fails

cp config.example.yaml config.yaml          # then fill in everything marked TODO, and pick an LLM
python bot.py --login                       # once: opens a browser window, log in to each site you search
python bot.py

In Telegram, open your bot and press Start. --login needs a display; on a headless server use ssh -X or VNC. python bot.py --login kleinanzeigen logs in to one site only. If you only search ImmoScout24, skip --login: the agent then never starts a browser.

Choosing an LLM

The agent talks to any OpenAI-compatible chat API, so you set llm.base_url, llm.model and, for cloud providers, llm.api_key (or the LLM_API_KEY environment variable). config.example.yaml lists the URLs.

Local Cloud
Examples Ollama, LM Studio, vLLM OpenAI, Anthropic, Google Gemini, Mistral, Groq, OpenRouter
Hardware A strong GPU or Apple Silicon None beyond the always-on computer
Cost Free Pay per use (one or two requests per ad you see)
Privacy Everything stays on your machine Your profile and the ads are sent to the provider

German quality matters more than speed, because you read every draft anyway. Local results from our tests: gemma4:31b wrote the best German; gemma4:26b and qwen3:30b-a3b-instruct-2507 are much faster with a few slips; small 8B models tend to invent facts about you. With Ollama, run ollama pull <model> first. Whatever you pick, try it with /test before turning off dry run.

Configuration

Everything lives in config.yaml; config.example.yaml documents every option. The most important parts:

  • me: is who you are. The model only uses facts written here, so be complete and honest. If an ad asks something your profile doesn't answer (smoker? pets?), the model may guess. preferences tells it how to score; message_guidelines tells it how to write.
  • search.searches: one entry per search URL (WG-Gesucht, Kleinanzeigen or ImmoScout24), each with its own budget and minimum score. For ImmoScout24, set your filters on its website and paste the URL.
  • sites: per-site settings: dry_run and that site's own page budget, and sites.immoscout.plus if you have ImmoScout24 Plus.
  • commute.destinations: the places you travel to, with limits. action: exclude drops ads that are too far; mark only adds a warning.
  • me.mode:
    • template (what config.example.yaml uses): your own fixed text (me.templates, one per WG/studio and German/English) is sent word for word. The model only fills in the greeting and one plain sentence about the ad, plus answers to questions the ad asks and a code word if there is one. It sees your text so it doesn't repeat it, and sentences where it invents where you live are dropped.
    • full: the model writes the whole message, following message_guidelines. More varied, but it tends to sound like an AI. In both modes a code word found in the ad is always kept; a greeting-style one ("Servus zusammen!") replaces the greeting.
  • Other cities: set city, use that city's search URLs, and adjust districts_exclude and commute.viewbox.

First test

Keep send.dry_run: true and send /test <ad url> for a few ads. Pressing ✅ then fills in the contact form and sends you a screenshot instead of sending. When you're happy with the drafts, set dry_run: false. Kleinanzeigen has its own switch, sites.kleinanzeigen.dry_run: check a dry-run screenshot there first.

Several people on one machine

Friends looking too? Run one agent per person. Each person gets a folder with their own config.yaml (profile, templates, searches, commute) and their own Telegram bot from @BotFather, and the agent keeps that person's database, spreadsheet and browser login in that folder. They all share the code and the LLM.

mkdir -p ~/wg-agent-people/anna
cp ~/wg-agent/config.example.yaml ~/wg-agent-people/anna/config.yaml   # fill it in for Anna
cd ~/wg-agent-people/anna && ~/wg-agent/.venv/bin/python ~/wg-agent/bot.py

As a service, wg-agent@.service runs the agent in ~/wg-agent-people/<name>: systemctl --user enable --now wg-agent@anna (see the comments in the file).

Someone who only searches ImmoScout24 needs no --login and no browser; they send from their own ImmoScout24 app. For WG-Gesucht or Kleinanzeigen they log in with their own account, from their folder (~/wg-agent/.venv/bin/python ~/wg-agent/bot.py --login). Every agent has its own page budget, but they all come from your IP address: with several people, check each site less often (every_n_polls: 2 or more).

Telegram commands

Command
/status what the agent is doing, page budget, database counts
/check check the searches now
/replies check the inboxes now
/excel get applications.xlsx
/pause, /resume stop / restart polling
/test <url> score and draft any ad (ignores filters)
/login_check is the browser still logged in to each site?
/summary the daily review right now

Staying polite to the sites

guard.py makes sure the agent stays well below anything that looks like scraping:

  • Every page load (on ImmoScout24, every API request) counts against that site's budget of 40 per hour and 650 per day, saved to disk so restarts don't reset it. Normal use is about 30 per hour per site.
  • Searches run at random intervals (2–3.5 min, every ~20 min at night). At most 3 ads are opened per check, freshest first.
  • A captcha, HTTP 403/429/503, the terms-of-use block page, or a search that suddenly comes back empty pauses that site for 15 min, then 1 h, 3 h and 12 h for repeat blocks, and you get a Telegram note. There is no captcha solving and no anti-detection stealth.
  • Images and fonts are never downloaded; Telegram fetches the photos itself.
  • Routing requests (Transitous / OpenStreetMap) are throttled to 1 per second and cached.

Run it as a service

wg-agent.service is a systemd user unit (no root needed). It assumes the repo is in ~/wg-agent; edit the paths if not.

mkdir -p ~/.config/systemd/user && cp wg-agent.service ~/.config/systemd/user/
systemctl --user daemon-reload && systemctl --user enable --now wg-agent
sudo loginctl enable-linger "$USER"    # keep it running after you log out
journalctl --user -u wg-agent -f

Troubleshooting

  • 0 listings parsed / nothing found: the sites change their HTML now and then. The selectors are in wg.py and kleinanzeigen.py (CARDS_JS, DETAIL_JS, INBOX_JS, send_message).
  • ImmoScout24 stops answering: the agent pauses that site and tells you. If it keeps happening, the API may want a newer app version: set sites.immoscout.user_agent.
  • Logged out: WG-Gesucht needs a session-only cookie; the agent saves it in browser-session.json and restores it on start. If the server-side session expires, run python bot.py --login again (or --login kleinanzeigen for just that site).
  • Send problems: a screenshot of every send attempt lands in screenshots/.
  • Commute missing: check the destination coordinates logged at startup, or set lat/lng yourself.

Privacy

Your config.yaml, the browser profile and session, the database, the spreadsheet and the screenshots are personal and are all in .gitignore. Don't commit them. With a local LLM nothing leaves your machine except the requests to the flat sites, Telegram and the routing service; with a cloud LLM, your me: profile and the text of each ad are also sent to that provider.

Credits

Ideas and selectors borrowed from other open-source WG-Gesucht and flat-hunting projects:

  • Fredy: current page markup, skipping paid cards, coordinates from the map, Transitous commute and its etiquette, repost fingerprints, scam signals, ImmoScout24 through its app's API.
  • Flathunter: commute filtering, photo-first Telegram cards.
  • MietRadar: reading all description tabs, embedded-question prompts, template mode, inbox reply tracking, visible-captcha detection.
  • jonasdieker, AykoSc, nickirk/immo: the #message_timestamp "already contacted" check, send-form selectors, removing overlays.
  • isaksolheim/gesucht: handling code words.
  • Lauchturm: WG-Gesucht's terms-of-use block page as a block signal.

License

MIT

About

WG-Gesucht flat-hunting assistant: an LLM scores ads and drafts messages, you approve every one in Telegram

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages