mirror of
https://github.com/hempyhemp/hh-auto-reply.git
synced 2026-06-08 18:04:57 +00:00
🔧 fix(bot-commands): Исправлен импорт и добавлены обработчики исключений в listResumes.
All checks were successful
Deploy / deploy (push) Successful in 50s
All checks were successful
Deploy / deploy (push) Successful in 50s
This commit is contained in:
@@ -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')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
|||||||
12
src/hh/ui.ts
12
src/hh/ui.ts
@@ -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, '&').replace(/</g, '<').replace(/>/g, '>')
|
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user