swamprunner7
9March
Discord php чат бот с обучением
Давно хотел сделать чат бота для discord, чтобы он реагировал на фразы и отвечал в юморной форме. Для этого было решено сделать базу вопросов и ответов "знаний" бота, которая будет постоянно наполняться пользователями чата.

Ответов на фразу или слово может быть множество, также, как и ключевых фраз связанных с ответами. То есть бота можно научить отвечать по-разному на одну фразу или целую серию похожих фраз. Если в базе будет несколько ответов, то они будут выводиться случайно. Также для большей интерактивности бот сохраняет в виде ответа и ссылки с картинками или emoji.

Для работы бота нужен доступ к серверу с установленным php и mysql, бот очень простой, поэтому особой мощности не потребуется. Ниже код с комментариями.


Пример реакции бота на текст в чате.


Дополнение базы данных бота новыми ответами.

Инструкция для использования и настройки бота в чате:

Добавление или дополнение данных:
"ключ" "текст"
ключ; текст

Дополнение ключа к тексту:
номер_данных "ключ"

Номер данных выводится при запросе любого из ключей.

Список всего по ключу:
номер_ключа

Удаление всех данных:
удалить номер_данных

Удаление ключа:
удалить ключ номер_ключа

Редактирование ключа:
редактировать ключ номер_ключа "новый ключ"

Номер ключа можно узнать в списке ключей

Удаление текста:
удалить текст номер_текста

Редактирование текста:
редактировать текст номертекста "новыйтекст"
Номер текста можно узнать в списке текстов

Весь код проекта с комментариями:

<?php
/*
Запуск бота только с консоли, для отладки в просмотра текста чата запускать так (после выхода с консоли скрипт прекратит работу):
php /var/www/discord_bot.php

Для запуска без отладки (после запуска консоль выдаст номер процесса, который можно использовать для его остановки в случае необходимости):
nohup php /var/www/discord_bot.php &
*/

#Так как бот запускается с консоли, для отладки ошибок кода можно вручную задать использование файла для лога ошибок
ini_set("log_errors", 1);
ini_set("log_errors_max_len", "1024");
ini_set("error_log", "/var/log/my_php_bot.log");

/*
Сначала нужно создать бота в discord: https://discord.com/developers/applications и подключить его к вашему чат серверу и включить режим разработчика в настройках дискорда, чтобы видеть id всех каналов чата

Для работы бота нужна библиотека discord_php: https://github.com/discord-php/DiscordPHP
Но так как мы не используем composer, нужно сконвертировать библиотеку перед использованием через https://php-download.com/
*/

require_once 'discord_php/autoload.php'; 

/*
Для простой работы с mysql нужна библиотека godb: https://github.com/vasa-c/godb-old которая требует mysqli в настройках php, также mb_string тк работаем с utf8

Также нужно создать базу данных для бота и создать две таблицы:

CREATE TABLE `texts` (
  `text_n` int(10) NOT NULL,
  `data_n` int(10) NOT NULL,
  `text` text COLLATE utf8mb4_bin NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

CREATE TABLE `titles` (
  `title_n` int(10) NOT NULL,
  `data_n` int(10) NOT NULL,
  `title` varchar(100) COLLATE utf8mb4_bin NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

ALTER TABLE `texts`
  ADD PRIMARY KEY (`text_n`) USING BTREE,
  ADD KEY `data_n` (`data_n`);
ALTER TABLE `texts` ADD FULLTEXT KEY `text` (`text`);

ALTER TABLE `titles`
  ADD PRIMARY KEY (`title_n`),
  ADD KEY `title` (`title`,`data_n`);
*/

require_once 'godb.php';

use Discord\Discord;

$discord = new Discord([
	'token' => 'TOKEN бота',
	'pmChannels' => true
]);

#Функция для очистки ненужных символов в начале и конце текста
function ClearSymbols($title)
{
	return preg_replace('#[\!\?\.\,\(\)]+#', '', $title);
}

