Русский · English
A Telegram bot that automates job search and applications on hh.ru with LLM-based relevance scoring and auto-generated cover letters.
- Search vacancies via configurable filters using browser automation (Playwright).
- Pre-filter by an optional title blacklist (empty by default — you add title fragments of role types that are off-target for you), skipping obvious mismatches before spending LLM tokens.
- Score each vacancy via any OpenAI-compatible LLM (Groq, DeepSeek,
OpenRouter, Z.AI, Together, OpenAI, local Ollama, etc.). The analyzer
prompt is generic — it scores fit against the candidate profile from
prompts/analyzer_summary.txtand ships with an inline guide on how to tighten scoring (role-type rules, anti-domains, base scores) for your own profile. - Route by score (thresholds configurable):
- ≥
SCORE_AUTO_APPLY(default 90) → auto-apply with a generated cover letter — only whenAUTO_APPLY_ENABLED=true(off by default). - middle band → sent to Telegram for manual decision (buttons: «Apply / Skip / Open on hh.ru»).
- <
SCORE_AUTO_SKIP(default 10) → auto-skip.
- ≥
- Cover letter generation uses your
prompts/candidate.txtprofile, goes through an adversarial self-critique pass (regenerates on fabricated numbers / HR clichés / off-tone phrasing). - Apply by link: paste an hh.ru vacancy URL into the Telegram chat and the bot fetches it, scores it, drafts a cover letter, and shows it with Send / Rewrite / Cancel buttons — no need to wait for the autopilot to find it. Handles regional subdomains and tracking query params; refuses to double-apply.
- Autopilot: background search loop (paced to avoid hh.ru anti-bot throttling) + a periodic summary posted to your Telegram chat.
- Inbox monitor (
messages_loop): checks hh.ru negotiations every 5 min, tags incoming asTEST_TASK/QUESTION/MESSAGE, pings you in Telegram. - Company blacklist (whole-word match), employer rating floor, «remote-only» filter, cross-city deduplication.
- Telegram channel monitor (optional): reads public job channels via
their web mirror (
t.me/s/<channel>, no API keys), extracts and scores posts, and pings relevant ones to your Telegram — runs alongside hh.ru.
Using automation on hh.ru may violate the user agreement and result in account suspension. Use at your own risk. The bot is a pet project for a single user — multi-tenant deploy is not intended and the code has not undergone legal review.
- Python 3.10+ (requires
Browser | Nonesyntax) aiogram 3.x— Telegram botplaywright— hh.ru browser automationhttpx[socks]— HTTP client with SOCKS5 proxy supportpython-dotenv— configsqlite3(Python stdlib) — storage
git clone https://github.com/<YOUR_USERNAME>/hh-bot.git
cd hh-bot
python -m venv .venv
# Windows:
.venv\Scripts\activate
# macOS/Linux:
source .venv/bin/activate
pip install -r requirements.txt
playwright install chromium
playwright install msedge # for interactive login — visible Edge windowCopy .env.example to .env and fill in values:
cp .env.example .envTELEGRAM_BOT_TOKEN=... # get from @BotFather
TELEGRAM_ADMIN_ID=... # your Telegram user_id (ask @userinfobot)
HH_LOGIN=+79001234567 # phone number for hh.ru
LLM_BASE_URL=https://api.groq.com/openai/v1
LLM_API_KEY=...
LLM_MODEL=llama-3.3-70b-versatile
RESUME_FILE=resume.txt
Place resume.txt in the project root — a plain-text file with your
résumé. The bot uses it as candidate context when scoring vacancies and
generating cover letters.
Any provider with an OpenAI-compatible Chat Completions API is
supported. Examples in .env.example. Easiest is Groq (free tier):
- Sign up at https://console.groq.com
- Create an API key
- Set
LLM_API_KEY
If your LLM provider is blocked on your network:
LLM_PROXY=socks5://user:pass@host:port
python main.pyOn first run the bot will prompt for authorization. In Telegram, send:
/login
A visible Edge browser window will open — log in to hh.ru manually (captcha, SMS, whatever). The bot will pick up the session and switch to headless mode.
| Command | What it does |
|---|---|
/start |
Status, command list |
/login |
Open the browser for interactive hh.ru login |
/addfilter |
Add a search filter (title, keywords, city, salary, experience) |
/filters |
List active filters |
/search |
Run a manual search across all filters |
/autopilot |
Trigger one autopilot cycle manually |
/recheck |
Re-score un-scored vacancies |
/stats |
Application statistics |
hh-bot/
├── main.py # Entry point: bot + autopilot
├── config.py # Config from .env
├── ai/
│ ├── llm_client.py # Universal OpenAI-compatible client
│ ├── analyzer.py # Vacancy relevance scoring (role-type +
│ │ # AI-focus + anti-domain gates)
│ ├── vacancy_analyzer.py # Deep structured JD analysis for
│ │ # cover-letter targeting
│ └── cover_letter.py # Cover letter generation + adversarial
│ # self-critique
├── bot/
│ ├── handlers.py # Telegram commands and callbacks
│ ├── autopilot.py # Background search + filter + apply loop
│ ├── messages_loop.py # hh.ru negotiations inbox monitor
│ └── keyboards.py # Inline keyboards
├── parser/
│ ├── hh_client.py # Playwright client for hh.ru
│ └── tg_client.py # Reads TG channels via t.me/s/ web mirror
├── scripts/
│ └── tg_pull_test.py # Manual smoke test for TG channel parser
├── prompts/
│ ├── candidate.example.txt # Template — copy to candidate.txt
│ ├── analyzer_summary.example.txt
│ └── candidate.txt # YOUR private profile (gitignored)
├── data/
│ └── tg_channels.example.yaml # Template TG channel list
└── db/
├── models.py # SQLite schema
└── storage.py # CRUD
- Access to all commands is restricted by
TELEGRAM_ADMIN_ID— the bot is single-user. - hh.ru cookies are stored in
hh_cookies.json(in.gitignore). - Vacancy descriptions are isolated with markers in the LLM prompt — partial protection against prompt injection via vacancy text.
- All secrets via
.env, nothing is hardcoded.
The bot can also ingest job posts from public Telegram channels
(@datasciencejobs, @workinai, @prog_jobs, etc.) alongside hh.ru.
Channels are read through their public web mirror
(https://t.me/s/<channel>) — no API keys, no my.telegram.org, no login
or session files.
Setup:
- Copy
data/tg_channels.example.yamltodata/tg_channels.yaml, list the channels you want and tunerole_filterper channel to your profile. - (Optional) Test it:
python -m parser.tg_client test— prints the latest posts from each channel without running the bot. - Start the bot — the monitor runs automatically and every
TG_PULL_INTERVAL(default 30 min) pushes relevant posts to your Telegram with «Open in Telegram / Draft letter / Skip» buttons.
The monitor only notifies (on Telegram you apply by messaging a recruiter
yourself), so it runs even in MANUAL_MODE. If t.me is blocked on your
network, set TG_HTTP_PROXY in .env.
- hh.ru selectors are hardcoded — UI redesigns may require updates
to
parser/hh_client.py. - City grid in
_resolve_areacovers only the 15 largest cities. Others are searched without region filter (with a warning in logs). - DB is sync —
sqlite3blocks the event loop at large volumes. For a single user this is fine. - No tests on business logic.
- TG channel monitor only notifies — the autopilot does not apply for TG-sourced vacancies (on Telegram you apply by messaging a recruiter yourself); they are surfaced for manual review and letter drafting.
PolyForm Noncommercial License 1.0.0 — full text in LICENSE.
In short:
- Allowed: read the code, clone, fork, study, experiment, use for personal non-commercial purposes, in coursework and research, in hobby projects.
- Allowed for organizations: charitable, educational, research, government, healthcare, environmental.
- Not allowed: using the code in commercial products, reselling, building paid services or features on it, monetizing in any way.
- On distribution: preserve the license text and the author's copyright.
If you need commercial use — contact me for a separate commercial license.