diff --git a/src/hh/bot-commands.ts b/src/hh/bot-commands.ts index d53d506..e7735b1 100644 --- a/src/hh/bot-commands.ts +++ b/src/hh/bot-commands.ts @@ -3,16 +3,14 @@ import bot from '@bot' import prisma from '@prisma' import cron, { type ScheduledTask } from 'node-cron' import { applyToJobs, checkIsAuth, listResumes, login, NoResumeError, saveResume } from './scraper.js' -import { BACK_MARKUP, createStatusReporter, escapeHtml, LOGIN_MARKUP, MAIN_MARKUP, NO_RESUME_MARKUP, safeEdit, showResult } from './ui.js' +import { BACK_MARKUP, BTN, createStatusReporter, escapeHtml, LOGIN_REPLY_KEYBOARD, MAIN_REPLY_KEYBOARD, NO_RESUME_MARKUP, safeEdit } from './ui.js' interface UserState { autoCron: ScheduledTask | null awaitingEmail: boolean awaitingQuery: boolean awaitingMax: boolean - tempEmail: string pendingResumes: ResumeListItem[] - menuMessageId: number | null loginPromptMessageId: number | null } @@ -22,9 +20,7 @@ function makeUserState(): UserState { awaitingEmail: false, awaitingQuery: false, awaitingMax: false, - tempEmail: '', pendingResumes: [], - menuMessageId: null, loginPromptMessageId: null, } } @@ -37,52 +33,6 @@ function getState(chatId: number): UserState { return states.get(chatId)! } -async function showMenu(chatId: number, messageId?: number | null): Promise { - const state = getState(chatId) - const targetId = messageId ?? state.menuMessageId - - if (targetId) { - try { - await safeEdit('🤖 HH Auto-Apply', { - chat_id: chatId, - message_id: targetId, - reply_markup: MAIN_MARKUP, - }) - state.menuMessageId = targetId - return - } - catch { - // Сообщение устарело или недоступно — отправим новое - } - } - - const msg = await bot.sendMessage(chatId, '🤖 HH Auto-Apply', { reply_markup: MAIN_MARKUP }) - state.menuMessageId = msg.message_id -} - -async function sendResumeSelector(chatId: number, resumes: ResumeListItem[], messageId: number): Promise { - const state = getState(chatId) - state.pendingResumes = resumes - await safeEdit('📄 Выбери резюме:', { - chat_id: chatId, - message_id: messageId, - reply_markup: { - inline_keyboard: [ - ...resumes.map((r, i) => [{ text: r.title, callback_data: `hh_resume_pick_${i}` }]), - [{ text: '◀️ Назад', callback_data: 'hh_back' }], - ], - }, - }) -} - -async function resetMenuToBottom(chatId: number): Promise { - const state = getState(chatId) - if (state.menuMessageId) { - await bot.deleteMessage(chatId, state.menuMessageId).catch(() => {}) - state.menuMessageId = null - } -} - async function doLogin(chatId: number, email: string): Promise { await bot.sendMessage(chatId, '🔄 Логинюсь...') try { @@ -91,7 +41,6 @@ async function doLogin(chatId: number, email: string): Promise { const state = getState(chatId) - // listResumes может упасть по таймауту сразу после логина — это не критично let resumes: ResumeListItem[] | null = null try { resumes = await listResumes(chatId) @@ -100,10 +49,10 @@ async function doLogin(chatId: number, email: string): Promise { await bot.sendMessage(chatId, '⚠️ Не удалось загрузить резюме — выбери вручную через меню') } - await resetMenuToBottom(chatId) + await bot.sendMessage(chatId, '✅ Вход выполнен!', { reply_markup: MAIN_REPLY_KEYBOARD }) if (resumes === null) { - // таймаут при загрузке резюме — просто показываем меню + // таймаут при загрузке резюме } else if (resumes.length === 0) { await bot.sendMessage(chatId, '⚠️ Резюме не найдены. Создайте резюме на hh.ru') @@ -113,38 +62,197 @@ async function doLogin(chatId: number, email: string): Promise { await bot.sendMessage(chatId, `✅ Резюме сохранено: ${resumes[0].title}`) } else { - // несколько резюме — отправляем новый селектор внизу state.pendingResumes = resumes - const selectorMsg = await bot.sendMessage(chatId, '📄 Выбери резюме:', { + await bot.sendMessage(chatId, '📄 Выбери резюме:', { reply_markup: { inline_keyboard: [ ...resumes.map((r, i) => [{ text: r.title, callback_data: `hh_resume_pick_${i}` }]), - [{ text: '◀️ Назад', callback_data: 'hh_back' }], + [{ text: '◀️ Закрыть', callback_data: 'hh_back' }], ], }, }) - state.menuMessageId = selectorMsg.message_id - return } - - await showMenu(chatId) } catch (e) { - await resetMenuToBottom(chatId) await bot.sendMessage(chatId, `❌ Ошибка: ${(e as Error).message}`) - await showMenu(chatId) } } +async function handleApply(chatId: number): Promise { + const settings = await prisma.settings.findUnique({ where: { telegramId: chatId } }) + if (!settings) + return + + const reporter = createStatusReporter(chatId) + await reporter.status(`🔄 Ищу вакансии по запросу "${settings.searchQuery}"...`) + + applyToJobs({ query: settings.searchQuery, maxApplies: settings.maxApplies }, { chatId, reporter }) + .then(async (result) => { + if (result.error) { + await bot.sendMessage(chatId, `❌ ${result.error}`) + return + } + + const lines: string[] = [] + lines.push(`📊 Итого по запросу «${settings.searchQuery}»`) + lines.push(`✅ Откликнулся: ${result.applied.length}`) + lines.push(`⏭ Пропущено: ${result.skipped.length}`) + if (result.errors.length) + lines.push(`❌ Ошибок: ${result.errors.length}`) + + if (result.skipped.length) { + lines.push('') + lines.push('⏭ Пропущенные:') + result.skipped.forEach(v => lines.push(`• ${v.title}`)) + } + + if (result.errors.length) { + lines.push('') + lines.push('❌ Ошибки:') + result.errors.forEach(v => lines.push(`• ${escapeHtml(v.title)} — ${escapeHtml(v.message ?? '')}`)) + } + + const fullText = lines.join('\n') + const LIMIT = 4000 + for (let i = 0; i < fullText.length; i += LIMIT) { + await bot.sendMessage(chatId, fullText.slice(i, i + LIMIT), { + parse_mode: 'HTML', + disable_web_page_preview: true, + }) + } + }) +} + +async function handleStatus(chatId: number): Promise { + const state = getState(chatId) + const settings = await prisma.settings.findUnique({ where: { telegramId: chatId } }) + const isAuth = await checkIsAuth(chatId) + await bot.sendMessage( + chatId, + `⚙️ Настройки:\n\nЗапрос: ${settings?.searchQuery ?? '--'}\nМакс откликов: ${settings?.maxApplies ?? '--'}\nАвто: ${state.autoCron ? '✅ включено' : '❌ выключено'}\nАвторизован: ${isAuth ? '✅' : '❌'}`, + { reply_markup: BACK_MARKUP }, + ) +} + +async function handleLogin(chatId: number): Promise { + const state = getState(chatId) + const user = await prisma.user.findUnique({ where: { telegramId: chatId } }) + state.awaitingEmail = true + + if (!user?.hhEmail) { + await bot.sendMessage(chatId, '📧 Введи email от hh.ru:') + } + else { + const prompt = await bot.sendMessage( + chatId, + `📧 Текущий email: ${user.hhEmail}\n\nИспользовать его или введи другой:`, + { + parse_mode: 'HTML', + reply_markup: { + inline_keyboard: [[ + { text: `✅ Войти как ${user.hhEmail}`, callback_data: 'hh_login_use_current' }, + ]], + }, + }, + ) + state.loginPromptMessageId = prompt.message_id + } +} + +async function handleResumeList(chatId: number): Promise { + const state = getState(chatId) + const loadingMsg = await bot.sendMessage(chatId, '🔄 Загружаю список резюме...') + + let resumes: ResumeListItem[] + try { + resumes = await listResumes(chatId) + console.log(`[handleResumeList ${chatId}]: ${resumes}`) + } + catch (e) { + await bot.deleteMessage(chatId, loadingMsg.message_id).catch(() => {}) + if (e instanceof NoResumeError) { + await bot.sendMessage( + chatId, + '📝 Резюме не найдено.\n\nСоздайте резюме на hh.ru, затем нажмите Повторить.', + { parse_mode: 'HTML', reply_markup: NO_RESUME_MARKUP }, + ) + } + else { + await bot.sendMessage(chatId, '❌ Не удалось загрузить резюме. Попробуйте войти заново через «Войти на hh.ru».') + } + return + } + + await bot.deleteMessage(chatId, loadingMsg.message_id).catch(() => {}) + + if (resumes.length === 0) { + await bot.sendMessage(chatId, '⚠️ Резюме не найдены. Создайте резюме на hh.ru') + } + else if (resumes.length === 1) { + await saveResume(chatId, resumes[0]) + await bot.sendMessage(chatId, `✅ Резюме сохранено: ${resumes[0].title}`) + } + else { + state.pendingResumes = resumes + await bot.sendMessage(chatId, '📄 Выбери резюме:', { + reply_markup: { + inline_keyboard: [ + ...resumes.map((r, i) => [{ text: r.title, callback_data: `hh_resume_pick_${i}` }]), + [{ text: '◀️ Закрыть', callback_data: 'hh_back' }], + ], + }, + }) + } +} + +async function handleMyResume(chatId: number): Promise { + const settings = await prisma.settings.findUnique({ where: { telegramId: chatId } }) + const resume = settings?.selectedResumeId + ? await prisma.resume.findUnique({ where: { id: settings.selectedResumeId } }) + : await prisma.resume.findFirst({ where: { telegramId: chatId } }) + + if (!resume) { + await bot.sendMessage(chatId, '📋 Резюме не найдено.\n\nВыбери резюме через кнопку 📄 Выбрать резюме.') + return + } + + const MAX = 3500 + const text = resume.data.length > MAX + ? `${resume.data.slice(0, MAX)}\n\n… (текст обрезан)` + : resume.data + + await bot.sendMessage( + chatId, + `📋 Твоё резюме:\n${resume.title}\n
${escapeHtml(text)}
`, + { parse_mode: 'HTML', reply_markup: BACK_MARKUP }, + ) +} + +async function handleSkipped(chatId: number): Promise { + const skipped = await prisma.skippedVacancy.findMany({ + where: { telegramId: chatId }, + orderBy: { createdAt: 'desc' }, + take: 50, + }) + + if (!skipped.length) { + await bot.sendMessage(chatId, '✅ Проблемных вакансий нет') + return + } + + const lines = ['🚫 Вакансии с опросником (бот не может откликнуться):', ''] + skipped.forEach(v => lines.push(`• ${escapeHtml(v.title)}`)) + await bot.sendMessage(chatId, lines.join('\n'), { + parse_mode: 'HTML', + disable_web_page_preview: true, + reply_markup: BACK_MARKUP, + }) +} + export async function triggerHHStart(chatId: number): Promise { const user = await prisma.user.findUnique({ where: { telegramId: chatId } }) - if (!user?.session) { - const state = getState(chatId) - const msg = await bot.sendMessage(chatId, '🤖 HH Auto-Apply', { reply_markup: LOGIN_MARKUP }) - state.menuMessageId = msg.message_id - return - } - await showMenu(chatId) + const keyboard = user?.session ? MAIN_REPLY_KEYBOARD : LOGIN_REPLY_KEYBOARD + await bot.sendMessage(chatId, '🤖 HH Auto-Apply', { reply_markup: keyboard }) } export function registerHHCommands() { @@ -162,242 +270,45 @@ export function registerHHCommands() { await bot.answerCallbackQuery(query.id).catch(() => {}) - const user = await prisma.user.findUnique({ - where: { telegramId: chatId }, - include: { Settings: true }, - }) - const settings = user!.Settings! - switch (query.data) { case 'hh_back': - await showMenu(chatId, messageId) - break - - case 'hh_apply': { await bot.deleteMessage(chatId, messageId).catch(() => {}) - state.menuMessageId = null - - const reporter = createStatusReporter(chatId) - await reporter.status(`🔄 Ищу вакансии по запросу "${settings.searchQuery}"...`) - - applyToJobs({ query: settings.searchQuery, maxApplies: settings.maxApplies }, { chatId, reporter }) - .then(async (result) => { - if (result.error) { - await bot.sendMessage(chatId, `❌ ${result.error}`) - } - else { - const lines: string[] = [] - lines.push(`📊 Итого по запросу «${settings.searchQuery}»`) - lines.push(`✅ Откликнулся: ${result.applied.length}`) - lines.push(`⏭ Пропущено: ${result.skipped.length}`) - if (result.errors.length) - lines.push(`❌ Ошибок: ${result.errors.length}`) - - if (result.skipped.length) { - lines.push('') - lines.push('⏭ Пропущенные:') - result.skipped.forEach(v => lines.push(`• ${v.title}`)) - } - - if (result.errors.length) { - lines.push('') - lines.push('❌ Ошибки:') - result.errors.forEach(v => lines.push(`• ${escapeHtml(v.title)} — ${escapeHtml(v.message ?? '')}`)) - } - - const fullText = lines.join('\n') - const LIMIT = 4000 - for (let i = 0; i < fullText.length; i += LIMIT) { - await bot.sendMessage(chatId, fullText.slice(i, i + LIMIT), { - parse_mode: 'HTML', - disable_web_page_preview: true, - }) - } - } - await showMenu(chatId) - }) break - } - - case 'hh_status': { - const isAuth = await checkIsAuth(chatId) - await showResult( - chatId, - messageId, - `⚙️ Настройки:\n\nЗапрос: ${settings.searchQuery}\nМакс откликов: ${settings.maxApplies}\nАвто: ${state.autoCron ? '✅ включено' : '❌ выключено'}\nАвторизован: ${isAuth ? '✅' : '❌'}`, - ) - break - } - - case 'hh_my_resume': { - const resume = settings?.selectedResumeId - ? await prisma.resume.findUnique({ where: { id: settings.selectedResumeId } }) - : await prisma.resume.findFirst({ where: { telegramId: chatId } }) - if (!resume) { - await showResult(chatId, messageId, '📋 Резюме не найдено.\n\nВыбери резюме через кнопку 📄 Выбрать резюме.') - break - } - const MAX = 3500 - const text = resume.data.length > MAX - ? `${resume.data.slice(0, MAX)}\n\n… (текст обрезан)` - : resume.data - await safeEdit( - `📋 Твоё резюме:\n ${resume.title}\n
${escapeHtml(text)}
`, - { - chat_id: chatId, - message_id: messageId, - parse_mode: 'HTML', - reply_markup: BACK_MARKUP, - }, - ) - break - } case 'hh_login': - state.menuMessageId = messageId - if (!user?.hhEmail) { - state.awaitingEmail = true - await bot.sendMessage(chatId, '📧 Введи email от hh.ru:') - } - else { - state.awaitingEmail = true - const prompt = await bot.sendMessage( - chatId, - `📧 Текущий email: ${user.hhEmail}\n\nИспользовать его или введи другой:`, - { - parse_mode: 'HTML', - reply_markup: { - inline_keyboard: [[ - { text: `✅ Войти как ${user.hhEmail}`, callback_data: 'hh_login_use_current' }, - ]], - }, - }, - ) - state.loginPromptMessageId = prompt.message_id - } + await bot.deleteMessage(chatId, messageId).catch(() => {}) + await handleLogin(chatId) break case 'hh_login_use_current': { state.awaitingEmail = false await bot.deleteMessage(chatId, messageId).catch(() => {}) state.loginPromptMessageId = null - const email = user?.hhEmail - if (!email) { + const user = await prisma.user.findUnique({ where: { telegramId: chatId } }) + if (!user?.hhEmail) { await bot.sendMessage(chatId, '❌ Email не найден, введи вручную') state.awaitingEmail = true break } - await doLogin(chatId, email) + await doLogin(chatId, user.hhEmail) break } - case 'hh_query': - { - state.awaitingQuery = true - state.menuMessageId = messageId - - const q = await prisma.settings.findFirst({ - where: { telegramId: chatId }, - }) - - await bot.sendMessage(chatId, `🔍Текущий запрос: ${q?.searchQuery || '--'}`) - await bot.sendMessage(chatId, '🔍 Введи поисковый запрос:') + case 'hh_resume_list': + await bot.deleteMessage(chatId, messageId).catch(() => {}) + await handleResumeList(chatId) break - } - - case 'hh_max': - state.awaitingMax = true - state.menuMessageId = messageId - await bot.sendMessage(chatId, '🔢 Введи максимальное количество откликов (1-50):') - break - - case 'hh_auto_start': - if (state.autoCron) { - await showResult(chatId, messageId, '⚠️ Авто уже запущено') - break - } - state.autoCron = cron.schedule('0 10 * * 1-5', async () => { - await bot.sendMessage(chatId, '⏰ Авто-отклик...') - }) - await showResult(chatId, messageId, '✅ Авто включён (пн-пт, 10:00)') - break - - case 'hh_auto_stop': - state.autoCron?.stop() - state.autoCron = null - await showResult(chatId, messageId, '⛔ Авто остановлен') - break - - case 'hh_skipped': { - const skipped = await prisma.skippedVacancy.findMany({ - where: { telegramId: chatId }, - orderBy: { createdAt: 'desc' }, - take: 50, - }) - if (!skipped.length) { - await showResult(chatId, messageId, '✅ Проблемных вакансий нет') - break - } - const lines = ['🚫 Вакансии с опросником (бот не может откликнуться):', ''] - skipped.forEach(v => lines.push(`• ${escapeHtml(v.title)}`)) - await safeEdit(lines.join('\n'), { - chat_id: chatId, - message_id: messageId, - parse_mode: 'HTML', - disable_web_page_preview: true, - reply_markup: BACK_MARKUP, - }) - break - } - - case 'hh_resume_list': { - await safeEdit('🔄 Загружаю список резюме...', { - chat_id: chatId, - message_id: messageId, - reply_markup: { inline_keyboard: [] }, - }) - let resumes: Awaited> - try { - resumes = await listResumes(chatId) - } - catch (e) { - if (e instanceof NoResumeError) { - await safeEdit( - '📝 Резюме не найдено.\n\nСоздайте резюме на hh.ru, затем нажмите Повторить.', - { 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) { - await showResult(chatId, messageId, '⚠️ Резюме не найдены. Создайте резюме на hh.ru') - } - else if (resumes.length === 1) { - await safeEdit('🔄 Сохраняю резюме...', { - chat_id: chatId, - message_id: messageId, - reply_markup: { inline_keyboard: [] }, - }) - await saveResume(chatId, resumes[0]) - await showResult(chatId, messageId, `✅ Резюме сохранено: ${resumes[0].title}`) - } - else { - state.menuMessageId = messageId - await sendResumeSelector(chatId, resumes, messageId) - } - break - } default: { if (query.data?.startsWith('hh_resume_pick_')) { const idx = Number(query.data.replace('hh_resume_pick_', '')) const resume = state.pendingResumes[idx] if (!resume) { - await showResult(chatId, messageId, '❌ Резюме не найдено, попробуйте снова') + await safeEdit('❌ Резюме не найдено, попробуйте снова', { + chat_id: chatId, + message_id: messageId, + reply_markup: { inline_keyboard: [] }, + }) break } await safeEdit('🔄 Сохраняю резюме...', { @@ -407,7 +318,11 @@ export function registerHHCommands() { }) await saveResume(chatId, resume) state.pendingResumes = [] - await showResult(chatId, messageId, `✅ Резюме выбрано: ${resume.title}`) + await safeEdit(`✅ Резюме выбрано: ${resume.title}`, { + chat_id: chatId, + message_id: messageId, + reply_markup: BACK_MARKUP, + }) } break } @@ -422,11 +337,6 @@ export function registerHHCommands() { const state = getState(chatId) - const user = await prisma.user.findUnique({ - where: { telegramId: chatId }, - include: { Settings: true }, - }) - if (state.awaitingEmail) { state.awaitingEmail = false await bot.deleteMessage(chatId, msg.message_id).catch(() => {}) @@ -446,7 +356,6 @@ export function registerHHCommands() { data: { searchQuery: msg.text }, }) await bot.sendMessage(chatId, `✅ Запрос: "${updated.searchQuery}"`) - await showMenu(chatId) return } @@ -463,7 +372,67 @@ export function registerHHCommands() { data: { maxApplies: num }, }) await bot.sendMessage(chatId, `✅ Макс откликов: ${updated.maxApplies}`) - await showMenu(chatId) + return + } + + switch (msg.text) { + case BTN.APPLY: + await handleApply(chatId) + break + + case BTN.STATUS: + await handleStatus(chatId) + break + + case BTN.QUERY: { + state.awaitingQuery = true + const q = await prisma.settings.findFirst({ where: { telegramId: chatId } }) + await bot.sendMessage(chatId, `🔍 Текущий запрос: ${q?.searchQuery || '--'}`) + await bot.sendMessage(chatId, '🔍 Введи новый поисковый запрос:') + break + } + + case BTN.MAX: + state.awaitingMax = true + await bot.sendMessage(chatId, '🔢 Введи максимальное количество откликов (1-50):') + break + + case BTN.AUTO_ON: { + const s = getState(chatId) + if (s.autoCron) { + await bot.sendMessage(chatId, '⚠️ Авто уже запущено') + break + } + s.autoCron = cron.schedule('0 10 * * 1-5', async () => { + await bot.sendMessage(chatId, '⏰ Авто-отклик...') + }) + await bot.sendMessage(chatId, '✅ Авто включён (пн-пт, 10:00)') + break + } + + case BTN.AUTO_OFF: { + const s = getState(chatId) + s.autoCron?.stop() + s.autoCron = null + await bot.sendMessage(chatId, '⛔ Авто остановлен') + break + } + + case BTN.LOGIN: + await handleLogin(chatId) + break + + case BTN.RESUME_LIST: + await handleResumeList(chatId) + break + + case BTN.MY_RESUME: + await handleMyResume(chatId) + break + + case BTN.SKIPPED: + await handleSkipped(chatId) + break } }) } diff --git a/src/hh/scraper.ts b/src/hh/scraper.ts index 6efed5e..a675cdd 100644 --- a/src/hh/scraper.ts +++ b/src/hh/scraper.ts @@ -158,14 +158,17 @@ export async function listResumes(chatId: number): Promise { items = await page.$$eval( '[data-qa^="resume-card-link-"]', links => links.map((a) => { - const card = a.closest('[data-qa^="resume-card"]') ?? a.parentElement - const titleEl = card?.querySelector('[data-qa="resume-title"] h3') ?? card?.querySelector('[data-qa="title"]') + const card = a.parentElement + const titleEl = card?.querySelector('[data-qa="resume-title"]') ?? card?.querySelector('[data-qa="title"]') + console.log(titleEl) return { href: (a as HTMLAnchorElement).getAttribute('href') ?? '', - title: titleEl?.textContent?.trim() ?? '(без названия)', + title: titleEl?.innerText?.trim() ?? '(Ошибка в получении названия)', } }), ) + + console.log(items.length) } else { const href = await cardLinks[0].getAttribute('href') ?? '' diff --git a/src/hh/ui.ts b/src/hh/ui.ts index 296257e..e75ac14 100644 --- a/src/hh/ui.ts +++ b/src/hh/ui.ts @@ -1,32 +1,34 @@ import bot from '@bot' -export const MAIN_MARKUP = { - inline_keyboard: [ - [{ text: '🚀 Откликнуться сейчас', callback_data: 'hh_apply' }], - [ - { text: '🔍 Изменить запрос', callback_data: 'hh_query' }, - { text: '🔢 Макс откликов', callback_data: 'hh_max' }, - ], - [ - { text: '⏰ Авто вкл', callback_data: 'hh_auto_start' }, - { text: '⛔ Авто выкл', callback_data: 'hh_auto_stop' }, - ], - [ - { text: '🔑 Логин', callback_data: 'hh_login' }, - { text: '⚙️ Статус', callback_data: 'hh_status' }, - ], - [ - { text: '📄 Выбрать резюме', callback_data: 'hh_resume_list' }, - { text: '📋 Моё резюме', callback_data: 'hh_my_resume' }, - ], - [{ text: '🚫 Проблемные вакансии', callback_data: 'hh_skipped' }], - ], +export const BTN = { + APPLY: '🚀 Откликнуться', + STATUS: '⚙️ Статус', + QUERY: '🔍 Изменить запрос', + MAX: '🔢 Макс откликов', + AUTO_ON: '⏰ Авто вкл', + AUTO_OFF: '⛔ Авто выкл', + LOGIN: '🔑 Войти на hh.ru', + RESUME_LIST: '📄 Выбрать резюме', + MY_RESUME: '📋 Моё резюме', + SKIPPED: '🚫 Проблемные', +} as const + +export const LOGIN_REPLY_KEYBOARD = { + keyboard: [[{ text: BTN.LOGIN }]], + resize_keyboard: true, + persistent: true, } -export const LOGIN_MARKUP = { - inline_keyboard: [ - [{ text: '🔑 Войти через hh.ru', callback_data: 'hh_login' }], +export const MAIN_REPLY_KEYBOARD = { + keyboard: [ + [{ text: BTN.APPLY }, { text: BTN.STATUS }], + [{ text: BTN.QUERY }, { text: BTN.MAX }], + [{ text: BTN.AUTO_ON }, { text: BTN.AUTO_OFF }], + [{ text: BTN.LOGIN }, { text: BTN.RESUME_LIST }], + [{ text: BTN.MY_RESUME }, { text: BTN.SKIPPED }], ], + resize_keyboard: true, + persistent: true, } export const BACK_MARKUP = { @@ -58,22 +60,14 @@ export async function safeEdit( }) } -export async function showResult(chatId: number, messageId: number, text: string): Promise { - await safeEdit(text, { - chat_id: chatId, - message_id: messageId, - reply_markup: BACK_MARKUP, - }) -} - export interface StatusReporter { status: (text: string) => Promise keep: (text: string) => Promise clear: () => Promise } -export function createStatusReporter(chatId: number, initialMsgId?: number | null): StatusReporter { - let msgId: number | null = initialMsgId ?? null +export function createStatusReporter(chatId: number): StatusReporter { + let msgId: number | null = null async function deleteCurrent(): Promise { if (msgId) {