Хочешь уверенно проходить IT-интервью?

Мы понимаем, как сложно подготовиться: стресс, алгоритмы, вопросы, от которых голова идёт кругом. Но с AI тренажёром всё гораздо проще.
💡 Почему Т1 тренажёр — это мастхэв?
- Получишь настоящую обратную связь: где затык, что подтянуть и как стать лучше
- Научишься не только решать задачи, но и объяснять своё решение так, чтобы интервьюер сказал: "Вау!".
- Освоишь все этапы собеседования, от вопросов по алгоритмам до диалога о твоих целях.
Зачем листать миллион туториалов? Просто зайди в Т1 тренажёр, потренируйся и уверенно удиви интервьюеров. Мы не обещаем лёгкой прогулки, но обещаем, что будешь готов!
Реклама. ООО «Смарт Гико», ИНН 7743264341. Erid 2VtzqwP8vqy
Описание работы Telegram-бота
В боте присутствует 4 категории вопросов: HTML, CSS, JavaScript и React.

Чтобы было интереснее решать вопросы, я добавил еще 🏆Рейтинговый режим, в котором отсутствует выбор категории и вам предстоит решать все имеющиеся в базе вопросы. За каждый правильный ответ вам присуждается 1 балл. В случае неправильного ответа игра прекратится, а ее результат будет записан в базу данных и сохранен в вашем профиле.
Для добавления элемента соревнования между другими пользователями бота я добавил кнопку – Таблица лидеров, после нажатия на которую будет выведено ТОП-10 игроков Рейтингового режима.
У каждого игрока есть свой 👨🏼💻Профиль, в котором можно увидеть статистику по решенным вопросам в 4 категориях и баллы за игру в 🏆Рейтинговом режиме.

