mirror of
https://github.com/hempyhemp/hh-auto-reply.git
synced 2026-06-08 18:04:57 +00:00
252 lines
11 KiB
Markdown
252 lines
11 KiB
Markdown
# HH Auto-Apply Bot
|
||
|
||
Telegram-бот для автоматизации откликов на вакансии с **hh.ru**. Авторизуется на сайте от имени пользователя, парсит вакансии по заданному запросу и генерирует персонализированное сопроводительное письмо под каждую позицию — с учётом резюме и описания вакансии.
|
||
|
||
> **Статус:** активная разработка. Генерация писем работает; финальный клик «Откликнуться» реализован, логика отклика отлажена на реальных вакансиях.
|
||
|
||
---
|
||
|
||
## Что умеет бот
|
||
|
||
- **Авторизация через OTP** — входит на hh.ru по email или номеру телефона + одноразовый код, сессия (cookies) сохраняется в БД и переиспользуется
|
||
- **Парсинг резюме** — скачивает резюме с hh.ru после логина, сохраняет текстом в БД
|
||
- **Поиск вакансий** — по настраиваемому запросу, с лимитом откликов за сессию
|
||
- **AI-письма** — уникальное сопроводительное письмо под каждую вакансию через LLM (DeepSeek V4 Flash / OpenRouter)
|
||
- **Пропуск анкет** — вакансии с опросником автоматически пропускаются и логируются
|
||
- **Авто-режим** — cron-задача (пн–пт, 10:00) для ежедневного автозапуска
|
||
- **Полное управление через Telegram** — настройки, промпт, резюме, статус — всё в чате
|
||
|
||
---
|
||
|
||
## Стек
|
||
|
||
| Слой | Технология |
|
||
|---|---|
|
||
| Runtime | Node.js 22+ · TypeScript · ESM |
|
||
| Telegram | node-telegram-bot-api (long polling) |
|
||
| Браузерная автоматизация | Playwright (headless Chromium) |
|
||
| LLM | DeepSeek V4 Flash · OpenRouter · `@opencode-ai/sdk`; Groq (`llama-3.3-70b-versatile`) как запасной |
|
||
| ORM | Prisma 6 · SQLite |
|
||
| Планировщик | node-cron |
|
||
| Контейнеризация | Docker |
|
||
| Пакетный менеджер | Yarn 4 (PnP off) |
|
||
|
||
---
|
||
|
||
## Как это работает
|
||
|
||
```
|
||
Пользователь в Telegram
|
||
│
|
||
├─ /start → главное меню
|
||
│
|
||
├─ Войти на hh.ru
|
||
│ └─ Playwright открывает браузер, вводит email или телефон
|
||
│ └─ OTP-код пользователь присылает в чат
|
||
│ └─ Сессия (cookies) сохраняется в БД
|
||
│
|
||
├─ Выбрать резюме → Playwright парсит список резюме с hh.ru
|
||
│
|
||
└─ Откликнуться
|
||
└─ Playwright ищет вакансии по запросу
|
||
└─ Для каждой вакансии:
|
||
├─ Парсит описание (название, требования, компания)
|
||
├─ OpenCode SDK → DeepSeek V4 Flash генерирует письмо (резюме + описание → письмо)
|
||
├─ Playwright вставляет письмо и кликает «Откликнуться»
|
||
└─ Отчёт в Telegram: применено / пропущено / ошибки
|
||
```
|
||
|
||
---
|
||
|
||
## AI: как генерируются письма
|
||
|
||
Используется **DeepSeek V4 Flash** через [OpenRouter](https://openrouter.ai). Интеграция реализована через `@opencode-ai/sdk` — при первом запросе бот поднимает локальный OpenCode-сервер (`localhost:4096`), при последующих переиспользует его.
|
||
|
||
Каждая сессия генерации состоит из двух шагов:
|
||
|
||
1. **Первый промпт (без ответа)** — системная инструкция + полный текст резюме
|
||
2. **Второй промпт** — описание вакансии, модель возвращает готовое письмо
|
||
|
||
Пользователь настраивает системный промпт прямо в боте через кнопку `📝 Промт`. По умолчанию модель пишет коротко, опирается только на факты из резюме и добавляет контакты в конце.
|
||
|
||
---
|
||
|
||
## Архитектура
|
||
|
||
Одиночный Node.js процесс без HTTP-сервера. Весь UI — Telegram reply-клавиатуры и inline-кнопки.
|
||
|
||
```
|
||
src/
|
||
├── index.ts # точка входа, регистрация хендлеров
|
||
├── bot-singleton.ts # singleton TelegramBot (@bot)
|
||
├── prisma.ts # singleton PrismaClient (@prisma)
|
||
├── openai.ts # LLM: OpenCode SDK (DeepSeek V4 Flash/OpenRouter) + Groq fallback
|
||
└── hh/
|
||
├── bot-commands.ts # маршрутизация: handler maps вместо switch
|
||
├── state.ts # UserState (awaiting-флаги, cron, pending resumes)
|
||
├── scraper.ts # Playwright-автоматизация hh.ru
|
||
├── browser.ts # stealth-контекст, сессии, утилиты
|
||
├── ui.ts # клавиатуры, escapeHtml, StatusReporter
|
||
├── types.ts # общие типы
|
||
└── handlers/
|
||
├── apply.ts # запуск поиска и откликов
|
||
├── auth.ts # логин (email / телефон), OTP-флоу
|
||
├── info.ts # статус, проблемные вакансии
|
||
├── onboarding.ts # онбординг нового пользователя
|
||
├── resume.ts # список резюме, просмотр, выбор
|
||
├── settings.ts # запрос, лимит, промпт, авто-режим
|
||
└── debug.ts # отладочные функции
|
||
```
|
||
|
||
**Path-алиасы** (tsconfig.json): `@bot`, `@prisma`, `@/*` → `src/*`
|
||
|
||
Состояние диалога (`awaitingEmail`, `awaitingQuery` и т.д.) хранится in-memory в `Map<chatId, UserState>` — сбрасывается при рестарте процесса. Персистентные данные (сессия, резюме, настройки) — только в БД.
|
||
|
||
---
|
||
|
||
## База данных
|
||
|
||
```prisma
|
||
model User {
|
||
telegramId BigInt @unique
|
||
username String?
|
||
firstName String?
|
||
hhEmail String?
|
||
hhPhone String?
|
||
session String? // cookies hh.ru (JSON)
|
||
prompt String // системный промпт для AI
|
||
resumes Resume[]
|
||
Settings Settings?
|
||
}
|
||
|
||
model Resume {
|
||
id String @id // хэш от URL резюме на hh.ru
|
||
title String
|
||
data String // полный текст резюме
|
||
telegramId BigInt
|
||
}
|
||
|
||
model Settings {
|
||
telegramId BigInt @id
|
||
searchQuery String @default("Vue")
|
||
maxApplies Int @default(1)
|
||
selectedResumeId String?
|
||
}
|
||
|
||
model SkippedVacancy {
|
||
telegramId BigInt
|
||
href String
|
||
title String
|
||
createdAt DateTime
|
||
@@unique([telegramId, href])
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Запуск локально
|
||
|
||
### 1. Зависимости
|
||
|
||
```bash
|
||
yarn
|
||
```
|
||
|
||
### 2. Переменные окружения
|
||
|
||
Создай `.env` в корне проекта:
|
||
|
||
```env
|
||
# База данных (обязательно)
|
||
DATABASE_URL="file:./prisma/dev.db"
|
||
|
||
# Telegram Bot Token — получить у @BotFather
|
||
TG_BOT_TOKEN=1234567890:AAFxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||
|
||
# OpenRouter API Key — нужен для DeepSeek V4 Flash (openrouter.ai)
|
||
OPENROUTER_API_KEY=sk-or-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||
|
||
# Groq API Key — используется функцией askGPT (запасной путь)
|
||
GROQ_API_KEY=gsk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||
```
|
||
|
||
### 3. Миграции и запуск
|
||
|
||
```bash
|
||
yarn db-migrate # создать БД и применить миграции
|
||
yarn dev # запуск с hot reload
|
||
```
|
||
|
||
---
|
||
|
||
## Команды
|
||
|
||
```bash
|
||
yarn dev # запуск с hot reload (tsx watch + .env)
|
||
yarn build # компиляция TypeScript → dist/
|
||
yarn start # запуск скомпилированного dist/index.js
|
||
yarn lint # ESLint проверка
|
||
yarn lint:fix # ESLint автофикс
|
||
yarn db-view # Prisma Studio — GUI для БД
|
||
yarn db-migrate # создать и применить миграцию (dev)
|
||
yarn db:deploy # применить миграции без создания новых (prod)
|
||
```
|
||
|
||
---
|
||
|
||
## Docker
|
||
|
||
### Сборка
|
||
|
||
```bash
|
||
docker build -t hh-auto-apply-bot .
|
||
```
|
||
|
||
### Запуск
|
||
|
||
Создай `.env.docker`:
|
||
|
||
```env
|
||
DATABASE_URL=file:/data/dev.db
|
||
TG_BOT_TOKEN=...
|
||
OPENROUTER_API_KEY=sk-or-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||
```
|
||
|
||
```bash
|
||
# Linux / macOS
|
||
docker run --rm --env-file .env.docker -v $(pwd)/data:/data hh-auto-apply-bot
|
||
|
||
# Windows PowerShell
|
||
docker run --rm --env-file .env.docker -v ${PWD}/data:/data hh-auto-apply-bot
|
||
```
|
||
|
||
SQLite-база сохраняется в `./data/` и переживает перезапуски контейнера.
|
||
|
||
---
|
||
|
||
## UI бота
|
||
|
||
**Главное меню**
|
||
| Кнопка | Действие |
|
||
|---|---|
|
||
| `🚀 Откликнуться` | Запустить поиск и отклики |
|
||
| `⚙️ Настройки` | Открыть меню настроек |
|
||
| `ℹ️ Информация` | Открыть меню информации |
|
||
|
||
**Настройки**
|
||
| Кнопка | Действие |
|
||
|---|---|
|
||
| `🔢 Макс. откликов` | Лимит откликов за сессию (1–50) |
|
||
| `🔍 Изменить запрос` | Поисковый запрос на hh.ru |
|
||
| `⏰ Авто` | Вкл/выкл ежедневный cron (пн–пт 10:00) |
|
||
| `📄 Выбрать резюме` | Загрузить список резюме с hh.ru |
|
||
| `📝 Промт` | Настроить системный промпт для AI |
|
||
| `🔑 Войти на hh.ru` | Авторизация (email или телефон + OTP) |
|
||
|
||
**Информация**
|
||
| Кнопка | Действие |
|
||
|---|---|
|
||
| `⚙️ Статус` | Текущие настройки и статус авторизации |
|
||
| `📋 Моё резюме` | Просмотр сохранённого резюме |
|
||
| `🚫 Проблемные вакансии` | Вакансии с анкетой (бот не может откликнуться) |
|