🔧 fix(bot-commands): Исправлен импорт и добавлены обработчики исключений в listResumes.
All checks were successful
Deploy / deploy (push) Successful in 50s

This commit is contained in:
Oscar
2026-05-28 21:47:30 +03:00
parent 43cc28f1cf
commit e50eb58799
3 changed files with 48 additions and 5 deletions

View File

@@ -2,8 +2,8 @@ import type { ResumeListItem } from './types.js'
import bot from '@bot' import bot from '@bot'
import prisma from '@prisma' import prisma from '@prisma'
import cron, { type ScheduledTask } from 'node-cron' import cron, { type ScheduledTask } from 'node-cron'
import { applyToJobs, checkIsAuth, listResumes, login, saveResume } from './scraper.js' import { applyToJobs, checkIsAuth, listResumes, login, NoResumeError, saveResume } from './scraper.js'
import { BACK_MARKUP, createStatusReporter, escapeHtml, LOGIN_MARKUP, MAIN_MARKUP, safeEdit, showResult } from './ui.js' import { BACK_MARKUP, createStatusReporter, escapeHtml, LOGIN_MARKUP, MAIN_MARKUP, NO_RESUME_MARKUP, safeEdit, showResult } from './ui.js'
interface UserState { interface UserState {
autoCron: ScheduledTask | null autoCron: ScheduledTask | null
@@ -356,7 +356,23 @@ export function registerHHCommands() {
message_id: messageId, message_id: messageId,
reply_markup: { inline_keyboard: [] }, reply_markup: { inline_keyboard: [] },
}) })
const resumes = await listResumes(chatId) let resumes: Awaited<ReturnType<typeof listResumes>>
try {
resumes = await listResumes(chatId)
}
catch (e) {
if (e instanceof NoResumeError) {
await safeEdit(
'📝 Резюме не найдено.\n\nСоздайте резюме на <a href="https://hh.ru/applicant/resumes/new">hh.ru</a>, затем нажмите <b>Повторить</b>.',
{ chat_id: chatId, message_id: messageId, reply_markup: NO_RESUME_MARKUP, parse_mode: 'HTML' },
)
}
else {
console.error('[hh_resume_list] listResumes failed:', e)
await showResult(chatId, messageId, '❌ Не удалось загрузить резюме. Попробуйте войти заново через «Войти на hh.ru».')
}
break
}
if (resumes.length === 0) { if (resumes.length === 0) {
await showResult(chatId, messageId, '⚠️ Резюме не найдены. Создайте резюме на hh.ru') await showResult(chatId, messageId, '⚠️ Резюме не найдены. Создайте резюме на hh.ru')
} }

View File

@@ -8,6 +8,13 @@ import { createMessage } from '@/openai'
import { getBrowser, loadSession, newStealthContext, randomDelay, randomScroll } from './browser.js' import { getBrowser, loadSession, newStealthContext, randomDelay, randomScroll } from './browser.js'
import { escapeHtml } from './ui.js' import { escapeHtml } from './ui.js'
export class NoResumeError extends Error {
constructor() {
super('no_resume')
this.name = 'NoResumeError'
}
}
function waitForOtp(chatId: number): Promise<string> { function waitForOtp(chatId: number): Promise<string> {
return new Promise((resolve) => { return new Promise((resolve) => {
const handler = (msg: Message) => { const handler = (msg: Message) => {
@@ -135,6 +142,13 @@ export async function listResumes(chatId: number): Promise<ResumeListItem[]> {
for (let attempt = 1; attempt <= 2; attempt++) { for (let attempt = 1; attempt <= 2; attempt++) {
try { try {
await page.goto('https://hh.ru/applicant/resumes', { waitUntil: 'domcontentloaded' }) await page.goto('https://hh.ru/applicant/resumes', { waitUntil: 'domcontentloaded' })
const finalUrl = page.url()
if (finalUrl.includes('/profile/resume/professional_role')) {
throw new NoResumeError()
}
if (!finalUrl.includes('/applicant/resumes')) {
throw new Error(`Session expired or redirected: ${finalUrl}`)
}
await page.waitForSelector('[data-qa^="resume-card-link-"]', { timeout: 10000 }) await page.waitForSelector('[data-qa^="resume-card-link-"]', { timeout: 10000 })
const cardLinks = await page.$$('[data-qa^="resume-card-link-"]') const cardLinks = await page.$$('[data-qa^="resume-card-link-"]')
@@ -195,6 +209,8 @@ export async function listResumes(chatId: number): Promise<ResumeListItem[]> {
return items return items
} }
catch (e) { catch (e) {
if (e instanceof NoResumeError)
throw e
lastError = e as Error lastError = e as Error
if (attempt < 2) if (attempt < 2)
await page.waitForTimeout(4000) await page.waitForTimeout(4000)
@@ -389,7 +405,8 @@ export async function applyToJobs(
let letterInput = null let letterInput = null
for (const sel of LETTER_SELECTORS) { for (const sel of LETTER_SELECTORS) {
letterInput = await page.$(sel) letterInput = await page.$(sel)
if (letterInput) break if (letterInput)
break
} }
await letterInput?.click() await letterInput?.click()
await letterInput?.fill(letter, { force: true }) await letterInput?.fill(letter, { force: true })

View File

@@ -33,6 +33,13 @@ export const BACK_MARKUP = {
inline_keyboard: [[{ text: '◀️ Назад', callback_data: 'hh_back' }]], inline_keyboard: [[{ text: '◀️ Назад', callback_data: 'hh_back' }]],
} }
export const NO_RESUME_MARKUP = {
inline_keyboard: [[
{ text: '🔄 Повторить', callback_data: 'hh_resume_list' },
{ text: '🔑 Другой аккаунт', callback_data: 'hh_login' },
]],
}
export function escapeHtml(text: string): string { export function escapeHtml(text: string): string {
return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
} }
@@ -42,7 +49,10 @@ export async function safeEdit(
options: Parameters<typeof bot.editMessageText>[1], options: Parameters<typeof bot.editMessageText>[1],
): Promise<void> { ): Promise<void> {
await bot.editMessageText(text, options).catch((e: unknown) => { await bot.editMessageText(text, options).catch((e: unknown) => {
if (e instanceof Error && e.message.includes('message is not modified')) if (e instanceof Error && (
e.message.includes('message is not modified')
|| e.message.includes('message to edit not found')
))
return return
throw e throw e
}) })