diff --git a/package.json b/package.json index 05b5293..c7b818f 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "build": "tsc", "start": "node --env-file=.env dist/index.js", "db-view": "yarn prisma studio", - "db-migrate": "npx prisma migrate dev --name init npx prisma generate ", + "db-migrate": "npx prisma migrate dev", "db-deploy": "npx prisma migrate deploy", "db-migrate-server": "git pull npx prisma migrate deploy", "lint": "eslint .", diff --git a/prisma/migrations/20260527092716_resume_field/migration.sql b/prisma/migrations/20260527092716_resume_field/migration.sql new file mode 100644 index 0000000..d5f22a9 --- /dev/null +++ b/prisma/migrations/20260527092716_resume_field/migration.sql @@ -0,0 +1,15 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Resume" ( + "id" TEXT NOT NULL PRIMARY KEY, + "data" TEXT NOT NULL, + "title" TEXT NOT NULL DEFAULT '', + "telegramId" BIGINT NOT NULL, + CONSTRAINT "Resume_telegramId_fkey" FOREIGN KEY ("telegramId") REFERENCES "User" ("telegramId") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "new_Resume" ("data", "id", "telegramId") SELECT "data", "id", "telegramId" FROM "Resume"; +DROP TABLE "Resume"; +ALTER TABLE "new_Resume" RENAME TO "Resume"; +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a4c9432..8401c7a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -31,6 +31,7 @@ model Resume { id String @id user User @relation(references: [telegramId], fields: [telegramId], onDelete: Cascade) data String + title String @default("") telegramId BigInt } diff --git a/src/hh/bot-commands.ts b/src/hh/bot-commands.ts index 0c1da0f..fb9e4d5 100644 --- a/src/hh/bot-commands.ts +++ b/src/hh/bot-commands.ts @@ -109,7 +109,7 @@ async function doLogin(chatId: number, email: string): Promise { await bot.sendMessage(chatId, '⚠️ Резюме не найдены. Создайте резюме на hh.ru') } else if (resumes.length === 1) { - await saveResume(chatId, resumes[0].href) + await saveResume(chatId, resumes[0]) await bot.sendMessage(chatId, `✅ Резюме сохранено: ${resumes[0].title}`) } else { @@ -234,7 +234,7 @@ export function registerHHCommands() { await showResult(chatId, messageId, '📋 Резюме не найдено.\n\nВыбери резюме через кнопку 📄 Выбрать резюме.') break } - const MAX = 3800 + const MAX = 10000 const text = resume.data.length > MAX ? `${resume.data.slice(0, MAX)}\n\n… (текст обрезан)` : resume.data @@ -341,7 +341,7 @@ export function registerHHCommands() { message_id: messageId, reply_markup: { inline_keyboard: [] }, }) - await saveResume(chatId, resumes[0].href) + await saveResume(chatId, resumes[0]) await showResult(chatId, messageId, `✅ Резюме сохранено: ${resumes[0].title}`) } else { @@ -364,7 +364,7 @@ export function registerHHCommands() { message_id: messageId, reply_markup: { inline_keyboard: [] }, }) - await saveResume(chatId, resume.href) + await saveResume(chatId, resume) state.pendingResumes = [] await showResult(chatId, messageId, `✅ Резюме выбрано: ${resume.title}`) } diff --git a/src/hh/scraper.ts b/src/hh/scraper.ts index 9d5ac29..ef5b61d 100644 --- a/src/hh/scraper.ts +++ b/src/hh/scraper.ts @@ -81,6 +81,9 @@ export async function checkIsAuth(telegramId: bigint | number) { catch { return null } + finally { + await browser.close() + } } export async function listResumes(chatId: number): Promise { @@ -96,12 +99,18 @@ export async function listResumes(chatId: number): Promise { await page.waitForSelector('[data-qa^="resume-card-link-"]', { timeout: 10000 }) const resumes = await page.$$eval( '[data-qa^="resume-card-link-"]', - links => links.map(a => ({ - href: (a as HTMLAnchorElement).getAttribute('href') ?? '', - title: a.textContent?.trim() ?? '(без названия)', - })), + links => links.map((a) => { + const card = a.closest('[data-qa^="resume-card"]') ?? a.parentElement + const titleEl = card?.querySelector('[data-qa="cell-text-content"]') + return { + href: (a as HTMLAnchorElement).getAttribute('href') ?? '', + title: titleEl?.textContent?.trim() ?? '(без названия)', + } + }), ) + await browser.close() + console.log(resumes) return resumes } catch (e) { @@ -115,12 +124,15 @@ export async function listResumes(chatId: number): Promise { throw lastError! } -export async function saveResume(chatId: number, resumeHref: string): Promise { +export async function saveResume(chatId: number, resumeItem: ResumeListItem): Promise { const browser = await getBrowser() const context = await browser.newContext() const page = await context.newPage() await loadSession(page, chatId) + const resumeHref = resumeItem.href + const title = resumeItem.title + const id = new URL(`https://hh.ru${resumeHref}`).pathname.split('/').pop()! const resumeUrl = `https://hh.ru/resume_converter/resume.txt?hash=${id}&type=txt&hhtmFrom=&hhtmSource=resume` @@ -131,8 +143,8 @@ export async function saveResume(chatId: number, resumeHref: string): Promise${vacancy.title}\n\n${letter}`) + const letterPromise = createMessage(resume.data, description, user!.prompt) const applyBtn = await page.$('[data-qa="vacancy-response-link-top"]') if (!applyBtn) { @@ -220,19 +231,30 @@ export async function applyToJobs( await applyBtn.click() await page.waitForTimeout(randomDelay()) - // Выбор реюзме - const currentResume = await page.$('[data-qa="resume-title"]') - console.log(currentResume?.textContent()) + // Выбор резюме + const currentResumeEl = await page.$('[data-qa="resume-title"]') + const currentResumeTitle = (await currentResumeEl?.innerText())?.trim() ?? '' + console.log('Текущее резюме на странице:', currentResumeTitle) + console.log('Ожидаемое резюме из БД:', resume.title) + + if (currentResumeTitle !== resume.title) { + // TODO: добавить логику смены резюме + console.log('Резюме не совпадает, нужно сменить') + currentResumeEl?.focus() + currentResumeEl?.click() + } await page.pause() const addLetter = await page.$('[data-qa="add-cover-letter"]') if (addLetter) { - await page.pause() await addLetter?.hover() await addLetter?.click() } + await keep(`✅ ${vacancy.title}\n\n${letter}`) + const letter = await letterPromise + if (letter) { const letterInput = await page.$('[data-qa="vacancy-response-popup-form-letter-input"]')