Телеграм-бот на NodeJS.

2. Отправить музыку, фото.
Tags: telegram, node
Published: 2021-01-03 13:35:10

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

```js
bot.command("audio", (ctx) => {
  return ctx.replyWithAudio({ source: "./song.mp3" });
});
```
Диалог с ботом должен быть похожим на этот:
![Передать песню](https://dev-to-uploads.s3.amazonaws.com/i/ar2grizxgbbricm964ur.png)

А теперь давайте научим бот команде, по которой он будет отправлять случайное фото какого-нибудь милого животного. Для этого нужно сперва запастись парой или больше фото. Но есть способ лучше. Можно воспользоваться [списком доступных апи](https://github.com/public-api-lists/public-api-lists#animals), которые могут выдать некое фото. Возьмём для примера https://aws.random.cat/meow .
Приступим к написанию команды:
```js
bot.command("photo", async (ctx) => {
  const response = await fetch("https://aws.random.cat/meow");
  const data = await response.json();
  return ctx.replyWithPhoto(data.file);
});
```
Всё выглядит логичным, но в NodeJS эта команда не сработает и даже не запустится скрипт. Потому что у него нет команды fetch и нам нужно её установить: `npm i node-fetch -S` . Итого, к настоящему времени файл bot.js должен начинаться с таких строк:
```js
require("dotenv").config();
const fetch = require("node-fetch");
const { Telegraf } = require("telegraf");
```
Напомню: первая строка делает доступным обращение к файлу .env *(который можно добавить в .gitignore, тем самым никому не рассказав свой ключ)*; вторая строка- мы сделали сейчас, подключили команду fetch; третья строка- подключение библиотеки telegraf.
Бот можно запустить командой node bot.js, но давайте сразу же оформим запуск командой. В раздел scripts файла package.json добавьте команду dev-bot:
```
  "scripts": {
    "dev-bot": "node edu.js"
  },
```
И теперь после запуска скрипта *npm run dev-bot* можно убедиться, что всё работает:
![Фото](https://dev-to-uploads.s3.amazonaws.com/i/yqf1zqh52nmf17584erc.png)
Итак, всё у нас работает, можно закончить на этом, но ... выше вы видели список многих апи, которые возвращают фото. Хотелось бы сделать так, что бы бот выдавал фото по какому-то признаку или скачивал по одному из каждого апи и выдавал это альбомом.
Рассмотрим эти возможности по-порядку.
### Скачивание фото по признаку

Для этого нужно подготовить сам бот, чтобы он мог понять команду, например, такую: `/photo dog` , а если вызывать */photo* без параметров, то бот понимал бы, что от него хотят любое фото.
Команда для бота - это, в принципе, текст сообщения, которое начинается с символа / . Поэтому разбор введённой строки может быть таким:
```js
const whatAnimal = ctx.message.text.split(" ")[1] || "";
```
Здесь мы получаем часть строки, которая начинается после первого пробела. И да, такой подход имеет право быть, но я предложу способ лучше. В telegraf можно написать свой мидлвар, который бы обрабатывал вводимый текст и снабжал наш чат некоей дополнительной информацией. Например- параметры команды.
Вот сейчас мы и напишем такой мидлвар:
```js
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();
};
```
Сохраните эти строки в файле lib/commandParts.js и подключите их в bot.js:
```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);
....
```
Команда для бота будет выглядеть по-другому:
```js
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);
});
```
Здесь появляется новая функция randomAnimal, которая записана в файле lib/animalPhoto . Вот его листинг:
```js
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)]();
};
```
О чём эти функции: Если параметр команды не пустой, то обращаемся к случайному апи, сгруппированным по параметру. Иначе- выбирается случайный апи. После вызова возвращается урл картинки.
Итак, в завершении этой заметки файл bot.js теперь стал таким:
```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();
```
Работа бота будет такой:
![Выбор фото](https://dev-to-uploads.s3.amazonaws.com/i/vntygpvdptqtlh34uart.png)
В следующий раз я расскажу, как выдавать фото-альбомы.