mirror of
https://github.com/hempyhemp/hh-auto-reply.git
synced 2026-06-08 18:04:57 +00:00
🚀 feat(file): Добавлен выбор резюме и сохранение, улучшена работа с обработкой данных.
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import bot from '@bot'
|
import bot from '@bot'
|
||||||
import { triggerHHStart } from '@/hh/bot-commands'
|
import { triggerHHStart } from '@/hh/bot-commands'
|
||||||
import { getResume } from '@/hh/scraper'
|
|
||||||
|
|
||||||
export function sendMenu(chatId: number, isFirstTime: boolean) {
|
export function sendMenu(chatId: number, isFirstTime: boolean) {
|
||||||
const text = isFirstTime
|
const text = isFirstTime
|
||||||
@@ -34,9 +33,9 @@ bot.on('message', async (msg) => {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case '👤 Debug': {
|
case '👤 Debug': {
|
||||||
const resume = await getResume(chatId)
|
// const resume = await getResume(chatId)
|
||||||
|
//
|
||||||
await bot.sendMessage(chatId, resume || '--')
|
// await bot.sendMessage(chatId, resume || '--')
|
||||||
|
|
||||||
// const letter = await askGPT('test')
|
// const letter = await askGPT('test')
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,16 +1,45 @@
|
|||||||
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, login } from './scraper.js'
|
import { applyToJobs, checkIsAuth, listResumes, login, type ResumeListItem, saveResume } from './scraper.js'
|
||||||
|
|
||||||
interface State {
|
interface UserState {
|
||||||
autoCron: ScheduledTask | null
|
autoCron: ScheduledTask | null
|
||||||
awaitingEmail: boolean
|
awaitingEmail: boolean
|
||||||
awaitingPassword: boolean
|
|
||||||
awaitingQuery: boolean
|
awaitingQuery: boolean
|
||||||
awaitingMax: boolean
|
awaitingMax: boolean
|
||||||
awaitingOTP: boolean
|
|
||||||
tempEmail: string
|
tempEmail: string
|
||||||
|
pendingResumes: ResumeListItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeUserState(): UserState {
|
||||||
|
return {
|
||||||
|
autoCron: null,
|
||||||
|
awaitingEmail: false,
|
||||||
|
awaitingQuery: false,
|
||||||
|
awaitingMax: false,
|
||||||
|
tempEmail: '',
|
||||||
|
pendingResumes: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const states = new Map<number, UserState>()
|
||||||
|
|
||||||
|
function getState(chatId: number): UserState {
|
||||||
|
if (!states.has(chatId))
|
||||||
|
states.set(chatId, makeUserState())
|
||||||
|
return states.get(chatId)!
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendResumeSelector(chatId: number, resumes: ResumeListItem[]) {
|
||||||
|
getState(chatId).pendingResumes = resumes
|
||||||
|
await bot.sendMessage(chatId, '📄 Выбери резюме:', {
|
||||||
|
reply_markup: {
|
||||||
|
inline_keyboard: resumes.map((r, i) => [
|
||||||
|
{ text: r.title, callback_data: `hh_resume_pick_${i}` },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function triggerHHStart(chatId: number): void {
|
export function triggerHHStart(chatId: number): void {
|
||||||
@@ -32,31 +61,24 @@ export function triggerHHStart(chatId: number): void {
|
|||||||
{ text: '🔑 Логин', callback_data: 'hh_login' },
|
{ text: '🔑 Логин', callback_data: 'hh_login' },
|
||||||
{ text: '⚙️ Статус', callback_data: 'hh_status' },
|
{ text: '⚙️ Статус', callback_data: 'hh_status' },
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
{ text: '📄 Выбрать резюме', callback_data: 'hh_resume_list' },
|
||||||
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerHHCommands() {
|
export function registerHHCommands() {
|
||||||
const state: State = {
|
|
||||||
autoCron: null,
|
|
||||||
awaitingEmail: false,
|
|
||||||
awaitingPassword: false,
|
|
||||||
awaitingQuery: false,
|
|
||||||
awaitingMax: false,
|
|
||||||
awaitingOTP: false,
|
|
||||||
tempEmail: '',
|
|
||||||
}
|
|
||||||
|
|
||||||
bot.onText(/\/hhstart/, (msg) => {
|
bot.onText(/\/hhstart/, (msg) => {
|
||||||
triggerHHStart(msg.chat.id)
|
triggerHHStart(msg.chat.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Инлайн кнопки
|
|
||||||
bot.on('callback_query', async (query) => {
|
bot.on('callback_query', async (query) => {
|
||||||
if (!query.message)
|
if (!query.message)
|
||||||
return
|
return
|
||||||
const chatId = query.message.chat.id
|
const chatId = query.message.chat.id
|
||||||
|
const state = getState(chatId)
|
||||||
|
|
||||||
bot.answerCallbackQuery(query.id)
|
bot.answerCallbackQuery(query.id)
|
||||||
|
|
||||||
@@ -84,7 +106,7 @@ export function registerHHCommands() {
|
|||||||
|
|
||||||
case 'hh_status':
|
case 'hh_status':
|
||||||
await bot.sendMessage(chatId, `⚙️ Настройки:\n
|
await bot.sendMessage(chatId, `⚙️ Настройки:\n
|
||||||
Запрос: ${settings.searchQuery}\n
|
Запрос: ${settings.searchQuery}\n
|
||||||
Макс откликов: ${settings.maxApplies}\n
|
Макс откликов: ${settings.maxApplies}\n
|
||||||
Авто: ${state.autoCron ? '✅ включено' : '❌ выключено'}\n
|
Авто: ${state.autoCron ? '✅ включено' : '❌ выключено'}\n
|
||||||
Авторизован: ${await checkIsAuth(chatId)}`)
|
Авторизован: ${await checkIsAuth(chatId)}`)
|
||||||
@@ -92,7 +114,6 @@ export function registerHHCommands() {
|
|||||||
|
|
||||||
case 'hh_login':
|
case 'hh_login':
|
||||||
state.awaitingEmail = true
|
state.awaitingEmail = true
|
||||||
|
|
||||||
if (!user?.hhEmail) {
|
if (!user?.hhEmail) {
|
||||||
await bot.sendMessage(chatId, '📧 Введи email от hh.ru:')
|
await bot.sendMessage(chatId, '📧 Введи email от hh.ru:')
|
||||||
}
|
}
|
||||||
@@ -118,8 +139,6 @@ export function registerHHCommands() {
|
|||||||
}
|
}
|
||||||
state.autoCron = cron.schedule('0 10 * * 1-5', async () => {
|
state.autoCron = cron.schedule('0 10 * * 1-5', async () => {
|
||||||
await bot.sendMessage(chatId, '⏰ Авто-отклик...')
|
await bot.sendMessage(chatId, '⏰ Авто-отклик...')
|
||||||
// const result = await applyToJobs({ query: state.searchQuery, maxApplies: state.maxApplies }, { bot, chatId, msg })
|
|
||||||
// await bot.sendMessage(chatId, `✅ Откликнулся на ${result.applied.length} вакансий`)
|
|
||||||
})
|
})
|
||||||
await bot.sendMessage(chatId, '✅ Авто включён (пн-пт, 10:00)')
|
await bot.sendMessage(chatId, '✅ Авто включён (пн-пт, 10:00)')
|
||||||
break
|
break
|
||||||
@@ -129,6 +148,39 @@ export function registerHHCommands() {
|
|||||||
state.autoCron = null
|
state.autoCron = null
|
||||||
await bot.sendMessage(chatId, '⛔ Авто остановлен')
|
await bot.sendMessage(chatId, '⛔ Авто остановлен')
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'hh_resume_list': {
|
||||||
|
await bot.sendMessage(chatId, '🔄 Загружаю список резюме...')
|
||||||
|
const resumes = await listResumes(chatId)
|
||||||
|
if (resumes.length === 0) {
|
||||||
|
await bot.sendMessage(chatId, '❌ Резюме не найдены. Создайте резюме на hh.ru')
|
||||||
|
}
|
||||||
|
else if (resumes.length === 1) {
|
||||||
|
await bot.sendMessage(chatId, '🔄 Сохраняю резюме...')
|
||||||
|
await saveResume(chatId, resumes[0].href)
|
||||||
|
await bot.sendMessage(chatId, `✅ Резюме сохранено: ${resumes[0].title}`)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await sendResumeSelector(chatId, resumes)
|
||||||
|
}
|
||||||
|
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 bot.sendMessage(chatId, '❌ Резюме не найдено, попробуйте снова')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
await bot.sendMessage(chatId, '🔄 Сохраняю резюме...')
|
||||||
|
await saveResume(chatId, resume.href)
|
||||||
|
await bot.sendMessage(chatId, `✅ Резюме выбрано: ${resume.title}`)
|
||||||
|
state.pendingResumes = []
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -138,38 +190,40 @@ export function registerHHCommands() {
|
|||||||
if (!msg.text || msg.text.startsWith('/'))
|
if (!msg.text || msg.text.startsWith('/'))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
const state = getState(chatId)
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { telegramId: chatId },
|
where: { telegramId: chatId },
|
||||||
include: { Settings: true },
|
include: { Settings: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
if (state.awaitingEmail) {
|
if (state.awaitingEmail) {
|
||||||
if (!user?.hhEmail) {
|
state.tempEmail = user?.hhEmail || msg.text
|
||||||
state.tempEmail = msg.text
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
state.tempEmail = user?.hhEmail
|
|
||||||
}
|
|
||||||
|
|
||||||
state.awaitingEmail = false
|
state.awaitingEmail = false
|
||||||
|
|
||||||
await bot.deleteMessage(chatId, msg.message_id).catch(() => {
|
await bot.deleteMessage(chatId, msg.message_id).catch(() => {})
|
||||||
})
|
|
||||||
|
|
||||||
await bot.sendMessage(chatId, '🔄 Логинюсь...')
|
await bot.sendMessage(chatId, '🔄 Логинюсь...')
|
||||||
try {
|
try {
|
||||||
await bot.sendMessage(chatId, `${state.tempEmail}, ${msg.text}`)
|
|
||||||
|
|
||||||
await login(state.tempEmail, chatId)
|
await login(state.tempEmail, chatId)
|
||||||
await bot.sendMessage(chatId, '✅ Авторизован! Куки сохранены.')
|
await bot.sendMessage(chatId, '✅ Авторизован! Куки сохранены.')
|
||||||
|
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { telegramId: chatId },
|
where: { telegramId: chatId },
|
||||||
data: {
|
data: { hhEmail: state.tempEmail },
|
||||||
hhEmail: state.tempEmail,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const resumes = await listResumes(chatId)
|
||||||
|
if (resumes.length === 0) {
|
||||||
|
await bot.sendMessage(chatId, '❌ Резюме не найдены. Создайте резюме на hh.ru')
|
||||||
|
}
|
||||||
|
else if (resumes.length === 1) {
|
||||||
|
await saveResume(chatId, resumes[0].href)
|
||||||
|
await bot.sendMessage(chatId, `✅ Резюме сохранено: ${resumes[0].title}`)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await sendResumeSelector(chatId, resumes)
|
||||||
|
}
|
||||||
|
|
||||||
triggerHHStart(chatId)
|
triggerHHStart(chatId)
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
@@ -180,14 +234,10 @@ export function registerHHCommands() {
|
|||||||
|
|
||||||
if (state.awaitingQuery) {
|
if (state.awaitingQuery) {
|
||||||
state.awaitingQuery = false
|
state.awaitingQuery = false
|
||||||
|
|
||||||
const updated = await prisma.settings.update({
|
const updated = await prisma.settings.update({
|
||||||
where: { telegramId: chatId },
|
where: { telegramId: chatId },
|
||||||
data: {
|
data: { searchQuery: msg.text },
|
||||||
searchQuery: msg.text,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await bot.sendMessage(chatId, `✅ Запрос: "${updated.searchQuery}"`)
|
await bot.sendMessage(chatId, `✅ Запрос: "${updated.searchQuery}"`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -199,12 +249,9 @@ export function registerHHCommands() {
|
|||||||
await bot.sendMessage(chatId, '❌ Число от 1 до 50')
|
await bot.sendMessage(chatId, '❌ Число от 1 до 50')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = await prisma.settings.update({
|
const updated = await prisma.settings.update({
|
||||||
where: { telegramId: chatId },
|
where: { telegramId: chatId },
|
||||||
data: {
|
data: { maxApplies: num },
|
||||||
maxApplies: num,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
await bot.sendMessage(chatId, `✅ Макс откликов: ${updated.maxApplies}`)
|
await bot.sendMessage(chatId, `✅ Макс откликов: ${updated.maxApplies}`)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user