mirror of
https://github.com/hempyhemp/hh-auto-reply.git
synced 2026-06-08 18:04:57 +00:00
🔧 Изменение(bot-commands): Изменил передаваемый аргумент для saveResume на объект.ResumeListItem.
This commit is contained in:
@@ -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 .",
|
||||
|
||||
15
prisma/migrations/20260527092716_resume_field/migration.sql
Normal file
15
prisma/migrations/20260527092716_resume_field/migration.sql
Normal file
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ async function doLogin(chatId: number, email: string): Promise<void> {
|
||||
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}`)
|
||||
}
|
||||
|
||||
@@ -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<ResumeListItem[]> {
|
||||
@@ -96,12 +99,18 @@ export async function listResumes(chatId: number): Promise<ResumeListItem[]> {
|
||||
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<ResumeListItem[]> {
|
||||
throw lastError!
|
||||
}
|
||||
|
||||
export async function saveResume(chatId: number, resumeHref: string): Promise<string | undefined> {
|
||||
export async function saveResume(chatId: number, resumeItem: ResumeListItem): Promise<string | undefined> {
|
||||
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<st
|
||||
resume = await page.locator('.resume').innerText()
|
||||
await prisma.resume.upsert({
|
||||
where: { id },
|
||||
create: { data: resume, id, telegramId: chatId },
|
||||
update: { data: resume },
|
||||
create: { data: resume, id, telegramId: chatId, title },
|
||||
update: { data: resume, title },
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
@@ -206,8 +218,7 @@ export async function applyToJobs(
|
||||
|
||||
await status(`✍️ Генерирую письмо: ${vacancy.title}`)
|
||||
|
||||
const letter = await createMessage(resume.data, description, user!.prompt)
|
||||
await keep(`✅ <b>${vacancy.title}</b>\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(`✅ <b>${vacancy.title}</b>\n\n${letter}`)
|
||||
const letter = await letterPromise
|
||||
|
||||
if (letter) {
|
||||
const letterInput = await page.$('[data-qa="vacancy-response-popup-form-letter-input"]')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user