Используемые технологии
- Node.js: Серверная платформа для выполнения JavaScript-кода.
- grammY: Фреймворк для создания Telegram-ботов.
- sqlite: Встраиваемая база данных для хранения результатов пользователей.
- date-fns: Библиотека для форматирования дат и времени.
- dotenv: Модуль для загрузки переменных окружения из
.env
файла.
"dependencies": {
"date-fns": "^3.6.0",
"dotenv": "^16.4.5",
"grammy": "^1.23.0",
"nodemon": "^3.1.0",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7"
}
Структура проекта
Рассмотрим структуру проекта, которая включает все необходимые файлы и директории.
tech-interview-trainer/
├── .env
├── index.js
├── package.json
├── package-lock.json
├── README.md
└── questions/
├── html_questions.json
├── css_questions.json
├── js_questions.json
└── react_questions.json
└── leaderboard.db
- index.js: Основной файл проекта, содержащий весь код логики бота, включая инициализацию, обработку команд и взаимодействие с базой данных SQLite.
- questions/: Директория, содержащая JSON-файлы с вопросами по различным категориям (HTML, CSS, JavaScript, React).
Пример файла html_questions.json
:
{
"questions": [
{
"question": "Какой тег используется для создания ссылки?",
"options": ["<link>", "<a>", "<div>", "<img>"],
"correctOption": 1
},
{
"question": "Какой тег используется для создания списка?",
"options": ["<list>", "<ul>", "<ol>", "<menu>"],
"correctOption": 2
},
]
}
- leaderboard.db: Файл базы данных SQLite, в котором хранятся данные о пользователях, их результаты и время последней игры.
- .env: Этот файл содержит конфиденциальные данные, такие как токен API Telegram и ID администратора.
Пример файла .env:
BOT_API_KEY=your-telegram-bot-api-key
ADMIN_ID=your-telegram-id
Создание Telegram-бота
Создадим экземпляр бота и инициализируем его с API-ключом Telegram:
const bot = new Bot(process.env.BOT_API_KEY);
Определим начальное состояние сессии для каждого пользователя, которое взаимодействует с ботом:
bot.use(session({
initial: () => ({
correctAnswers: {
html: 0,
css: 0,
js: 0,
react: 0
},
hasStartedRatingMode: false
})
}));
Для скорости работы Telegram-бота добавим функцию для загрузки вопросов из JSON-файлов при запуске бота:
async function loadQuestions() {
const categories = {
html: 'html_questions.json',
css: 'css_questions.json',
js: 'js_questions.json',
react: 'react_questions.json'
};
for (const [category, file] of Object.entries(categories)) {
try {
const data = await fs.readFile(`questions/${file}`, 'utf8');
questionsData[category] = JSON.parse(data).questions;
} catch (error) {
console.error(`Ошибка при загрузке вопросов из файла ${file}:`, error);
}
}
}
Для хранения данных о пользователях и их результатах в таблице лидеров инициализируем базу данных SQLite. Эта функция открывает (или создает) базу данных и создает таблицу leaderboard, если она еще не существует:
async function initDatabase() {
db = await open({
filename: 'leaderboard.db',
driver: sqlite3.Database
});
await db.exec(`
CREATE TABLE IF NOT EXISTS leaderboard (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
score INTEGER NOT NULL,
last_played TEXT NOT NULL
)
`);
}
Добавим функции для работы с базой данных SQLite для управления профилями пользователей и их результатами в таблице лидеров.
- Функция createProfile. Эта функция создает профиль для пользователя, если его еще нет в таблице leaderboard.
async function createProfile(username) {
const existingEntry = await db.get('SELECT * FROM leaderboard WHERE username = ?', username);
if (!existingEntry) {
await db.run('INSERT INTO leaderboard (username, score, last_played) VALUES (?, ?, ?)', username, 0, 'Еще не играл');
}
}
- Функция updateLeaderboard. Эта функция обновляет таблицу лидеров для пользователя, добавляя или обновляя его запись в зависимости от результатов.
async function updateLeaderboard(username, score) {
const now = new Date().toISOString();
const existingEntry = await db.get('SELECT * FROM leaderboard WHERE username = ?', username);
if (existingEntry) {
if (existingEntry.score < score) {
await db.run('UPDATE leaderboard SET score = ?, last_played = ? WHERE username = ?', score, now, username);
} else {
await db.run('UPDATE leaderboard SET last_played = ? WHERE username = ?', now, username);
}
} else {
await db.run('INSERT INTO leaderboard (username, score, last_played) VALUES (?, ?, ?)', username, score, now);
}
}
- Функция getLeaderboard. Эта функция возвращает топ 10 пользователей из таблицы лидеров, отсортированных по убыванию очков.
async function getLeaderboard() {
return await db.all('SELECT username, score FROM leaderboard ORDER BY score DESC LIMIT 10');
}
- Функция getTotalUsers. Эта функция возвращает общее количество пользователей в таблице лидеров.
async function getTotalUsers() {
const result = await db.get('SELECT COUNT(*) AS count FROM leaderboard');
return result.count;
}
Добавим функции обработки для команд Telegram-бота /start, /profile, /admin.
- Команда /start: Эта команда инициализирует бота и приветствует пользователя, предлагая начать использование бота.
bot.command('start', async (ctx) => {
//Получение имени пользователя:
const username = ctx.from.username || ctx.from.first_name;
//Вызывается функция createProfile, чтобы создать профиль пользователя, если он еще не существует.
await createProfile(username);
//Создается клавиатура с кнопками для выбора темы.
const startKeyboard = getStartKeyboard();
//Отправляется приветственное сообщение и клавиатура с кнопками.
await ctx.reply(
'Привет! Я помогу тебе подготовиться к собеседованию. Используй команды ниже для взаимодействия с ботом:\n' +
'/start - Начать использование бота\n' +
'/profile - Просмотр вашего профиля',
{ reply_markup: startKeyboard }
);
await ctx.reply('С чего начнем? Выбирай тему👇', {
reply_markup: startKeyboard,
});
});
- Команда /profile: Эта команда отображает профиль пользователя, включая его результаты в различных категориях.
bot.command('profile', async (ctx) => {
//Получение имени пользователя:
const username = ctx.from.username || ctx.from.first_name;
//Получение данных пользователя из базы данных:
const result = await db.get('SELECT * FROM leaderboard WHERE username = ?', username);
//Получение общего количества вопросов по категориям:
const htmlQuestionsTotal = questionsData.html.length;
const cssQuestionsTotal = questionsData.css.length;
const jsQuestionsTotal = questionsData.js.length;
const reactQuestionsTotal = questionsData.react.length;
//Получение количества правильных ответов пользователя из сессии:
const htmlCorrect = ctx.session.correctAnswers.html;
const cssCorrect = ctx.session.correctAnswers.css;
const jsCorrect = ctx.session.correctAnswers.js;
const reactCorrect = ctx.session.correctAnswers.react;
if (result) {
const formattedDate = result.last_played === 'Еще не играл' ? result.last_played : format(new Date(result.last_played), 'dd MMMM yyyy, HH:mm', { locale: ru });
const profileMessage = `👤 Профиль пользователя ${username}:\n` +
`🏆 Счет в рейтинговой игре: ${result.score} очков\n` +
`📅 Дата последней игры: ${formattedDate}\n` +
`📚 Вопросы по HTML: решено верно ${htmlCorrect} из ${htmlQuestionsTotal}\n` +
`📚 Вопросы по CSS: решено верно ${cssCorrect} из ${cssQuestionsTotal}\n` +
`📚 Вопросы по JavaScript: решено верно ${jsCorrect} из ${jsQuestionsTotal}\n` +
`📚 Вопросы по React: решено верно ${reactCorrect} из ${reactQuestionsTotal}`;
await ctx.reply(profileMessage);
} else {
await ctx.reply('Профиль не найден. Начните игру в рейтинговом режиме, чтобы создать профиль.');
}
});
- Команда /admin: Эта команда предназначена для администрирования и доступна только пользователю с ID администратора. Она отображает общее количество пользователей.
bot.command('admin', async (ctx) => {
const userId = ctx.from.id;
const adminId = parseInt(process.env.ADMIN_ID, 10);
if (userId === adminId) {
const totalUsers = await getTotalUsers();
await ctx.reply(`Общее количество пользователей: ${totalUsers}`);
} else {
await ctx.reply('У вас нет прав для использования этой команды.');
}
});
Осталось добавить функционал бота, связанный с обработкой сообщений пользователей и викторинами.
- Основная функция обработки сообщений. В зависимости от текста сообщения, бот вызывает соответствующую функцию для начала викторины по выбранной категории, запуска рейтингового режима или отображения таблицы лидеров.
bot.on('message', async (ctx) => {
const { text } = ctx.message;
if (text === 'Назад ↩️') {
const startKeyboard = getStartKeyboard();
await ctx.reply('Выберите категорию:', {
reply_markup: startKeyboard,
});
} else {
switch (text) {
case 'HTML':
await startQuiz(ctx, 'html');
break;
case 'CSS':
await startQuiz(ctx, 'css');
break;
case 'JavaScript':
await startQuiz(ctx, 'js');
break;
case 'React':
await startQuiz(ctx, 'react');
break;
case '🏆Рейтинговый режим':
if (!ctx.session.hasStartedRatingMode) {
ctx.session.hasStartedRatingMode = true;
await ctx.reply(
'Рейтинговый режим содержит вопросы из всех категорий. За каждый правильный ответ дается балл, а при неверном ответе игра прекращается. Таблица лидеров выводит топ 10 игроков в рейтинге.'
);
}
initializeRatingMode(ctx);
await startRatingQuiz(ctx);
break;
case '📣Таблица лидеров':
await showLeaderboard(ctx);
break;
default:
handleQuizAnswer(ctx, text);
}
}
});
- Функция handleQuizAnswer. Эта функция обрабатывает ответы пользователя на вопросы викторины.
async function handleQuizAnswer(ctx, answer) {
try {
if (!ctx.session.currentQuestion) {
await ctx.reply('Кажется, я забыл вопрос. Давай начнем заново.');
return;
}
const correctAnswer = ctx.session.currentQuestion.options[ctx.session.currentQuestion.correctOption];
if (answer === correctAnswer) {
await ctx.reply('Верно!');
ctx.session.correctAnswers[ctx.session.currentCategory]++;
if (ctx.session.ratingMode) {
ctx.session.score += 1;
await startRatingQuiz(ctx);
} else {
await startQuiz(ctx, ctx.session.currentCategory);
}
} else {
if (ctx.session.ratingMode) {
const username = ctx.from.username || ctx.from.first_name;
await updateLeaderboard(username, ctx.session.score);
ctx.session.ratingMode = false;
const startKeyboard = getStartKeyboard();
await ctx.reply(`Ошибка! Вы набрали ${ctx.session.score} очков.`, {
reply_markup: startKeyboard,
});
ctx.session.score = 0;
} else {
await ctx.reply('Неправильно. Попробуйте еще раз.');
}
}
} catch (error) {
console.error('Ошибка обработки ответа на вопрос:', error);
await ctx.reply('Произошла ошибка при обработке ответа на вопрос. Попробуйте еще раз позже.');
}
}
- Функция startQuiz. Эта функция начинает викторину по указанной категории.
async function startQuiz(ctx, category) {
initializeQuizState(ctx, category);
const questions = questionsData[category];
if (!questions) {
await ctx.reply(`Не удалось загрузить вопросы для категории ${category.toUpperCase()}. Проверьте файл: questions/${category}_questions.json`);
return;
}
const questionData = getRandomQuestion(questions, ctx.session.askedQuestions[category]);
if (!questionData) {
const startKeyboard = getStartKeyboard();
await ctx.reply(`Вы ответили на все вопросы по ${category.toUpperCase()}!`, {
reply_markup: startKeyboard,
});
return;
}
Заключение
В этой статье я рассмотрел основные составляющие моего Telegram-бота, необходимые для его работы. Полный код проекта можно посмотреть в моем Github репозитории. Опробовать бота можно по ссылке. У меня есть еще пара идей для улучшения его функциональности в будущем, планирую добавить блок вопросов с задачами по JavaScript и добавить больше статистик в профилях пользователей.

Комментарии