Приветствую всех читателей Хабра!

Расскажу предысторию создания данного поста

Недавно, скучая после безумно скучного учебного дня и бездумно листая главную страницу моего любимого видеохостинга в поисках чего-нибудь интересного, мне попалось одно любопытное видео, сподвигшее к созданию невероятного (внизу версия на RuTube, выложенная мной для удобства читателей из России).

Как можно видеть в самом видео, человек, нажимая на кнопку в Майнкрафте, включает у себя лампу в реальной жизни.

Круто! Но как это работает?

После прочтения комментариев (и ответа самого создателя видео) всё встало на свои места:

Комментарий создателя

Перевод:

Прежде чем я начну объяснять, как это работает, давайте назовем устройства, которые участвуют в процессе:
MCPC - компьютер ComputerCraft в Minecraft
MyServ - мой IRL-сервер
PowSwitch - интеллектуальный выключатель питания, подключенный к лампе

Lamp подключен к коммутатору питания Wi-Fi (PowSwitch). PowSwitch подключается к Интернету и имеет API для управления им.

Компьютеры ComputerCraft могут выполнять простые HTTP-веб-запросы. Они не способны выполнять запросы / вызовы API.

Вот тут-то и появляется MyServ - он реагирует на HTTP-веб-запрос MCPC и "переводит" его в вызов API PowSwitch.

Проще говоря - когда вы нажимаете кнопку в Minecraft, MCPC пытается загрузить случайные данные из MyServ. MyServ обнаруживает запрос MCPC и реагирует отправкой команды через API PowSwitch. Эта команда API заставляет PowSwitch переключать питание и, следовательно, включать лампу enitre.

"Ну ничего себе!" - подумал я - "Это сколько же возможностей даёт один единственный мод!".
И... так оно и есть. Через модификацию и HTTP можно связаться с сервером и через него передавать данные!

Однако обо всём по порядку.

Сейчас я собираюсь рассказать о том, как конкретно можно это сделать на примере отправки сообщений через Telegram-бота через фреймворк Express.js (на базе Node).

Опишем все "инструменты" по порядку (Если вдруг чего-то из этого не знаете, не волнуйтесь! В процессе объяснения всё будет понятно, а вместо node.js Вы можете использовать любой инструмент, позволяющий прописывать API - в данном случае, это большой роли не играет):

  • Майнкрафт мод - "Computer Craft (CC) Tweaked".

  • Версия Майнкрафта - 1.20.1 (Forge).

  • Туннель ("зеркало" нашего локального сервера под уникальным доменом + созданным с включенным SSL (https), откуда будут идти запросы) - ngrok.

  • Обращения к телеграм-боту - node-telegram-bot-api.

  • API на Node + Express.js.

Важно учитывать то, что подчёркнуто!


Возможно, у внимательных читателей возникнет вполне резонный вопрос

Почему создатель того видео использует Computer Craft, а мы используем именно CC Tweaked?

Вопрос хороший! На него есть ответы.

- Во-первых, CC Tweaked всё ещё развивается, дышит и поддерживается, о чём свидетельствует частое появление коммитов в его репозитории. У него простая официальная документация, описывающая всё лучше (особенно в моментах взаимодействия с сервером), чем оригинальная, а также живое коммьюнити, так что при наличии каких-то вопросов Вам, скорее всего, ответят достаточно быстро.

CC Tweaked - это форк оригинального Computer Craft.

Всё, что применимо к Computer Craft - применимо к CC Tweaked.

Репозиторий CC Tweaked на момент 14.12.2024

К сожалению, то же самое нельзя сказать про оригинальный Computer Craft - его последняя версия была выпущена в 2017 году на версию 1.12.2, а репозиторий обновляется раз в несколько лет ради каких-то совсем незначительных изменений.

Коммиты репозитория Computer Craft

Официальный веб-сайт и сам говорит, что, увы, жизнеспособности у него нет.

Скрин с официального сайта www.computercraft.info

- Во-вторых, протокол HTTP живёт своей жизнью на оригинальном моде.

Скрин с версии 1.8.9 (стабильной) Computer Craft при попытках обратиться по http к "www.google.com" (что используется в туториалах) и к собственному ngrok-туннелю.

При попытках взаимодействия с http постоянно вылазят ошибки, что "невозможно получить данные с сервера" - грубо говоря, nil. На оригинальном моде вообще В ПРИНЦИПЕ при обращении к любому эндпоинту будет возвращаться ниловое значение.

Забавно, что в поиске решений проблемы люди рекомендуют поменять значения в конфигах мода для работы с http. (Представили моё лицо, когда оказалось, что там уже прописано ровно то, что и предлагают при решении этой ошибки?)

Предложение решения на Computer Craft
Буквально тот же самый запрос без замен конфига, но уже на CC Tweaked (Правильный респонс)

Вероятно, CC Tweaked, в связи с рефакторингом, исправлением старых ошибок и привнесением нового функционала в мод (напомню, последняя версия оригинала - 2017 год), настроен на более новую отправку запросов по http, поэтому Computer Craft в этом плане выглядит проигрышно, нежели его форк.

Если уяснить это, вопросов на данный счёт возникать не должно.
Ну и, вероятно, автор сразу имел в виду конкретно Tweaked, т.к. оригинальное видео вышло всего-лишь 4 года назад, но это лишь мелочи.


Приступим к созданию!

Для начала нужно подготовить всё нужное для мода:

  1. Скачать мод;

  2. Создать бота;

  3. Зарегистрироваться и установить ngrok.

  4. Создать наш node.js проект.

  5. Включить ngrok и создать туннель между локалхостом.

Начнём с первого и самого простого пункта - мод

Переходим по ссылке (у вас сразу должна начаться загрузка на версию Forge 1.20.1).
После скачивания копируем из папки загрузки и переходим в путь - "C:\Users\Пользователь\AppData\Roaming\.minecraft\mods" - куда и вставляем наш мод.

Конгратулатионс - мод у нас в кармане.

Двигаемся дальше - создание бота

Здесь всё тоже не очень сложно.
Заходим в Telegram и ищем BotFather.

Начинаем с ним диалог, после чего создаём нового бота:

Создание бота в BotFather
Любопытненько.

Не пытайтесь использовать этого бота.
На момент выкладки поста его уже не существует.

Далее просто начинаем переписку с ним:

/start у нашего бота

"HTTP API token", который он нам выдал, понадобится при создании нашего бота с помощью Ноды.

Третий пункт - ngrok.

Если Вы из России (как я), то просто так Вам на этой платформе не зарегистрироваться:

Your location was determined using your IP address

Однако это нас не остановит.

Качайте VPN (любой, который нравится и работает), используйте почту НЕ MAIL.RU и смело снова пытайтесь зарегистрироваться - нажимайте на кнопочку "Sign Up".
Я обычно захожу через GitHub, но можете зарегистрироваться прямо через почту или Гугл-аккаунт.

Далее нас встречает приветственная страничка.

Welcome!

Ngrok требует VPN только на этапе регистрации.
Далее он не понадобится - можете его отключить.

Windows окошко
  1. Переходим вниз к пункту "Connect" - выбираем "Download" и качаем для своей системы (64\32 бита).

  2. Нам скачался архив с exe-файлом. Просто перекидываем на своё рабочее место и открываем.

  3. После открытия вставляем всю команду с нашим "add-authtoken" и нажимаем Enter.

  4. Радуемся жизни!

Четвёртый пункт - создание проекта.

В моём случае, Express.js (Однако Вы можете писать, на чём душе будет угодно).

Всё как всегда.

  1. Инициализируете проект (npm init -y);

  2. Скачиваете пакеты express, nodemon (для реал-тайм перезагрузок) и node-telegram-bot-api (npm i express nodemon node-telegram-bot-api).

Создание проекта

