Files
Tg-job/mtucijobsbot/src/index.ts

470 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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}`);
});