$discord->on('ready', function ($discord)
{
	#Исполняется каждый раз, как в чат добавляется новое сообщение
	$discord->on('message', function ($message, $discord)
	{
		#Подключение к базе данных
		$db = new goDB("адрес_сервера", "имя_пользователя", "пароль", "название_базы");

		$db->query("SET NAMES utf8mb4", array());
		
		#Имя бота можно узнать с вывода в текста в консоль при обращении к боту, имя можно в дальнейшем использовать для команд
		$bot_name = '\<@\!?450704734647484416\>';

		#Но в данном случае обращение к боту просто удаляется с текста
		$text = preg_replace('#' . $bot_name . ' #', '', $message->content);
		
		$username = $message->author->username;
		
		#id ветки в чате, где будуд работать команды добавления и редактирования бота
		$bot_room = '811320121108725791';
		
		#Настройка номера сервера чата
		$guild = $discord->guilds->get('id', 'id_сервера');
		$channel = $guild->channels->get('id', $message->channel->id);
		
		$channel_id = $message->channel->id;
		
		#Выключение бота
		if($channel_id == $bot_room && $username == 'имя_администратора' && $message->content == 'die')
			$discord->close();
		
		#Вывод всего текста чата в консоль
		echo "{$username}: {$message->content}" . "\n";

		#Чтение чата, имя бота проверяется чтобы бот не циклился на бесконечное чтение и ответ на команду
		if($username != 'имя_бота' && (mb_strlen($text) <= 40) && preg_match('#^(.{2,40})$#ui', $text, $res))
		{
			$title = trim(mb_strtolower($res[1]));
			$title = ClearSymbols($title);
			
			$temp = $db->query('SELECT data_n, text FROM titles LEFT JOIN texts USING(data_n) WHERE title=?', array($title), 'assoc');
			
			if($temp)
			{
				$res = array();
				
				foreach($temp as $val)
					$res[] = $val['text'];
				
				$res_index = array_rand($res);
				
				#Если данные пришли с чат сервера
				if($channel != '')
				{
					if($channel_id == $bot_room)
						$channel->sendMessage('#' . $temp[0]['data_n'] . ' ' . $res[$res_index]);
					else
						$channel->sendMessage($res[array_rand($res)]);
				}
				else
				{
					#Если данные пришли с личных сообщений
					$message->reply($res[array_rand($res)]);
				}
			}
		}

		#Показать всё по номеру данных
		if($username != 'имя_бота' && $channel_id == $bot_room && preg_match('#^([0-9]{1,10})$#ui', $text, $res))
		{
			$data_n = $db->query('SELECT data_n FROM titles WHERE data_n=?i', array(mb_strtolower($res[1])), 'rowassoc');
			
			if($data_n)
			{
				$titles = $db->query('SELECT title_n, title FROM titles WHERE data_n=?i', array(mb_strtolower($data_n['data_n'])), 'assoc');
				$texts = $db->query('SELECT text_n, text FROM texts WHERE data_n=?i', array(mb_strtolower($data_n['data_n'])), 'assoc');
				
				$data = 'Все ключи:' . "\n";
				
				foreach($titles as $val)
					$data .= '#' . $val['title_n'] . ' ' . $val['title'] . "\n";
				
				$data .= "\n";
				
				$data .= 'Все тексты по ключу:' . "\n";
				
				foreach($texts as $val)
					$data .= '#' . $val['text_n'] . ' ' . $val['text'] . "\n";
				
				$channel->sendMessage($data);
			}
		}

		#Запись		
		if($username != 'имя_бота' && $channel_id == $bot_room  && (preg_match('#^"(.+)" "(.+)"#ui', $text, $res) || preg_match('#^(.+);(.+)#ui', $text, $res)))
		{
			$title = trim(mb_strtolower($res[1]));
			$title = ClearSymbols($title);
			
			$text = trim($res[2]);
			
			if(mb_strlen($title) < 40)
			{
				#Если ключ уже есть
				$title_check = $db->query('SELECT data_n FROM titles WHERE title=?', array($title), 'rowassoc');
				
				if(!$title_check)
				{
					$last_data_n = $db->query('SELECT MAX(data_n)AS max_data_n FROM texts', array(), 'el');
					
					$db->query('INSERT INTO titles SET data_n=?i, title=?', array($last_data_n + 1, $title));
					$db->query('INSERT INTO texts SET data_n=?i, text=?', array($last_data_n + 1, $text));
					
					$channel->sendMessage('добавлено: #' . ($last_data_n + 1) . ' ключ: "' . $title . '" данные: "' . $text . '"');
				}
				else
				{
					$db->query('INSERT INTO texts SET data_n=?i, text=?', array($title_check['data_n'], $text));
					
					$channel->sendMessage('дополнено: #' . $title_check['data_n'] . ' данные: "' . $text . '"');
				}
			}
			else
				$channel->sendMessage('нужно меньше 40 символов в ключе');
		}
		
		#Добавить title
		if($username != 'имя_бота' && $channel_id == $bot_room && preg_match('#^([0-9]+) "(.+)"#ui', $text, $res))
		{
			$data_n = $res[1];
			$title = trim(mb_strtolower($res[2]));
			$title = ClearSymbols($title);
			
			if(mb_strlen($title) < 40)
			{
				$data_n_check = $db->query('SELECT data_n FROM titles WHERE data_n=?i', array($data_n), 'rowassoc');
				
				if($data_n_check)
				{
					$title_check = $db->query('SELECT title FROM titles WHERE title=?', array($title), 'rowassoc');
					
					if(!$title_check)
					{
						$db->query('INSERT INTO titles SET data_n=?i, title=?', array($data_n, $title));
					
						$channel->sendMessage('дополнено: #' . $data_n . ' ' . ' ключ: "' . $title . '"');
					}
					else
						$channel->sendMessage('такое ключ уже есть в: #' . $data_n);
				}
			}
			else
				$channel->sendMessage('нужно меньше 40 символов в ключе');
		}

		#Удаление данных
		if($username != 'имя_бота' && $channel_id == $bot_room && preg_match('#^удалить данные ([0-9]+)$#ui', $text, $res))
		{
			$data_n = trim($res[1]);
			
			$db->query('DELETE FROM titles WHERE data_n=?i', array($data_n));
			$db->query('DELETE FROM texts WHERE data_n=?i', array($data_n));
				
			$channel->sendMessage('данные #' . $data_n . ' удалёны');
		}

		#Удаление title
		if($username != 'имя_бота' && $channel_id == $bot_room && preg_match('#^удалить ключ ([0-9]+)$#ui', $text, $res))
		{
			$title_n = trim($res[1]);
			
			$db->query('DELETE FROM titles WHERE title_n=?i', array($title_n));

			$channel->sendMessage('ключ #' . $title_n . ' удалён');
		}
		
		#Удаление text
		if($username != 'имя_бота' && $channel_id == $bot_room && preg_match('#^удалить текст ([0-9]+)$#ui', $text, $res))
		{
			$text_n = trim($res[1]);
			
			$db->query('DELETE FROM texts WHERE text_n=?i', array($text_n));
	
			$channel->sendMessage('текст #' . $text_n . ' удалён');
		}
		
		#Редактирование title
		if($username != 'имя_бота' && $channel_id == $bot_room && preg_match('#^редактировать ключ ([0-9]+) "(.+)"$#ui', $text, $res))
		{
			$title_n = trim($res[1]);
			$title = trim(mb_strtolower($res[2]));
			
			if(mb_strlen($title) < 40)
			{
				if(!$check = $db->query('SELECT title FROM titles WHERE title=?', array($title), 'rowassoc'))
				{
					$db->query('UPDATE titles SET title=? WHERE title_n=?i', array($title, $title_n));
			
					$channel->sendMessage('ключ #' . $title_n . ' отредактирован');
				}
				else
					$channel->sendMessage('такой ключ уже есть');
			}
			else
				$channel->sendMessage('нужно меньше 40 символов в ключе');
		}
		
		#Редактирование text
		if($username != 'имя_бота' && $channel_id == $bot_room && preg_match('#^редактировать текст ([0-9]+) "(.+)"$#ui', $text, $res))
		{
			$text_n = trim($res[1]);
			$text = trim(mb_strtolower($res[2]));
			
			$db->query('UPDATE texts SET text=? WHERE text_n=?i', array($text, $text_n));
	
			$channel->sendMessage('текст #' . $text_n . ' отредактирован');
		}
				
		$db->close();
	});
});

$discord->run();
?>