forked from spacejewelhosting/raspberry-bot
Migrated to Gitea
This commit is contained in:
16
src/Bot/Actions/MessageLogger.php
Normal file
16
src/Bot/Actions/MessageLogger.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace nogueiracodes\RaspberryBot\Bot\Actions;
|
||||
|
||||
use nogueiracodes\RaspberryBot\Core\Interfaces\Action;
|
||||
|
||||
class MessageLogger implements Action
|
||||
{
|
||||
public function processAction($message)
|
||||
{
|
||||
|
||||
echo "Sniffed message from " . $message->author->username . ": " . $message->content . PHP_EOL;
|
||||
|
||||
}
|
||||
|
||||
}
|
81
src/Bot/Commands/MinecraftInfo.php
Normal file
81
src/Bot/Commands/MinecraftInfo.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace nogueiracodes\RaspberryBot\Bot\Commands;
|
||||
|
||||
use Ely\Mojang\Api;
|
||||
use Exception;
|
||||
use nogueiracodes\RaspberryBot\Core\Interfaces\Command;
|
||||
use nogueiracodes\RaspberryBot\Traits\HasSignature;
|
||||
|
||||
// Inspired by Laravel's artisan commands
|
||||
class MinecraftInfo implements Command
|
||||
{
|
||||
use HasSignature;
|
||||
|
||||
/**
|
||||
* Must be included at all times. This describes the command's parameters, which can be used in the run method.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $signature = "minecraft {operation: The The kind of operation; for now, only convert is supported} {subOperationType: The sub operation type} {subOperationArgument: The argument you want to pass to to the sub operation}";
|
||||
|
||||
|
||||
/**
|
||||
* Parrots back whatever the user said. Proof of concept.
|
||||
*
|
||||
* @param string $parameters This is an array of named parameters with values, based on the signature. First value is always the command's name, like how PHP's $argv is organized.
|
||||
* @return void
|
||||
*/
|
||||
public function run(array $parameters)
|
||||
{
|
||||
$mojangAPI = new Api();
|
||||
switch($parameters['operation'])
|
||||
{
|
||||
// convert to username
|
||||
case 'convert':
|
||||
|
||||
switch($parameters['subOperationType'])
|
||||
{
|
||||
case 'username':
|
||||
|
||||
$response = $parameters['subOperationType'] . "\'s UUID is " . $mojangAPI->usernameToUUID($parameters['subOperationArgument'])->getId();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Sorry, but at the moment you can only convert usernames to UUIDs.");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'blacklist':
|
||||
|
||||
switch($parameters['subOperationType'])
|
||||
{
|
||||
case 'is-banned':
|
||||
|
||||
$blockedServerCollection = $mojangAPI->blockedServers();
|
||||
|
||||
$response = ($blockedServerCollection->isBlocked($parameters['subOperationArgument']))
|
||||
? 'Sorry! It appears that this server is indeed being blocked by Mojang. Users won\'t be able to join this Minecraft server using the official client.'
|
||||
: 'A little bird told me that this server is NOT blocked by Mojang. Users can freely join this server.';
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Sorry, but at the moment you can only check if servers are banned.");
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
throw new Exception("Invalid usage! Please refer to !!rb help for more information.");
|
||||
|
||||
}
|
||||
|
||||
return $response;
|
||||
|
||||
}
|
||||
|
||||
}
|
34
src/Bot/Commands/NumberFact.php
Normal file
34
src/Bot/Commands/NumberFact.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace nogueiracodes\RaspberryBot\Bot\Commands;
|
||||
|
||||
use Exception;
|
||||
use nogueiracodes\RaspberryBot\Core\Interfaces\Command;
|
||||
use nogueiracodes\RaspberryBot\Traits\HasSignature;
|
||||
use Zttp\Zttp;
|
||||
|
||||
class NumberFact implements Command
|
||||
{
|
||||
use HasSignature;
|
||||
|
||||
|
||||
public $signature = "numberfact {number: The number for which you want facts for.}";
|
||||
|
||||
|
||||
public function run(array $parameters)
|
||||
{
|
||||
|
||||
$number = $parameters['number'];
|
||||
$result = Zttp::get('http://numbersapi.com/' . $number . '?json');
|
||||
|
||||
if ($result->isOk())
|
||||
{
|
||||
return $result->json()['text'];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Sorry, but I can\'t fetch number facts right now. Try again later.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
52
src/Bot/Commands/SimplePlayback.php
Normal file
52
src/Bot/Commands/SimplePlayback.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace nogueiracodes\RaspberryBot\Bot\Commands;
|
||||
|
||||
use nogueiracodes\RaspberryBot\Core\Interfaces\Command;
|
||||
use nogueiracodes\RaspberryBot\Traits\HasSignature;
|
||||
|
||||
// Inspired by Laravel's artisan commands
|
||||
class SimplePlayback implements Command
|
||||
{
|
||||
use HasSignature;
|
||||
|
||||
/**
|
||||
* Must be included at all times. This describes the command's parameters, which can be used in the run method.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $signature = "capitalize {word: The word you want to convert} {state: lower or upper}";
|
||||
|
||||
|
||||
/**
|
||||
* Parrots back whatever the user said. Proof of concept.
|
||||
*
|
||||
* @param string $parameters This is an array of named parameters with values, based on the signature. First value is always the command's name, like how PHP's $argv is organized.
|
||||
* @return void
|
||||
*/
|
||||
public function run(array $parameters)
|
||||
{
|
||||
|
||||
// Play with the string first before sending it
|
||||
switch($parameters['state'])
|
||||
{
|
||||
case 'lower':
|
||||
|
||||
$capitalized = strtolower($parameters['word']);
|
||||
|
||||
break;
|
||||
case 'upper':
|
||||
|
||||
$capitalized = strtoupper($parameters['word']);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$capitalized = "Usage: !!rb capitalize [word] [upper|lower]. Use !!rb help for more.";
|
||||
}
|
||||
|
||||
return $capitalized;
|
||||
|
||||
}
|
||||
|
||||
}
|
10
src/Bot/init.php
Normal file
10
src/Bot/init.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
require realpath(__DIR__ . '/../../vendor/autoload.php');
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(realpath(__DIR__ . '/../../'));
|
||||
$dotenv->load();
|
||||
|
||||
$dotenv->required('BOT_AUTH_TOKEN')->notEmpty();
|
||||
$dotenv->required('COMMAND_PREFIX')->notEmpty();
|
||||
|
15
src/Core/Interfaces/Action.php
Normal file
15
src/Core/Interfaces/Action.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace nogueiracodes\RaspberryBot\Core\Interfaces;
|
||||
|
||||
interface Action
|
||||
{
|
||||
/**
|
||||
* Process the said message. Can be used for cursing filters, etc.
|
||||
*
|
||||
* @param stdClass $message
|
||||
* @return void
|
||||
*/
|
||||
public function processAction($message);
|
||||
|
||||
}
|
37
src/Core/Interfaces/Command.php
Normal file
37
src/Core/Interfaces/Command.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace nogueiracodes\RaspberryBot\Core\Interfaces;
|
||||
|
||||
use Discord\DiscordCommandClient;
|
||||
use Discord\Parts\Channel\Message;
|
||||
|
||||
interface Command
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Implement what the command should do here.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run(array $parameters);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Obtains the signature in an array from, from the command's internal signature.
|
||||
*
|
||||
* @param string $signature
|
||||
* @return array
|
||||
*/
|
||||
public function getCommandSignature(string $signature): array;
|
||||
|
||||
|
||||
/**
|
||||
* Same as getCommandSignature, but for convenience.
|
||||
*
|
||||
* @see getCommandSignature()
|
||||
* @return array
|
||||
*/
|
||||
public function getCommandArguments(): array;
|
||||
}
|
174
src/Core/RaspberryBot.php
Normal file
174
src/Core/RaspberryBot.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace nogueiracodes\RaspberryBot\Core;
|
||||
|
||||
use Discord\Discord;
|
||||
use Discord\DiscordCommandClient;
|
||||
use Exception;
|
||||
use nogueiracodes\RaspberryBot\Core\Interfaces\Action;
|
||||
use nogueiracodes\RaspberryBot\Core\Interfaces\Command;
|
||||
use nogueiracodes\RaspberryBot\Traits\ParsesCommands;
|
||||
|
||||
class RaspberryBot
|
||||
{
|
||||
use ParsesCommands;
|
||||
|
||||
/**
|
||||
* The Discord Command Client.
|
||||
*
|
||||
* @var \Discord\DiscordCommandClient;
|
||||
*/
|
||||
private $commandClient;
|
||||
|
||||
|
||||
/**
|
||||
* The loaded commands for this bot instance.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $commands = [];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The loaded actions for this bot instance. Actions run on messages sent by users, and not when a prefix is detected, unlike commands.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $actions = [];
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The command prefix the bot should look for when parsing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $commandPrefix;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Stards the Discord Client and registers commands. Allows fluent chaining.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize(): RaspberryBot
|
||||
{
|
||||
|
||||
$this->commandClient = new DiscordCommandClient([
|
||||
'token' => $_ENV['BOT_AUTH_TOKEN'],
|
||||
'description' => 'A helpful robot giving you access to the Staff Manager Web App.',
|
||||
]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the command prefix the bot responds to.
|
||||
*
|
||||
* @param string $prefix The prefix.
|
||||
* @return RaspberryBot
|
||||
*/
|
||||
public function setCommandPrefix(string $prefix): RaspberryBot
|
||||
{
|
||||
$this->commandPrefix = $prefix;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a command the bot will recognise. Must implement the Command interface.
|
||||
*
|
||||
* @param Command $command
|
||||
* @return RaspberryBot
|
||||
*/
|
||||
public function addCommand(array $command): RaspberryBot
|
||||
{
|
||||
foreach ($command as $singleCommand)
|
||||
{
|
||||
if ($singleCommand instanceof Command)
|
||||
{
|
||||
echo "INFO: Registering command " . $singleCommand->getCommandArguments()[0]['commandName'] . PHP_EOL;
|
||||
array_push($this->commands, $singleCommand);
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "WARNING: Checked command, but it is not valid. Discarding!" . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Adds an action the bot will work on.
|
||||
* Actions run on messages, and they can be used to detect swearing, or reply to a user saying hello, etc.
|
||||
*
|
||||
* @param Action $action The action.
|
||||
* @return RaspberryBot
|
||||
*/
|
||||
public function addAction(Action $action): RaspberryBot
|
||||
{
|
||||
array_push($this->actions, $action);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs the event loop, registers commands, and starts listening for mesesages and processing actions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$this->commandClient->on('ready', function()
|
||||
{
|
||||
|
||||
$this->commandClient->on('message', function($message)
|
||||
{
|
||||
if (!empty($this->commands) && $this->isCommand($message))
|
||||
{
|
||||
$reply = "";
|
||||
|
||||
try
|
||||
{
|
||||
$command = $this->determineCommand($message);
|
||||
$arguments = $this->getNamedArguments($message, $command);
|
||||
|
||||
$reply = $command->run($arguments);
|
||||
}
|
||||
catch (Exception $ex)
|
||||
{
|
||||
$reply = $ex->getMessage();
|
||||
}
|
||||
|
||||
return $message->reply($reply);
|
||||
}
|
||||
|
||||
|
||||
if (!empty($this->actions))
|
||||
{
|
||||
foreach($this->actions as $action)
|
||||
{
|
||||
$action->processAction($message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "(Debug) WARNING: No actions have been defined. However, we received a message, but don\'t know what to do with it.";
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
$this->commandClient->run();
|
||||
}
|
||||
}
|
94
src/Traits/HasSignature.php
Normal file
94
src/Traits/HasSignature.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace nogueiracodes\RaspberryBot\Traits;
|
||||
|
||||
trait HasSignature
|
||||
{
|
||||
// sample signature
|
||||
// "commandName {argument: description} {argument2: description}";
|
||||
|
||||
public function getCommandSignature(string $signature): array
|
||||
{
|
||||
$argumentList = [];
|
||||
|
||||
|
||||
$firstArgPos = strpos($signature, '{');
|
||||
$commandName = substr($signature, 0, $firstArgPos - 1);
|
||||
|
||||
$hasRemainingArguments = true;
|
||||
$argumentCounter = 0;
|
||||
$executionTimer = 0;
|
||||
|
||||
|
||||
while($hasRemainingArguments)
|
||||
{
|
||||
// Bug check: This is an hardcoded parameter limit.
|
||||
// The bot will purposefully crash if it finds a command that defined more than twenty parameters,
|
||||
// or if somehow an unknown bug caused an infinite loop.
|
||||
// This can happen if something (or someone) messes with the internal counters
|
||||
// This piece of code can be safely removed if there are unit tests ensuring this won't happen. (insecurity lol)
|
||||
$executionTimer++;
|
||||
if ($executionTimer > 20)
|
||||
die('FATAL: Command signature parser - Infinite loop detected!');
|
||||
|
||||
|
||||
// Here we conditionally set the current argument bound start.
|
||||
// It can't be rewritten if it already has a value, which had been set to allow for the next iteration.
|
||||
if (!isset($currentArgBoundStart))
|
||||
{
|
||||
$currentArgBoundStart = $firstArgPos;
|
||||
}
|
||||
|
||||
/*
|
||||
* An argument bound is defined by each curly bracket, e.g. Start and End {}.
|
||||
* Here it's position and value is recorded for later use.
|
||||
* An argument namespace is everything inside the curly brackets, including the brackets themsevles, e.g.
|
||||
* {test: description} <-- arg namespace
|
||||
*/
|
||||
$currentArgBoundEnd = strpos($signature, '}', $currentArgBoundStart);
|
||||
$currentArgNamespace = substr($signature, $currentArgBoundStart , $currentArgBoundEnd - $currentArgBoundStart + 1);
|
||||
|
||||
// Here we obtain the name and description values inside the namespace, basing ourselves off of the current namespace and the whole signature.
|
||||
$currentArgName = substr($signature, $currentArgBoundStart, strpos($currentArgNamespace, ':'));
|
||||
$currentArgDescription = substr($currentArgNamespace, strpos($currentArgNamespace, ':') + 2, strpos($currentArgNamespace, '}') - strlen($currentArgNamespace));
|
||||
|
||||
// The next arg bound position is the start of the next arg bound, e.g. an opening curly bracket,
|
||||
// that will define the next namespace to scan.
|
||||
$nextArgBoundPos = strpos($signature, '{', $currentArgBoundEnd + 1);
|
||||
array_push($argumentList, [
|
||||
'commandName' => $commandName,
|
||||
'argumentName' => str_replace("{", "", $currentArgName), //FIXME: This is a cheap workaround hack, the first iteration always has that character and the bug is somewhere above
|
||||
'argumentDescription' => $currentArgDescription,
|
||||
'argumentPosition' => $argumentCounter
|
||||
]);
|
||||
$argumentCounter++;
|
||||
|
||||
// Here we check if we can find the next namespace.
|
||||
// strpos() will always return false if it doesn't find what you told it to find.
|
||||
// Following that logic, a non integer value means no other namespace is here and therefore we quit the loop.
|
||||
if (!is_int($nextArgBoundPos))
|
||||
{
|
||||
$hasRemainingArguments = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// To prevent an infinite PC crashing loop, we reset the current loop argument namespace's bounds to the next namespace.
|
||||
// Not doing this will cause an infinite loop where the function tries to parse the first namespace forever, because
|
||||
// in this scenario there would always be a next namespace, causing the loop to not be exited.
|
||||
$currentArgBoundStart = $nextArgBoundPos + 1;
|
||||
$currentArgBoundEnd = strpos($signature, '}', $currentArgBoundStart) + 1;
|
||||
|
||||
}
|
||||
}
|
||||
return $argumentList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getCommandArguments(): array
|
||||
{
|
||||
return $this->getCommandSignature($this->signature);
|
||||
}
|
||||
|
||||
|
||||
}
|
73
src/Traits/ParsesCommands.php
Normal file
73
src/Traits/ParsesCommands.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace nogueiracodes\RaspberryBot\Traits;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Discord\Parts\Channel\Message;
|
||||
use Exception;
|
||||
use nogueiracodes\RaspberryBot\Core\Interfaces\Command;
|
||||
|
||||
trait ParsesCommands
|
||||
{
|
||||
|
||||
/**
|
||||
* Checks if said message is a command.
|
||||
*
|
||||
* @param Message $message
|
||||
* @return boolean
|
||||
*/
|
||||
private function isCommand(Message $message): bool
|
||||
{
|
||||
if (strtok($message->content, " ") == $this->commandPrefix)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private function determineCommand(Message $message): Command
|
||||
{
|
||||
$commandName = strtok(str_replace($this->commandPrefix, "", $message->content), " ");
|
||||
|
||||
foreach ($this->commands as $command)
|
||||
{
|
||||
// Due to a bug, the command name is returned on all parameters. It doesn't matter which parameter we
|
||||
// access to get the name, since they're all the same.
|
||||
if ($command->getCommandArguments()[0]['commandName'] == $commandName)
|
||||
{
|
||||
return $command;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \Exception("Unknown command (" . $commandName . "). Please try again or use {$this->commandPrefix} help.");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function getNamedArguments(Message $message, Command $command): array
|
||||
{
|
||||
|
||||
$commandArguments = [];
|
||||
$parameters = [];
|
||||
|
||||
|
||||
foreach($command->getCommandArguments() as $argument)
|
||||
{
|
||||
array_push($commandArguments, $argument['argumentName']);
|
||||
}
|
||||
|
||||
$incomingString = $message->content;
|
||||
$splicedMessage = preg_split('/\s+/', str_replace($this->commandPrefix, "", $incomingString), -1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
|
||||
unset($splicedMessage[0]); // Get rid of the cmd name (always same index)
|
||||
sort($splicedMessage); // Reindex the array
|
||||
|
||||
if (count($splicedMessage) !== count($commandArguments))
|
||||
throw new Exception("Invalid usage! Please supply the correct arguments. Use {$this->commandPrefix} help for more information.");
|
||||
|
||||
return array_combine($commandArguments, $splicedMessage);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user