После чего создаёте index.js в той же директории и меняете скрипты запуска в package.json, вставляя туда обращение к nodemon при дев-сервере и к node при старте билда.
Не забываем также указать, что мы используем ES-модули, чтобы использовать импорты как в обычном проекте на js.

package.json

Инициализируем Express в нашем index.js файле:

Код express.js

И далее в терминале просто запускаем наш код через npm run dev:

Запуск проекта

Мы запустили наш проект, однако этого недостаточно для Tweaked. Мод не может слушать localhost, поэтому мы должны, скажем, отзеркалить его на какой-то рабочий домен.

Последнее - туннель ngrok

Чувствую, что немного устали)
Не волнуйтесь, данный шаг станет последним, после чего зайдём в сам Майнкрафт.

Чтобы создать туннель с проектом нужно воспользоваться командой ngrok http 3000. Где "3000" - это порт, который мы задали через Express (app.listen(3000)).
Грубо говоря, мы говорим ngrok'у: "Чувак, создай нам туннель через http по порту, который мы дали локалхосту":

Создаём туннель

После нажатия Enter у нас открывается окошко, в котором мы можем взять наш URL, по которому сможем обращаться в Майнкрафте (https://be84-95-106-165-71.ngrok-free.app):

Мы создали туннель!

Поздравляю! Мы готовы двигаться дальше! А дальше только интереснее

Мы подошли к тому, чтобы запустить наш Майнкрафт

Я НЕ буду углубляться в сам CC Tweaked, ровно как и в язык, на котором пишется код в нём - Lua.

Моя задача - лишь познакомить с ним и показать Вам, насколько просто использование HTTP через этот мод.

Если всё же интересно изучить конкретно мод, вот плейлист с уроками на все самые важные темы на русском языке:

https://www.youtube.com/watch?v=7HrWg_P7uKk&list=PL3A9AC22762B7829E

Запускаем на версии Forge 1.20.1 (Как было обговорено раньше) и создаём карту.

Инвентарь в творческом режиме

Можем обратить внимание, сколько предметов может предложить мод, однако нам интересна вещь, без которой мы не сможем обращаться к нашему серверу - компьютер.

Так как мы хотим, чтобы компьютер, как TabNine, предлагал нам автокомплит, а также в принципе имел больше возможностей, чем обычный компьютер, возьмём продвинутый (большой жёлтый блок в самом начале):

Продвинутый компьютер в инвентаре

Далее открываем его, после чего вводим команду edit (название файла).lua.
Запоминаем, что "edit" в Computer Craft - это открытие файла для изменения.

edit index.lua

Нам открывается пустое окошко, которое является своего рода блокнотом, куда мы можем вводить наш код, а по нажатию ctrl, и выбрав "Run" стрелочками слева снизу, дополнительно нажав Enter, запустить (выйти из этого режима можно также нажав ctrl):

Блокнот CC Tweaked

Переменные здесь создаются без объявления каких-либо типов или даже ключевых слов. Просто какому-то названию переменной присваиваем какое-то значение (Пайтонисты радуются).

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

NODE_ENPOINT

Теперь мы даже можем обратиться через http и сделать какой-то демо-запрос.

Для этого у нас есть глобальный класс "http", через который мы можем создать условный GET-запрос.
Вывести результат в консоль мы можем с помощью функции print() (Пайтонисты радуются).

Наравне с GET мы также можем сделать POST-запрос. Хедеры для него указываются вторым аргументом после эндпоинта.

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

Полная таблица методов для http в CC Tweaked выглядит так:

Таблица методов http

Переходим к написанию кода на JavaScript

Суть программы

Суть нашей программы будет заключаться в том, что у каждой переписки есть свой id (так называемый "chat id"). Мы будем следить за его наличием в боте. Если таковой имеется, будем отправлять туда сообщения до тех пор, пока пользователь нажимает на кнопку, которая подведена редстоуном. Если новый пользователь введёт сообщение в него, то бот переключится на нового пользователя, и при "накликивании" в Майнкрафте сообщения будут идти на него. Так будет до тех пор, пока новый пользователь не заблокирует нашего бота. Если всё же произойдёт блок, но в Майнкрафте произошёл ещё один клик на кнопку, то мы шлём ошибку, а затем снова (с нуля всего цикла) запрашиваем написать боту сообщение.

Мне подсказали, что подобного бота стоит назвать "Бот-ждун".
Ради бога, думаю, правда можно назвать так)

Подключаемся к боту

Сначала, чтобы наш бот работал, нам нужен его HTTP API token, который так к стати нам дал BotFather ещё в самом начале.

Далее нам нужно инициализировать бота через класс из библиотеки, указав также polling (Грубо говоря, мы будем следить за тем, что пришло к нашему боту в реальном времени).

Давайте для теста также укажем боту, чтобы на сообщение он нам отвечал тем же сообщением (Подобные боты называются "Эхо-ботами").
Перед этим давайте выведем то, что бот даёт при ответе на то, что мы написали ему какое-то сообщение. Для этого воспользуемся методом ".on('message', () => {})" и вызовем console.log() с ответом на событие.

Ответ на событие отправки сообщение

Видим, что ответом является большой объект, из которого нам на данный момент нужно вытянуть текст - ключ "text", а также айди чата (ключ "chat.id"), по которому нужно отправить сообщение.

Чтобы создать «эхо» просто воспользуемся методом ".sendMessage(msg.chat.id, msg.text)".

Результат работы

Как видим - всё работает!

Пишем Express.js

Далее мы напишем небольшое API.

В связи с тем, что приложение тестовое, небольшое, будем обращаться к обычному "/"

Для начала создадим обращение к нашему Экспресс'у через GET-запрос.

app.get("/", function (request, response) { response.send("Welcome") })

"Welcome"

На основе чего зададим следующую логику:

  1. На глобальном уровне пропишем мутируемую переменную chatId, которой будет являться чат, на который в данный момент будут слаться сообщения.

  2. В теле callback'а запроса будем смотреть, если наш чат не нулевой.
    Если такое условие соблюдается, то мы выполняем отправку сообщения боту, а также обрабатываем этот промис на наличие reject'а (Коим и является проверкой на блок у пользователя. Как раз таки в .catch() мы и будем его обнулять, ожидая нового айди).
    Если условие не соблюдается, то мы просто возвращаем, что хотим от пользователя ввести какое-то сообщение.

  3. Дополним наше ".on('message')" присваиванием нового значения для chatId, а также отправкой его через бота.

  4. Также пропишем функцию "sendResponse", которая будет возвращать void - отправку на запрос (грубо говоря, вывод в консоль в Майнкрафте) функции со строкой, которая разделяется с помощью "\n", а также трёх сплошных линий и принимать два аргумента: саму функцию для отправки и строчку, которую мы должны отправить.

  5. Важной частью всего колбэка является наличие хедера ngrok-skip-browser-warning .
    Видите ли, когда Вы переходите по URL, который Вам дал ngrok, то вначале мы видим приветственное "Хеллоу!" от нашего дорогого туннеля.

    You are about to visit ngrok

    В чём основная проблема - запрос тоже видит это приветственное сообщение, которое, словно кирпичная стена, не даёт получить нам тот ответ, который нужен, из-за чего мы будем получать nil вместо ответа.

  6. Наш хедер, который мы поставим с помощью "response.set('ngrok-skip-browser-warning', 'skip-browser-warning')" будет важной частью правильной работы.

    Результат кода по итогу должен выглядеть так:

    Код проекта

Или (для копирования):

import Express from 'express';
import TelegramBot from "node-telegram-bot-api";
const app = Express();
const TOKEN = "7876670462:AAHVTSEIoblDhauy1x0U2fQG9lT-LoqKLvI";

const bot = new TelegramBot(TOKEN, {polling: true});
let chatId = 0;
const sendResponse = (respFoo, str) => {
return respFoo.send(str + '\n ---');
}

