470 lines
15 KiB
TypeScript
470 lines
15 KiB
TypeScript
import 'dotenv/config';
|
||
import { Markup, Scenes, Telegraf, session } from 'telegraf';
|
||
import { startCommand } from './commands/start';
|
||
import { menuSceneMiddleware } from './modules/menuScenes';
|
||
import express, { Request, Response, NextFunction } from 'express';
|
||
import cors from 'cors';
|
||
import { events } from './modules/scenes/events';
|
||
import { Resume } from './db/db';
|
||
|
||
import FormData from 'form-data';
|
||
import fs from 'fs';
|
||
import path from 'path';
|
||
import axios from 'axios';
|
||
import { saveVacansy } from './modules/scenes/savevacansy';
|
||
import { accept } from './modules/scenes/accept';
|
||
|
||
export type MyWizardContext = Scenes.WizardContext<Scenes.WizardSessionData>;
|
||
|
||
const app = express();
|
||
declare global {
|
||
namespace Express {
|
||
interface Request {
|
||
bot: Telegraf<Scenes.WizardContext>;
|
||
}
|
||
}
|
||
}
|
||
const bot = new Telegraf<Scenes.WizardContext>(process.env.BOT_TOKEN as string);
|
||
|
||
app.use((req: Request, res: Response, next: NextFunction) => {
|
||
req.bot = bot;
|
||
next();
|
||
});
|
||
app.use(express.json());
|
||
app.use(cors());
|
||
bot.start(startCommand);
|
||
|
||
app.post('/api/resume', async (req: Request, res: Response) => {
|
||
try {
|
||
const postData = req.body;
|
||
const resumeTemplatePath = path.resolve(
|
||
__dirname,
|
||
'assets',
|
||
'шаблон МТУСИ.docx'
|
||
);
|
||
await bot.telegram.sendDocument(
|
||
postData.id,
|
||
{ source: resumeTemplatePath },
|
||
{ caption: 'Пример резюме' }
|
||
);
|
||
|
||
await bot.telegram.sendMessage(
|
||
postData.id,
|
||
`Ваша анкета заполнена! Отправьте своё резюме для завершения регистрации`,
|
||
{
|
||
reply_markup: {
|
||
inline_keyboard: [
|
||
[
|
||
{ text: 'Загрузить резюме', callback_data: 'uploadresume' },
|
||
// { text: 'Пропустить загрузку', callback_data: 'skip' },
|
||
],
|
||
],
|
||
},
|
||
}
|
||
);
|
||
|
||
console.log(`Ваш id ${postData.id}`);
|
||
return res.json({ status: 'success' });
|
||
} catch (e) {
|
||
console.error('Error:', e);
|
||
res.status(500).json({ error: 'An error occurred' });
|
||
throw e;
|
||
}
|
||
});
|
||
// const resumeScene: Scenes.WizardScene<MyWizardContext> = new Scenes.WizardScene(
|
||
// 'resumeScene',
|
||
// async ctx => {
|
||
// await ctx.reply(
|
||
// 'Отправьте своё резюме в PDF формате или отправьте /cancel для отмены.'
|
||
// );
|
||
// return ctx.wizard.next();
|
||
// },
|
||
|
||
// async ctx => {
|
||
// if (
|
||
// ctx.message &&
|
||
// 'text' in ctx.message &&
|
||
// ctx.from &&
|
||
// ctx.message.text == '/cancel'
|
||
// ) {
|
||
// await ctx.reply('Отправка резюме отменена.');
|
||
// await ctx.reply(
|
||
// 'Меню',
|
||
// Markup.keyboard([
|
||
// ['Моя анкета'],
|
||
// ['Вакансии'],
|
||
// ['Уведомления: включены'],
|
||
// ]).resize()
|
||
// );
|
||
// return ctx.scene.leave();
|
||
// }
|
||
|
||
// if (ctx.message && 'document' in ctx.message && ctx.from) {
|
||
// const file = ctx.message.document;
|
||
|
||
// if (file.mime_type !== 'application/pdf') {
|
||
// ctx.reply('Пожалуйста, отправьте файл в формате PDF.');
|
||
// return;
|
||
// }
|
||
|
||
// try {
|
||
// const fileLink = await ctx.telegram.getFileLink(file.file_id);
|
||
// const filePath = path.join(__dirname, `${file.file_id}.pdf`);
|
||
|
||
// // Загрузка файла на локальную машину
|
||
// const response = await axios.get(fileLink.href, {
|
||
// responseType: 'stream',
|
||
// });
|
||
// response.data
|
||
// .pipe(fs.createWriteStream(filePath))
|
||
// .on('finish', async () => {
|
||
// // Создание формы данных
|
||
// const form = new FormData();
|
||
// form.append('file', fs.createReadStream(filePath));
|
||
|
||
// try {
|
||
// // Установка времени ожидания (в миллисекундах)
|
||
// const uploadResponse = await axios.post(
|
||
// `${process.env.API}services/resume/`,
|
||
// form,
|
||
// {
|
||
// headers: {
|
||
// ...form.getHeaders(),
|
||
// 'X-API-KEY':
|
||
// 'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||
// },
|
||
// timeout: 10000, // Увеличьте время ожидания до 10 секунд
|
||
// }
|
||
// );
|
||
|
||
// const fileName = uploadResponse.data.filename;
|
||
|
||
// // Выполнение PUT-запроса для обновления поля Link у студента
|
||
// await axios.patch(
|
||
// `${process.env.API}students/${
|
||
// ctx.from?.id
|
||
// }?Link=${encodeURIComponent(fileName)}`,
|
||
// {},
|
||
// {
|
||
// headers: {
|
||
// 'X-API-KEY':
|
||
// 'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||
// 'Content-Type': 'application/json',
|
||
// },
|
||
// }
|
||
// );
|
||
|
||
// await ctx.reply('Резюме успешно загружено.');
|
||
// await ctx.reply(
|
||
// 'Меню',
|
||
// Markup.keyboard([
|
||
// ['Моя анкета'],
|
||
// ['Вакансии'],
|
||
// ['Уведомления: включены'],
|
||
// ]).resize()
|
||
// );
|
||
// return ctx.scene.leave();
|
||
// } catch (uploadError) {
|
||
// console.error('Ошибка при загрузке резюме на API:', uploadError);
|
||
// await ctx.reply('Произошла ошибка при загрузке резюме.');
|
||
// return ctx.scene.leave();
|
||
// } finally {
|
||
// // Удаление временного файла после загрузки
|
||
// fs.unlinkSync(filePath);
|
||
// }
|
||
// });
|
||
// } catch (error) {
|
||
// console.error('Ошибка при загрузке файла:', error);
|
||
// await ctx.reply('Произошла ошибка при загрузке файла.');
|
||
// }
|
||
// } else {
|
||
// await ctx.reply('Отправьте файл в формате PDF');
|
||
// }
|
||
// }
|
||
// );
|
||
|
||
const resumeScene: Scenes.WizardScene<MyWizardContext> = new Scenes.WizardScene(
|
||
'resumeScene',
|
||
async ctx => {
|
||
await ctx.reply(
|
||
'Отправьте своё резюме в формате Word (DOC/DOCX) или отправьте /cancel для отмены.'
|
||
);
|
||
return ctx.wizard.next();
|
||
},
|
||
|
||
async ctx => {
|
||
if (
|
||
ctx.message &&
|
||
'text' in ctx.message &&
|
||
ctx.from &&
|
||
ctx.message.text === '/cancel'
|
||
) {
|
||
await ctx.reply('Отправка резюме отменена.');
|
||
await ctx.reply(
|
||
'Меню',
|
||
Markup.keyboard([
|
||
['Моя анкета'],
|
||
['Вакансии'],
|
||
['Уведомления: включены'],
|
||
]).resize()
|
||
);
|
||
return ctx.scene.leave();
|
||
}
|
||
|
||
if (ctx.message && 'document' in ctx.message && ctx.from) {
|
||
const file = ctx.message.document;
|
||
const allowedMimeTypes = [
|
||
// 'application/pdf',
|
||
'application/msword',
|
||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
];
|
||
|
||
if (!allowedMimeTypes.includes(file.mime_type || '')) {
|
||
await ctx.reply('Пожалуйста, отправьте файл в формате DOC или DOCX.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const fileLink = await ctx.telegram.getFileLink(file.file_id);
|
||
const fileExtension =
|
||
file.mime_type === 'application/pdf'
|
||
? 'pdf'
|
||
: file.mime_type === 'application/msword'
|
||
? 'doc'
|
||
: 'docx';
|
||
const filePath = path.join(
|
||
__dirname,
|
||
`${file.file_id}.${fileExtension}`
|
||
);
|
||
|
||
// Загрузка файла на локальную машину
|
||
const response = await axios.get(fileLink.href, {
|
||
responseType: 'stream',
|
||
});
|
||
response.data
|
||
.pipe(fs.createWriteStream(filePath))
|
||
.on('finish', async () => {
|
||
const form = new FormData();
|
||
form.append('file', fs.createReadStream(filePath));
|
||
|
||
try {
|
||
const uploadResponse = await axios.post(
|
||
`${process.env.API}services/resume/`,
|
||
form,
|
||
{
|
||
headers: {
|
||
...form.getHeaders(),
|
||
'X-API-KEY':
|
||
'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||
},
|
||
timeout: 10000,
|
||
}
|
||
);
|
||
|
||
const fileName = uploadResponse.data.filename;
|
||
|
||
await axios.patch(
|
||
`${process.env.API}students/${
|
||
ctx.from?.id
|
||
}?Link=${encodeURIComponent(fileName)}`,
|
||
{},
|
||
{
|
||
headers: {
|
||
'X-API-KEY':
|
||
'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
}
|
||
);
|
||
|
||
await ctx.reply('Резюме успешно загружено.');
|
||
await ctx.reply(
|
||
'Меню',
|
||
Markup.keyboard([
|
||
['Моя анкета'],
|
||
['Вакансии'],
|
||
['Уведомления: включены'],
|
||
]).resize()
|
||
);
|
||
return ctx.scene.leave();
|
||
} catch (uploadError) {
|
||
console.error('Ошибка при загрузке резюме на API:', uploadError);
|
||
await ctx.reply('Произошла ошибка при загрузке резюме.');
|
||
return ctx.scene.leave();
|
||
} finally {
|
||
fs.unlinkSync(filePath);
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка при загрузке файла:', error);
|
||
await ctx.reply('Произошла ошибка при загрузке файла.');
|
||
}
|
||
} else {
|
||
await ctx.reply(
|
||
'Пожалуйста, отправьте файл в формате PDF, DOC или DOCX.'
|
||
);
|
||
}
|
||
}
|
||
);
|
||
|
||
// app.post('/api/resume', async (req: Request, res: Response) => {
|
||
// try {
|
||
// const postData = req.body;
|
||
|
||
// // Проверяем наличие chatId в теле запроса
|
||
// if (!postData.StudentID) {
|
||
// throw new Error('Chat ID is missing in the request body');
|
||
// }
|
||
|
||
// const [userInstance, created] = await Resume.findOrCreate({
|
||
// where: { id: postData.StudentID },
|
||
// defaults: {
|
||
// id: postData.StudentID || 0,
|
||
// name: postData.Name,
|
||
// group: postData.Group,
|
||
// type: postData.Type,
|
||
// skills: '',
|
||
// softskills: postData.Soft_skills,
|
||
// email: postData.Email,
|
||
// },
|
||
// });
|
||
// await UserBase.update(
|
||
// { resume_id: postData.StudentID },
|
||
// { where: { id: postData.StudentID } }
|
||
// );
|
||
// if (userInstance instanceof Resume) {
|
||
// // userInstance теперь имеет тип User
|
||
// if (created) {
|
||
// console.log('New user created:', userInstance);
|
||
// } else {
|
||
// console.log('User already exists:', userInstance);
|
||
// }
|
||
|
||
// console.log('Привет! Вы успешно зарегистрированы.');
|
||
// } else {
|
||
// console.error('Ошибка: userInstance не является экземпляром User.');
|
||
// }
|
||
|
||
// console.log('Received data:', postData);
|
||
|
||
// res.status(200).json({ message: 'Data received successfully' });
|
||
// } catch (error) {
|
||
// console.error(error);
|
||
// res.status(500).json({ error: 'Internal Server Error' });
|
||
// }
|
||
// });
|
||
app.post('/webhook', async (req: Request, res: Response) => {
|
||
try {
|
||
const postData = req.body;
|
||
const entityType = postData.data.entity_type;
|
||
const updatedMatches = postData.data.updated_matches;
|
||
console.log(postData);
|
||
|
||
if (entityType === 'job') {
|
||
for (const match of updatedMatches) {
|
||
const chatId = match.student_id;
|
||
const jobId = match.entity_id; // Получаем ID вакансии
|
||
const messageText =
|
||
'Ваше совпадение с вакансией было обновлено. Вот детали вакансии:';
|
||
|
||
if (!chatId) {
|
||
throw new Error('Неправильный Chat ID (student_id) в теле запроса.');
|
||
}
|
||
|
||
// Выполняем GET-запрос для получения данных о вакансии
|
||
const response = await axios.get(
|
||
`${process.env.API}jobs/${jobId}`, // Запрашиваем данные о вакансии по её ID
|
||
{
|
||
headers: {
|
||
'X-API-KEY':
|
||
'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||
},
|
||
}
|
||
);
|
||
|
||
const vacancyData = response.data; // Данные о вакансии
|
||
console.log(vacancyData);
|
||
|
||
// Отправляем вакансию пользователю
|
||
await sendVacancyToUser(chatId, vacancyData);
|
||
|
||
console.log(
|
||
`Сообщение с вакансией отправлено пользователю с ID ${chatId}`
|
||
);
|
||
}
|
||
}
|
||
|
||
return res.status(200).json({ status: 'success' });
|
||
} catch (e) {
|
||
console.error('Error:', e);
|
||
res.status(500).json({ error: 'Произошла ошибка при отправке сообщения' });
|
||
}
|
||
});
|
||
|
||
// Функция для отправки вакансии пользователю
|
||
async function sendVacancyToUser(chatId: number, data: any) {
|
||
await bot.telegram.sendMessage(
|
||
chatId,
|
||
`<b>${data.Job_name}</b>\n\n` +
|
||
`<b>Компания:</b> ${data.Company_name}\n` +
|
||
`<b>Заработная плата:</b> ${data.Salary} руб/мес\n` +
|
||
`<b>Контактные данные:</b> ${data.Email}\n\n` +
|
||
`<b>Требования к кандидату:</b>\n` +
|
||
` - ${data.Year} курс\n` +
|
||
` - Опыт работы по специальности: ${data.Qualification}\n` +
|
||
` - Soft skills: ${data.Soft_skills}\n` +
|
||
`<b>Обязанности:</b>\n` +
|
||
`${data.Responsibilities}`,
|
||
{
|
||
parse_mode: 'HTML', // Используем HTML для форматирования текста
|
||
reply_markup: {
|
||
inline_keyboard: [
|
||
[
|
||
{
|
||
text: 'Сохранить вакансию',
|
||
callback_data: `savevacancy+${data.JobID}`,
|
||
},
|
||
],
|
||
],
|
||
},
|
||
}
|
||
);
|
||
}
|
||
|
||
// Обработчик для получения файла резюме
|
||
|
||
const stage = new Scenes.Stage<MyWizardContext>([resumeScene]);
|
||
bot.use(session());
|
||
bot.use(stage.middleware());
|
||
bot.use(menuSceneMiddleware);
|
||
events(bot);
|
||
accept(bot);
|
||
|
||
bot.action('uploadresume', async ctx => {
|
||
ctx.scene.enter('resumeScene');
|
||
ctx.answerCbQuery();
|
||
});
|
||
|
||
saveVacansy(bot);
|
||
|
||
bot
|
||
.launch({
|
||
webhook: {
|
||
domain: process.env.DOMAIN || '',
|
||
port: parseInt(process.env.HOOKPORT || ''),
|
||
},
|
||
})
|
||
.then(() =>
|
||
console.log(
|
||
'Webhook bot listening on port',
|
||
parseInt(process.env.HOOKPORT || '')
|
||
)
|
||
);
|
||
// bot.launch().then(() => {
|
||
// console.log('Бот запущен');
|
||
// });
|
||
|
||
app.listen(process.env.PORT, () => {
|
||
console.log(`Server is running at http://localhost:${process.env.PORT}`);
|
||
});
|