Hola a todos. En este artículo, quiero mostrarles cómo construí el Chatbot
@laraquizprobot en Telegram con Laravel y BotMan. Si aún no lo ha probado, es una buena idea comprobarlo antes de leer este artículo. Esto ayudará a entender de lo que estoy hablando.
El chatbot proporciona un cuestionario con todo tipo de preguntas de cuatro pistas diferentes que incluyen Marco Laravel, Django, React JS y CSS. Cada pregunta viene con hasta tres posibles respuestas. Debe elegir el correcto para acumular puntos y llegar a la puntuación más alta. Pero además de los rangos, el cuestionario se trata de pasar un buen rato. Disfrute de las preguntas y vea lo que sabe sobre su marco favorito. ¡Divertirse!
Nota: Todo el código se puede encontrar en el repositorio público.
Puede integrar fácilmente Botman en una aplicación laravel existente, pero por el bien de este tutorial, instalemos una nueva aplicación laravel. Podemos usar composer o el instalador de laravel.
Simplemente ejecute el siguiente comando para instalarlo. Ya que usé Laravel 8 para esto, instalemos el mismo
composer create-project --prefer-dist laravel/laravel quizbot 8.*
A continuación, instalemos Botman.
composer require botman/botman
Finalmente, instalemos BotMan Telegram Driver.
composer require botman/driver-telegram
Ahora hemos instalado todo lo que necesitamos para empezar. A continuación, comprendamos el proyecto que queremos crear. Si ha visto el bot en acción, lo obtendrá.
Necesitamos tres modelos: modelo de pista, modelo de pregunta, modelo de respuesta. Crearemos otros a medida que los necesitemos.
Primero, use el código a continuación para crear los 5 modelos que necesitamos para nuestra aplicación, creará sus archivos de migración junto con:
php artisan make:model Track -m
php artisan make:model Question -m
php artisan make:model Answer -m
php artisan make:model Highscore -m
php artisan make:model Played -m
A continuación, abra sus respectivas Migraciones y agregue los códigos a continuación para crear las tablas que necesitamos.
// TRACK Migration
Schema::create('tracks', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});// Questions Migration
Schema::create('questions', function (Blueprint $table) {
$table->id();
$table->string('text');
$table->integer('points')->unsigned();
$table->integer('track_id');
$table->timestamps();
});
// Answer Migration
Schema::create('answers', function (Blueprint $table) {
$table->id();
$table->integer('question_id');
$table->text('text');
$table->boolean('correct_one');
$table->timestamps();
});
// Highscore Migration
Schema::create('highscores', function (Blueprint $table) {
$table->id();
$table->integer('chat_id');
$table->string('name');
$table->integer('points')->default(0);
$table->integer('correct_answers')->default(0);
$table->integer('tries')->default(0);
$table->timestamps();
});
// Played Migration
Schema::create('playeds', function (Blueprint $table) {
$table->id();
$table->string('chat_id');
$table->integer('points')->default(0);
$table->timestamps();
});
Decidí usar una clase Laravel Seeder para hacerlo. De esta manera puedo mantener todas las preguntas y respuestas dentro del repositorio y usar un comando artesanal para llenar la base de datos.
Puede crear una sembradora por separado para esto, pero usé el valor predeterminado para esto. En el database/seeders/DatabaseSeeder.php
,
En el run
método de la sembradora, trunco la tabla de pistas, creo las pistas con el añadirPistas() método, luego recorro cada pista y agrego preguntas y a las preguntas, agrego las respuestas.
Para los datos reales, utilizo un separado getData()
método para mantenerlo más limpio. En este método, devuelvo una gran colección con todas las preguntas y respuestas. Para una mejor lectura, solo muestro tres de ellos aquí. Como puede ver, el código es bastante sencillo. Es solo una gran matriz con todos los datos para preguntas y respuestas. No olvide importar los espacios de nombres para el Track, Answer
y Question
clases
Nota: Obtendrá todas las preguntas en el repositorio público.
Ahora todo lo que tenemos que hacer es ejecutar la migración. Asegúrese de que los detalles de su base de datos se agreguen correctamente en el .env
archivo. Luego ejecute el siguiente comando para crear y generar las tablas a la vez.
php artisan migrate --seed
Ahora tenemos nuestras pistas, preguntas y respuestas en nuestra base de datos.
Ahora, creemos un controlador que usaremos para vincular nuestras Conversaciones a nuestra ruta.
Ejecute el siguiente comando en su terminal:
php artisan make:controller BotManController
Abramos el controlador, creamos un método llamado manejar() y agrega el siguiente código
<?phpnamespace AppHttpControllers;
use BotManBotManBotMan;
use BotManBotManBotManFactory;
use BotManBotManCacheLaravelCache;
use BotManBotManDriversDriverManager;
class BotManController extends Controller
{
/**
* Place your BotMan logic here.
*/
public function handle()
{
DriverManager::loadDriver(BotManDriversTelegramTelegramDriver::class);
$config = [
'user_cache_time' => 720,
'config' => [
'conversation_cache_time' => 720,
],
"telegram" => [
"token" => env('TELEGRAM_TOKEN'),
]
];
// // Create BotMan instance
$botman = BotManFactory::create($config, new LaravelCache());
$botman->hears('hello', function (BotMan $bot) {
$bot->reply('Hello yourself.');
});
$botman->listen();
}
}
Antes de continuar, quiero comprobar si estamos listos para ir. Lo que sucede en este controlador es que inicié una instancia de Botman y luego la configuré para escuchar «hola», si escucha «hola», debería responder con «hola».
Tenemos que conectar nuestro bot a Telegram, y para hacer esto, necesitamos exponer nuestro localhost. Cuando construí este bot, usé Expose de BeyondCode para compartir mi sitio local a través de un túnel seguro. puede usar laravel valet o servicios como nginx.
Tienes que aprender a crear un bot de Telegram. Cuando creas esto, obtienes un token. Agregue ese token a su archivo .env como
TELEGRAM_TOKEN=your-telegram-token-here
Si observa nuestro controlador, verá que en $config, estamos llamando al token de Telegram desde nuestro .env a través de env(‘TELEGRAM_TOKEN’).
Vamos a crear rápidamente nuestra ruta. Sirve como punto de partida de nuestra comunicación con el bot. dentro de routes/web.php
agregar:
use AppHttpControllersBotManController;Route::match(['get', 'post'], 'botman', [BotManController::class, 'handle']);
Bien. Tenemos un punto final los puntos a la handle()
método en nuestro BotManController.
Pasemos a probar nuestra configuración hasta ahora. Asegúrese de haber expuesto su servidor local a través de un túnel seguro utilizando valet, nginx o exposición, o cualquier otro servicio similar.
Luego ejecute el comando artesanal para ayudarlo a registrar su webhook:
php artisan botman:telegram:register
Luego, ingrese el enlace seguro que se le proporcionó y agregue el punto final que creamos ‘/ botman’. Al igual que ‘https://su-enlace-seguro.com/botman’. si la respuesta es correcta, significa que su webhook se ha registrado correctamente en Telegram.
Debe agregar el punto final a la excepción en el aplicación/Http/Middleware/VerifyCsrfToken.php
// app/Http/Middleware/VerifyCsrfToken.phpprotected $except = [
'botman'
];
Ahora ve a Telegram, al bot que creaste, el mío es @laraquizprobot, y escribe ‘hola’. Si recibe una respuesta ‘Hola’, entonces está listo para comenzar.
Si no recibe ninguna respuesta, verifique los pasos para asegurarse de que no se perdió nada, también puede consultar el repositorio para ponerse al día.
¡Excelente! Pasemos a las Conversaciones.
Ahora que podemos comunicarnos con nuestro bot en Telegram, creemos conversaciones. La primera conversación que crearemos será la Conversación de prueba. Todas nuestras conversaciones vivirán en el app/Conversations
así que cree un nuevo archivo llamado QuizConversation.php allí con este código.
app/Conversations/QuizConversation.php
<?phpnamespace AppConversations;
use BotManBotManMessagesConversationsConversation;
class QuizConversation extends Conversation
{
/**
* Start the conversation.
*
* @return mixed
*/
public function run()
{
}
}
Las preguntas se almacenan en la base de datos. Queremos agarrarlos y preguntarle al usuario cada uno de ellos. En primer lugar, el usuario tiene que seleccionar una pista, luego el bot presentará el cuestionario y luego comenzará a hacer preguntas de esa pista y mostrará las respuestas si son correctas o incorrectas. Así que modifiquemos nuestro QuizConversation.
Nota: El método run() es el método que se ejecuta cuando se llama a la conversación.
<?phpnamespace AppConversations;
use AppModelsTrack;
use BotManBotManMessagesOutgoingActionsButton;
use BotManBotManMessagesConversationsConversation;
use BotManBotManMessagesIncomingAnswer as BotManAnswer;
use BotManBotManMessagesOutgoingQuestion as BotManQuestion;
class QuizConversation extends Conversation
{
/** @var Track */
protected $quizTracks;
/** @var Question */
protected $quizQuestions;
/** @var integer */
protected $userPoints = 0;
/** @var integer */
protected $userCorrectAnswers = 0;
/** @var integer */
protected $questionCount;
/** @var integer */
protected $currentQuestion = 1;
/**
* Start the conversation.
*
* @return mixed
*/
public function run()
{
$this->quizTracks = Track::all();
$this->selectTrack();
}
private function selectTrack()
{
$this->say(
"We have " . $this->quizTracks->count() . " tracks. n You have to choose one to continue.",
['parse_mode' => 'Markdown']
);
$this->bot->typesAndWaits(1);
return $this->ask($this->chooseTrack(), function (BotManAnswer $answer) {
$selectedTrack = Track::find($answer->getValue());
if (!$selectedTrack) {
$this->say('Sorry, I did not get that. Please use the buttons.');
return $this->selectTrack();
}
return $this->setTrackQuestions($selectedTrack);
}, [
'parse_mode' => 'Markdown'
]);
}
}
Lo primero que hace la conversación es establecer $this->quizTracks y luego ejecute el seleccionar pista () método. Este método le pide al usuario que elija una pista y muestra el número de pistas y las opciones disponibles. Toma la respuesta de los usuarios y, en base a ella, llama a la siguiente función. $this->setTrackQuestions($selectedTrack) para establecer la pregunta para la pista seleccionada y luego pasar al siguiente método.
Debido a las acciones realizadas aquí, explicaré algunas de las funciones clave. Puede obtener el código completo de esta conversación aquí
Barajamos las preguntas y cambiamos la clave de la colección a la id. Esto nos facilitará la eliminación de elementos de la colección. Antes de mostrar una pregunta, comprobamos si queda alguna. Después de cada pregunta la eliminaremos. Cuando no quedan más preguntas, mostramos al usuario el resultado del cuestionario.
Aquí está el askQuestion
método. Siempre obtiene el primer elemento de las colecciones de preguntas y utiliza el BotMan ask
método para mostrar el texto al usuario, así como las respuestas. Recorremos las respuestas de las preguntas para agregar un botón para cada una de ellas a la plantilla. También estamos comprobando si la respuesta del usuario proviene de un clic de botón. Si no, repetiré esta pregunta. Esto es posible porque los botones tienen el id de la respuesta como valor. Lo usamos para encontrar la respuesta.
Cuando un usuario completa una prueba, la siguiente conversación es la conversación de puntuación más alta.
El modelo Highscore incluirá algunas cosas. Primero, definimos el fillable
property para incluir en la lista blanca los valores que podemos almacenar más adelante. Además, el nombre de la tabla debe definirse aquí porque difiere de los nombres tradicionales.
protected $fillable = ['chat_id', 'name', 'points', 'correct_answers', 'tries'];
protected $table = 'highscore';
Luego añadimos un método para almacenar las entradas de puntuación más alta. Yo uso el updateOrCreate
método para evitar entradas duplicadas. Cada usuario solo debe tener una entrada. Con cada nuevo intento, la puntuación más alta se actualizará. El campo único para estas entradas es el chat_id
y no, como normalmente, el id
campo. El updateOrCreate
El método obtiene esta información como el primer parámetro. También tenga en cuenta cómo aumento el tries
campo si la entrada no es nueva. Para esto, el wasRecentlyCreated
método viene muy bien.
public static function saveUser(UserInterface $botUser, int $userPoints, int $userCorrectAnswers)
{
$user = static::updateOrCreate(['chat_id' => $botUser->getId()], [
'chat_id' => $botUser->getId(),
'name' => $botUser->getFirstName().' '.$botUser->getLastName(),
'points' => $userPoints,
'correct_answers' => $userCorrectAnswers,
]);
$user->increment('tries'); $user->save(); return $user;
}
Al final de la prueba, le mostramos al usuario su rango. Para eso es el siguiente método. Contamos cuántos usuarios tienen más puntos que el actual. Solo nos interesan los valores únicos porque los usuarios con el mismo recuento compartirán una clasificación.
public function getRank()
{
return static::query()->where('points', '>', $this->points)->pluck('points')->unique()->count() + 1;
}
Para la puntuación más alta, solo se mostrarán los 10 mejores usuarios.
public static function topUsers($size = 10)
{
return static::query()->orderByDesc('points')->take($size)->get();
}
Buscamos a los usuarios con la mayor cantidad de puntos y agregamos un campo de clasificación a cada entrada.
Aquí está nuestro modelo final de puntuación más alta
<?phpnamespace AppModels;
use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;
use BotManBotManInterfacesUserInterface;
class Highscore extends Model
{
use HasFactory;
protected $fillable = ['chat_id', 'name', 'points', 'correct_answers', 'tries'];
public static function saveUser(UserInterface $botUser, int $userPoints, int $userCorrectAnswers)
{
$user = static::updateOrCreate(['chat_id' => $botUser->getId()], [
'chat_id' => $botUser->getId(),
'name' => $botUser->getFirstName().' '.$botUser->getLastName(),
'points' => $userPoints,
'correct_answers' => $userCorrectAnswers,
]);
$user->increment('tries');
$user->save();
return $user;
}
public function getRankAttribute()
{
return static::query()->where('points', '>', $this->points)->pluck('points')->unique()->count() + 1;
}
public static function topUsers($size = 15)
{
return static::query()->orderByDesc('points')->take($size)->get();
}
public static function deleteUser(string $chatId)
{
Highscore::where('chat_id', $chatId)->delete();
}
}
Para mostrar nuestra puntuación más alta, creamos una conversación para ella en app/Conversations/HighscoreConversation.php
<?phpnamespace AppConversations;
use AppModelsHighscore;
use BotManBotManMessagesOutgoingActionsButton;
use BotManBotManMessagesConversationsConversation;
use BotManBotManMessagesIncomingAnswer as BotManAnswer;
use BotManBotManMessagesOutgoingQuestion as BotManQuestion;
class HighscoreConversation extends Conversation
{
/**
* Start the conversation.
*
* @return mixed
*/
public function run()
{
$this->showHighscore();
}
private function showHighscore()
{
$topUsers = Highscore::topUsers();
if (! $topUsers->count()) {
return $this->say('The highscore is still empty. Be the first one! 👍');
}
$topUsers->transform(function ($user) {
return "_{$user->rank} - {$user->name}_ *{$user->points} points*";
});
$this->say('Here is the current highscore showing the top 15 results.');
$this->bot->typesAndWaits(1);
$this->say('🏆 HIGHSCORE 🏆');
$this->bot->typesAndWaits(1);
$this->say($topUsers->implode("n"), ['parse_mode' => 'Markdown']);
$this->bot->typesAndWaits(2);
$this->say("If you want to play another round click: /start nOne of the ways to improve what you know about Laravel is by going hrough their documentation at https://laravel.com/docs/.");
}
}
Para desencadenar esta y otras conversaciones, necesitamos oyentes. junto al start
palabra clave que también agrego /start
que se convertirá en un comando de Telegram más adelante. haré lo mismo por highscore
.
// appHttp/Controllers/BotManController.php$botman->hears('start|/start', function (BotMan $bot) {
$bot->startConversation(new QuizConversation());
})->stopsConversation();
$botman->hears('/highscore|highscore', function (BotMan $bot) {
$bot->startConversation(new HighscoreConversation());
})->stopsConversation();
Para probar, puede escribir una de las palabras clave en el bot para ver qué sucede. Le dirá que la puntuación más alta todavía está vacía.
Desde el RGPD, es esencial dar al usuario la posibilidad de eliminar sus datos almacenados. Quiero manejar eso en un nuevo app/Conversations/PrivacyConversation.php
clase. Primero nos aseguramos de haber almacenado este usuario actual. Si es así, le preguntamos si realmente quiere borrar su entrada de puntuación más alta.
<?phpnamespace AppConversations;use AppHighscore;
use BotManBotManMessagesIncomingAnswer;
use BotManBotManMessagesOutgoingQuestion;
use BotManBotManMessagesOutgoingActionsButton;
use BotManBotManMessagesConversationsConversation;class PrivacyConversation extends Conversation
{
/**
* Start the conversation.
*
* @return mixed
*/
public function run()
{
$this->askAboutDataDeletion();
} private function askAboutDataDeletion()
{
$user = Highscore::where('chat_id', $this->bot->getUser()->getId())->first();if (! $user) {
return $this->say('We have not stored any data of you.');
}$this->say('We have stored your name and chat ID for showing you in the highscore.');
$question = Question::create('Do you want to get deleted?')->addButtons([
Button::create('Yes please')->value('yes'),
Button::create('Not now')->value('no'),
]);$this->ask($question, function (Answer $answer) {
switch ($answer->getValue()) {
case 'yes':
Highscore::deleteUser($this->bot->getUser()->getId());
return $this->say('Done! Your data has been deleted.');
case 'no':
return $this->say('Great to keep you 👍');
default:
return $this->repeat('Sorry, I did not get that. Please use the buttons.');
}
});
}
}
El deleteUser
es un nuevo método de modelo Highscore que debe agregar.
public static function deleteUser(string $chatId)
{
Highscore::where('chat_id', $chatId)->delete();
}
Si alguien quiere saber más sobre este bot, le daré la posibilidad agregando algo de información. Como solo devuelvo un pequeño mensaje de texto, lo manejaré dentro del oyente.
$botman->hears('/about|about', function (BotMan $bot) {
$bot->reply('This is a BotMan and Laravel 8 project by Ejimadu Prevail.');
})->stopsConversation();
Nuestro controlador ahora se verá así:
<?phpnamespace AppHttpControllers;
use BotManBotManBotMan;
use AppConversationsQuizConversation;
use AppConversationsPrivacyConversation;
use AppConversationsHighscoreConversation;
use AppHttpMiddlewarePreventDoubleClicks;
use BotManBotManBotManFactory;
use BotManBotManCacheLaravelCache;
use BotManBotManDriversDriverManager;
class BotManController extends Controller
{
/**
* Place your BotMan logic here.
*/
public function handle()
{
DriverManager::loadDriver(BotManDriversTelegramTelegramDriver::class);
$config = [
'user_cache_time' => 720,
'config' => [
'conversation_cache_time' => 720,
],
"telegram" => [
"token" => env('TELEGRAM_TOKEN'),
]
];
// // Create BotMan instance
$botman = BotManFactory::create($config, new LaravelCache());
$botman->middleware->captured(new PreventDoubleClicks);
$botman->hears('start|/start', function (BotMan $bot) {
$bot->startConversation(new QuizConversation());
})->stopsConversation();
$botman->hears('/highscore|highscore', function (BotMan $bot) {
$bot->startConversation(new HighscoreConversation());
})->stopsConversation();
$botman->hears('/about|about', function (BotMan $bot) {
$bot->reply('This is a BotMan and Laravel 8 project by Ejimadu Prevail.');
})->stopsConversation();
$botman->hears('/deletedata|deletedata', function (BotMan $bot) {
$bot->startConversation(new PrivacyConversation());
})->stopsConversation();
$botman->listen();
}
}
Este artículo se hizo más largo de lo que esperaba, pero todavía hay mucho que cubrir sobre cómo crear chatbots dinámicos usando PHP. Este artículo lo ayudará a crear su propio chatbot, ya que cubre la mayoría de los conceptos de BotMan 2.0. Siempre puede consultar el repositorio y probar cosas diferentes tanto como sea posible.