mirror of
https://github.com/hempyhemp/hh-auto-reply.git
synced 2026-06-08 18:04:57 +00:00
🔧 refactor(bot-commands): Убран лишний код, оптимизированы импорты и функции.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import process from 'node:process'
|
||||
|
||||
import TelegramBot from 'node-telegram-bot-api'
|
||||
|
||||
const token = '8150213101:AAFfkqu32aWImOfIaarnQqtWaUj8ZoAwHLE'
|
||||
const token = process.env.TG_BOT_TOKEN!
|
||||
const bot = new TelegramBot(token, { polling: true })
|
||||
|
||||
export default bot
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import bot from '@bot'
|
||||
import prisma from '@prisma'
|
||||
import cron, { type ScheduledTask } from 'node-cron'
|
||||
import { applyToJobs, checkIsAuth, listResumes, login, type ResumeListItem, saveResume } from './scraper.js'
|
||||
|
||||
function escapeHtml(text: string): string {
|
||||
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
}
|
||||
import { applyToJobs, checkIsAuth, listResumes, login, saveResume } from './scraper.js'
|
||||
import { type ResumeListItem } from './types.js'
|
||||
import { BACK_MARKUP, createStatusReporter, escapeHtml, LOGIN_MARKUP, MAIN_MARKUP, showResult } from './ui.js'
|
||||
|
||||
interface UserState {
|
||||
autoCron: ScheduledTask | null
|
||||
@@ -39,33 +37,6 @@ function getState(chatId: number): UserState {
|
||||
return states.get(chatId)!
|
||||
}
|
||||
|
||||
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' },
|
||||
],
|
||||
],
|
||||
}
|
||||
|
||||
const BACK_MARKUP = {
|
||||
inline_keyboard: [[{ text: '◀️ Назад', callback_data: 'hh_back' }]],
|
||||
}
|
||||
|
||||
// Редактирует существующее сообщение-меню или отправляет новое
|
||||
async function showMenu(chatId: number, messageId?: number | null): Promise<void> {
|
||||
const state = getState(chatId)
|
||||
const targetId = messageId ?? state.menuMessageId
|
||||
@@ -85,21 +56,10 @@ async function showMenu(chatId: number, messageId?: number | null): Promise<void
|
||||
}
|
||||
}
|
||||
|
||||
const msg = await bot.sendMessage(chatId, '🤖 HH Auto-Apply', {
|
||||
reply_markup: MAIN_MARKUP,
|
||||
})
|
||||
const msg = await bot.sendMessage(chatId, '🤖 HH Auto-Apply', { reply_markup: MAIN_MARKUP })
|
||||
state.menuMessageId = msg.message_id
|
||||
}
|
||||
|
||||
// Редактирует сообщение с меню: показывает текст + кнопку "Назад"
|
||||
async function showResult(chatId: number, messageId: number, text: string): Promise<void> {
|
||||
await bot.editMessageText(text, {
|
||||
chat_id: chatId,
|
||||
message_id: messageId,
|
||||
reply_markup: BACK_MARKUP,
|
||||
})
|
||||
}
|
||||
|
||||
async function sendResumeSelector(chatId: number, resumes: ResumeListItem[], messageId: number): Promise<void> {
|
||||
const state = getState(chatId)
|
||||
state.pendingResumes = resumes
|
||||
@@ -146,6 +106,13 @@ async function doLogin(chatId: number, email: string): Promise<void> {
|
||||
}
|
||||
|
||||
export async function triggerHHStart(chatId: number): Promise<void> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -176,21 +143,19 @@ export function registerHHCommands() {
|
||||
break
|
||||
|
||||
case 'hh_apply': {
|
||||
await bot.editMessageText(`🔄 Ищу вакансии по запросу "${settings.searchQuery}"...`, {
|
||||
chat_id: chatId,
|
||||
message_id: messageId,
|
||||
reply_markup: { inline_keyboard: [] },
|
||||
})
|
||||
await bot.deleteMessage(chatId, messageId).catch(() => {})
|
||||
state.menuMessageId = null
|
||||
|
||||
applyToJobs({ query: settings.searchQuery, maxApplies: settings.maxApplies }, { chatId })
|
||||
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(`📊 <b>Итого по запросу «${settings.searchQuery}»</b>`)
|
||||
lines.push(`✅ Откликнулся: ${result.applied.length}`)
|
||||
lines.push(`⏭ Пропущено: ${result.skipped.length}`)
|
||||
@@ -224,7 +189,7 @@ export function registerHHCommands() {
|
||||
await showResult(
|
||||
chatId,
|
||||
messageId,
|
||||
`⚙️ Настройки:\n\nЗапрос: ${settings.searchQuery}\nМакс откликов: ${settings.maxApplies}\nАвто: ${state.autoCron ? '✅ включено' : '😬 выключено'}\nАвторизован: ${isAuth}`,
|
||||
`⚙️ Настройки:\n\nЗапрос: ${settings.searchQuery}\nМакс откликов: ${settings.maxApplies}\nАвто: ${state.autoCron ? '✅ включено' : '❌ выключено'}\nАвторизован: ${isAuth}`,
|
||||
)
|
||||
break
|
||||
}
|
||||
@@ -238,12 +203,10 @@ export function registerHHCommands() {
|
||||
await showResult(chatId, messageId, '📋 Резюме не найдено.\n\nВыбери резюме через кнопку 📄 Выбрать резюме.')
|
||||
break
|
||||
}
|
||||
|
||||
const MAX = 3800
|
||||
const text = resume.data.length > MAX
|
||||
? `${resume.data.slice(0, MAX)}\n\n… (текст обрезан)`
|
||||
: resume.data
|
||||
|
||||
await bot.editMessageText(
|
||||
`📋 <b>Твоё резюме</b>\n<pre>${escapeHtml(text)}</pre>`,
|
||||
{
|
||||
@@ -331,7 +294,7 @@ export function registerHHCommands() {
|
||||
})
|
||||
const resumes = await listResumes(chatId)
|
||||
if (resumes.length === 0) {
|
||||
await showResult(chatId, messageId, '😬 Резюме не найдены. Создайте резюме на hh.ru')
|
||||
await showResult(chatId, messageId, '⚠️ Резюме не найдены. Создайте резюме на hh.ru')
|
||||
}
|
||||
else if (resumes.length === 1) {
|
||||
await bot.editMessageText('🔄 Сохраняю резюме...', {
|
||||
@@ -354,7 +317,7 @@ export function registerHHCommands() {
|
||||
const idx = Number(query.data.replace('hh_resume_pick_', ''))
|
||||
const resume = state.pendingResumes[idx]
|
||||
if (!resume) {
|
||||
await showResult(chatId, messageId, '😬 Резюме не найдено, попробуйте снова')
|
||||
await showResult(chatId, messageId, '❌ Резюме не найдено, попробуйте снова')
|
||||
break
|
||||
}
|
||||
await bot.editMessageText('🔄 Сохраняю резюме...', {
|
||||
@@ -410,7 +373,7 @@ export function registerHHCommands() {
|
||||
if (state.awaitingMax) {
|
||||
const num = Number(msg.text)
|
||||
if (Number.isNaN(num) || num < 1 || num > 50) {
|
||||
await bot.sendMessage(chatId, '😬 Введи число от 1 до 50:')
|
||||
await bot.sendMessage(chatId, '❌ Введи число от 1 до 50:')
|
||||
return
|
||||
}
|
||||
state.awaitingMax = false
|
||||
|
||||
27
src/hh/browser.ts
Normal file
27
src/hh/browser.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import prisma from '@prisma'
|
||||
import { type Browser, chromium, type Page } from 'playwright'
|
||||
|
||||
export function randomDelay(min = 300, max = 2000): number {
|
||||
return min + Math.random() * (max - min)
|
||||
}
|
||||
|
||||
export async function humanDelay(min = 300, max = 2000): Promise<void> {
|
||||
return new Promise(r => setTimeout(r, randomDelay(min, max)))
|
||||
}
|
||||
|
||||
export async function randomScroll(page: Page): Promise<void> {
|
||||
await page.mouse.move(100 + Math.random() * 500, 200 + Math.random() * 500)
|
||||
await page.mouse.wheel(0, 300 + Math.random() * 1000)
|
||||
}
|
||||
|
||||
export async function getBrowser(): Promise<Browser> {
|
||||
return chromium.launch({ headless: false })
|
||||
}
|
||||
|
||||
export async function loadSession(page: Page, telegramId: bigint | number): Promise<boolean> {
|
||||
const user = await prisma.user.findUnique({ where: { telegramId } })
|
||||
if (!user?.session)
|
||||
return false
|
||||
await page.context().addCookies(JSON.parse(user.session))
|
||||
return true
|
||||
}
|
||||
@@ -1,153 +1,71 @@
|
||||
import type { Message } from 'node-telegram-bot-api'
|
||||
import type { ApplyOptions, ApplyResult, ResumeListItem, VacancyRef } from './types.js'
|
||||
import type { StatusReporter } from './ui.js'
|
||||
import bot from '@bot'
|
||||
import prisma from '@prisma'
|
||||
import { type Browser, chromium, type Page } from 'playwright'
|
||||
import { createMessage } from '../openai'
|
||||
|
||||
// const SESSION_FILE = path.resolve('./session.json')
|
||||
|
||||
interface ApplyOptions {
|
||||
query: string
|
||||
area?: number
|
||||
maxApplies?: number
|
||||
}
|
||||
|
||||
interface VacancyRef {
|
||||
title: string
|
||||
href: string
|
||||
}
|
||||
|
||||
interface ApplyResult {
|
||||
applied: VacancyRef[]
|
||||
skipped: VacancyRef[]
|
||||
errors: Array<VacancyRef & { message: string }>
|
||||
error?: string
|
||||
}
|
||||
|
||||
function randomDelay(min = 300, max = 2000) {
|
||||
return min + Math.random() * (max - min)
|
||||
}
|
||||
|
||||
async function humanDelay(min = 300, max = 2000) {
|
||||
return new Promise(r => setTimeout(r, randomDelay(min, max)))
|
||||
}
|
||||
|
||||
async function randomScroll(page: Page) {
|
||||
await page.mouse.move(100 + Math.random() * 500, 200 + Math.random() * 500)
|
||||
await page.mouse.wheel(0, 300 + Math.random() * 1000)
|
||||
}
|
||||
|
||||
async function getBrowser(): Promise<Browser> {
|
||||
return chromium.launch({ headless: false })
|
||||
}
|
||||
|
||||
async function loadSession(page: Page, telegramId: bigint | number): Promise<boolean> {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { telegramId },
|
||||
})
|
||||
|
||||
const session = user?.session
|
||||
|
||||
if (!session)
|
||||
return false
|
||||
|
||||
const cookies = JSON.parse(session)
|
||||
await page.context().addCookies(cookies)
|
||||
return true
|
||||
}
|
||||
import { getBrowser, loadSession, randomDelay } from './browser.js'
|
||||
|
||||
function waitForOtp(chatId: number): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
const handler = (msg: Message) => {
|
||||
if (msg.chat.id !== chatId)
|
||||
if (msg.chat.id !== chatId || !msg.text)
|
||||
return
|
||||
|
||||
if (!msg.text)
|
||||
return
|
||||
|
||||
bot.removeListener('message', handler)
|
||||
resolve(msg.text)
|
||||
}
|
||||
|
||||
bot.on('message', handler)
|
||||
})
|
||||
}
|
||||
|
||||
export async function login(
|
||||
email: string,
|
||||
chatId: number,
|
||||
): Promise<void> {
|
||||
export async function login(email: string, chatId: number): Promise<void> {
|
||||
const browser = await getBrowser()
|
||||
await bot.sendMessage(chatId, `Browser is connected: ${browser.isConnected()}`)
|
||||
if (!browser.version())
|
||||
// await bot.sendMessage(chatId, `Browser is connected: ${browser.isConnected()}`)
|
||||
if (!browser.version()) {
|
||||
console.log('browser error')
|
||||
return
|
||||
// await bot.sendMessage(chatId, `Browser version: ${browser.version()}`)
|
||||
}
|
||||
|
||||
const context = await browser.newContext()
|
||||
const page = await context.newPage()
|
||||
|
||||
await page.goto('https://hh.ru/account/login', { waitUntil: 'networkidle' })
|
||||
|
||||
await bot.sendMessage(chatId, `page: ${page.url()}`)
|
||||
|
||||
await page.click('[data-qa="submit-button"]')
|
||||
|
||||
await page.waitForTimeout(randomDelay())
|
||||
|
||||
await bot.sendMessage(chatId, `Клик по "Войти"`)
|
||||
// await bot.sendMessage(chatId, `Клик по "Войти"`)
|
||||
|
||||
await page.click('label:has([data-qa="credential-type-EMAIL"])')
|
||||
|
||||
await page.waitForTimeout(randomDelay())
|
||||
|
||||
await bot.sendMessage(chatId, `Клик по "Email"`)
|
||||
|
||||
await page.waitForTimeout(randomDelay())
|
||||
// await bot.sendMessage(chatId, `Клик по "Email"`)
|
||||
|
||||
await page.fill('[data-qa="applicant-login-input-email"]', email)
|
||||
|
||||
await page.waitForTimeout(randomDelay())
|
||||
|
||||
await bot.sendMessage(chatId, `Ввод "Email"`)
|
||||
// await bot.sendMessage(chatId, `Ввод "Email"`)
|
||||
|
||||
await page.click('[data-qa="submit-button"]')
|
||||
|
||||
await bot.sendMessage(chatId, `Клик по "Дальше"`)
|
||||
|
||||
// await bot.sendMessage(chatId, `Клик по "Дальше"`)
|
||||
await page.waitForTimeout(randomDelay())
|
||||
|
||||
await bot.sendMessage(chatId, '🔑 Введи код из email')
|
||||
|
||||
await page.waitForTimeout(randomDelay())
|
||||
|
||||
await page.click('[data-qa="applicant-login-input-otp"]')
|
||||
|
||||
const otp = await waitForOtp(chatId)
|
||||
|
||||
await page.fill('[data-qa="applicant-login-input-otp"] input', otp)
|
||||
|
||||
await bot.sendMessage(chatId, `Введён ОТП: ${otp}`)
|
||||
// await bot.sendMessage(chatId, `Введён ОТП: ${otp}`)
|
||||
|
||||
await page.waitForTimeout(randomDelay())
|
||||
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
const cookies = await context.cookies()
|
||||
|
||||
await prisma.user.update({
|
||||
where: { telegramId: chatId },
|
||||
data: {
|
||||
session: JSON.stringify(cookies, null, 2),
|
||||
},
|
||||
data: { session: JSON.stringify(cookies, null, 2) },
|
||||
})
|
||||
|
||||
if (cookies.length > 0) {
|
||||
await bot.sendMessage(chatId, `✅ Авторизация выполнена`)
|
||||
}
|
||||
else {
|
||||
await bot.sendMessage(chatId, `😬 Произошла ошибка`)
|
||||
}
|
||||
|
||||
await bot.sendMessage(chatId, cookies.length > 0 ? '✅ Авторизация выполнена' : '❌ Произошла ошибка')
|
||||
await browser.close()
|
||||
}
|
||||
|
||||
@@ -155,27 +73,14 @@ export async function checkIsAuth(telegramId: bigint | number) {
|
||||
const browser = await getBrowser()
|
||||
const context = await browser.newContext()
|
||||
const page = await context.newPage()
|
||||
|
||||
await loadSession(page, telegramId)
|
||||
|
||||
console.log('Сессия загружена')
|
||||
|
||||
const url = `https://hh.ru/search/vacancy`
|
||||
|
||||
await page.goto(url, { waitUntil: 'networkidle' })
|
||||
|
||||
await page.goto('https://hh.ru/search/vacancy', { waitUntil: 'networkidle' })
|
||||
try {
|
||||
return await page.$('[data-qa="profileAndResumes-button"]')
|
||||
}
|
||||
catch (e) {
|
||||
return e
|
||||
}
|
||||
// return await page.$('[data-qa="mainmenu_createResume"]')
|
||||
}
|
||||
|
||||
export interface ResumeListItem {
|
||||
title: string
|
||||
href: string
|
||||
}
|
||||
|
||||
export async function listResumes(chatId: number): Promise<ResumeListItem[]> {
|
||||
@@ -219,7 +124,7 @@ export async function saveResume(chatId: number, resumeHref: string): Promise<st
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
await bot.sendMessage(chatId, 'Нет резюме на ХХ, создайте')
|
||||
await bot.sendMessage(chatId, 'Нет резюме на hh.ru, создайте')
|
||||
}
|
||||
finally {
|
||||
await browser.close()
|
||||
@@ -228,36 +133,15 @@ export async function saveResume(chatId: number, resumeHref: string): Promise<st
|
||||
return resume
|
||||
}
|
||||
|
||||
export async function applyToJobs({
|
||||
query,
|
||||
area = 1,
|
||||
maxApplies = 10,
|
||||
}: ApplyOptions, { chatId }: {
|
||||
chatId: number
|
||||
}): Promise<ApplyResult> {
|
||||
export async function applyToJobs(
|
||||
{ query, area = 1, maxApplies = 10 }: ApplyOptions,
|
||||
{ chatId, reporter }: { chatId: number, reporter: StatusReporter },
|
||||
): Promise<ApplyResult> {
|
||||
const browser = await getBrowser()
|
||||
const context = await browser.newContext()
|
||||
const page = await context.newPage()
|
||||
const results: ApplyResult = { applied: [], skipped: [], errors: [] }
|
||||
|
||||
let statusMsgId: number | null = null
|
||||
|
||||
async function status(text: string): Promise<void> {
|
||||
if (statusMsgId) {
|
||||
await bot.deleteMessage(chatId, statusMsgId).catch(() => {})
|
||||
statusMsgId = null
|
||||
}
|
||||
const msg = await bot.sendMessage(chatId, text)
|
||||
statusMsgId = msg.message_id
|
||||
}
|
||||
|
||||
async function keep(text: string): Promise<void> {
|
||||
if (statusMsgId) {
|
||||
await bot.deleteMessage(chatId, statusMsgId).catch(() => {})
|
||||
statusMsgId = null
|
||||
}
|
||||
await bot.sendMessage(chatId, text, { parse_mode: 'HTML' })
|
||||
}
|
||||
const { status, keep, clear } = reporter
|
||||
|
||||
try {
|
||||
await loadSession(page, chatId)
|
||||
@@ -265,12 +149,11 @@ export async function applyToJobs({
|
||||
const url = `https://hh.ru/search/vacancy?text=${encodeURIComponent(query)}&area=${area}`
|
||||
await page.goto(url, { waitUntil: 'networkidle' })
|
||||
|
||||
const isLoggedIn = await page.$('[data-qa="profileAndResumes-button"]')
|
||||
if (!isLoggedIn) {
|
||||
if (!await page.$('[data-qa="profileAndResumes-button"]')) {
|
||||
return { ...results, error: 'Не авторизован. Выполните login' }
|
||||
}
|
||||
|
||||
await status(`✅ Авторизация выполнена`)
|
||||
await status('✅ Авторизация выполнена')
|
||||
|
||||
const vacancies = await page.$$eval(
|
||||
'[data-qa="serp-item__title"]',
|
||||
@@ -309,7 +192,6 @@ 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}`)
|
||||
|
||||
results.applied.push(ref)
|
||||
@@ -319,10 +201,7 @@ export async function applyToJobs({
|
||||
}
|
||||
}
|
||||
|
||||
if (statusMsgId) {
|
||||
await bot.deleteMessage(chatId, statusMsgId).catch(() => {})
|
||||
statusMsgId = null
|
||||
}
|
||||
await clear()
|
||||
}
|
||||
finally {
|
||||
await browser.close()
|
||||
|
||||
22
src/hh/types.ts
Normal file
22
src/hh/types.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export interface ApplyOptions {
|
||||
query: string
|
||||
area?: number
|
||||
maxApplies?: number
|
||||
}
|
||||
|
||||
export interface VacancyRef {
|
||||
title: string
|
||||
href: string
|
||||
}
|
||||
|
||||
export interface ApplyResult {
|
||||
applied: VacancyRef[]
|
||||
skipped: VacancyRef[]
|
||||
errors: Array<VacancyRef & { message: string }>
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface ResumeListItem {
|
||||
title: string
|
||||
href: string
|
||||
}
|
||||
77
src/hh/ui.ts
Normal file
77
src/hh/ui.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
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' },
|
||||
],
|
||||
],
|
||||
}
|
||||
|
||||
export const LOGIN_MARKUP = {
|
||||
inline_keyboard: [
|
||||
[{ text: '🔑 Войти через hh.ru', callback_data: 'hh_login' }],
|
||||
],
|
||||
}
|
||||
|
||||
export const BACK_MARKUP = {
|
||||
inline_keyboard: [[{ text: '◀️ Назад', callback_data: 'hh_back' }]],
|
||||
}
|
||||
|
||||
export function escapeHtml(text: string): string {
|
||||
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
}
|
||||
|
||||
export async function showResult(chatId: number, messageId: number, text: string): Promise<void> {
|
||||
await bot.editMessageText(text, {
|
||||
chat_id: chatId,
|
||||
message_id: messageId,
|
||||
reply_markup: BACK_MARKUP,
|
||||
})
|
||||
}
|
||||
|
||||
export interface StatusReporter {
|
||||
status: (text: string) => Promise<void>
|
||||
keep: (text: string) => Promise<void>
|
||||
clear: () => Promise<void>
|
||||
}
|
||||
|
||||
export function createStatusReporter(chatId: number, initialMsgId?: number | null): StatusReporter {
|
||||
let msgId: number | null = initialMsgId ?? null
|
||||
|
||||
async function deleteCurrent(): Promise<void> {
|
||||
if (msgId) {
|
||||
await bot.deleteMessage(chatId, msgId).catch(() => {})
|
||||
msgId = null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
async status(text): Promise<void> {
|
||||
await deleteCurrent()
|
||||
const msg = await bot.sendMessage(chatId, text)
|
||||
msgId = msg.message_id
|
||||
},
|
||||
async keep(text): Promise<void> {
|
||||
await deleteCurrent()
|
||||
await bot.sendMessage(chatId, text, { parse_mode: 'HTML' })
|
||||
},
|
||||
async clear(): Promise<void> {
|
||||
await deleteCurrent()
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import bot from '@bot'
|
||||
import prisma from '@prisma'
|
||||
|
||||
import { triggerHHStart } from './hh/bot-commands.js'
|
||||
import { registerHHCommands } from './hh/bot-commands.js'
|
||||
import prisma from '@prisma'
|
||||
import { registerHHCommands, triggerHHStart } from './hh/bot-commands.js'
|
||||
|
||||
registerHHCommands()
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import process from 'node:process'
|
||||
import { createOpencode, createOpencodeClient } from '@opencode-ai/sdk'
|
||||
// import Anthropic from '@anthropic-ai/sdk'
|
||||
import OpenAI from 'openai'
|
||||
|
||||
Reference in New Issue
Block a user