Идея создания бота пришла после замедления YouTube в России. Многие блогеры стали активно призывать подписчиков переходить в Telegram, чтобы не потерять связь с аудиторией. Я решил сделать удобный инструмент для быстрого поиска Telegram-каналов любимых авторов.
Что я использовал?
Поскольку опыта в разработке у меня было мало, я выбрал следующий стек технологий:
Node.js с библиотекой telegraf.js для работы с Telegram API
MongoDB и mongoose для работы с базой данных
Express.js для создания веб-сервера
Google API для работы с YouTube
Lemnos API для получения дополнительной информации о каналах
Реализация
1. Основные команды бота
Начнем с главной команды /start
, которая инициализирует работу с ботом:
bot.start(async (ctx) => {
const chatId = ctx.chat.id;
let chat = await Analytics.findOne({ chatId: chatId })
// Создаем новую запись пользователя, если его нет в базе
if (chat === null) {
try {
let username = ctx.message.chat.username
let newChat = new Analytics({
chatId: ctx.message.chat.id,
username: username,
awatingChannels: true,
status: "member",
count: 0
})
await newChat.save()
} catch {
// Если username недоступен, используем first_name
let newChat = new Analytics({
chatId: ctx.message.chat.id,
username: ctx.message.chat.first_name,
awatingChannels: true,
status: "member",
count: 0
})
await newChat.save()
}
} else {
chat.awatingChannels = true
await chat.save()
}
// Отправляем приветственное сообщение с кнопками
await setBotCommands()
ctx.replyWithHTML(
'<b>Приветствуем вас в нашем сервисе поиска Telegram-каналов ютуберов!</b>\n' +
'Бот безопасен, так как представляет собой открытый исходный код, ' +
'который может посмотреть каждый желающий. (/faq или пишите @vitosperansky)\n\n' +
'Поддержать проект: https://www.donationalerts.com/r/vitosperansky\n\n' +
'Выберите опцию:',
Markup.inlineKeyboard([
[Markup.button.callback('Найти YouTube-каналы в Telegram', 'find_channels')],
[Markup.button.callback('Связать YouTube-канал с Telegram-каналом', 'link_channel')]
]), {
disable_web_page_preview: true
}
);
});
2. Авторизация через Google
Для работы с YouTube API необходима авторизация через Google. Вот как реализована генерация URL для авторизации:
async function generateAuthUrl(chatId) {
const credentials = JSON.parse(fs.readFileSync(CREDENTIALS_PATH));
const { client_id, client_secret } = credentials.web;
const oAuth2Client = new OAuth2Client(
client_id,
client_secret,
REDIRECT_URL
);
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'online',
scope: SCOPES,
state: chatId.toString()
});
return authUrl;
}
Когда пользователь нажимает на кнопку поиска каналов, запускается следующий обработчик:
const find_channels = async (ctx) => {
const chatId = ctx.chat.id;
const authUrl = await generateAuthUrl(chatId, ctx);
ctx.replyWithMarkdown(
'*Нажмите кнопку ниже для авторизации на Youtube и получения списка ваших подписок:*\n\n' +
'❗Авторизация нужна только для получения списка ваших подписок ' +
'(запрашиваются права youtube.readonly - только чтения, подробнее /faq)❗\n\n' +
'_Процесс займет время: ~50 секунд. (в зависимости от количества ваших подписок)_',
{
reply_markup: {
inline_keyboard: [
[{ text: 'Авторизоваться и найти подписки', url: authUrl }]
]
}
}
);
};
// Обработчики команды поиска каналов
bot.action('find_channels', async (ctx) => {
ctx.answerCbQuery();
await find_channels(ctx)
})
bot.command('find_channels', async (ctx) => {
await find_channels(ctx)
})
3. Дополнительные команды
Бот также имеет несколько дополнительных команд для удобства использования:
// Команда FAQ
bot.command('faq', async (ctx) => {
ctx.replyWithMarkdown(`
**Ответы на вопросы о проекте:**
Какова цель проекта?
— Максимально упростить поиск Телеграмм каналов ваших любимых авторов.
У меня не украдут Google Аккаунт?
— Нет, бот имеет открытый исходный код, который может посмотреть каждый желающий на Github - https://github.com/VitoSperansky/FromYoutubeToTelegram.
Как работает бот?
— Бот просит вас авторизоваться в свой Google аккаунт, чтобы получить список ваших подписок на YouTube.
Затем система обращается к своей базе данных, где хранятся соответствия YouTube-каналов и их Телеграмм-каналов.
Если бот находит соответствия в базе данных, он записывает их в список найденных каналов.
Если YouTube-каналы, на которые вы подписаны, отсутствуют в нашей базе данных, бот отправляет запрос
в YouTube на получение ссылок социальных сетей, привязанных к каналу. Среди этих ссылок бот ищет
ссылку на Телеграмм. Найдя новую ссылку на Телеграмм-канал, бот добавляет её в базу данных.
В итоге, пользователь получает список YouTube-каналов с их Телеграмм-каналами.
Остались вопросы? - Пишите @vitosperansky
`);
});
// Команда для рассылки сообщений (только для администратора)
bot.command('send', async (ctx) => {
if (ctx.message.chat.id == MODERATOR_CHAT_ID) {
let chatId = ctx.message.text.replace('/send ', '').replace(/ [\s\S]+/, '');
let text = ctx.message.text.replace('/send ', '').replace(`${chatId} `, '').toString();
if(chatId === 'all') {
let Users = await Analytics.find()
let goodSend = [];
let badSend = [];
ctx.reply("Рассылка началась.")
for (let i = 0; i < Users.length; i++) {
try {
await bot.telegram.sendMessage(Users[i].chatId, text, { parse_mode: "HTML" });
goodSend.push(Users[i]);
} catch (error) {
badSend.push(Users[i]);
}
}
ctx.reply(`Рассылка завершена\n\nУспешно отправлено: ${goodSend.length} сообщений.\n` +
`Не получилось отправить: ${badSend.length} сообщений.`)
} else {
try {
await bot.telegram.sendMessage(chatId, text, { parse_mode: "HTML" });
ctx.reply(`Сообщение успешно отправлено пользователю. \n\nChatId: ${chatId}\nТекст: ${text}`)
} catch {
ctx.reply("Ошибка при отправке сообщения.")
}
}
} else {
ctx.reply("Вы не админ!")
}
});
Проблемы и их решения
1. Проблема дублирования запросов
При авторизации в Google-аккаунте возникла проблема с дублированием запросов, если у пользователя несколько аккаунтов. Для решения этой проблемы я использовал флаг awaitingChannels
в базе данных, который позволяет отслеживать состояние запроса и избегать дублирования.
2. Ограничение длины сообщений
В первых версиях бот пытался отправить все найденные каналы одним сообщением, но столкнулся с ограничением Telegram на длину сообщения. Решение было простым - разбить информацию на несколько сообщений. (присылать txt файлом к примеру не совсем верно, ведь тогда теряется легкость в переходе на телеграм канал).
3. Сайт
Сайт работает на порту 3000 (fytt.tech:3000), что не совсем стандартно для веб-приложений. Это связано с тем, что порты ниже 1024 по умолчанию закрыты для установки серверов из соображений безопасности. В идеале следовало бы настроить переадресацию с порта 443 (стандартный HTTPS порт) с помощью инструмента вроде ngrok, но поскольку сайт служит в основном для верификации Google, эта задача была отложена.
Процесс верификации Google
Получение доступа к YouTube API потребовало пройти верификацию Google. Процесс включал несколько этапов:
Создание логотипа: Первая версия логотипа была отклонена из-за слишком явного использования элементов YouTube и Telegram. Пришлось создать более оригинальный дизайн.
Разработка сайта: Потребовалось создать сайт с политикой конфиденциальности и пользовательским соглашением. При этом возникли следующие требования:
Необходимость владения доменом
Настройка SSL-сертификатов через certbot
Корректная политика конфиденциальности
Демонстрация работы: Создание демо-видео для показа функционала бота. (пришлось им видео записать под смешную музыку)
Продвижение проекта
Потом я решил продвинуть бота и записал два смешных shorts:
https://youtu.be/N0IGLuufSCE — text to speech (elevenlabs)
https://youtu.be/MlXEUIDBhE0 — Speech to speech моей записи на ии оригинального голоса Рика из Рика и Морти (сделал на этом сайте).
Полезные ссылки
Исходный код: https://github.com/VitoSperansky/FromYoutubeToTelegram
Сайт: fytt.tech:3000
Контакты для связи: https://t.me/vitosperansky
Планы на будущее
Улучшение алгоритма поиска каналов
Оптимизация работы с базой данных
Добавление новых функций по запросам пользователей
Настройка правильной маршрутизации портов на сервере
Заключение
Проект показал, что даже с минимальным опытом разработки можно создать полезный инструмент, который решает реальную проблему пользователей. Открытый исходный код и прозрачность работы позволили завоевать доверие пользователей, а интеграция с популярными платформами обеспечила удобство использования.