DEV Community

Stanislav Karol
Stanislav Karol

Posted on • Edited on

6 2

Телеграм-бот на 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.

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay