copied the code from the working repo
This commit is contained in:
13
mtucijobsbot/.dockerignore
Normal file
13
mtucijobsbot/.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
||||
**/node_modules
|
||||
*.md
|
||||
*.env
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
**/.npmignore
|
||||
**/.dockerignore
|
||||
**/*.md
|
||||
**/*.log
|
||||
**/.vscode
|
||||
**/.git
|
||||
**/.eslintrc.json
|
||||
*.sh
|
||||
9
mtucijobsbot/.env.development
Normal file
9
mtucijobsbot/.env.development
Normal file
@@ -0,0 +1,9 @@
|
||||
BOT_TOKEN=
|
||||
DB_NAME=
|
||||
DB_HOST=
|
||||
DB_USER=
|
||||
DB_PASSWORD=
|
||||
PORT=
|
||||
HOOKPORT=
|
||||
DOMAIN=
|
||||
WEB_APP=
|
||||
4
mtucijobsbot/.gitignore
vendored
Normal file
4
mtucijobsbot/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.DS_store
|
||||
**/.DS_Store
|
||||
node_modules
|
||||
.env
|
||||
12
mtucijobsbot/Dockerfile
Normal file
12
mtucijobsbot/Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM node:21.5.0-alpine3.19
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm i
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
5
mtucijobsbot/build.sh
Normal file
5
mtucijobsbot/build.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
docker build -f Dockerfile . \
|
||||
-t mtuci-jobs-image:latest \
|
||||
--compress \
|
||||
--force-rm
|
||||
5
mtucijobsbot/deploy.sh
Normal file
5
mtucijobsbot/deploy.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
git pull
|
||||
docker-compose -f docker-compose.yml up -d --build
|
||||
docker exec -t bot-mtuci-jobs ash -c "NODE_ENV=production npx sequelize-cli db:migrate"
|
||||
docker-compose -f docker-compose.yml logs --tail=10 -f
|
||||
40
mtucijobsbot/dist/api/resume.js
vendored
Normal file
40
mtucijobsbot/dist/api/resume.js
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const express_1 = __importDefault(require("express"));
|
||||
require("dotenv/config");
|
||||
const router = express_1.default.Router();
|
||||
router.post('/api/resume', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
try {
|
||||
const postData = req.body;
|
||||
// Проверяем наличие chatId в теле запроса
|
||||
if (!postData.chatId) {
|
||||
throw new Error('Chat ID is missing in the request body');
|
||||
}
|
||||
// Ваша логика обработки данных
|
||||
console.log('Received data:', postData.id);
|
||||
// Отправка сообщения в боте
|
||||
// await req.bot.telegram.sendMessage(
|
||||
// postData.chatId,
|
||||
// `Received data: ${postData.id}`
|
||||
// );
|
||||
// MessagesService.sendMessage(postData.id, )
|
||||
res.status(200).json({ message: 'Data received successfully' });
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
}));
|
||||
exports.default = router;
|
||||
BIN
mtucijobsbot/dist/assets/шаблон МТУСИ.docx
vendored
Normal file
BIN
mtucijobsbot/dist/assets/шаблон МТУСИ.docx
vendored
Normal file
Binary file not shown.
90
mtucijobsbot/dist/commands/start.js
vendored
Normal file
90
mtucijobsbot/dist/commands/start.js
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.startCommand = void 0;
|
||||
const db_1 = require("../db/db");
|
||||
const User_1 = require("../models/User");
|
||||
const axios_1 = __importDefault(require("axios"));
|
||||
function startCommand(ctx) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
||||
function isBotOwner(userId) {
|
||||
const ownerUserId = 1053404914; // Замените этот ID на фактический ID владельца
|
||||
return userId === ownerUserId;
|
||||
}
|
||||
try {
|
||||
yield db_1.sequelize.authenticate(); // Проверка подключения к базе данных
|
||||
yield db_1.UserBase.sync(); // Создание таблицы, если её нет
|
||||
console.log('User table has been created or already exists.');
|
||||
// Проверка, существует ли пользователь в базе данных
|
||||
const [userInstance, created] = yield db_1.UserBase.findOrCreate({
|
||||
where: { id: (_a = ctx.from) === null || _a === void 0 ? void 0 : _a.id },
|
||||
defaults: {
|
||||
id: ((_b = ctx.from) === null || _b === void 0 ? void 0 : _b.id) || 0,
|
||||
username: ((_c = ctx.from) === null || _c === void 0 ? void 0 : _c.username) || '',
|
||||
vacansyindex: 0,
|
||||
role: isBotOwner((_d = ctx.from) === null || _d === void 0 ? void 0 : _d.id) ? User_1.UserRole.OWNER : User_1.UserRole.CLIENT,
|
||||
language: ((_e = ctx.from) === null || _e === void 0 ? void 0 : _e.language_code) || 'ru',
|
||||
message_id: ((_g = (_f = ctx.callbackQuery) === null || _f === void 0 ? void 0 : _f.message) === null || _g === void 0 ? void 0 : _g.message_id) || 0,
|
||||
last_obj: '',
|
||||
chat_id: (_h = ctx.chat) === null || _h === void 0 ? void 0 : _h.id,
|
||||
resume_id: null,
|
||||
},
|
||||
});
|
||||
// Выполняем запрос на удаление резюме, если оно существует
|
||||
try {
|
||||
const response = yield axios_1.default.delete(`${process.env.API}students/${(_j = ctx.from) === null || _j === void 0 ? void 0 : _j.id}`, {
|
||||
headers: {
|
||||
'X-API-KEY': 'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
},
|
||||
});
|
||||
if (response.status === 204) {
|
||||
console.log('Резюме успешно удалено на сервере.');
|
||||
yield db_1.Resume.destroy({ where: { id: (_k = ctx.from) === null || _k === void 0 ? void 0 : _k.id } }); // Удаляем запись из базы данных
|
||||
yield ctx.reply('Ваше резюме было удалено.');
|
||||
}
|
||||
else {
|
||||
console.warn('Не удалось удалить резюме, сервер вернул статус:', response.status);
|
||||
yield ctx.reply('Не удалось удалить ваше резюме. Попробуйте позже.');
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Ошибка при удалении резюме:', error);
|
||||
yield ctx.reply('Произошла ошибка при удалении резюме. Пожалуйста, попробуйте позже.');
|
||||
}
|
||||
// Приветственное сообщение
|
||||
const greetingMessage = {
|
||||
text: 'Вас приветствует бот MTUCI jobs! \n\n' +
|
||||
'Для подтверждения того, что вы студент МТУСИ, вам потребуется привязать вашу учетную запись LMS к проекту MtuciTech (https://mtucitech.ru/) и привязать свой телеграм через бота @apimtucibot. Мы также можем запросить у них некоторые ваши данные, чтобы упростить заполнение анкеты.',
|
||||
buttons: [[{ text: 'Понял', callback: 'accept2' }]],
|
||||
};
|
||||
// Отправка приветственного сообщения с кнопками
|
||||
yield ctx.reply(greetingMessage.text, {
|
||||
reply_markup: {
|
||||
inline_keyboard: greetingMessage.buttons.map(row => row.map(button => ({
|
||||
text: button.text,
|
||||
callback_data: button.callback,
|
||||
}))),
|
||||
},
|
||||
parse_mode: 'HTML',
|
||||
});
|
||||
console.log('Бот запущен');
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Произошла ошибка при запуске бота', e);
|
||||
ctx.reply('Произошла ошибка при запуске. Пожалуйста, попробуйте позже.');
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.startCommand = startCommand;
|
||||
109
mtucijobsbot/dist/db/db.js
vendored
Normal file
109
mtucijobsbot/dist/db/db.js
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Resume = exports.UserBase = exports.sequelize = void 0;
|
||||
require("dotenv/config");
|
||||
const sequelize_1 = require("sequelize");
|
||||
const sequelize = new sequelize_1.Sequelize(process.env.DB_NAME || 'custdev', process.env.DB_USER || 'postgres', process.env.DB_PASSWORD, {
|
||||
host: process.env.DB_HOST,
|
||||
dialect: 'postgres',
|
||||
});
|
||||
exports.sequelize = sequelize;
|
||||
class UserBase extends sequelize_1.Model {
|
||||
}
|
||||
exports.UserBase = UserBase;
|
||||
UserBase.init({
|
||||
id: {
|
||||
type: sequelize_1.DataTypes.BIGINT,
|
||||
primaryKey: true,
|
||||
},
|
||||
username: {
|
||||
type: sequelize_1.DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
vacansyindex: {
|
||||
type: sequelize_1.DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
role: {
|
||||
type: sequelize_1.DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: 'client',
|
||||
},
|
||||
language: {
|
||||
type: sequelize_1.DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
message_id: {
|
||||
type: sequelize_1.DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
last_obj: {
|
||||
type: sequelize_1.DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
chat_id: {
|
||||
type: sequelize_1.DataTypes.BIGINT,
|
||||
allowNull: true,
|
||||
},
|
||||
resume_id: {
|
||||
type: sequelize_1.DataTypes.BIGINT,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'Resumes', // имя таблицы резюме
|
||||
key: 'id',
|
||||
},
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'User',
|
||||
});
|
||||
class Resume extends sequelize_1.Model {
|
||||
}
|
||||
exports.Resume = Resume;
|
||||
Resume.init({
|
||||
id: {
|
||||
type: sequelize_1.DataTypes.BIGINT,
|
||||
primaryKey: true,
|
||||
},
|
||||
name: {
|
||||
type: sequelize_1.DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
group: {
|
||||
type: sequelize_1.DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
type: {
|
||||
type: sequelize_1.DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
skills: {
|
||||
type: sequelize_1.DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
softskills: {
|
||||
type: sequelize_1.DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
email: {
|
||||
type: sequelize_1.DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
resumefile: {
|
||||
type: sequelize_1.DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Resume',
|
||||
});
|
||||
UserBase.belongsTo(Resume, { foreignKey: 'resume_id', as: 'resume' });
|
||||
Resume.hasOne(UserBase, { foreignKey: 'resume_id', as: 'user' });
|
||||
sequelize
|
||||
.sync()
|
||||
.then(() => {
|
||||
console.log('Model has been synchronized successfully.');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error synchronizing model:', error);
|
||||
});
|
||||
375
mtucijobsbot/dist/index.js
vendored
Normal file
375
mtucijobsbot/dist/index.js
vendored
Normal file
@@ -0,0 +1,375 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
require("dotenv/config");
|
||||
const telegraf_1 = require("telegraf");
|
||||
const start_1 = require("./commands/start");
|
||||
const menuScenes_1 = require("./modules/menuScenes");
|
||||
const express_1 = __importDefault(require("express"));
|
||||
const cors_1 = __importDefault(require("cors"));
|
||||
const events_1 = require("./modules/scenes/events");
|
||||
const form_data_1 = __importDefault(require("form-data"));
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const axios_1 = __importDefault(require("axios"));
|
||||
const savevacansy_1 = require("./modules/scenes/savevacansy");
|
||||
const accept_1 = require("./modules/scenes/accept");
|
||||
const app = (0, express_1.default)();
|
||||
const bot = new telegraf_1.Telegraf(process.env.BOT_TOKEN);
|
||||
app.use((req, res, next) => {
|
||||
req.bot = bot;
|
||||
next();
|
||||
});
|
||||
app.use(express_1.default.json());
|
||||
app.use((0, cors_1.default)());
|
||||
bot.start(start_1.startCommand);
|
||||
app.post('/api/resume', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
try {
|
||||
const postData = req.body;
|
||||
const resumeTemplatePath = path_1.default.resolve(__dirname, 'assets', 'шаблон МТУСИ.docx');
|
||||
yield bot.telegram.sendDocument(postData.id, { source: resumeTemplatePath }, { caption: 'Пример резюме' });
|
||||
yield 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 = new telegraf_1.Scenes.WizardScene('resumeScene', (ctx) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
yield ctx.reply('Отправьте своё резюме в формате Word (DOC/DOCX) или отправьте /cancel для отмены.');
|
||||
return ctx.wizard.next();
|
||||
}), (ctx) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
if (ctx.message &&
|
||||
'text' in ctx.message &&
|
||||
ctx.from &&
|
||||
ctx.message.text === '/cancel') {
|
||||
yield ctx.reply('Отправка резюме отменена.');
|
||||
yield ctx.reply('Меню', telegraf_1.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 || '')) {
|
||||
yield ctx.reply('Пожалуйста, отправьте файл в формате DOC или DOCX.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const fileLink = yield ctx.telegram.getFileLink(file.file_id);
|
||||
const fileExtension = file.mime_type === 'application/pdf'
|
||||
? 'pdf'
|
||||
: file.mime_type === 'application/msword'
|
||||
? 'doc'
|
||||
: 'docx';
|
||||
const filePath = path_1.default.join(__dirname, `${file.file_id}.${fileExtension}`);
|
||||
// Загрузка файла на локальную машину
|
||||
const response = yield axios_1.default.get(fileLink.href, {
|
||||
responseType: 'stream',
|
||||
});
|
||||
response.data
|
||||
.pipe(fs_1.default.createWriteStream(filePath))
|
||||
.on('finish', () => __awaiter(void 0, void 0, void 0, function* () {
|
||||
var _a;
|
||||
const form = new form_data_1.default();
|
||||
form.append('file', fs_1.default.createReadStream(filePath));
|
||||
try {
|
||||
const uploadResponse = yield axios_1.default.post(`${process.env.API}services/resume/`, form, {
|
||||
headers: Object.assign(Object.assign({}, form.getHeaders()), { 'X-API-KEY': 'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n' }),
|
||||
timeout: 10000,
|
||||
});
|
||||
const fileName = uploadResponse.data.filename;
|
||||
yield axios_1.default.patch(`${process.env.API}students/${(_a = ctx.from) === null || _a === void 0 ? void 0 : _a.id}?Link=${encodeURIComponent(fileName)}`, {}, {
|
||||
headers: {
|
||||
'X-API-KEY': 'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
yield ctx.reply('Резюме успешно загружено.');
|
||||
yield ctx.reply('Меню', telegraf_1.Markup.keyboard([
|
||||
['Моя анкета'],
|
||||
['Вакансии'],
|
||||
['Уведомления: включены'],
|
||||
]).resize());
|
||||
return ctx.scene.leave();
|
||||
}
|
||||
catch (uploadError) {
|
||||
console.error('Ошибка при загрузке резюме на API:', uploadError);
|
||||
yield ctx.reply('Произошла ошибка при загрузке резюме.');
|
||||
return ctx.scene.leave();
|
||||
}
|
||||
finally {
|
||||
fs_1.default.unlinkSync(filePath);
|
||||
}
|
||||
}));
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Ошибка при загрузке файла:', error);
|
||||
yield ctx.reply('Произошла ошибка при загрузке файла.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
yield 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', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
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 = yield axios_1.default.get(`${process.env.API}jobs/${jobId}`, // Запрашиваем данные о вакансии по её ID
|
||||
{
|
||||
headers: {
|
||||
'X-API-KEY': 'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
},
|
||||
});
|
||||
const vacancyData = response.data; // Данные о вакансии
|
||||
console.log(vacancyData);
|
||||
// Отправляем вакансию пользователю
|
||||
yield 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: 'Произошла ошибка при отправке сообщения' });
|
||||
}
|
||||
}));
|
||||
// Функция для отправки вакансии пользователю
|
||||
function sendVacancyToUser(chatId, data) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
yield 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 telegraf_1.Scenes.Stage([resumeScene]);
|
||||
bot.use((0, telegraf_1.session)());
|
||||
bot.use(stage.middleware());
|
||||
bot.use(menuScenes_1.menuSceneMiddleware);
|
||||
(0, events_1.events)(bot);
|
||||
(0, accept_1.accept)(bot);
|
||||
bot.action('uploadresume', (ctx) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
ctx.scene.enter('resumeScene');
|
||||
ctx.answerCbQuery();
|
||||
}));
|
||||
(0, savevacansy_1.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}`);
|
||||
});
|
||||
24
mtucijobsbot/dist/languages/en.json
vendored
Normal file
24
mtucijobsbot/dist/languages/en.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"greeting": {
|
||||
"photo": "",
|
||||
"file": "",
|
||||
"text": "Вас приветствует бот MTUCI jobs! \n\nДля подтверждения того, что вы студент МТУСИ вам потребуется привязать вашу учетную запись lms к проекту MtuciTech (https://mtucitech.ru/) и привязать свой телеграм через бота @apimtucibot. Мы также можем запросить у них некоторые ваши данные, чтобы упростить заполнение анкеты",
|
||||
"buttons": [
|
||||
[
|
||||
{ "text": "Понял", "callback": "accept" },
|
||||
{ "text": "Понял, не запрашивать данные", "callback": "accept" }
|
||||
]
|
||||
]
|
||||
},
|
||||
"greeting2": {
|
||||
"photo": "",
|
||||
"file": "",
|
||||
"text": "Вас приветствует бот MTUCI jobs! \n\nДля подтверждения того, что вы студент МТУСИ вам потребуется привязать вашу учетную запись lms к проекту MtuciTech (https://mtucitech.ru/) и привязать свой телеграм через бота @apimtucibot. Мы также можем запросить у них некоторые ваши данные, чтобы упростить заполнение анкеты",
|
||||
"buttons": [
|
||||
[
|
||||
{ "text": "Понял", "callback": "accept2" },
|
||||
{ "text": "Понял, не запрашивать данные", "callback": "accept2" }
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
24
mtucijobsbot/dist/languages/ru.json
vendored
Normal file
24
mtucijobsbot/dist/languages/ru.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"greeting": {
|
||||
"photo": "",
|
||||
"file": "",
|
||||
"text": "Вас приветствует бот MTUCI jobs! \n\nДля подтверждения того, что вы студент МТУСИ вам потребуется привязать вашу учетную запись lms к проекту MtuciTech (https://mtucitech.ru/) и привязать свой телеграм через бота @apimtucibot. Мы также можем запросить у них некоторые ваши данные, чтобы упростить заполнение анкеты",
|
||||
"buttons": [
|
||||
[
|
||||
{ "text": "Понял", "callback": "accept" },
|
||||
{ "text": "Понял, не запрашивать данные", "callback": "accept" }
|
||||
]
|
||||
]
|
||||
},
|
||||
"greeting2": {
|
||||
"photo": "",
|
||||
"file": "",
|
||||
"text": "Вас приветствует бот MTUCI jobs! \n\nДля подтверждения того, что вы студент МТУСИ вам потребуется привязать вашу учетную запись lms к проекту MtuciTech (https://mtucitech.ru/) и привязать свой телеграм через бота @apimtucibot. Мы также можем запросить у них некоторые ваши данные, чтобы упростить заполнение анкеты",
|
||||
"buttons": [
|
||||
[
|
||||
{ "text": "Понял", "callback": "accept2" },
|
||||
{ "text": "Понял, не запрашивать данные", "callback": "accept2" }
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
11
mtucijobsbot/dist/models/User.js
vendored
Normal file
11
mtucijobsbot/dist/models/User.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.UserRole = void 0;
|
||||
// Перечисление ролей пользователя
|
||||
var UserRole;
|
||||
(function (UserRole) {
|
||||
UserRole["OWNER"] = "owner";
|
||||
UserRole["ADMIN"] = "admin";
|
||||
UserRole["MODERATOR"] = "moderator";
|
||||
UserRole["CLIENT"] = "client";
|
||||
})(UserRole || (exports.UserRole = UserRole = {}));
|
||||
50
mtucijobsbot/dist/modules/menuController.js
vendored
Normal file
50
mtucijobsbot/dist/modules/menuController.js
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.MenuController = void 0;
|
||||
const db_1 = require("../db/db");
|
||||
class MenuController {
|
||||
static showLanguageMenu(ctx) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// await MessagesService.sendMessage(ctx, 'greeting');
|
||||
});
|
||||
}
|
||||
static setLanguage(ctx) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
var _a;
|
||||
try {
|
||||
if (!ctx.callbackQuery) {
|
||||
throw new Error('CallbackQuery is not defined.');
|
||||
}
|
||||
const languageCode = ctx.callbackQuery && 'data' in ctx.callbackQuery
|
||||
? ctx.callbackQuery.data.split('_')[1]
|
||||
: undefined;
|
||||
if (languageCode) {
|
||||
// Обновляем язык пользователя в базе данных
|
||||
yield db_1.UserBase.update({ language: languageCode }, { where: { id: (_a = ctx.from) === null || _a === void 0 ? void 0 : _a.id } });
|
||||
// Обновляем язык в объекте контекста
|
||||
if (ctx.from) {
|
||||
ctx.from.language_code = languageCode;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Если data отсутствует в callbackQuery, обработка не может быть выполнена
|
||||
throw new Error('Missing data property in callbackQuery.');
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error during setLanguage:', error);
|
||||
console.log('Произошла ошибка при обновлении языка. Пожалуйста, попробуйте снова позже.');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.MenuController = MenuController;
|
||||
38
mtucijobsbot/dist/modules/menuScenes.js
vendored
Normal file
38
mtucijobsbot/dist/modules/menuScenes.js
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.menuSceneMiddleware = void 0;
|
||||
const db_1 = require("../db/db");
|
||||
const messagesService_1 = require("../services/messagesService");
|
||||
// Middleware для обработки callback'ов
|
||||
const callbackQueryMiddleware = (ctx, next) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
var _a;
|
||||
try {
|
||||
const languageCode = ctx.callbackQuery && 'data' in ctx.callbackQuery
|
||||
? ctx.callbackQuery.data.split('_')[1]
|
||||
: undefined;
|
||||
// Обновляем язык пользователя в базе данных
|
||||
if (languageCode && ((_a = ctx.from) === null || _a === void 0 ? void 0 : _a.id)) {
|
||||
yield db_1.UserBase.update({ language: languageCode }, { where: { id: ctx.from.id } });
|
||||
// Отправляем сообщение об успешном обновлении языка
|
||||
console.log(`Язык обновлен: ${languageCode}`);
|
||||
yield ctx.answerCbQuery();
|
||||
yield messagesService_1.MessagesService.sendMessage(ctx, 'greeting');
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error during callback handling:', error);
|
||||
console.log("Произошла ошибка при обработке callback'а. Пожалуйста, попробуйте снова позже.");
|
||||
}
|
||||
// Вызываем следующий middleware
|
||||
return next();
|
||||
});
|
||||
exports.menuSceneMiddleware = callbackQueryMiddleware;
|
||||
112
mtucijobsbot/dist/modules/scenes/accept.js
vendored
Normal file
112
mtucijobsbot/dist/modules/scenes/accept.js
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.accept = void 0;
|
||||
const axios_1 = __importDefault(require("axios"));
|
||||
require("dotenv/config");
|
||||
// const API_BASE_URL = 'https://mtucitech.ru/api/external/mtuci_jobs';
|
||||
// const BEARER_TOKEN = 'zKbgaXQv{pvGtQm~U9$urtD#QsiHc@Ie';
|
||||
function accept(bot) {
|
||||
bot.action('accept', (ctx) => __awaiter(this, void 0, void 0, function* () {
|
||||
var _a;
|
||||
try {
|
||||
const response = yield axios_1.default.get(`${process.env.MTUCI_TECH}/check?telegram_id=${ctx.from.id}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.BEARER_TOKEN}`,
|
||||
},
|
||||
});
|
||||
const data = response.data;
|
||||
// Логика обработки ответа
|
||||
switch (data.status) {
|
||||
case 338:
|
||||
ctx.answerCbQuery('Ошибка на стороне сервера. Пожалуйста, сообщите нам.');
|
||||
break;
|
||||
case 339:
|
||||
ctx.answerCbQuery('Пользователь не привязан к проекту.');
|
||||
break;
|
||||
case 340:
|
||||
ctx.reply('Можете заполнить своё резюме', {
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Резюме',
|
||||
web_app: { url: process.env.WEB_APP || '' },
|
||||
},
|
||||
],
|
||||
],
|
||||
resize_keyboard: true,
|
||||
one_time_keyboard: true,
|
||||
},
|
||||
});
|
||||
ctx.answerCbQuery('Пользователь найден, но не привязан LMS.');
|
||||
break;
|
||||
case 341:
|
||||
ctx.reply('Можете заполнить своё резюме', {
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Резюме',
|
||||
web_app: { url: process.env.WEB_APP || '' },
|
||||
},
|
||||
],
|
||||
],
|
||||
resize_keyboard: true,
|
||||
one_time_keyboard: true,
|
||||
},
|
||||
});
|
||||
ctx.answerCbQuery('Пользователь найден, LMS привязан. Можно запросить дополнительные данные.');
|
||||
default:
|
||||
ctx.answerCbQuery('Неизвестный статус.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
if (axios_1.default.isAxiosError(error)) {
|
||||
// Обработка ошибок Axios
|
||||
if (error.response && error.response.status === 409) {
|
||||
ctx.answerCbQuery('Вакансия уже добавлена в избранное');
|
||||
}
|
||||
else {
|
||||
ctx.answerCbQuery('Произошла ошибка');
|
||||
console.error(((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) || error.message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Обработка других типов ошибок
|
||||
ctx.answerCbQuery('Произошла неизвестная ошибка');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}));
|
||||
bot.action('accept2', (ctx) => __awaiter(this, void 0, void 0, function* () {
|
||||
ctx.reply('Можете заполнить своё резюме', {
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Резюме',
|
||||
web_app: { url: process.env.WEB_APP || '' },
|
||||
},
|
||||
],
|
||||
],
|
||||
resize_keyboard: true,
|
||||
one_time_keyboard: true,
|
||||
},
|
||||
});
|
||||
ctx.answerCbQuery();
|
||||
}));
|
||||
}
|
||||
exports.accept = accept;
|
||||
292
mtucijobsbot/dist/modules/scenes/events.js
vendored
Normal file
292
mtucijobsbot/dist/modules/scenes/events.js
vendored
Normal file
@@ -0,0 +1,292 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.events = void 0;
|
||||
const telegraf_1 = require("telegraf");
|
||||
const db_1 = require("../../db/db");
|
||||
const axios_1 = __importDefault(require("axios"));
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
let vacancies = [];
|
||||
function events(bot) {
|
||||
bot.action('skip', (ctx) => __awaiter(this, void 0, void 0, function* () {
|
||||
ctx.reply('Меню', telegraf_1.Markup.keyboard([
|
||||
['Моя анкета'],
|
||||
['Вакансии'],
|
||||
// ['Уведомления: включены'],
|
||||
]).resize());
|
||||
ctx.answerCbQuery();
|
||||
}));
|
||||
bot.hears('Вернуться в меню', (ctx) => __awaiter(this, void 0, void 0, function* () {
|
||||
ctx.reply('Меню', telegraf_1.Markup.keyboard([
|
||||
['Моя анкета'],
|
||||
['Вакансии'],
|
||||
// ['Уведомления: включены'],
|
||||
]).resize());
|
||||
}));
|
||||
bot.hears('Моя анкета', (ctx) => __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
// Выполнение первого GET-запроса для получения данных пользователя
|
||||
const response = yield axios_1.default.get(`${process.env.API}students/${ctx.from.id}`, {
|
||||
headers: {
|
||||
'X-API-KEY': 'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
},
|
||||
});
|
||||
const data = response.data;
|
||||
const resumeFile = yield db_1.Resume.findOne({ where: { id: ctx.from.id } });
|
||||
// Проверяем, существует ли резюме
|
||||
if (resumeFile && resumeFile.resumefile.length > 1) {
|
||||
// Выполнение второго GET-запроса для получения файла с резюме
|
||||
const resumeResponse = yield (0, axios_1.default)({
|
||||
method: 'GET',
|
||||
url: `${process.env.API}services/resume/${resumeFile.resumefile}`,
|
||||
headers: {
|
||||
'X-API-KEY': 'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
},
|
||||
responseType: 'stream',
|
||||
timeout: 10000,
|
||||
});
|
||||
// Сохраняем файл временно на диск
|
||||
const resumePath = path_1.default.join(__dirname, 'resume.pdf');
|
||||
const writer = fs_1.default.createWriteStream(resumePath);
|
||||
resumeResponse.data.pipe(writer);
|
||||
writer.on('finish', () => __awaiter(this, void 0, void 0, function* () {
|
||||
// Отправка файла и сообщения с данными из API
|
||||
yield ctx.replyWithDocument({ source: resumePath });
|
||||
yield ctx.replyWithHTML(`<b>Имя:</b> ${data.Name}\n<b>Группа:</b> ${data.Group}\n<b>Тип:</b> ${data.Type}\n<b>Готов на занятость:</b> ${data.Time.join()}\n<b>О себе:</b> ${data.Soft_skills}\n<b>Почта:</b> ${data.Email}\n`, {
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Редактировать анкету',
|
||||
web_app: { url: process.env.WEB_APP || '' },
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
text: 'Обновить резюме',
|
||||
callback_data: 'update_resume',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
});
|
||||
// Удаляем временный файл
|
||||
fs_1.default.unlinkSync(resumePath);
|
||||
}));
|
||||
writer.on('error', err => {
|
||||
console.error('Ошибка при записи файла резюме:', err);
|
||||
ctx.reply('Произошла ошибка при получении файла резюме.');
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Отправка только сообщения с данными из API, если резюме не существует
|
||||
yield ctx.replyWithHTML(`<b>Имя:</b> ${data.Name}\n<b>Группа:</b> ${data.Group}\n<b>Тип:</b> ${data.Type}\n<b>Готов на занятость:</b> ${data.Time.join()}\n<b>О себе:</b> ${data.Soft_skills}\n<b>Почта:</b> ${data.Email}\n`, {
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Редактировать',
|
||||
web_app: { url: process.env.WEB_APP || '' },
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
text: 'Загрузить резюме',
|
||||
callback_data: 'update_resume',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Ошибка при выполнении запроса к API:', error);
|
||||
ctx.reply('Произошла ошибка при получении данных из API.');
|
||||
}
|
||||
}));
|
||||
bot.hears('Вакансии', (ctx) => __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
yield ctx.reply('Выберите тип вакансии:', telegraf_1.Markup.keyboard([
|
||||
['Актуальные вакансии'],
|
||||
['Сохраненные вакансии'],
|
||||
['Поиск'],
|
||||
['Вернуться в меню'],
|
||||
]).resize());
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Ошибка при выполнении запроса к API:', error);
|
||||
ctx.reply('Произошла ошибка при получении данных из API.');
|
||||
}
|
||||
}));
|
||||
bot.hears('Поиск', (ctx) => __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
yield ctx.reply('Поиск вакансий:', telegraf_1.Markup.keyboard([
|
||||
['Актуальные вакансии'],
|
||||
['Сохраненные вакансии'],
|
||||
['Вернуться в меню'],
|
||||
]).resize());
|
||||
ctx.reply('Страница с поиском вакансий', {
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Перейти',
|
||||
web_app: { url: process.env.WEB_APP_SEARCH || '' },
|
||||
},
|
||||
],
|
||||
],
|
||||
resize_keyboard: true,
|
||||
one_time_keyboard: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Ошибка при выполнении запроса к API:', error);
|
||||
ctx.reply('Произошла ошибка при получении данных из API.');
|
||||
}
|
||||
}));
|
||||
bot.hears('Актуальные вакансии', (ctx) => __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
yield db_1.UserBase.update({ vacansyindex: 0 }, {
|
||||
where: {
|
||||
id: ctx.from.id,
|
||||
},
|
||||
});
|
||||
// Выполнение первого GET-запроса для получения данных пользователя
|
||||
const response = yield axios_1.default.get(`${process.env.API}students/matches/${ctx.from.id}`, {
|
||||
headers: {
|
||||
'X-API-KEY': 'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
},
|
||||
});
|
||||
vacancies = response.data;
|
||||
console.log(vacancies);
|
||||
if (vacancies.length === 0) {
|
||||
ctx.reply('На данный момент нет актуальных вакансий.');
|
||||
return;
|
||||
}
|
||||
let user = yield db_1.UserBase.findOne({ where: { id: ctx.from.id } });
|
||||
if (user) {
|
||||
let currentIndex = user === null || user === void 0 ? void 0 : user.vacansyindex;
|
||||
yield sendVacancy(ctx, currentIndex, true);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Ошибка при выполнении запроса к API:', error);
|
||||
ctx.reply('Произошла ошибка при получении данных из API.');
|
||||
}
|
||||
}));
|
||||
bot.hears('Сохраненные вакансии', (ctx) => __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
yield db_1.UserBase.update({ vacansyindex: 0 }, {
|
||||
where: {
|
||||
id: ctx.from.id,
|
||||
},
|
||||
});
|
||||
// Выполнение первого GET-запроса для получения данных пользователя
|
||||
const response = yield axios_1.default.get(`${process.env.API}students/favourites/${ctx.from.id}`, {
|
||||
headers: {
|
||||
'X-API-KEY': 'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
},
|
||||
});
|
||||
vacancies = response.data;
|
||||
console.log(vacancies);
|
||||
if (vacancies.length === 0) {
|
||||
ctx.reply('На данный момент нет сохранённых вакансий.');
|
||||
return;
|
||||
}
|
||||
let user = yield db_1.UserBase.findOne({ where: { id: ctx.from.id } });
|
||||
if (user) {
|
||||
let currentIndex = user === null || user === void 0 ? void 0 : user.vacansyindex;
|
||||
yield sendVacancy(ctx, currentIndex, false);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Ошибка при выполнении запроса к API:', error);
|
||||
ctx.reply('На данный момент нет сохранённых вакансий.');
|
||||
// ctx.reply('Произошла ошибка при получении данных из API.');
|
||||
}
|
||||
}));
|
||||
bot.action('next', (ctx) => __awaiter(this, void 0, void 0, function* () {
|
||||
let user = yield db_1.UserBase.findOne({ where: { id: ctx.from.id } });
|
||||
if (user) {
|
||||
let currentIndex = (user === null || user === void 0 ? void 0 : user.vacansyindex) + 1;
|
||||
if (currentIndex <= vacancies.length - 1) {
|
||||
yield db_1.UserBase.update({ vacansyindex: currentIndex }, { where: { id: ctx.from.id } });
|
||||
yield sendVacancy(ctx, currentIndex, true);
|
||||
ctx.answerCbQuery();
|
||||
}
|
||||
else {
|
||||
ctx.answerCbQuery('Это последняя вакансия.');
|
||||
}
|
||||
}
|
||||
}));
|
||||
bot.action('update_resume', (ctx) => __awaiter(this, void 0, void 0, function* () {
|
||||
yield ctx.scene.enter('resumeScene');
|
||||
yield ctx.answerCbQuery();
|
||||
}));
|
||||
bot.action('prev', (ctx) => __awaiter(this, void 0, void 0, function* () {
|
||||
let user = yield db_1.UserBase.findOne({ where: { id: ctx.from.id } });
|
||||
if (user) {
|
||||
let currentIndex = (user === null || user === void 0 ? void 0 : user.vacansyindex) - 1;
|
||||
if (currentIndex >= 0) {
|
||||
yield db_1.UserBase.update({ vacansyindex: currentIndex }, { where: { id: ctx.from.id } });
|
||||
yield sendVacancy(ctx, currentIndex, true);
|
||||
ctx.answerCbQuery();
|
||||
}
|
||||
else {
|
||||
ctx.answerCbQuery('Это первая вакансия.');
|
||||
}
|
||||
}
|
||||
}));
|
||||
function sendVacancy(ctx, index, showSaveButton) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const data = vacancies[index];
|
||||
// Формируем кнопки навигации
|
||||
let inlineKeyboard = [
|
||||
[
|
||||
{ text: 'Назад', callback_data: 'prev' },
|
||||
{ text: `${index + 1}/${vacancies.length}`, callback_data: 'new' },
|
||||
{ text: 'Далее', callback_data: 'next' },
|
||||
],
|
||||
];
|
||||
// Добавляем кнопку "Сохранить вакансию" только если showSaveButton = true
|
||||
if (showSaveButton) {
|
||||
inlineKeyboard.push([
|
||||
{
|
||||
text: 'Сохранить вакансию',
|
||||
callback_data: `savevacancy+${data.JobID}`,
|
||||
},
|
||||
]);
|
||||
}
|
||||
// Отправляем сообщение с вакансиями
|
||||
yield ctx.replyWithHTML(`<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}`, {
|
||||
reply_markup: {
|
||||
inline_keyboard: inlineKeyboard,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.events = events;
|
||||
39
mtucijobsbot/dist/modules/scenes/invest.js
vendored
Normal file
39
mtucijobsbot/dist/modules/scenes/invest.js
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
"use strict";
|
||||
// import { Telegraf, Context, Scenes } from 'telegraf';
|
||||
// import { MessagesService } from '../../services/messagesService';
|
||||
// import fs from 'fs';
|
||||
// import fetch from 'node-fetch';
|
||||
// import { MyWizardContext } from '../..';
|
||||
// import { updateUrls } from '../../db/db';
|
||||
// export function invest(bot: Telegraf<Scenes.WizardContext>) {
|
||||
// bot.action('invest', async ctx => {
|
||||
// await MessagesService.sendMessage(ctx, 'invest');
|
||||
// await ctx.answerCbQuery();
|
||||
// });
|
||||
// bot.action('investscreen', async ctx => {
|
||||
// await ctx.scene.enter('investHandler');
|
||||
// await ctx.answerCbQuery();
|
||||
// });
|
||||
// }
|
||||
// export const investHandler: Scenes.WizardScene<MyWizardContext> =
|
||||
// new Scenes.WizardScene(
|
||||
// 'investHandler',
|
||||
// async ctx => {
|
||||
// await MessagesService.sendMessage(ctx, 'getpoint');
|
||||
// return ctx.wizard.next();
|
||||
// },
|
||||
// async ctx => {
|
||||
// if (ctx.message && 'text' in ctx.message && ctx.from) {
|
||||
// const userUrls = ctx.message.text;
|
||||
// await updateUrls(ctx.from.id, 'invest', userUrls, true);
|
||||
// await MessagesService.sendMessage(ctx, 'thanks');
|
||||
// await MessagesService.sendMessage(ctx, 'invest');
|
||||
// return ctx.scene.leave();
|
||||
// } else {
|
||||
// console.error(
|
||||
// 'Ошибка: отсутствует текстовое сообщение или свойство from в контексте.'
|
||||
// );
|
||||
// return ctx.scene.leave();
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
55
mtucijobsbot/dist/modules/scenes/savevacansy.js
vendored
Normal file
55
mtucijobsbot/dist/modules/scenes/savevacansy.js
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.saveVacansy = void 0;
|
||||
const axios_1 = __importDefault(require("axios"));
|
||||
require("dotenv/config");
|
||||
function saveVacansy(bot) {
|
||||
bot.on('callback_query', (ctx) => __awaiter(this, void 0, void 0, function* () {
|
||||
var _a;
|
||||
if (ctx.callbackQuery && 'data' in ctx.callbackQuery) {
|
||||
const callbackData = ctx.callbackQuery.data;
|
||||
if (callbackData.startsWith('savevacancy+')) {
|
||||
const jobID = callbackData.split('+')[1];
|
||||
try {
|
||||
const data = yield axios_1.default.post(`${process.env.API}students/favourites/`, { StudentID: ctx.from.id, JobID: jobID }, {
|
||||
headers: {
|
||||
'X-API-KEY': 'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
},
|
||||
});
|
||||
console.log(data);
|
||||
ctx.answerCbQuery('Вакансия была сохранена');
|
||||
}
|
||||
catch (error) {
|
||||
if (axios_1.default.isAxiosError(error)) {
|
||||
// Обработка ошибок Axios
|
||||
if (error.response && error.response.status === 409) {
|
||||
ctx.answerCbQuery('Вакансия уже добавлена в избранное');
|
||||
}
|
||||
else {
|
||||
ctx.answerCbQuery('Произошла ошибка при сохранении вакансии');
|
||||
console.error(((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) || error.message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Обработка других типов ошибок
|
||||
ctx.answerCbQuery('Произошла неизвестная ошибка');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
exports.saveVacansy = saveVacansy;
|
||||
277
mtucijobsbot/dist/services/messagesService.js
vendored
Normal file
277
mtucijobsbot/dist/services/messagesService.js
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.MessagesService = void 0;
|
||||
// Импортируем необходимые модули
|
||||
const fs = __importStar(require("fs"));
|
||||
// import { CustomContext } from '../types/telegram';
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const db_1 = require("../db/db");
|
||||
const telegraf_1 = require("telegraf");
|
||||
class MessagesService {
|
||||
static loadMessages(lang) {
|
||||
if (!this.messages[lang]) {
|
||||
const filePath = path_1.default.join(__dirname, `../languages/${lang}.json`);
|
||||
const data = fs.readFileSync(filePath, 'utf-8');
|
||||
this.messages[lang] = JSON.parse(data);
|
||||
}
|
||||
return this.messages[lang];
|
||||
}
|
||||
static backMessage(ctx) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
var _a, _b, _c;
|
||||
try {
|
||||
const user = (yield db_1.UserBase.findOne({
|
||||
where: {
|
||||
id: (_a = ctx.from) === null || _a === void 0 ? void 0 : _a.id,
|
||||
},
|
||||
}));
|
||||
if (user !== null && ctx.chat) {
|
||||
yield ctx.telegram.copyMessage((_b = ctx.chat) === null || _b === void 0 ? void 0 : _b.id, (_c = ctx.chat) === null || _c === void 0 ? void 0 : _c.id, user.message_id - 1);
|
||||
}
|
||||
else {
|
||||
console.log('error');
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
static hiddenMessage(ctx, key) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
var _a, _b, _c, _d, _e, _f;
|
||||
try {
|
||||
const user = (yield db_1.UserBase.findOne({
|
||||
where: {
|
||||
id: (_a = ctx.from) === null || _a === void 0 ? void 0 : _a.id,
|
||||
},
|
||||
}));
|
||||
if (user && user.message_id && ((_b = ctx.callbackQuery) === null || _b === void 0 ? void 0 : _b.message)) {
|
||||
yield ctx.telegram.deleteMessage(((_c = ctx.chat) === null || _c === void 0 ? void 0 : _c.id) || 0, (_e = (_d = ctx.callbackQuery) === null || _d === void 0 ? void 0 : _d.message) === null || _e === void 0 ? void 0 : _e.message_id);
|
||||
console.log('Hidden message deleted successfully.');
|
||||
yield db_1.UserBase.update({ last_obj: key }, // Новое значение
|
||||
{
|
||||
where: {
|
||||
id: (_f = ctx.from) === null || _f === void 0 ? void 0 : _f.id, // Условие для выбора записей
|
||||
},
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.log('User or message not found.');
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error deleting last message:', error);
|
||||
ctx.reply('An error occurred while deleting the last message. Please try again later.');
|
||||
}
|
||||
});
|
||||
}
|
||||
static deleteLastMessage(ctx) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h;
|
||||
try {
|
||||
const user = (yield db_1.UserBase.findOne({
|
||||
where: {
|
||||
id: (_a = ctx.from) === null || _a === void 0 ? void 0 : _a.id,
|
||||
},
|
||||
}));
|
||||
if (user && user.message_id && ((_b = ctx.callbackQuery) === null || _b === void 0 ? void 0 : _b.message)) {
|
||||
yield ctx.telegram.deleteMessage(((_c = ctx.chat) === null || _c === void 0 ? void 0 : _c.id) || 0, (_e = (_d = ctx.callbackQuery) === null || _d === void 0 ? void 0 : _d.message) === null || _e === void 0 ? void 0 : _e.message_id);
|
||||
console.log('Last message deleted successfully.');
|
||||
yield db_1.UserBase.update({ message_id: (_g = (_f = ctx.callbackQuery) === null || _f === void 0 ? void 0 : _f.message) === null || _g === void 0 ? void 0 : _g.message_id }, // Новое значение
|
||||
{
|
||||
where: {
|
||||
id: (_h = ctx.from) === null || _h === void 0 ? void 0 : _h.id, // Условие для выбора записей
|
||||
},
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.log('User or message not found.');
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error deleting last message:', error);
|
||||
ctx.reply('An error occurred while deleting the last message. Please try again later.');
|
||||
}
|
||||
});
|
||||
}
|
||||
static sendMessage(ctx, key) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
||||
try {
|
||||
const user = (yield db_1.UserBase.findOne({
|
||||
where: {
|
||||
id: (_a = ctx.from) === null || _a === void 0 ? void 0 : _a.id,
|
||||
},
|
||||
}));
|
||||
if (user !== null) {
|
||||
const lang = user.language;
|
||||
const messages = this.loadMessages(lang);
|
||||
const message = messages[key];
|
||||
if (message) {
|
||||
// Определяем, есть ли у сообщения фотография
|
||||
const hasPhoto = message.photo !== '';
|
||||
const hasFile = message.file !== '';
|
||||
if (hasPhoto &&
|
||||
message.buttons &&
|
||||
message.buttons.length > 0 &&
|
||||
message.photo) {
|
||||
const photoPath = path_1.default.join(__dirname, message.photo);
|
||||
if (message.buttons && message.buttons.length > 0) {
|
||||
const rows = message.buttons;
|
||||
if (Array.isArray(rows) &&
|
||||
rows.every(row => Array.isArray(row))) {
|
||||
const sentMessage = yield ctx.replyWithPhoto({ source: fs.createReadStream(photoPath) || '' }, Object.assign({ protect_content: true, caption: message.text, parse_mode: 'HTML' }, telegraf_1.Markup.inlineKeyboard(rows.map(row => row.map(button => {
|
||||
if (button.url) {
|
||||
return telegraf_1.Markup.button.url(button.text, button.url);
|
||||
}
|
||||
else {
|
||||
return telegraf_1.Markup.button.callback(button.text, button.callback);
|
||||
}
|
||||
})))));
|
||||
if (sentMessage.message_id) {
|
||||
yield db_1.UserBase.update({ message_id: sentMessage.message_id }, {
|
||||
where: {
|
||||
id: (_b = ctx.from) === null || _b === void 0 ? void 0 : _b.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error('rows должен быть массивом массивов');
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (hasPhoto && message.photo) {
|
||||
const photoPath = path_1.default.join(__dirname, message.photo);
|
||||
const sentMessage = yield ctx.replyWithPhoto({ source: fs.createReadStream(photoPath) || '' }, {
|
||||
protect_content: true,
|
||||
caption: message.text,
|
||||
parse_mode: 'HTML',
|
||||
});
|
||||
if (sentMessage.message_id) {
|
||||
yield db_1.UserBase.update({ message_id: sentMessage.message_id }, {
|
||||
where: {
|
||||
id: (_c = ctx.from) === null || _c === void 0 ? void 0 : _c.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (hasFile && message.file) {
|
||||
const pdfFilePath = path_1.default.join(__dirname, message.file);
|
||||
const fileName = path_1.default.basename(pdfFilePath);
|
||||
// Отправляем файл
|
||||
const sentMessage = yield ctx.replyWithDocument({
|
||||
source: fs.createReadStream(pdfFilePath) || '',
|
||||
filename: fileName,
|
||||
}, {
|
||||
protect_content: true,
|
||||
caption: message.text,
|
||||
parse_mode: 'HTML',
|
||||
});
|
||||
if (sentMessage.message_id) {
|
||||
yield db_1.UserBase.update({ message_id: sentMessage.message_id }, {
|
||||
where: {
|
||||
id: (_d = ctx.from) === null || _d === void 0 ? void 0 : _d.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Отправляем текст и кнопки, если они есть
|
||||
if (message.buttons && message.buttons.length > 0) {
|
||||
const rows = message.buttons;
|
||||
if (Array.isArray(rows) &&
|
||||
rows.every(row => Array.isArray(row))) {
|
||||
const sentMessage = yield ctx.reply(message.text, Object.assign({ protect_content: true, parse_mode: 'HTML' }, telegraf_1.Markup.inlineKeyboard(rows.map(row => row.map(button => {
|
||||
if (button.url) {
|
||||
return telegraf_1.Markup.button.url(button.text, button.url);
|
||||
}
|
||||
else {
|
||||
return telegraf_1.Markup.button.callback(button.text, button.callback);
|
||||
}
|
||||
})))));
|
||||
if (sentMessage.message_id) {
|
||||
yield db_1.UserBase.update({ message_id: sentMessage.message_id }, {
|
||||
where: {
|
||||
id: (_e = ctx.from) === null || _e === void 0 ? void 0 : _e.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error('rows должен быть массивом массивов');
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Отправляем только текст, если нет кнопок
|
||||
const sentMessage = yield ctx.replyWithHTML(message.text, {
|
||||
protect_content: true,
|
||||
});
|
||||
yield db_1.UserBase.update({ message_id: (_g = (_f = ctx.callbackQuery) === null || _f === void 0 ? void 0 : _f.message) === null || _g === void 0 ? void 0 : _g.message_id }, {
|
||||
where: {
|
||||
id: (_h = ctx.from) === null || _h === void 0 ? void 0 : _h.id,
|
||||
},
|
||||
});
|
||||
if (sentMessage.message_id) {
|
||||
yield db_1.UserBase.update({ message_id: sentMessage.message_id }, {
|
||||
where: {
|
||||
id: (_j = ctx.from) === null || _j === void 0 ? void 0 : _j.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error(`Key not found: ${key}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error('User not found.');
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error fetching user language:', error);
|
||||
console.log('An error occurred. Please try again later.');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.MessagesService = MessagesService;
|
||||
MessagesService.messages = {};
|
||||
62
mtucijobsbot/docker-compose.yml
Normal file
62
mtucijobsbot/docker-compose.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
version: '3.9'
|
||||
|
||||
x-common-logging: &common-logging
|
||||
logging:
|
||||
# limit logs retained on host to 25MB
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
x-common-network: &common-network
|
||||
networks:
|
||||
- mtuci-jobs
|
||||
|
||||
services:
|
||||
bot-mtuci-jobs:
|
||||
image: image-mtuci-jobs
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: bot-mtuci-jobs
|
||||
restart: always
|
||||
depends_on:
|
||||
postgres-mtuci-jobs:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- '127.0.0.1:3005:3005'
|
||||
- '127.0.0.1:3006:3006'
|
||||
environment:
|
||||
DATABASE_URL: 'postgres://postgres:password@postgres-mtuci-jobs:5432/mtuci-jobs'
|
||||
DB_NAME: mtucijobs
|
||||
DB_USER: postgres
|
||||
DB_PASSWORD: password
|
||||
DB_HOST: postgres-mtuci-jobs
|
||||
BOT_TOKEN: '7306496213:AAGuvha0ytpF8keCZpes8BdP1AnfP7yEiSE'
|
||||
HOOKPORT: 3005
|
||||
PORT: 3006
|
||||
DOMAIN: ''
|
||||
WEB_APP: ''
|
||||
<<: [*common-network, *common-logging]
|
||||
|
||||
postgres-mtuci-jobs:
|
||||
image: postgres:16.1-alpine3.19
|
||||
container_name: bot-postgres-mtuci-jobs
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: password
|
||||
POSTGRES_DB: mtucijobs
|
||||
PGDATA: /data/postgres
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
healthcheck:
|
||||
test: pg_isready -U postgres
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
volumes:
|
||||
- /data/mtuci-jobs/data/postgres:/data/postgres
|
||||
<<: [*common-network, *common-logging]
|
||||
|
||||
networks:
|
||||
mtuci-jobs:
|
||||
driver: bridge
|
||||
3007
mtucijobsbot/package-lock.json
generated
Normal file
3007
mtucijobsbot/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
mtucijobsbot/package.json
Normal file
37
mtucijobsbot/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "mtucijobs",
|
||||
"version": "1.0.0",
|
||||
"description": "This bot for afree",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "tsc && copyfiles -u 1 src/**/*.json src/assets/**/* dist",
|
||||
"start": "node ./dist/index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "student-programmer",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"form-data": "^4.0.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"path": "^0.12.7",
|
||||
"pg": "^8.12.0",
|
||||
"sequelize": "^6.37.3",
|
||||
"telegraf": "^4.16.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/dotenv": "^8.2.0",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/form-data": "^2.5.0",
|
||||
"@types/node": "^20.14.2",
|
||||
"copyfiles": "^2.4.1",
|
||||
"eslint": "^9.4.0"
|
||||
}
|
||||
}
|
||||
14
mtucijobsbot/run.sh
Normal file
14
mtucijobsbot/run.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker stop mtuci-jobs || echo "1"
|
||||
docker rm mtuci-jobs || echo "2"
|
||||
|
||||
docker run -d \
|
||||
--restart always \
|
||||
-h mtuci-jobs \
|
||||
--name mtuci-jobs \
|
||||
-e BOT_TOKEN="7306496213:AAGuvha0ytpF8keCZpes8BdP1AnfP7yEiSE" \
|
||||
--log-opt mode=non-blocking \
|
||||
--log-opt max-size=10m \
|
||||
--log-opt max-file=3 \
|
||||
mtuci-jobs-image:latest
|
||||
34
mtucijobsbot/src/api/resume.ts
Normal file
34
mtucijobsbot/src/api/resume.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import express from 'express';
|
||||
|
||||
import 'dotenv/config';
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/api/resume', async (req, res) => {
|
||||
try {
|
||||
const postData = req.body;
|
||||
|
||||
// Проверяем наличие chatId в теле запроса
|
||||
if (!postData.chatId) {
|
||||
throw new Error('Chat ID is missing in the request body');
|
||||
}
|
||||
|
||||
// Ваша логика обработки данных
|
||||
console.log('Received data:', postData.id);
|
||||
|
||||
// Отправка сообщения в боте
|
||||
// await req.bot.telegram.sendMessage(
|
||||
// postData.chatId,
|
||||
// `Received data: ${postData.id}`
|
||||
// );
|
||||
// MessagesService.sendMessage(postData.id, )
|
||||
|
||||
res.status(200).json({ message: 'Data received successfully' });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
BIN
mtucijobsbot/src/assets/шаблон МТУСИ.docx
Normal file
BIN
mtucijobsbot/src/assets/шаблон МТУСИ.docx
Normal file
Binary file not shown.
90
mtucijobsbot/src/commands/start.ts
Normal file
90
mtucijobsbot/src/commands/start.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Context } from 'telegraf';
|
||||
import { MenuController } from '../modules/menuController';
|
||||
import { Resume, UserBase, sequelize } from '../db/db';
|
||||
import { UserRole } from '../models/User';
|
||||
import axios from 'axios';
|
||||
|
||||
export async function startCommand(ctx: Context): Promise<void> {
|
||||
function isBotOwner(userId: number | undefined): boolean {
|
||||
const ownerUserId = 1053404914; // Замените этот ID на фактический ID владельца
|
||||
return userId === ownerUserId;
|
||||
}
|
||||
|
||||
try {
|
||||
await sequelize.authenticate(); // Проверка подключения к базе данных
|
||||
await UserBase.sync(); // Создание таблицы, если её нет
|
||||
console.log('User table has been created or already exists.');
|
||||
|
||||
// Проверка, существует ли пользователь в базе данных
|
||||
const [userInstance, created] = await UserBase.findOrCreate({
|
||||
where: { id: ctx.from?.id },
|
||||
defaults: {
|
||||
id: ctx.from?.id || 0,
|
||||
username: ctx.from?.username || '',
|
||||
vacansyindex: 0,
|
||||
role: isBotOwner(ctx.from?.id) ? UserRole.OWNER : UserRole.CLIENT,
|
||||
language: ctx.from?.language_code || 'ru',
|
||||
message_id: ctx.callbackQuery?.message?.message_id || 0,
|
||||
last_obj: '',
|
||||
chat_id: ctx.chat?.id,
|
||||
resume_id: null,
|
||||
},
|
||||
});
|
||||
|
||||
// Выполняем запрос на удаление резюме, если оно существует
|
||||
try {
|
||||
const response = await axios.delete(
|
||||
`${process.env.API}students/${ctx.from?.id}`,
|
||||
{
|
||||
headers: {
|
||||
'X-API-KEY':
|
||||
'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status === 204) {
|
||||
console.log('Резюме успешно удалено на сервере.');
|
||||
await Resume.destroy({ where: { id: ctx.from?.id } }); // Удаляем запись из базы данных
|
||||
await ctx.reply('Ваше резюме было удалено.');
|
||||
} else {
|
||||
console.warn(
|
||||
'Не удалось удалить резюме, сервер вернул статус:',
|
||||
response.status
|
||||
);
|
||||
await ctx.reply('Не удалось удалить ваше резюме. Попробуйте позже.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при удалении резюме:', error);
|
||||
await ctx.reply(
|
||||
'Произошла ошибка при удалении резюме. Пожалуйста, попробуйте позже.'
|
||||
);
|
||||
}
|
||||
|
||||
// Приветственное сообщение
|
||||
const greetingMessage = {
|
||||
text:
|
||||
'Вас приветствует бот MTUCI jobs! \n\n' +
|
||||
'Для подтверждения того, что вы студент МТУСИ, вам потребуется привязать вашу учетную запись LMS к проекту MtuciTech (https://mtucitech.ru/) и привязать свой телеграм через бота @apimtucibot. Мы также можем запросить у них некоторые ваши данные, чтобы упростить заполнение анкеты.',
|
||||
buttons: [[{ text: 'Понял', callback: 'accept2' }]],
|
||||
};
|
||||
|
||||
// Отправка приветственного сообщения с кнопками
|
||||
await ctx.reply(greetingMessage.text, {
|
||||
reply_markup: {
|
||||
inline_keyboard: greetingMessage.buttons.map(row =>
|
||||
row.map(button => ({
|
||||
text: button.text,
|
||||
callback_data: button.callback,
|
||||
}))
|
||||
),
|
||||
},
|
||||
parse_mode: 'HTML',
|
||||
});
|
||||
|
||||
console.log('Бот запущен');
|
||||
} catch (e) {
|
||||
console.error('Произошла ошибка при запуске бота', e);
|
||||
ctx.reply('Произошла ошибка при запуске. Пожалуйста, попробуйте позже.');
|
||||
}
|
||||
}
|
||||
176
mtucijobsbot/src/db/db.ts
Normal file
176
mtucijobsbot/src/db/db.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import 'dotenv/config';
|
||||
|
||||
import { DataTypes, Sequelize, Model } from 'sequelize';
|
||||
|
||||
|
||||
const sequelize = new Sequelize(
|
||||
process.env.DB_NAME || 'custdev',
|
||||
process.env.DB_USER || 'postgres',
|
||||
process.env.DB_PASSWORD,
|
||||
{
|
||||
host: process.env.DB_HOST,
|
||||
dialect: 'postgres',
|
||||
}
|
||||
);
|
||||
|
||||
// Определение интерфейса для модели
|
||||
interface UserAttributes {
|
||||
id: number;
|
||||
username: string;
|
||||
vacansyindex: number;
|
||||
role: string;
|
||||
language: string;
|
||||
message_id: number | null;
|
||||
last_obj: string | null;
|
||||
chat_id: number | null;
|
||||
resume_id: number | null; // добавляем внешний ключ
|
||||
}
|
||||
|
||||
interface UserCreationAttributes extends UserAttributes {}
|
||||
|
||||
class UserBase
|
||||
extends Model<UserAttributes, UserCreationAttributes>
|
||||
implements UserAttributes
|
||||
{
|
||||
public id!: number;
|
||||
public username!: string;
|
||||
public vacansyindex!: number;
|
||||
public role!: string;
|
||||
public language!: string;
|
||||
public message_id!: number | null;
|
||||
public last_obj!: string | null;
|
||||
public chat_id!: number | null;
|
||||
public resume_id!: number | null; // добавляем внешний ключ
|
||||
}
|
||||
UserBase.init(
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.BIGINT,
|
||||
primaryKey: true,
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
vacansyindex: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
role: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: 'client',
|
||||
},
|
||||
language: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
message_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
last_obj: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
chat_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: true,
|
||||
},
|
||||
resume_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'Resumes', // имя таблицы резюме
|
||||
key: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
modelName: 'User',
|
||||
}
|
||||
);
|
||||
interface ResumeAttributes {
|
||||
id: number;
|
||||
name: string;
|
||||
group: string;
|
||||
type: string;
|
||||
skills: string;
|
||||
softskills: string;
|
||||
email: string;
|
||||
resumefile: string;
|
||||
}
|
||||
|
||||
interface ResumeCreationAttributes extends ResumeAttributes {}
|
||||
|
||||
class Resume
|
||||
extends Model<ResumeAttributes, ResumeCreationAttributes>
|
||||
implements ResumeAttributes
|
||||
{
|
||||
public id!: number;
|
||||
public name!: string;
|
||||
public group!: string;
|
||||
public type!: string;
|
||||
public skills!: string;
|
||||
public softskills!: string;
|
||||
public email!: string;
|
||||
public resumefile!: string;
|
||||
}
|
||||
Resume.init(
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.BIGINT,
|
||||
primaryKey: true,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
group: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
type: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
skills: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
softskills: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
resumefile: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
modelName: 'Resume',
|
||||
}
|
||||
);
|
||||
UserBase.belongsTo(Resume, { foreignKey: 'resume_id', as: 'resume' });
|
||||
Resume.hasOne(UserBase, { foreignKey: 'resume_id', as: 'user' });
|
||||
|
||||
|
||||
|
||||
export { sequelize, UserBase, Resume };
|
||||
|
||||
sequelize
|
||||
.sync()
|
||||
.then(() => {
|
||||
console.log('Model has been synchronized successfully.');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error synchronizing model:', error);
|
||||
});
|
||||
469
mtucijobsbot/src/index.ts
Normal file
469
mtucijobsbot/src/index.ts
Normal file
@@ -0,0 +1,469 @@
|
||||
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}`);
|
||||
});
|
||||
24
mtucijobsbot/src/languages/en.json
Normal file
24
mtucijobsbot/src/languages/en.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"greeting": {
|
||||
"photo": "",
|
||||
"file": "",
|
||||
"text": "Вас приветствует бот MTUCI jobs! \n\nДля подтверждения того, что вы студент МТУСИ вам потребуется привязать вашу учетную запись lms к проекту MtuciTech (https://mtucitech.ru/) и привязать свой телеграм через бота @apimtucibot. Мы также можем запросить у них некоторые ваши данные, чтобы упростить заполнение анкеты",
|
||||
"buttons": [
|
||||
[
|
||||
{ "text": "Понял", "callback": "accept" },
|
||||
{ "text": "Понял, не запрашивать данные", "callback": "accept" }
|
||||
]
|
||||
]
|
||||
},
|
||||
"greeting2": {
|
||||
"photo": "",
|
||||
"file": "",
|
||||
"text": "Вас приветствует бот MTUCI jobs! \n\nДля подтверждения того, что вы студент МТУСИ вам потребуется привязать вашу учетную запись lms к проекту MtuciTech (https://mtucitech.ru/) и привязать свой телеграм через бота @apimtucibot. Мы также можем запросить у них некоторые ваши данные, чтобы упростить заполнение анкеты",
|
||||
"buttons": [
|
||||
[
|
||||
{ "text": "Понял", "callback": "accept2" },
|
||||
{ "text": "Понял, не запрашивать данные", "callback": "accept2" }
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
24
mtucijobsbot/src/languages/ru.json
Normal file
24
mtucijobsbot/src/languages/ru.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"greeting": {
|
||||
"photo": "",
|
||||
"file": "",
|
||||
"text": "Вас приветствует бот MTUCI jobs! \n\nДля подтверждения того, что вы студент МТУСИ вам потребуется привязать вашу учетную запись lms к проекту MtuciTech (https://mtucitech.ru/) и привязать свой телеграм через бота @apimtucibot. Мы также можем запросить у них некоторые ваши данные, чтобы упростить заполнение анкеты",
|
||||
"buttons": [
|
||||
[
|
||||
{ "text": "Понял", "callback": "accept" },
|
||||
{ "text": "Понял, не запрашивать данные", "callback": "accept" }
|
||||
]
|
||||
]
|
||||
},
|
||||
"greeting2": {
|
||||
"photo": "",
|
||||
"file": "",
|
||||
"text": "Вас приветствует бот MTUCI jobs! \n\nДля подтверждения того, что вы студент МТУСИ вам потребуется привязать вашу учетную запись lms к проекту MtuciTech (https://mtucitech.ru/) и привязать свой телеграм через бота @apimtucibot. Мы также можем запросить у них некоторые ваши данные, чтобы упростить заполнение анкеты",
|
||||
"buttons": [
|
||||
[
|
||||
{ "text": "Понял", "callback": "accept2" },
|
||||
{ "text": "Понял, не запрашивать данные", "callback": "accept2" }
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
23
mtucijobsbot/src/models/User.ts
Normal file
23
mtucijobsbot/src/models/User.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// Интерфейс, представляющий структуру пользователя
|
||||
export interface User {
|
||||
id: number; // Уникальный идентификатор пользователя
|
||||
username: string; // Имя пользователя
|
||||
password: string; // Количество очков пользователя
|
||||
email: string;
|
||||
isAuth:boolean;
|
||||
points:number;
|
||||
//referrerId?: number; // Идентификатор пригласившего пользователя (необязательно)
|
||||
role: UserRole; // Роль пользователя (владелец, админ, модератор, клиент)
|
||||
language: string; // Язык, на котором взаимодействует пользователь с ботом
|
||||
message_id?: number;
|
||||
last_obj?: string;
|
||||
chat_id: number;
|
||||
}
|
||||
|
||||
// Перечисление ролей пользователя
|
||||
export enum UserRole {
|
||||
OWNER = 'owner',
|
||||
ADMIN = 'admin',
|
||||
MODERATOR = 'moderator',
|
||||
CLIENT = 'client',
|
||||
}
|
||||
43
mtucijobsbot/src/modules/menuController.ts
Normal file
43
mtucijobsbot/src/modules/menuController.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Context, Markup } from 'telegraf';
|
||||
import { UserBase } from '../db/db';
|
||||
import { MessagesService } from '../services/messagesService';
|
||||
|
||||
export class MenuController {
|
||||
static async showLanguageMenu(ctx: Context): Promise<void> {
|
||||
// await MessagesService.sendMessage(ctx, 'greeting');
|
||||
}
|
||||
|
||||
static async setLanguage(ctx: Context): Promise<void> {
|
||||
try {
|
||||
if (!ctx.callbackQuery) {
|
||||
throw new Error('CallbackQuery is not defined.');
|
||||
}
|
||||
|
||||
const languageCode =
|
||||
ctx.callbackQuery && 'data' in ctx.callbackQuery
|
||||
? ctx.callbackQuery.data.split('_')[1]
|
||||
: undefined;
|
||||
|
||||
if (languageCode) {
|
||||
// Обновляем язык пользователя в базе данных
|
||||
await UserBase.update(
|
||||
{ language: languageCode },
|
||||
{ where: { id: ctx.from?.id } }
|
||||
);
|
||||
|
||||
// Обновляем язык в объекте контекста
|
||||
if (ctx.from) {
|
||||
ctx.from.language_code = languageCode;
|
||||
}
|
||||
} else {
|
||||
// Если data отсутствует в callbackQuery, обработка не может быть выполнена
|
||||
throw new Error('Missing data property in callbackQuery.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during setLanguage:', error);
|
||||
console.log(
|
||||
'Произошла ошибка при обновлении языка. Пожалуйста, попробуйте снова позже.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
mtucijobsbot/src/modules/menuScenes.ts
Normal file
39
mtucijobsbot/src/modules/menuScenes.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Context, MiddlewareFn, Scenes } from 'telegraf';
|
||||
import { UserBase } from '../db/db';
|
||||
import { MessagesService } from '../services/messagesService';
|
||||
|
||||
// Middleware для обработки callback'ов
|
||||
const callbackQueryMiddleware: MiddlewareFn<Scenes.WizardContext> = async (
|
||||
ctx,
|
||||
next
|
||||
) => {
|
||||
try {
|
||||
|
||||
const languageCode =
|
||||
ctx.callbackQuery && 'data' in ctx.callbackQuery
|
||||
? ctx.callbackQuery.data.split('_')[1]
|
||||
: undefined;
|
||||
// Обновляем язык пользователя в базе данных
|
||||
if (languageCode && ctx.from?.id) {
|
||||
await UserBase.update(
|
||||
{ language: languageCode },
|
||||
{ where: { id: ctx.from.id } }
|
||||
);
|
||||
|
||||
// Отправляем сообщение об успешном обновлении языка
|
||||
console.log(`Язык обновлен: ${languageCode}`);
|
||||
await ctx.answerCbQuery();
|
||||
await MessagesService.sendMessage(ctx, 'greeting');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during callback handling:', error);
|
||||
console.log(
|
||||
"Произошла ошибка при обработке callback'а. Пожалуйста, попробуйте снова позже."
|
||||
);
|
||||
}
|
||||
|
||||
// Вызываем следующий middleware
|
||||
return next();
|
||||
};
|
||||
|
||||
export const menuSceneMiddleware = callbackQueryMiddleware;
|
||||
107
mtucijobsbot/src/modules/scenes/accept.ts
Normal file
107
mtucijobsbot/src/modules/scenes/accept.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import axios from 'axios';
|
||||
import { Scenes, Telegraf } from 'telegraf';
|
||||
import 'dotenv/config';
|
||||
|
||||
// const API_BASE_URL = 'https://mtucitech.ru/api/external/mtuci_jobs';
|
||||
// const BEARER_TOKEN = 'zKbgaXQv{pvGtQm~U9$urtD#QsiHc@Ie';
|
||||
|
||||
export function accept(bot: Telegraf<Scenes.WizardContext>) {
|
||||
bot.action('accept', async ctx => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${process.env.MTUCI_TECH}/check?telegram_id=${ctx.from.id}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.BEARER_TOKEN}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
const data = response.data;
|
||||
|
||||
// Логика обработки ответа
|
||||
switch (data.status) {
|
||||
case 338:
|
||||
ctx.answerCbQuery(
|
||||
'Ошибка на стороне сервера. Пожалуйста, сообщите нам.'
|
||||
);
|
||||
break;
|
||||
case 339:
|
||||
ctx.answerCbQuery('Пользователь не привязан к проекту.');
|
||||
|
||||
break;
|
||||
case 340:
|
||||
ctx.reply('Можете заполнить своё резюме', {
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Резюме',
|
||||
web_app: { url: process.env.WEB_APP || '' },
|
||||
},
|
||||
],
|
||||
],
|
||||
resize_keyboard: true,
|
||||
one_time_keyboard: true,
|
||||
},
|
||||
});
|
||||
ctx.answerCbQuery('Пользователь найден, но не привязан LMS.');
|
||||
|
||||
break;
|
||||
case 341:
|
||||
ctx.reply('Можете заполнить своё резюме', {
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Резюме',
|
||||
web_app: { url: process.env.WEB_APP || '' },
|
||||
},
|
||||
],
|
||||
],
|
||||
resize_keyboard: true,
|
||||
one_time_keyboard: true,
|
||||
},
|
||||
});
|
||||
ctx.answerCbQuery(
|
||||
'Пользователь найден, LMS привязан. Можно запросить дополнительные данные.'
|
||||
);
|
||||
default:
|
||||
ctx.answerCbQuery('Неизвестный статус.');
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
// Обработка ошибок Axios
|
||||
if (error.response && error.response.status === 409) {
|
||||
ctx.answerCbQuery('Вакансия уже добавлена в избранное');
|
||||
} else {
|
||||
ctx.answerCbQuery('Произошла ошибка');
|
||||
console.error(error.response?.data || error.message);
|
||||
}
|
||||
} else {
|
||||
// Обработка других типов ошибок
|
||||
ctx.answerCbQuery('Произошла неизвестная ошибка');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bot.action('accept2', async ctx => {
|
||||
|
||||
ctx.reply('Можете заполнить своё резюме', {
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Резюме',
|
||||
web_app: { url: process.env.WEB_APP || '' },
|
||||
},
|
||||
],
|
||||
],
|
||||
resize_keyboard: true,
|
||||
one_time_keyboard: true,
|
||||
},
|
||||
});
|
||||
ctx.answerCbQuery()
|
||||
});
|
||||
}
|
||||
366
mtucijobsbot/src/modules/scenes/events.ts
Normal file
366
mtucijobsbot/src/modules/scenes/events.ts
Normal file
@@ -0,0 +1,366 @@
|
||||
import { Telegraf, Context, Scenes, Markup } from 'telegraf';
|
||||
import { MessagesService } from '../../services/messagesService';
|
||||
import { Resume, UserBase } from '../../db/db';
|
||||
import axios from 'axios';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { MyWizardContext } from '../..';
|
||||
|
||||
interface Vacancies {
|
||||
JobID: number;
|
||||
Company_name: string;
|
||||
Job_name: string;
|
||||
Year: string;
|
||||
Qualification: boolean;
|
||||
Soft_skills: string;
|
||||
Salary: number;
|
||||
Email: string;
|
||||
Archive: boolean;
|
||||
Responsibilities: string;
|
||||
}
|
||||
|
||||
let vacancies: Vacancies[] = [];
|
||||
|
||||
export function events(bot: Telegraf<Scenes.WizardContext>) {
|
||||
bot.action('skip', async ctx => {
|
||||
ctx.reply(
|
||||
'Меню',
|
||||
Markup.keyboard([
|
||||
['Моя анкета'],
|
||||
['Вакансии'],
|
||||
// ['Уведомления: включены'],
|
||||
]).resize()
|
||||
);
|
||||
ctx.answerCbQuery();
|
||||
});
|
||||
bot.hears('Вернуться в меню', async ctx => {
|
||||
ctx.reply(
|
||||
'Меню',
|
||||
Markup.keyboard([
|
||||
['Моя анкета'],
|
||||
['Вакансии'],
|
||||
// ['Уведомления: включены'],
|
||||
]).resize()
|
||||
);
|
||||
});
|
||||
|
||||
bot.hears('Моя анкета', async ctx => {
|
||||
try {
|
||||
// Выполнение первого GET-запроса для получения данных пользователя
|
||||
const response = await axios.get(
|
||||
`${process.env.API}students/${ctx.from.id}`,
|
||||
{
|
||||
headers: {
|
||||
'X-API-KEY':
|
||||
'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
},
|
||||
}
|
||||
);
|
||||
const data = response.data;
|
||||
|
||||
const resumeFile = await Resume.findOne({ where: { id: ctx.from.id } });
|
||||
// Проверяем, существует ли резюме
|
||||
if (resumeFile && resumeFile.resumefile.length > 1) {
|
||||
// Выполнение второго GET-запроса для получения файла с резюме
|
||||
const resumeResponse = await axios({
|
||||
method: 'GET',
|
||||
url: `${process.env.API}services/resume/${resumeFile.resumefile}`,
|
||||
|
||||
headers: {
|
||||
'X-API-KEY':
|
||||
'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
},
|
||||
|
||||
responseType: 'stream',
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// Сохраняем файл временно на диск
|
||||
const resumePath = path.join(__dirname, 'resume.pdf');
|
||||
const writer = fs.createWriteStream(resumePath);
|
||||
resumeResponse.data.pipe(writer);
|
||||
|
||||
writer.on('finish', async () => {
|
||||
// Отправка файла и сообщения с данными из API
|
||||
await ctx.replyWithDocument({ source: resumePath });
|
||||
|
||||
await ctx.replyWithHTML(
|
||||
`<b>Имя:</b> ${data.Name}\n<b>Группа:</b> ${
|
||||
data.Group
|
||||
}\n<b>Тип:</b> ${
|
||||
data.Type
|
||||
}\n<b>Готов на занятость:</b> ${data.Time.join()}\n<b>О себе:</b> ${
|
||||
data.Soft_skills
|
||||
}\n<b>Почта:</b> ${data.Email}\n`,
|
||||
{
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Редактировать анкету',
|
||||
web_app: { url: process.env.WEB_APP || '' },
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
text: 'Обновить резюме',
|
||||
callback_data: 'update_resume',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Удаляем временный файл
|
||||
fs.unlinkSync(resumePath);
|
||||
});
|
||||
|
||||
writer.on('error', err => {
|
||||
console.error('Ошибка при записи файла резюме:', err);
|
||||
ctx.reply('Произошла ошибка при получении файла резюме.');
|
||||
});
|
||||
} else {
|
||||
// Отправка только сообщения с данными из API, если резюме не существует
|
||||
await ctx.replyWithHTML(
|
||||
`<b>Имя:</b> ${data.Name}\n<b>Группа:</b> ${
|
||||
data.Group
|
||||
}\n<b>Тип:</b> ${
|
||||
data.Type
|
||||
}\n<b>Готов на занятость:</b> ${data.Time.join()}\n<b>О себе:</b> ${
|
||||
data.Soft_skills
|
||||
}\n<b>Почта:</b> ${data.Email}\n`,
|
||||
{
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Редактировать',
|
||||
web_app: { url: process.env.WEB_APP || '' },
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
text: 'Загрузить резюме',
|
||||
callback_data: 'update_resume',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при выполнении запроса к API:', error);
|
||||
ctx.reply('Произошла ошибка при получении данных из API.');
|
||||
}
|
||||
});
|
||||
|
||||
bot.hears('Вакансии', async ctx => {
|
||||
try {
|
||||
await ctx.reply(
|
||||
'Выберите тип вакансии:',
|
||||
Markup.keyboard([
|
||||
['Актуальные вакансии'],
|
||||
['Сохраненные вакансии'],
|
||||
['Поиск'],
|
||||
['Вернуться в меню'],
|
||||
]).resize()
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при выполнении запроса к API:', error);
|
||||
ctx.reply('Произошла ошибка при получении данных из API.');
|
||||
}
|
||||
});
|
||||
bot.hears('Поиск', async ctx => {
|
||||
try {
|
||||
await ctx.reply(
|
||||
'Поиск вакансий:',
|
||||
Markup.keyboard([
|
||||
['Актуальные вакансии'],
|
||||
['Сохраненные вакансии'],
|
||||
['Вернуться в меню'],
|
||||
]).resize()
|
||||
);
|
||||
ctx.reply('Страница с поиском вакансий', {
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Перейти',
|
||||
web_app: { url: process.env.WEB_APP_SEARCH || '' },
|
||||
},
|
||||
],
|
||||
],
|
||||
resize_keyboard: true,
|
||||
one_time_keyboard: true,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка при выполнении запроса к API:', error);
|
||||
ctx.reply('Произошла ошибка при получении данных из API.');
|
||||
}
|
||||
});
|
||||
|
||||
bot.hears('Актуальные вакансии', async ctx => {
|
||||
try {
|
||||
await UserBase.update(
|
||||
{ vacansyindex: 0 },
|
||||
{
|
||||
where: {
|
||||
id: ctx.from.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
// Выполнение первого GET-запроса для получения данных пользователя
|
||||
const response = await axios.get(
|
||||
`${process.env.API}students/matches/${ctx.from.id}`,
|
||||
{
|
||||
headers: {
|
||||
'X-API-KEY':
|
||||
'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
},
|
||||
}
|
||||
);
|
||||
vacancies = response.data;
|
||||
console.log(vacancies);
|
||||
|
||||
if (vacancies.length === 0) {
|
||||
ctx.reply('На данный момент нет актуальных вакансий.');
|
||||
return;
|
||||
}
|
||||
let user = await UserBase.findOne({ where: { id: ctx.from.id } });
|
||||
if (user) {
|
||||
let currentIndex = user?.vacansyindex;
|
||||
await sendVacancy(ctx, currentIndex, true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при выполнении запроса к API:', error);
|
||||
ctx.reply('Произошла ошибка при получении данных из API.');
|
||||
}
|
||||
});
|
||||
|
||||
bot.hears('Сохраненные вакансии', async ctx => {
|
||||
try {
|
||||
await UserBase.update(
|
||||
{ vacansyindex: 0 },
|
||||
{
|
||||
where: {
|
||||
id: ctx.from.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
// Выполнение первого GET-запроса для получения данных пользователя
|
||||
const response = await axios.get(
|
||||
`${process.env.API}students/favourites/${ctx.from.id}`,
|
||||
{
|
||||
headers: {
|
||||
'X-API-KEY':
|
||||
'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
},
|
||||
}
|
||||
);
|
||||
vacancies = response.data;
|
||||
console.log(vacancies);
|
||||
|
||||
if (vacancies.length === 0) {
|
||||
ctx.reply('На данный момент нет сохранённых вакансий.');
|
||||
return;
|
||||
}
|
||||
let user = await UserBase.findOne({ where: { id: ctx.from.id } });
|
||||
if (user) {
|
||||
let currentIndex = user?.vacansyindex;
|
||||
await sendVacancy(ctx, currentIndex, false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при выполнении запроса к API:', error);
|
||||
ctx.reply('На данный момент нет сохранённых вакансий.');
|
||||
// ctx.reply('Произошла ошибка при получении данных из API.');
|
||||
}
|
||||
});
|
||||
|
||||
bot.action('next', async ctx => {
|
||||
let user = await UserBase.findOne({ where: { id: ctx.from.id } });
|
||||
if (user) {
|
||||
let currentIndex = user?.vacansyindex + 1;
|
||||
if (currentIndex <= vacancies.length - 1) {
|
||||
await UserBase.update(
|
||||
{ vacansyindex: currentIndex },
|
||||
{ where: { id: ctx.from.id } }
|
||||
);
|
||||
await sendVacancy(ctx, currentIndex, true);
|
||||
ctx.answerCbQuery();
|
||||
} else {
|
||||
ctx.answerCbQuery('Это последняя вакансия.');
|
||||
}
|
||||
}
|
||||
});
|
||||
bot.action('update_resume', async ctx => {
|
||||
await ctx.scene.enter('resumeScene');
|
||||
await ctx.answerCbQuery();
|
||||
});
|
||||
|
||||
bot.action('prev', async ctx => {
|
||||
let user = await UserBase.findOne({ where: { id: ctx.from.id } });
|
||||
if (user) {
|
||||
let currentIndex = user?.vacansyindex - 1;
|
||||
if (currentIndex >= 0) {
|
||||
await UserBase.update(
|
||||
{ vacansyindex: currentIndex },
|
||||
{ where: { id: ctx.from.id } }
|
||||
);
|
||||
await sendVacancy(ctx, currentIndex, true);
|
||||
ctx.answerCbQuery();
|
||||
} else {
|
||||
ctx.answerCbQuery('Это первая вакансия.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function sendVacancy(
|
||||
ctx: MyWizardContext,
|
||||
index: number,
|
||||
showSaveButton: boolean
|
||||
) {
|
||||
const data = vacancies[index];
|
||||
|
||||
// Формируем кнопки навигации
|
||||
let inlineKeyboard = [
|
||||
[
|
||||
{ text: 'Назад', callback_data: 'prev' },
|
||||
{ text: `${index + 1}/${vacancies.length}`, callback_data: 'new' },
|
||||
{ text: 'Далее', callback_data: 'next' },
|
||||
],
|
||||
];
|
||||
|
||||
// Добавляем кнопку "Сохранить вакансию" только если showSaveButton = true
|
||||
if (showSaveButton) {
|
||||
inlineKeyboard.push([
|
||||
{
|
||||
text: 'Сохранить вакансию',
|
||||
callback_data: `savevacancy+${data.JobID}`,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
// Отправляем сообщение с вакансиями
|
||||
await ctx.replyWithHTML(
|
||||
`<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}`,
|
||||
{
|
||||
reply_markup: {
|
||||
inline_keyboard: inlineKeyboard,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
49
mtucijobsbot/src/modules/scenes/savevacansy.ts
Normal file
49
mtucijobsbot/src/modules/scenes/savevacansy.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Telegraf, Context, Scenes, Markup } from 'telegraf';
|
||||
import { MessagesService } from '../../services/messagesService';
|
||||
import { Resume, UserBase } from '../../db/db';
|
||||
import axios from 'axios';
|
||||
import 'dotenv/config';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { MyWizardContext } from '../..';
|
||||
|
||||
export function saveVacansy(bot: Telegraf<Scenes.WizardContext>) {
|
||||
bot.on('callback_query', async ctx => {
|
||||
if (ctx.callbackQuery && 'data' in ctx.callbackQuery) {
|
||||
const callbackData = ctx.callbackQuery.data;
|
||||
|
||||
if (callbackData.startsWith('savevacancy+')) {
|
||||
const jobID = callbackData.split('+')[1];
|
||||
|
||||
try {
|
||||
const data = await axios.post(
|
||||
`${process.env.API}students/favourites/`,
|
||||
{ StudentID: ctx.from.id, JobID: jobID },
|
||||
{
|
||||
headers: {
|
||||
'X-API-KEY':
|
||||
'SbRHOVoK97GKCx3Lqx6hKXLbZZJEd0GTGbeglXdpK9PhSB9kpr4eWCsuIIwnD6F2mgpTDlVHFCRbeFmuSfqBVsb12lNwF3P1tmdxiktl7zH9sDS2YK7Pyj2DecCWAZ3n',
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log(data);
|
||||
ctx.answerCbQuery('Вакансия была сохранена');
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
// Обработка ошибок Axios
|
||||
if (error.response && error.response.status === 409) {
|
||||
ctx.answerCbQuery('Вакансия уже добавлена в избранное');
|
||||
} else {
|
||||
ctx.answerCbQuery('Произошла ошибка при сохранении вакансии');
|
||||
console.error(error.response?.data || error.message);
|
||||
}
|
||||
} else {
|
||||
// Обработка других типов ошибок
|
||||
ctx.answerCbQuery('Произошла неизвестная ошибка');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
371
mtucijobsbot/src/services/messagesService.ts
Normal file
371
mtucijobsbot/src/services/messagesService.ts
Normal file
@@ -0,0 +1,371 @@
|
||||
// Импортируем необходимые модули
|
||||
import * as fs from 'fs';
|
||||
// import { CustomContext } from '../types/telegram';
|
||||
import path from 'path';
|
||||
import { UserBase } from '../db/db';
|
||||
import { Context, Markup } from 'telegraf';
|
||||
|
||||
// Определяем тип для объекта сообщений
|
||||
|
||||
type Button = {
|
||||
text: string;
|
||||
callback: string;
|
||||
url?: string;
|
||||
};
|
||||
|
||||
// Определяем тип для объекта контента сообщения
|
||||
type Message = {
|
||||
text: string;
|
||||
photo?: string; // Путь к фотографии
|
||||
file?: string;
|
||||
buttons?: Button[][];
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Определяем тип для объекта сообщений
|
||||
type Messages = { [key: string]: Message };
|
||||
|
||||
export class MessagesService {
|
||||
private static messages: { [lang: string]: Messages } = {};
|
||||
|
||||
public static loadMessages(lang: string): Messages {
|
||||
if (!this.messages[lang]) {
|
||||
const filePath = path.join(__dirname, `../languages/${lang}.json`);
|
||||
const data = fs.readFileSync(filePath, 'utf-8');
|
||||
this.messages[lang] = JSON.parse(data);
|
||||
}
|
||||
|
||||
return this.messages[lang];
|
||||
}
|
||||
|
||||
public static async backMessage(ctx: Context) {
|
||||
try {
|
||||
const user = (await UserBase.findOne({
|
||||
where: {
|
||||
id: ctx.from?.id,
|
||||
},
|
||||
})) as {
|
||||
id: number;
|
||||
username: string;
|
||||
password: string;
|
||||
email: string;
|
||||
isAuth: boolean;
|
||||
points: number;
|
||||
role: string;
|
||||
language: string;
|
||||
message_id: number;
|
||||
last_obj: string;
|
||||
chat_id: number;
|
||||
} | null;
|
||||
if (user !== null && ctx.chat) {
|
||||
await ctx.telegram.copyMessage(
|
||||
ctx.chat?.id,
|
||||
ctx.chat?.id,
|
||||
user.message_id - 1
|
||||
);
|
||||
} else {
|
||||
console.log('error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
public static async hiddenMessage(ctx: Context, key: string) {
|
||||
try {
|
||||
const user = (await UserBase.findOne({
|
||||
where: {
|
||||
id: ctx.from?.id,
|
||||
},
|
||||
})) as {
|
||||
id: number;
|
||||
username: string;
|
||||
password: string;
|
||||
email: string;
|
||||
isAuth: boolean;
|
||||
points: number;
|
||||
role: string;
|
||||
language: string;
|
||||
message_id: number;
|
||||
last_obj: string;
|
||||
chat_id: number;
|
||||
} | null;
|
||||
|
||||
if (user && user.message_id && ctx.callbackQuery?.message) {
|
||||
await ctx.telegram.deleteMessage(
|
||||
ctx.chat?.id || 0,
|
||||
ctx.callbackQuery?.message?.message_id
|
||||
);
|
||||
console.log('Hidden message deleted successfully.');
|
||||
await UserBase.update(
|
||||
{ last_obj: key }, // Новое значение
|
||||
{
|
||||
where: {
|
||||
id: ctx.from?.id, // Условие для выбора записей
|
||||
},
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('User or message not found.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting last message:', error);
|
||||
ctx.reply(
|
||||
'An error occurred while deleting the last message. Please try again later.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async deleteLastMessage(ctx: Context) {
|
||||
try {
|
||||
const user = (await UserBase.findOne({
|
||||
where: {
|
||||
id: ctx.from?.id,
|
||||
},
|
||||
})) as {
|
||||
id: number;
|
||||
username: string;
|
||||
password: string;
|
||||
email: string;
|
||||
isAuth: boolean;
|
||||
points: number;
|
||||
role: string;
|
||||
language: string;
|
||||
message_id: number;
|
||||
last_obj: string;
|
||||
chat_id: number;
|
||||
} | null;
|
||||
|
||||
if (user && user.message_id && ctx.callbackQuery?.message) {
|
||||
await ctx.telegram.deleteMessage(
|
||||
ctx.chat?.id || 0,
|
||||
ctx.callbackQuery?.message?.message_id
|
||||
);
|
||||
console.log('Last message deleted successfully.');
|
||||
await UserBase.update(
|
||||
{ message_id: ctx.callbackQuery?.message?.message_id }, // Новое значение
|
||||
{
|
||||
where: {
|
||||
id: ctx.from?.id, // Условие для выбора записей
|
||||
},
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('User or message not found.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting last message:', error);
|
||||
ctx.reply(
|
||||
'An error occurred while deleting the last message. Please try again later.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async sendMessage(
|
||||
ctx: Context,
|
||||
key: string
|
||||
) {
|
||||
try {
|
||||
const user = (await UserBase.findOne({
|
||||
where: {
|
||||
id: ctx.from?.id,
|
||||
},
|
||||
})) as {
|
||||
id: number;
|
||||
username: string;
|
||||
password: string;
|
||||
email: string;
|
||||
isAuth: boolean;
|
||||
points: number;
|
||||
role: string;
|
||||
language: string;
|
||||
message_id: number;
|
||||
last_obj: string;
|
||||
chat_id: number;
|
||||
} | null;
|
||||
|
||||
if (user !== null) {
|
||||
const lang = user.language;
|
||||
const messages = this.loadMessages(lang);
|
||||
const message = messages[key];
|
||||
|
||||
if (message) {
|
||||
// Определяем, есть ли у сообщения фотография
|
||||
const hasPhoto = message.photo !== '';
|
||||
const hasFile = message.file !== '';
|
||||
|
||||
if (
|
||||
hasPhoto &&
|
||||
message.buttons &&
|
||||
message.buttons.length > 0 &&
|
||||
message.photo
|
||||
) {
|
||||
const photoPath = path.join(__dirname, message.photo);
|
||||
|
||||
if (message.buttons && message.buttons.length > 0) {
|
||||
const rows: Button[][] = message.buttons;
|
||||
if (
|
||||
Array.isArray(rows) &&
|
||||
rows.every(row => Array.isArray(row))
|
||||
) {
|
||||
const sentMessage = await ctx.replyWithPhoto(
|
||||
{ source: fs.createReadStream(photoPath) || '' },
|
||||
{
|
||||
protect_content: true,
|
||||
caption: message.text,
|
||||
parse_mode: 'HTML',
|
||||
...Markup.inlineKeyboard(
|
||||
rows.map(row =>
|
||||
row.map(button => {
|
||||
if (button.url) {
|
||||
return Markup.button.url(button.text, button.url);
|
||||
} else {
|
||||
return Markup.button.callback(
|
||||
button.text,
|
||||
button.callback
|
||||
);
|
||||
}
|
||||
})
|
||||
)
|
||||
),
|
||||
}
|
||||
);
|
||||
if (sentMessage.message_id) {
|
||||
await UserBase.update(
|
||||
{ message_id: sentMessage.message_id },
|
||||
{
|
||||
where: {
|
||||
id: ctx.from?.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.error('rows должен быть массивом массивов');
|
||||
}
|
||||
}
|
||||
} else if (hasPhoto && message.photo) {
|
||||
const photoPath = path.join(__dirname, message.photo);
|
||||
const sentMessage = await ctx.replyWithPhoto(
|
||||
{ source: fs.createReadStream(photoPath) || '' },
|
||||
{
|
||||
protect_content: true,
|
||||
caption: message.text,
|
||||
parse_mode: 'HTML',
|
||||
}
|
||||
);
|
||||
if (sentMessage.message_id) {
|
||||
await UserBase.update(
|
||||
{ message_id: sentMessage.message_id },
|
||||
{
|
||||
where: {
|
||||
id: ctx.from?.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if (hasFile && message.file) {
|
||||
const pdfFilePath = path.join(__dirname, message.file);
|
||||
|
||||
const fileName = path.basename(pdfFilePath);
|
||||
|
||||
// Отправляем файл
|
||||
const sentMessage = await ctx.replyWithDocument(
|
||||
{
|
||||
source: fs.createReadStream(pdfFilePath) || '',
|
||||
filename: fileName,
|
||||
},
|
||||
{
|
||||
protect_content: true,
|
||||
caption: message.text,
|
||||
parse_mode: 'HTML',
|
||||
}
|
||||
);
|
||||
if (sentMessage.message_id) {
|
||||
await UserBase.update(
|
||||
{ message_id: sentMessage.message_id },
|
||||
{
|
||||
where: {
|
||||
id: ctx.from?.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Отправляем текст и кнопки, если они есть
|
||||
if (message.buttons && message.buttons.length > 0) {
|
||||
const rows: Button[][] = message.buttons;
|
||||
if (
|
||||
Array.isArray(rows) &&
|
||||
rows.every(row => Array.isArray(row))
|
||||
) {
|
||||
const sentMessage = await ctx.reply(message.text, {
|
||||
protect_content: true,
|
||||
parse_mode: 'HTML',
|
||||
...Markup.inlineKeyboard(
|
||||
rows.map(row =>
|
||||
row.map(button => {
|
||||
if (button.url) {
|
||||
return Markup.button.url(button.text, button.url);
|
||||
} else {
|
||||
return Markup.button.callback(
|
||||
button.text,
|
||||
button.callback
|
||||
);
|
||||
}
|
||||
})
|
||||
)
|
||||
),
|
||||
});
|
||||
|
||||
if (sentMessage.message_id) {
|
||||
await UserBase.update(
|
||||
{ message_id: sentMessage.message_id },
|
||||
{
|
||||
where: {
|
||||
id: ctx.from?.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.error('rows должен быть массивом массивов');
|
||||
}
|
||||
} else {
|
||||
// Отправляем только текст, если нет кнопок
|
||||
const sentMessage = await ctx.replyWithHTML(message.text, {
|
||||
protect_content: true,
|
||||
});
|
||||
await UserBase.update(
|
||||
{ message_id: ctx.callbackQuery?.message?.message_id },
|
||||
{
|
||||
where: {
|
||||
id: ctx.from?.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (sentMessage.message_id) {
|
||||
await UserBase.update(
|
||||
{ message_id: sentMessage.message_id },
|
||||
{
|
||||
where: {
|
||||
id: ctx.from?.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error(`Key not found: ${key}`);
|
||||
}
|
||||
} else {
|
||||
console.error('User not found.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching user language:', error);
|
||||
console.log('An error occurred. Please try again later.');
|
||||
}
|
||||
}
|
||||
}
|
||||
109
mtucijobsbot/tsconfig.json
Normal file
109
mtucijobsbot/tsconfig.json
Normal file
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user