DEV Community

Stanislav Karol
Stanislav Karol

Posted on • Updated on

Телеграм-бот на NodeJS. 2. Отправить музыку, фото.

Итак у нас есть бот, давайте научим его отправлять аудио-файлы по команде audio. Сперва нужно скопировать аудио-файл в проект и научить бота команде /audio :

bot.command("audio", (ctx) => {
  return ctx.replyWithAudio({ source: "./song.mp3" });
});
Enter fullscreen mode Exit fullscreen mode

Диалог с ботом должен быть похожим на этот:
Передать песню

А теперь давайте научим бот команде, по которой он будет отправлять случайное фото какого-нибудь милого животного. Для этого нужно сперва запастись парой или больше фото. Но есть способ лучше. Можно воспользоваться списком доступных апи, которые могут выдать некое фото. Возьмём для примера https://aws.random.cat/meow .
Приступим к написанию команды:

bot.command("photo", async (ctx) => {
  const response = await fetch("https://aws.random.cat/meow");
  const data = await response.json();
  return ctx.replyWithPhoto(data.file);
});
Enter fullscreen mode Exit fullscreen mode

Всё выглядит логичным, но в NodeJS эта команда не сработает и даже не запустится скрипт. Потому что у него нет команды fetch и нам нужно её установить: npm i node-fetch -S . Итого, к настоящему времени файл bot.js должен начинаться с таких строк:

require("dotenv").config();
const fetch = require("node-fetch");
const { Telegraf } = require("telegraf");
Enter fullscreen mode Exit fullscreen mode

Напомню: первая строка делает доступным обращение к файлу .env (который можно добавить в .gitignore, тем самым никому не рассказав свой ключ); вторая строка- мы сделали сейчас, подключили команду fetch; третья строка- подключение библиотеки telegraf.
Бот можно запустить командой node bot.js, но давайте сразу же оформим запуск командой. В раздел scripts файла package.json добавьте команду dev-bot:

  "scripts": {
    "dev-bot": "node edu.js"
  },
Enter fullscreen mode Exit fullscreen mode

И теперь после запуска скрипта npm run dev-bot можно убедиться, что всё работает:
Фото
Итак, всё у нас работает, можно закончить на этом, но ... выше вы видели список многих апи, которые возвращают фото. Хотелось бы сделать так, что бы бот выдавал фото по какому-то признаку или скачивал по одному из каждого апи и выдавал это альбомом.
Рассмотрим эти возможности по-порядку.

Скачивание фото по признаку

Для этого нужно подготовить сам бот, чтобы он мог понять команду, например, такую: /photo dog , а если вызывать /photo без параметров, то бот понимал бы, что от него хотят любое фото.
Команда для бота - это, в принципе, текст сообщения, которое начинается с символа / . Поэтому разбор введённой строки может быть таким:

const whatAnimal = ctx.message.text.split(" ")[1] || "";
Enter fullscreen mode Exit fullscreen mode

Здесь мы получаем часть строки, которая начинается после первого пробела. И да, такой подход имеет право быть, но я предложу способ лучше. В telegraf можно написать свой мидлвар, который бы обрабатывал вводимый текст и снабжал наш чат некоей дополнительной информацией. Например- параметры команды.
Вот сейчас мы и напишем такой мидлвар:

const regex = /^\/([^@\s]+)@?(?:(\S+)|)\s?([\s\S]+)?$/i;

/**
 * Мидлвар для разбора текста и команд в групповом чате
 */
module.exports = commandParts = async (ctx, next) => {
  // В переменную text запишется текст сообщения для бота
  const {
    message: { text = "" },
  } = ctx;
  // Разобьём это сообщение на части
  const parts = regex.exec(text);
  // Если, к примеру, одно слово, то нечего разбивать
  if (!parts) return next();
  // Сформируем объект command, который присоеденим к ctx.state
  const command = {
    text,
    command: parts[1],
    bot: parts[2],
    args: parts[3],
    get splitArgs() {
      return !parts[3] ? [] : parts[3].split(/\s+/).filter((arg) => arg.length);
    },
  };
  ctx.state.command = command;
  return next();
};
Enter fullscreen mode Exit fullscreen mode

Сохраните эти строки в файле lib/commandParts.js и подключите их в bot.js:

require("dotenv").config();
const fetch = require("node-fetch");
const { Telegraf } = require("telegraf");

const commandParts = require("../lib/commandParts");

// Создать бота с полученным ключом
const bot = new Telegraf(process.env.TELEGRAM_TOKEN_EDU);
// Подключить мидлвар
bot.use(commandParts);
....
Enter fullscreen mode Exit fullscreen mode

Команда для бота будет выглядеть по-другому:

bot.command("photo", async (ctx) => {
  const chatId = ctx.message.chat.id;
  // Получение аргументов
  const { args = "" } = ctx.state.command;
  // Возможно стоит проверить: верные аргументы пришли или нет
  const whatAnimal = args;
  // Пользователь, не скучай, я начал работу
  ctx.telegram.sendMessage(chatId, "Ищу фото ...");
  // Запрос урла картинки
  const url = await randomAnimal(whatAnimal);
  // Предусмотрительно защититься от null, который может внезапно прийти из апи (увы, да)
  if (!url) {
    return ctx.reply("Поиск фото не удался");
  }
  // А это что- gif, что ли пришёл, да?
  const extension = url.split(".").pop();
  if (extension.toLowerCase() === "gif") {
    // Если gif, значит оформить анимешку
    return telegram.sendAnimation(chatId, url);
  }
  return ctx.telegram.sendPhoto(chatId, url);
});
Enter fullscreen mode Exit fullscreen mode

Здесь появляется новая функция randomAnimal, которая записана в файле lib/animalPhoto . Вот его листинг:

const fetch = require("node-fetch");

/**
 * Случайное фото котофея
 */
const theCatApi = async () => {
  const response = await fetch("https://api.thecatapi.com/v1/images/search");
  const data = await response.json();
  return data.url;
};

/**
 * Ещё одно фото котёнка
 */
const randomCat = async () => {
  const response = await fetch("https://aws.random.cat/meow");
  const data = await response.json();
  return data.file;
};

/**
 * Собачка
 */
const dogCeo = async () => {
  const response = await fetch("https://dog.ceo/api/breeds/image/random");
  const data = await response.json();
  return data.message;
};

/**
 * Собачка 2
 */
const woof = async () => {
  const response = await fetch("https://random.dog/woof.json");
  const data = await response.json();
  return data.url;
};

/**
 * Лисичка
 *
 */
const randomFox = async () => {
  const response = await fetch("https://randomfox.ca/floof/");
  const data = await response.json();
  return data.image;
};

/**
 * Получить случайное фото
 * @param {'cat' | 'dog | 'fox'} animal
 */
exports.randomAnimal = async (animal = "") => {
  const listApi = [];
  if (!animal || animal[0] === "@") {
    listApi.push(theCatApi);
    listApi.push(randomCat);
    listApi.push(dogCeo);
    listApi.push(woof);
    listApi.push(randomFox);
  }
  const checkWord = animal.toLowerCase();
  if (checkWord === "cat") {
    listApi.push(theCatApi);
    listApi.push(randomCat);
  }
  if (checkWord === "dog") {
    listApi.push(dogCeo);
    listApi.push(woof);
  }
  if (checkWord === "fox") {
    listApi.push(randomFox);
  }
  if (listApi.length === 0) {
    return null;
  }
  return await listApi[Math.floor(Math.random() * listApi.length)]();
};
Enter fullscreen mode Exit fullscreen mode

О чём эти функции: Если параметр команды не пустой, то обращаемся к случайному апи, сгруппированным по параметру. Иначе- выбирается случайный апи. После вызова возвращается урл картинки.
Итак, в завершении этой заметки файл bot.js теперь стал таким:

require("dotenv").config();
const { Telegraf } = require("telegraf");

const commandParts = require("./lib/commandParts");
const { randomAnimal } = require("./lib/animalPhoto");

// Создать бота с полученным ключом
const bot = new Telegraf(process.env.TELEGRAM_TOKEN_EDU);
// Подключить мидлвар
bot.use(commandParts);

// Обработчик начала диалога с ботом
bot.start((ctx) =>
  ctx.reply(
    `Приветствую, ${
      ctx.from.first_name ? ctx.from.first_name : "хороший человек"
    }! Набери /help и увидишь, что я могу.`
  )
);

// Обработчик команды /help
bot.help((ctx) => ctx.reply("Справка в процессе"));

// Обработчик команды /whoami
bot.command("whoami", (ctx) => {
  const { id, username, first_name, last_name } = ctx.from;
  return ctx.replyWithMarkdown(`Кто ты в телеграмме:
*id* : ${id}
*username* : ${username}
*Имя* : ${first_name}
*Фамилия* : ${last_name}
*chatId* : ${ctx.chat.id}`);
});
bot.command("photo", async (ctx) => {
  const chatId = ctx.message.chat.id;
  // Получение аргументов
  const { args = "" } = ctx.state.command;
  // Возможно стоит проверить: верные аргументы пришли или нет.
  // Но это Вам на домашнее задание ;-)
  const whatAnimal = args;
  // Пользователь, не скучай, я начал работу
  ctx.telegram.sendMessage(chatId, "Ищу фото ...");
  // Запрос урла картинки
  const url = await randomAnimal(whatAnimal);
  // Предусмотрительно защититься от null, который может внезапно прийти из апи (увы, да)
  if (!url) {
    return ctx.reply("Поиск фото не удался");
  }
  // А это что- gif, что ли пришёл, да?
  const extension = url.split(".").pop();
  if (extension.toLowerCase() === "gif") {
    // Если gif, значит оформить анимешку
    return telegram.sendAnimation(chatId, url);
  }
  return ctx.telegram.sendPhoto(chatId, url);
});

// Обработчик простого текста
bot.on("text", (ctx) => {
  return ctx.reply(ctx.message.text);
});

// Запуск бота
bot.launch();
Enter fullscreen mode Exit fullscreen mode

Работа бота будет такой:
Выбор фото
В следующий раз я расскажу, как выдавать фото-альбомы.

Top comments (3)

Collapse
 
rekonner profile image
Rekonner

C:\Users\hilki\OneDrive\Рабочий стол\Telegramm bot JS\bot.js:2
const fetch = require("node-fetch");
^

Error [ERR_REQUIRE_ESM]: require() of ES Module C:\Users\hilki\OneDrive\Рабочий стол\Telegramm bot JS\node_modules\node-fetch\src\index.js from C:\Users\hilki\OneDrive\Рабочий стол\Telegramm bot JS\bot.js not supported.
Instead change the require of index.js in C:\Users\hilki\OneDrive\Рабочий стол\Telegramm bot JS\bot.js to a dynamic import() which is available in all CommonJS modules.
at Object. (C:\Users\hilki\OneDrive\Рабочий стол\Telegramm bot JS\bot.js:2:15) {
code: ←[32m'ERR_REQUIRE_ESM'←[39m
}
Что делать?
Fetch подключил

Collapse
 
shiradzee profile image
shiradzee

npm install node-fetch@2
у меня с этим заработало.

Collapse
 
slkarol profile image
Stanislav Karol

С таким не встречался, но в описании написано, что вместо require сделайте import.