From 4238058583177f64edeccb32b68de4de13195074 Mon Sep 17 00:00:00 2001 From: Oscar Date: Mon, 1 Jun 2026 12:11:07 +0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20refactor(scraper.ts):=20=D1=83?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=89=D0=B0=D0=B5=D1=82=20=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D1=83=20OTP=20=D1=81=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BC=D0=BE=D1=89=D1=8C=D1=8E=20=D1=84=D1=83=D0=BD=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20handleOtpFlow=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B2=D1=8B=D1=88=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=87?= =?UTF-8?q?=D0=B8=D1=82=D0=B0=D0=B5=D0=BC=D0=BE=D1=81=D1=82=D0=B8=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hh/scraper.ts | 80 +++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/src/hh/scraper.ts b/src/hh/scraper.ts index 34d4638..4ce3114 100644 --- a/src/hh/scraper.ts +++ b/src/hh/scraper.ts @@ -1,5 +1,5 @@ import type { Message } from 'node-telegram-bot-api' -import type { Page } from 'playwright' +import type { BrowserContext, Page } from 'playwright' import type { ApplyOptions, ApplyResult, ResumeListItem, VacancyRef } from './types.js' import type { StatusReporter } from './ui.js' import bot from '@bot' @@ -30,6 +30,39 @@ function waitForOtp(chatId: number): Promise { }) } +async function handleOtpFlow(page: Page, context: BrowserContext, chatId: number, initialMessage: string): Promise { + await page.click('[data-qa="applicant-login-input-otp"]') + + let message = initialMessage + while (true) { + await bot.sendMessage(chatId, message) + const otp = await waitForOtp(chatId) + + await page.fill('[data-qa="applicant-login-input-otp"] input', otp) + + const outcome = await Promise.race([ + page.waitForSelector('[data-qa="profileAndResumes-button"]', { timeout: 15000 }).then(() => 'success' as const), + page.waitForSelector('[data-qa="magritte-pincode-input-field"][aria-invalid="true"]', { timeout: 8000 }).then(() => 'error' as const), + ]).catch(() => 'timeout' as const) + + if (outcome === 'success') + break + if (outcome === 'error') { + message = '❌ Неверный код. Введи код ещё раз:' + continue + } + throw new Error('OTP verification timed out') + } + + const cookies = await context.cookies() + await prisma.user.upsert({ + where: { telegramId: chatId }, + update: { session: JSON.stringify(cookies, null, 2) }, + create: { telegramId: chatId, session: JSON.stringify(cookies, null, 2), Settings: { create: {} } }, + }) + await bot.sendMessage(chatId, cookies.length > 0 ? '✅ Авторизация выполнена' : '❌ Произошла ошибка') +} + const APPLY_OUTCOME_SELECTOR = [ '[data-qa="employer-asking-for-test"]', '[data-qa="task-body"]', @@ -75,34 +108,14 @@ export async function loginByPhone(phone: string, chatId: number): Promise const page = await context.newPage() await page.goto('https://hh.ru/account/login', { waitUntil: 'domcontentloaded' }) - await page.click('[data-qa="submit-button"]') await page.waitForTimeout(randomDelay()) - await page.fill('[data-qa="magritte-phone-input-national-number-input"]', phone) await page.waitForTimeout(randomDelay()) - await page.click('[data-qa="submit-button"]') await page.waitForTimeout(randomDelay()) - await bot.sendMessage(chatId, '🔑 Введи код из SMS') - await page.waitForTimeout(randomDelay()) - - await page.click('[data-qa="applicant-login-input-otp"]') - const otp = await waitForOtp(chatId) - await page.fill('[data-qa="applicant-login-input-otp"] input', otp) - - await page.waitForTimeout(randomDelay()) - await page.waitForSelector('[data-qa="profileAndResumes-button"]', { timeout: 15000 }) - - const cookies = await context.cookies() - await prisma.user.upsert({ - where: { telegramId: chatId }, - update: { session: JSON.stringify(cookies, null, 2) }, - create: { telegramId: chatId, session: JSON.stringify(cookies, null, 2), Settings: { create: {} } }, - }) - - await bot.sendMessage(chatId, cookies.length > 0 ? '✅ Авторизация выполнена' : '❌ Произошла ошибка') + await handleOtpFlow(page, context, chatId, '🔑 Введи код из SMS') }) } @@ -117,37 +130,16 @@ export async function login(email: string, chatId: number): Promise { const page = await context.newPage() await page.goto('https://hh.ru/account/login', { waitUntil: 'domcontentloaded' }) - await page.click('[data-qa="submit-button"]') await page.waitForTimeout(randomDelay()) - await page.click('label:has([data-qa="credential-type-EMAIL"])') await page.waitForTimeout(randomDelay()) - await page.fill('[data-qa="applicant-login-input-email"]', email) await page.waitForTimeout(randomDelay()) - await page.click('[data-qa="submit-button"]') await page.waitForTimeout(randomDelay()) - await bot.sendMessage(chatId, '🔑 Введи код из email') - await page.waitForTimeout(randomDelay()) - - await page.click('[data-qa="applicant-login-input-otp"]') - const otp = await waitForOtp(chatId) - await page.fill('[data-qa="applicant-login-input-otp"] input', otp) - - await page.waitForTimeout(randomDelay()) - await page.waitForSelector('[data-qa="profileAndResumes-button"]', { timeout: 15000 }) - - const cookies = await context.cookies() - await prisma.user.upsert({ - where: { telegramId: chatId }, - update: { session: JSON.stringify(cookies, null, 2) }, - create: { telegramId: chatId, session: JSON.stringify(cookies, null, 2), Settings: { create: {} } }, - }) - - await bot.sendMessage(chatId, cookies.length > 0 ? '✅ Авторизация выполнена' : '❌ Произошла ошибка') + await handleOtpFlow(page, context, chatId, '🔑 Введи код из email') }) }