Wake on Lan бот для Telegram

от автора

После открытия API ботов для Telegram их популяция начала стремительно расти. Я решил не отставать и обзавестись собственным для возможности удаленно включать компьютер. Для разработки был выбран язык C#, а в качестве хостинга выбор пал на Azure.

Задача

Написать бота, который сможет отправлять «магические пакеты» для включения компьютера на указанный адрес и порт.

Примечание: в статье не рассматриваются получение токена для бота и деплой сайта на Azure. На эти вопросы без труда находится ответ на хабре или Google.

Немного теории и сетевые настройки

Для включения компьютера будет использоваться технология Wake on Lan. Поскольку бот будет находиться в облаке, нам нужно отправлять пакет на внешний адрес. В связи с этим, нужно позаботиться о том, чтобы пакет в итоге попал в локальную сеть и достиг нужной сетевой платы. Для этого нужно пробросить порт на маршрутизаторе:

Почему 7 порт и такой адрес?

Обычно, чтобы включить компьютер в локальной сети с помощью Wake on Lan, требуется отправить «волшебный пакет» на широковещательный адрес локальной сети, на порт 7/UDP (в некоторых случаях могут использоваться и другие порты). Когда сетевая карта получает “свой” пакет, она подает сигнал на включение компьютера.

Пакет состоит из набора байт в следующем порядке: первые 6 байтов — нулевые, затем идет последовательность байт из мак-адреса сетевой карты, который повторяется 16 раз. Собственно, именно по MAC адресу сетевая карта и понимает, что нужно включить именно ее компьютер.

Мы будем формировать такой пакет и отправлять его на наш внешний адрес (например, на адрес домашнего роутера), после чего пакет должен будет маршрутизироваться на broadcast адрес локальной сети.

В моем случае пробрасывается 7 порт (доступный из интернета) на шировещательный адрес сети (10.10.10.255).

Пишем код

Для работы с API бота была выбрана библиотека Telegram.Bot. Для получения обновлений будем использовать вариант с вебхуком. В этом случае каждое сообщение или событие, которые будет получать бот, будут отправлены на указанный URL.

В качестве хостинга было решено использовать Azure, так как он подходит по всем параметрам:

  • всегда доступен
  • на бесплатном тарифе есть сертификат, который позволяет обращаться к сайту по HTTPS (боты могут отправлять обновления только по зашифрованным соединениям)
  • быстрая и удобная публикация сайта прямо из Visual Studio

Для начала создаем класс, который будет возвращать нам объект для работы с ботом:

public static class Bot {     private static Api _bot;      /// <summary>     /// Получаем бота, а если он еще     /// не инициализирован - инициализируем     /// и возвращаем     /// </summary>     public static Api Get()     {         if (_bot != null) return _bot;         _bot = new Api(Config.BotApiKey);         _bot.SetWebhook(Config.WebHookUrl);         return _bot;     } } 

Настройки достаем из класса Config

Config.cs

public static class Config {     /// <summary>     /// Настройки для бота храним в настройках приложения     /// </summary>     private static readonly NameValueCollection Appsettings = ConfigurationManager.AppSettings;      /// <summary>     /// Полученный токен для бота     /// </summary>     public static string BotApiKey     {         get { return Appsettings["BotApiKey"]; }     }      /// <summary>     /// URL, на который должны приходить все обновления от бота     /// </summary>     public static string WebHookUrl     {         get { return Appsettings["WebHookUrl"]; }     } } 

Теперь нам нужно формировать и отправлять пакет. За это будет отвечать класс WakeOnLan

/// <summary> /// Может отправлять "волшебные" пакеты для включения удаленного компьютера /// </summary> public static class WakeOnLan {     public static void Up(string ip, string mac, int? port = null)     {         var client = new UdpClient();         var data = new byte[102];          for (var i = 0; i <= 5; i++) // первые шесть байт - нулевые             data[i] = 0xff;          var macDigits = GetMacDigits(mac);         if (macDigits.Length != 6)             throw new ArgumentException("Incorrect MAC address supplied!");          const int start = 6;         for (var i = 0; i < 16; i++) // создаем нужную последовательность байт для пакета             for (var x = 0; x < 6; x++)                 data[start + i * 6 + x] = (byte)Convert.ToInt32(macDigits[x], 16);          client.Send(data, data.Length, ip, port ?? 7); // отправляем пакет     }      private static string[] GetMacDigits(string mac) // парсим MAC     {         return mac.Split(mac.Contains("-") ? '-' : ':');     }      public static bool ValidateMac(string mac) // простая проверка на валидность MAC адреса     {         return GetMacDigits(mac).Length == 6;     } } 

Пакеты формируем и умеем отправлять. Осталось научить бота отвечать на команду, к примеру, /wol.
Для простоты реализована команда с параметрами, т.е. пользователь должен будет ввести примерно следующее
/wol 1.2.3.4 01:02:03:04:05:06 7
для того, чтобы отправить пакет на адрес 1.2.3.4, на 7 порт и разбудить компьютер с MAC адресом 01:02:03:04:05:06

public async void Handle(Message message) {     var text = message.Text.Split(' ');     if (text.First() != "/wol") return;     switch (text.Count())     {         case 1:         case 2:             await _bot.SendTextMessage(message.Chat.Id, "Пример использования: /wol 1.2.3.4 01:02:03:04:05:06 7");             break;         default:             if (!WakeOnLan.ValidateMac(text[2]))                 await _bot.SendTextMessage(message.Chat.Id, "Неверный MAC адрес");             else             {                 try                 {                     WakeOnLan.Up(text[1], text[2], GetPort(text));                     await _bot.SendTextMessage(message.Chat.Id, "Пакет отправлен!");                 }                 catch (Exception)                 {                     await _bot.SendTextMessage(message.Chat.Id, "Произошла ошибка :(");                 }             }             break;     } }  /// <summary> /// Получаем порт из параметров /// </summary> private static int? GetPort(IReadOnlyList<string> text) {     int port;     if (text.Count == 4 && int.TryParse(text[3], out port))         return port;     return null; } 

Отлично, осталось лишь создать контроллер, который будет принимать обновления от бота:

public class MessageController : ApiController {     [Route(@"api/message/wol")]     public OkResult Post([FromBody]Update value)     {         Task.Run(() => new Handler().Handle(value.Message));         return Ok();     } } 

После «заливки» приложения в Azure проверяем бота:

Ссылки:
Проект на GitHub
API ботов Telegram
Библиотека Telegram.Bot (GitHub)

ссылка на оригинал статьи http://habrahabr.ru/post/265305/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *