Создаем слэш команды в Discord на PHP

слэш команды в Discord

Если у вас есть свой Discord сервер, возможно вы уже написали простенького бота в Discord для ваших нужд. Как это сделать, я рассказал в этой статье. Однако, возможностей простого бота зачастую недостаточно, поэтому нам на помощь приходят интерактивные слэш команды в Discord. Если создать слэш команды, они будут появляться как подсказки, когда пользователь введет в поле для отправки сообщения символ слэш. В подсказке будет указана сама команда и ее описание, заданное вами. После отправки команды пользователем, он получит соответствующий ответ. В этой статье вы узнаете, как сделать интерактивные команды на PHP без использования каких-либо библиотек.

Для работы с интерактивными командами будет использоваться криптографический модуль Sodium, который включен по умолчанию в PHP, начиная с версии 7.2. Впрочем сейчас уже 2023 год и я надеюсь, что вы успели перейти на PHP 8. Давайте создадим 2 скрипта, чтобы реализовать слэш команды в Discord. Первый скрипт – это основной обработчик для этих команд, назовем его к примеру discord-commands.php. А второй скрипт просто для того, чтобы регистрировать команды в Discord можно сказать, что он тестовый, назовем его discord-commands-manage.php. Перед началом работы вам понадобятся данные приложения и бота из Discord Developer Portal. Добавьте в discord-commands-manage.php ID вашего приложения, полученный на вкладке “General Information”:

$app_id = Application_ID_Here;

А в discord-commands.php добавьте Public Key вашего приложения из той же вкладки:

$app_key = 'Public_Key_Here';

Затем перейдите на вкладку Bot и получите там токен. Обратите внимание, если вы уже используете бот, то не нужно нажимать “Reset Token” – просто скопируйте токен из других ваших скриптов, где работаете с ботом. Пропишите токен бота только в discord-commands-manage.php.

$bot_token = 'Bot_Token_Here';

discord-commands-manage.php

Этот скрипт предназначен для того, чтобы добавлять или удалять слэш команды в Discord. Он содержит всего 2 функции и примеры их использования.

<?php
$app_id = Application_ID_Here;
$bot_token = 'Bot_Token_Here';

// добавляем команду /test с описанием "Тестовая команда"
AddCommand('test', 'Тестовая команда');

// ID команды всегда можно посмотреть в Discord, если у вас включен режим разработчика, но если вам зачем-то понадобится id прямо здесь, то используйте такой вариант:
// $cmd_id = AddCommand('test', 'Тестовая команда'); // в переменной $cmd_id будет находиться id созданной команды

// удаляем команду по ID (удалять можно только по ID команды)
DeleteCommand(1058231194334015609); // сюда просто вписываем длинный id команды


// функция для добавления команды
function AddCommand($name, $description) {
	$api_url = 'https://discord.com/api/v10/applications/'.$GLOBALS['app_id'].'/commands';
	$command = [
		'name' => $name,
		'type' => 1,
		'description' => $description,
	];
	$ch = curl_init($api_url);
	curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($command));
	curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Authorization: Bot '.$GLOBALS['bot_token']));
	$res = curl_exec($ch);
	curl_close($ch);
	return json_decode($res, true)['id'];
}

// функция для удаления команды
function DeleteCommand($id) {
	$api_url = 'https://discord.com/api/v10/applications/'.$GLOBALS['app_id'].'/commands/'.$id;
	$ch = curl_init($api_url);
	curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Authorization: Bot '.$GLOBALS['bot_token']));
	curl_exec($ch);
	curl_close($ch);
}

Теперь при помощи этого скрипта, добавьте любую тестовую команду в ваше приложение и получите ее ID, он нам понадобится в основном обработчике.

discord-commands.php

Этот скрипт будет отвечать на ваши слэш команды в Discord. Ниже представлен код скрипта с комментариями

<?php
$app_key = 'Public_Key_Here';

$input = file_get_contents('php://input'); //  получаем данные входящего от Discord запроса
if (empty($input)) exit();
$result = endpointVerify($_SERVER, $input, $app_key); // выполняем верификацию
if (empty($result)) exit();

http_response_code($result['code']);
if ($result['data']['type'] == 1) { echo json_encode($result['data']); exit(); } // это нужно для проверочных запросов от Discord

// Обработка ваших команд
if ($result['data']['type'] == 2) {
	$input_data = json_decode($input, true);
	
	if ($input_data['data']['id'] == 1058231194334015609) { // сюда вписываете id команды /test
		$answer = 'Это тестовый ответ на команду.'; // здесь пишете ответ, который пользователь получит при использовании команды
		InteractionCallback($answer); // отправляете ответ
	}
}


// Функции
function InteractionCallback($message) {
	$data = ['type' => 4, 'data' => ['content' => $message]];
	header('Content-Type: application/json');
	exit(json_encode($data));
}

function endpointVerify(array $headers, string $data, string $publicKey): array
{
	if (!isset($headers['HTTP_X_SIGNATURE_ED25519']) || !isset($headers['HTTP_X_SIGNATURE_TIMESTAMP'])) return ['code' => 401, 'data' => null];
	$signature = $headers['HTTP_X_SIGNATURE_ED25519'];
	$timestamp = $headers['HTTP_X_SIGNATURE_TIMESTAMP'];
	if (!trim($signature, '0..9A..Fa..f') == '') return ['code' => 401, 'data' => null];
	$message = $timestamp . $data;
	$binarySignature = sodium_hex2bin($signature);
	$binaryKey = sodium_hex2bin($publicKey);
	if (!sodium_crypto_sign_verify_detached($binarySignature, $message, $binaryKey)) return ['code' => 401, 'data' => null];
	$data = json_decode($data, true);
	return match($data['type']) {
		1 => ['code' => 200, 'data' => ['type' => 1]],
		2 => ['code' => 200, 'data' => ['type' => 2]],
		default => ['code' => 400, 'data' => null]
	};
}

Если у вас PHP ниже 8 версии, функция endpointVerify может вызвать ошибку даже при наличии Sodium. Если это случится, попробуйте использовать такой вариант функции:

function endpointVerify($headers, $data, $publicKey) {
	if (!isset($headers['HTTP_X_SIGNATURE_ED25519']) || !isset($headers['HTTP_X_SIGNATURE_TIMESTAMP'])) return ['code' => 401, 'data' => null];
	$signature = $headers['HTTP_X_SIGNATURE_ED25519'];
	$timestamp = $headers['HTTP_X_SIGNATURE_TIMESTAMP'];
	if (!trim($signature, '0..9A..Fa..f') == '') return ['code' => 401, 'data' => null];
	$message = $timestamp . $data;
	$binarySignature = sodium_hex2bin($signature);
	$binaryKey = sodium_hex2bin($publicKey);
	if (!sodium_crypto_sign_verify_detached($binarySignature, $message, $binaryKey)) return ['code' => 401, 'data' => null];
	$data = json_decode($data, true);
	switch ($data['type']) {
		case 1: return ['code' => 200, 'data' => ['type' => 1]];
		case 2: return ['code' => 200, 'data' => ['type' => 2]];
		default: return ['code' => 400, 'data' => null];
	}
}

Теперь в настройках вашего приложения на вкладке “General Information” вы должны указать ссылку до вашего скрипта discord-commands.php в поле “Interactions Endpoint URL“. При сохранении настроек, Discord отправит проверочный запрос и если ваш обработчик ответит корректно, ссылка до него будет сохранена. В противном случае Discord выдаст ошибку.

Если вы заблаговременно добавили бот вашего приложения на свой сервер, а также успешно сохранили Interactions Endpoint URL, то теперь можете отправить команду /test на вашем сервере и в ответ вы получите сообщение, которое прописано в коде обработчика.

Все описанное в этой статье прекрасно работает на Discord сервере нашего MSS Project. Конечно я еще не рассказал про эмбеды и кнопки, но это тянет на отдельную статью и я постараюсь как нибудь написать ее. Надеюсь материал был вам полезен. Удачи!