[Из песочницы] Wake on Lan бот для Telegram
После открытия API ботов для Telegram их популяция начала стремительно расти. Я решил не отставать и обзавестись собственным для возможности удаленно включать компьютер. Для разработки был выбран язык C#, а в качестве хостинга выбор пал на Azure.
Задача
Написать бота, который сможет отправлять «магические пакеты» для включения компьютера на указанный адрес и порт.
Примечание: в статье не рассматриваются получение токена для бота и деплой сайта на Azure. На эти вопросы без труда находится ответ на хабре или Google.
Немного теории и сетевые настройки
Для включения компьютера будет использоваться технология Wake on Lan. Поскольку бот будет находиться в облаке, нам нужно отправлять пакет на внешний адрес. В связи с этим, нужно позаботиться о том, чтобы пакет в итоге попал в локальную сеть и достиг нужной сетевой платы. Для этого нужно пробросить порт на маршрутизаторе:
Пакет состоит из набора байт в следующем порядке: первые 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;
///
/// Получаем бота, а если он еще
/// не инициализирован - инициализируем
/// и возвращаем
///
public static Api Get()
{
if (_bot != null) return _bot;
_bot = new Api(Config.BotApiKey);
_bot.SetWebhook(Config.WebHookUrl);
return _bot;
}
}
Настройки достаем из класса Config
public static class Config
{
///
/// Настройки для бота храним в настройках приложения
///
private static readonly NameValueCollection Appsettings = ConfigurationManager.AppSettings;
///
/// Полученный токен для бота
///
public static string BotApiKey
{
get { return Appsettings["BotApiKey"]; }
}
///
/// URL, на который должны приходить все обновления от бота
///
public static string WebHookUrl
{
get { return Appsettings["WebHookUrl"]; }
}
}
Теперь нам нужно формировать и отправлять пакет. За это будет отвечать класс WakeOnLan
///
/// Может отправлять "волшебные" пакеты для включения удаленного компьютера
///
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;
}
}
///
/// Получаем порт из параметров
///
private static int? GetPort(IReadOnlyList 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 проверяем бота:
Ссылки:
WoL бот в Telegram
Проект на GitHub
API ботов Telegram
Библиотека Telegram.Bot (GitHub)