app.get("/", function (request, response) {
response.set('ngrok-skip-browser-warning', 'skip-browser-warning');
if (chatId > 0) {
bot.sendMessage(chatId, `Minecraft program was launched. Message was sent on chat id - "${chatId}"`).then(() => {
sendResponse(response, `Message sent. Current chat id - "${chatId}"`);
}).catch(() => {
sendResponse(response, `Oops! Something went wrong with chat id "${chatId}". Check, if your bot is not blocked and type it any message to set new chat id!`);
chatId = 0;
})
} else {
        sendResponse(response, `You need to pass a chat id to the bot. Pass it first and come back to send a message!`);
}
});

bot.on('message', (msg) => {

bot.sendMessage(msg.chat.id, `Current chat id for sending messages is ${msg.chat.id}`);

chatId = msg.chat.id;
})
app.listen(3000);

Также необходимо указать ".readAll()" при GET в CC Tweaked. Он позволяет прочитать всё содержимое файла правильно и показать нам именно строчку, а не таблицу ("table").

readAll()

Письмо счастья

И теперь, если при запуске программы (ctrl + Enter и выбрать "Run") Вам выдался текст в Майнкрафте, а при написании боту вернулся ответ "Message sent..." - поздравляю! Ваш код работает верно.

Для полноты картины давайте выведем наш результат в монитор

Мод позволяет нам использовать мониторы, чтобы смотреть результаты программы, не отходя от концепции блоков.

Давайте поставим справа четыре блока продвинутого монитора и выведем на него сообщение.

Вначале построим прямо справа от нашего компьютера 4 блока монитора (блоки расширяют друг друга, отчего превращаются в один большой монитор).

Затем нужно обратиться к монитору. Чтобы это сделать, нужно выйти из программы - (ctrl + Exit + Enter), после чего прописать ключевое слову "monitor", указать, в каком направлении от компьютера стоит монитор ("right" / "left" / "top" / т.д.) и написать название программы для вывода. Всё просто!

Прописываем обращение к монитору
Наш рабочий монитор

Осталось доделать совсем немного, чтобы назвать наши планы завершёнными ;)

Взаимодействие с кнопкой в Майнкрафте

Computer Craft из коробки позволяет взаимодействовать с редстоуном. Для этого у него на глобальном уровне прописан класс «rs» (Акроним — «RedStone»).

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

Методы взаимодействия с красным камнем (Input / Output)

Одним из таких, который нужен для того, чтобы смотреть нажалась кнопка или нет, является ".getInput()". Через него мы будем тыкать запросы, чтобы затем выводить их результаты.

В чём основная проблема?

".getInput()" НЕ слушает ввод через редстоун постоянно. Его главная задача заключается в том, чтобы лишь один раз посмотреть наличие и вывести какой-то ответ.
Поэтому было решено сделать троттлинг на прослушивание событий кнопки.


Что такое троттлинг?

Говоря грубо, мы раз в какое-то время будем смотреть изменение состояния чего-либо. В нашем случае, будет воспроизводиться функция "sleep()" с циклом while. Данная функция не даёт потоку программы двигаться, пока не пройдёт определённый промежуток времени, который был указан в параметры этой функции.

Давайте напишем этот цикл:

Троттлинг нажатия кнопки
Компьютер с красным камнем

И не забываем подвести строго слева (как прописано в направлении - "left") кнопку с красным камнем.

Далее делаем всё ту же процедуру:

  1. "monitor right index.lua";

  2. Затем нажимаете на кнопочку;

  3. Пишет ответ - вам приходит в Telegram уведомление!

Эпилог

Возможно, данное решение требует некоторой программной подготовки ради того, чтобы хотя бы что‑то написать, однако способ подобного взаимодействия сводит с ума. Сводит с ума также тот факт, сколько всего можно таким образом реализовать.

Попробуйте и Вы сделать что‑то интересное!

Буду очень рад узнать, что моё объяснение кому‑то хотя бы минимально в этом плане помогло).

Также буду очень рад услышать, что меня можно где‑то поправить.
Если есть какие‑то вопросы по коду, по содержанию — пишите, на всё постараюсь ответить.

Всем добра!