Browse Source

Техбот

Реализована ручная установка состояния
Вадим Королёв 1 year ago
parent
commit
fd3b50c5a0

+ 52 - 7
index.php

@@ -6,6 +6,7 @@ namespace BotKit;
 require_once __DIR__.'/src/bootstrap.php';
 use BotKit\Common\Bot;
 use BotKit\Common\Commands;
+use BotKit\Common\Message;
 use BotKit\Drivers\TestDriver;
 use BotKit\Events\PlainMessageEvent;
 use BotKit\Events\UnknownEvent;
@@ -13,24 +14,68 @@ use BotKit\Events\MemberJoinedEvent;
 use BotKit\Events\MemberLeftEvent;
 use BotKit\Enums\State;
 use BotKit\Enums\CallbackType;
+use BotKit\Keyboards\SelectGroupNameKeyboard;
 
 $bot = new Bot();
 $drv = new TestDriver();
 $bot->loadDriver($drv);
 $bot->ensureDriversLoaded();
 
-// Нажатие на кнопку условий использования
-$bot->onCallback(
-    CallbackType::TOS,
-    "BotKit\Common\Commands::showTermsOfService"
+// Выбор курса для группы
+$bot->onCallback(CallbackType::SelectGroupCourse, function($e, $u, $driver) {
+    // Переносим полученные параметры в новую клавиатуру
+    // А так же изменяем сообщение
+    $params = $e->getParams();
+    
+    $driver->editMessage(
+        $e->getMessageID(),
+        Message::create("Выбери группу")
+            ->withKeyboard(new SelectGroupNameKeyboard($params['intent'], $params['course']))
+    );
+});
+
+// Выбор непосредственно группы
+$bot->onCallback(CallbackType::SelectGroup, 'BotKit\Common\Commands::handleGroupSelection');
+
+// Просмотр условий использования
+$bot->onCallback(CallbackType::TOS, 'BotKit\Common\Commands::showTermsOfService');
+
+// Ввод логина и пароля при регистрации
+$bot->onCallback(CallbackType::EnterCredentials, 'BotKit\Common\Commands::enterCredentials');
+
+// Без ответа
+$bot->on(
+    PlainMessageEvent::class,
+    function ($e, $u, $driver) { return $u->getState() == State::NoResponse; },
+    function($e, $u, $driver) {}
+);
+
+// Начало диалога
+$bot->onCommand('/start', 'BotKit\Common\Commands::helloWorld');
+$bot->on(
+    PlainMessageEvent::class,
+    function ($e, $u, $driver) { return $u->getState() == State::HelloWorld; },
+    "BotKit\Common\Commands::helloWorld"
 );
 
+// Выбор типа аккаунта
 $bot->on(
     PlainMessageEvent::class,
     function ($e, $u, $driver) {
-        return $u->getState() == State::Registering;
+        return $u->getState() == State::RegSelectType;
     },
-    "BotKit\Common\Commands::helloWorld"
+    "BotKit\Common\Commands::handleSelectType"
+);
+
+// Ввод пароля при регистрации
+$bot->on(
+    PlainMessageEvent::class,
+    function ($e, $u, $driver) {
+        return $u->getState() == State::RegLogin;
+    },
+    "BotKit\Common\Commands::handleRegLogin"
 );
 
-$bot->onEvent(UnknownEvent::class, "BotKit\Common\Commands::fallback");
+// План "Б"
+$bot->onEvent(UnknownEvent::class, "BotKit\Common\Commands::unknownEvent");
+$bot->fallback("BotKit\Common\Commands::fallback");

+ 5 - 0
src/Common/Bot.php

@@ -161,4 +161,9 @@ class Bot {
         // Вызов обработки
         $this->processRequest($responseCallback, []);
     }
+
+    // Все условия не прошли, вызываем план Б
+    public function fallback(callable $callback) {
+        $this->processRequest($callback, []);
+    }
 }

+ 95 - 40
src/Common/Commands.php

@@ -7,11 +7,22 @@ namespace BotKit\Common;
 use BotKit\Common\Message;
 use BotKit\Enums\Platform;
 use BotKit\Enums\State;
-use BotKit\Keyboards\TermsOfServiceKeyboard;
 use BotKit\Attachments\ImageAttachment;
 
+use BotKit\Enums\GroupSelect;
+
+use BotKit\Keyboards\TermsOfServiceKeyboard;
+use BotKit\Keyboards\SelectUserTypeKeyboard;
+use BotKit\Keyboards\HubKeyboard;
+use BotKit\Keyboards\SelectGroupCourseKeyboard;
+use BotKit\Keyboards\AskEnterCredentialsKeyboard;
+
+use BotKit\Models;
+
 class Commands {
 
+    #region Callback
+
     // Отправка условий использования
     public static function showTermsOfService($e, $u, $driver) {
         $driver->editMessage(
@@ -21,9 +32,57 @@ class Commands {
         );
     }
 
+    // Выбор группы
+    public static function handleGroupSelection($e, $u, $driver) {
+        $params = $e->getParams();
+        $intent = GroupSelect::from($params['intent']);
+        switch ($intent) {
+            case GroupSelect::Register:
+                // Группа выбрана, зарегистрирован студент
+                StudentModel::create($u['id'], $params['group_id'], 0);
+                
+                $driver->editMessage(
+                    $e->getMessageID(),
+                    Message::create("Хочешь ввести логин и пароль для просмотра оценок?")
+                        ->withKeyboard(new AskEnterCredentialsKeyboard())
+                );
+                break;
+            default:
+                $driver->sendMessage($u, Message::create("[Unknown group select value]"));
+                break;
+        }
+    }
+
+    // Выбор хочу/не хочу
+    public static function enterCredentials($e, $u, $driver) {
+        $params = $e->getParams();
+        if ($params['enters'] == 0) {
+            // Пользователь не захотел вводить пароль и логин
+            $driver->editMessage(
+                $e->getMessageID(),
+                Message::create("Регистрация завершена!")
+                    ->withKeyboard(new HubKeyboard())
+            );
+            $u->setState(State::Hub);
+        } else {
+            // Пользователь захотел вводить пароль и логин
+            $driver->sendMessage($u, Message::create("Введи логин"));
+            $u->setState(State::RegLogin);
+        }
+    }
+
+    #endregion
+
     public static function helloWorld($e, $u, $driver) {
-        // Проверить: сообщение из чата или от пользователя
-        $url = 'https://www.wallpaperflare.com/static/929/413/133/artwork-aenami-abstract-painting-wallpaper.jpg';
+        //~ if ($u->getState() != State::HelloWorld) {
+            //~ $driver->reply(
+                //~ $e,
+                //~ Message::create("Ты уже зарегистрирован")
+                //~ ->withKeyboard(new HubKeyboard())
+            //~ );
+            //~ return;
+        //~ }
+
         $driver->reply(
             $e,
             Message::create("Привет, я - Техбот. Моя задача - облегчить твою жизнь, но, для начала, мне нужно задать несколько вопросов"),
@@ -36,51 +95,47 @@ class Commands {
                 ->withKeyboard(new TermsOfServiceKeyboard()),
             false
         );
-        $u->setState(State::Registering);
-    }
-
-    // Помощь
-    public static function help($e, $u, $driver) {
-        $driver->reply($e, Message::create("Помощь по BotKit"));
-    }
-
-    // Помощь по теме
-    public static function helpTopic($e, $u, $driver, $topic) {
-        $driver->reply($e, Message::create("Помощь по ".$topic));
+        $driver->reply(
+            $e,
+            Message
+                ::create("Ты студент или преподаватель?")
+                ->withKeyboard(new SelectUserTypeKeyboard()),
+            false
+        );
+        $u->setState(State::RegSelectType);
     }
 
-    // Помощь по теме
-    public static function fallback($e, $u, $driver) {
-        $driver->reply($e, Message::create("FALLBACK"));
+    // Обработка выбора типа аккаунта
+    public static function handleSelectType($e, $u, $driver) {
+        if ($e->getText() == "Я студент") {
+            // Создать объект студента
+            $driver->reply(
+                $e,
+                Message::create("Выбери курс, на котором сейчас учишься")
+                    ->withKeyboard(new SelectGroupCourseKeyboard(GroupSelect::Register)),
+                false
+            );
+            $u->setState(State::NoResponse);
+        } else {
+            // TODO
+            $driver->reply($e, Message::create("Функционал ещё не реализован"));
+        }
     }
 
-    public static function registered($e, $u, $driver) {
-        // Проверить: сообщение из чата или от пользователя
-        $driver->reply($e, Message::create("REGISTERED"));
-    }
+    // Обработка ввода логина при регистрации
+    public static function handleRegLogin($e, $u, $driver) {
+        $student = Models\StudentModel::where([
+            ['user_id', '=', $u['id']]
+        ])[0];
 
-    // Функция приветствия пользователя
-    public static function greetMember($e, $u, $driver) {
-        $username = self::getNick($u, $driver);
-        $driver->sendToChat(
-            $e->getChat(),
-            Message::create("Добро пожаловать в чат, ".$username)
-        );
+        $driver->varDump("Student", $student);
     }
 
-    // Функция прощания с пользователем
-    public static function bye($e, $u, $driver) {
-        $username = self::getNick($u, $driver);
-        echo $username;
-        $driver->sendMessage($u, Message::create("Будем скучать, ".$username));
+    public static function fallback($e, $u, $driver) {
+        $driver->sendMessage($u, Message::create("Функционал не реализован"));
     }
 
-    // Возвращает ник пользователя
-    private static function getNick($u, $driver) {
-        $username = $driver->getUserNick($u);
-        if (empty($username)) {
-            return $u->getPlatformID();
-        }
-        return $username;
+    public static function unknownEvent($e, $u, $driver) {
+        $driver->sendMessage($u, Message::create("Неизвестное событие"));
     }
 }

+ 54 - 18
src/Drivers/TestDriver.php

@@ -27,7 +27,7 @@ class TestDriver implements Driver {
     private Platform $platform;
 
     // Буфер действий
-    private array $actions;
+    private array $actions = [];
 
     public function __construct() {
         $this->platform = Platform::Test;
@@ -52,30 +52,60 @@ class TestDriver implements Driver {
     }
 
     public function getEvent() : Event {
-        $data = json_decode(file_get_contents("php://input"));
+        $data = json_decode(file_get_contents("php://input"), true);
         $db = Database::getConnection();
 
-        $details = $data->details;
+        $details = $data['details'];
         $chat = new Chat(42);
 
-        if ($data->type == 'callback') {
+        // Интерфейс тестов запрашивает установку состояния
+        if ($data['type'] == 'stateSet') {
+            $user = $this->getUser($details['userID']);
+            $user->setState(State::from($details['stateID']));
+
+            // Сохраняем пользователя вручную
+            UserModel::updateObject($user->getDbObject());
+            
+            $this->actions[] = [
+                "action" => "info",
+                "title" => "Состояние пользователя изменено вручную",
+                "body" => "Новое состояние: ".serialize($user->getState())
+            ];
+            // Дальнейшая обработка не требуется, завершаем выполнение здесь
+            $this->echoActions();
+        }
+
+        // Интерфейс тестов запрашивает все доступные состояния
+        if ($data['type'] == 'statesRequest') {
+            $this->actions[] = [
+                "action" => "statesResponse",
+                "states" => array_combine(
+                    array_column(State::cases(), 'name'),
+                    array_column(State::cases(), 'value')
+                )
+            ];
+            // Дальнейшая обработка не требуется, завершаем выполнение здесь
+            $this->echoActions();
+        }
+
+        if ($data['type'] == 'callback') {
             // Обратный вызов
-            $user = $this->getUser($details->userId);
+            $user = $this->getUser($details['userId']);
             return new CallbackEvent(
-                $details->msgId,
+                $details['msgId'],
                 $user,
                 $chat,
-                CallbackType::from($details->callbackType),
-                $details->params
+                CallbackType::from($details['callbackType']),
+                $details['params']
             );
         }
 
-        if ($data->type == 'botKitMsg') {
+        if ($data['type'] == 'botKitMsg') {
             // Обычное текстовое сообщение
-            $user = $this->getUser($details->userID);
-            $text = $details->text;
+            $user = $this->getUser($details['userID']);
+            $text = $details['text'];
             return new PlainMessageEvent(
-                $details->id,
+                $details['id'],
                 $user,
                 $chat,
                 $text,
@@ -242,14 +272,20 @@ class TestDriver implements Driver {
                             $button_color = "primary";
                             break;
                     }
-                    
-                    $serialized_row[] = [
+
+                    $button_data = [
                         "type" => $button_type,
                         "color" => $button_color,
-                        "label" => $button->getText(),
-                        "callbackType" => $button->getType(),
-                        "payload" => $button->getPayload()
+                        "label" => $button->getText()
                     ];
+
+                    // Добавление параметров обратного вызова
+                    if ($button_type == "callbackButton") {
+                        $button_data["callbackType"] = $button->getType();
+                        $button_data["payload"] = $button->getPayload();
+                    }
+
+                    $serialized_row[] = $button_data;
                 }
                 $serialized_layout[] = $serialized_row;
             }
@@ -296,7 +332,7 @@ class TestDriver implements Driver {
         $obj = UserModel::where([
             ['platform_id', '=', $userID],
             ['platform', '=', $this->platform->value]
-        ]);
+        ])[0];
 
         if ($obj === false) {
             // Пользователя нет в БД

+ 5 - 3
src/Enums/CallbackType.php

@@ -5,7 +5,9 @@
 namespace BotKit\Enums;
 
 enum CallbackType: int {
-	case None = 0;
-	case TOS = 1;
-	case SelectedAccountType = 2;
+	case None 				= 0;
+	case TOS				= 1; // Просмотр условий использования
+	case SelectGroupCourse 	= 2; // Выбран курс группы
+	case SelectGroup 		= 3; // Выбрана группа
+	case EnterCredentials	= 4; // Логин и пароль вводятся на регистрации?
 }

+ 8 - 0
src/Enums/GroupSelect.php

@@ -0,0 +1,8 @@
+<?php
+// Перечисление для доп. типов обратного вызова SelectedGroup
+
+namespace BotKit\Enums;
+
+enum GroupSelect: int {
+	case Register = 0;  // Регистрация
+}

+ 5 - 1
src/Enums/State.php

@@ -7,5 +7,9 @@ namespace BotKit\Enums;
 enum State: int {
 	case Any = 0;
 	case HelloWorld = 1;
-	case Registering = 2;
+	case RegSelectType = 2;
+	case NoResponse = 3;
+	case Hub = 4;
+	case RegLogin = 5;
+	case RegPassword = 6;
 }

+ 1 - 2
src/KeyboardButtons/PlainKeyboardButton.php

@@ -7,9 +7,8 @@ namespace BotKit\KeyboardButtons;
 use BotKit\Enums\KeyboardButtonColor;
 
 class PlainKeyboardButton extends KeyboardButton {
-	public function __construct(string $text, KeyboardButtonColor $color=KeyboardButtonColor::None, $payload=[]) {
+	public function __construct(string $text, KeyboardButtonColor $color=KeyboardButtonColor::None) {
 		$this->text = $text;
 		$this->color = $color;
-		$this->payload = $payload;
 	}
 }

+ 41 - 0
src/Keyboards/AskEnterCredentialsKeyboard.php

@@ -0,0 +1,41 @@
+<?php
+
+// Клавиатура с кнопками "Хочу" и "Пропустить"
+// Необходима для выбора пользователя вводить логин и пароль от журнала или нет
+
+namespace BotKit\Keyboards;
+
+use BotKit\KeyboardButtons\CallbackButton;
+use BotKit\Enums\KeyboardButtonColor;
+use BotKit\Enums\CallbackType;
+
+class AskEnterCredentialsKeyboard extends Keyboard {
+	#region Драйвер-зависимые свойства
+	public $tg_resize=true;
+	public $tg_onetime=false;
+	public $tg_specific=true;
+	public $inline=true;
+	#endregion
+
+	public function __construct() {
+		$this->layout =
+		[
+			[
+				new CallbackButton(
+					"Хочу",
+					CallbackType::EnterCredentials,
+					KeyboardButtonColor::Success,
+					["enters" => 1]
+				),
+				new CallbackButton(
+					"Пропустить",
+					CallbackType::SelectGroupCourse,
+					KeyboardButtonColor::Secondary,
+					["enters" => 0]
+				)
+			]
+		];
+	}
+
+	public static bool $cacheable = true;
+}

+ 40 - 0
src/Keyboards/HubKeyboard.php

@@ -0,0 +1,40 @@
+<?php
+// Клавиатура показа условий использования
+
+namespace BotKit\Keyboards;
+
+use BotKit\KeyboardButtons\CallbackButton;
+use BotKit\KeyboardButtons\PlainKeyboardButton;
+use BotKit\Enums\KeyboardButtonColor;
+use BotKit\Enums\CallbackType;
+
+class HubKeyboard extends Keyboard {
+	#region Драйвер-зависимые свойства
+	public $tg_resize=true;
+	public $tg_onetime=false;
+	public $tg_specific=true;
+	public $inline=false;
+	#endregion
+
+	public function __construct() {
+		
+		$this->layout =
+		[
+			[
+				new PlainKeyboardButton("Расписание"),
+				new PlainKeyboardButton("Оценки"),
+				new PlainKeyboardButton("Что дальше")
+			],
+			[
+				new PlainKeyboardButton("Где преподаватель"),
+				new PlainKeyboardButton("Расписание группы"),
+				new PlainKeyboardButton("Звонки")
+			],
+			[
+				new PlainKeyboardButton("Профиль")
+			]
+		];
+	}
+
+	public static bool $cacheable = true;
+}

+ 60 - 0
src/Keyboards/SelectGroupCourseKeyboard.php

@@ -0,0 +1,60 @@
+<?php
+
+// Клавиатура выбора курса группы
+// После выбора курса клавиатура изменится так, что будет показывать
+// специальности
+
+namespace BotKit\Keyboards;
+
+use BotKit\KeyboardButtons\CallbackButton;
+use BotKit\Enums\KeyboardButtonColor;
+use BotKit\Enums\CallbackType;
+
+class SelectGroupCourseKeyboard extends Keyboard {
+	#region Драйвер-зависимые свойства
+	public $tg_resize=true;
+	public $tg_onetime=false;
+	public $tg_specific=true;
+	public $inline=true;
+	#endregion
+
+	public function __construct($intent) {
+		// $intent - дополнительное значение для типа обратного вызова
+		// Обозначает что необходимо выполнить после того, как будет выбрана
+		// специальность
+		
+		$this->layout =
+		[
+			[
+				new CallbackButton(
+					"1",
+					CallbackType::SelectGroupCourse,
+					KeyboardButtonColor::Primary,
+					["intent" => $intent, "course" => 1]
+				),
+				new CallbackButton(
+					"2",
+					CallbackType::SelectGroupCourse,
+					KeyboardButtonColor::Primary,
+					["intent" => $intent, "course" => 2]
+				)
+			],
+			[
+				new CallbackButton(
+					"3",
+					CallbackType::SelectGroupCourse,
+					KeyboardButtonColor::Primary,
+					["intent" => $intent, "course" => 3]
+				),
+				new CallbackButton(
+					"4",
+					CallbackType::SelectGroupCourse,
+					KeyboardButtonColor::Primary,
+					["intent" => $intent, "course" => 4]
+				)
+			]
+		];
+	}
+
+	public static bool $cacheable = false;
+}

+ 58 - 0
src/Keyboards/SelectGroupNameKeyboard.php

@@ -0,0 +1,58 @@
+<?php
+
+// Клавиатура выбора группы
+
+namespace BotKit\Keyboards;
+
+use BotKit\KeyboardButtons\CallbackButton;
+use BotKit\Enums\KeyboardButtonColor;
+use BotKit\Enums\CallbackType;
+
+use BotKit\Models\CollegeGroupModel;
+
+class SelectGroupNameKeyboard extends Keyboard {
+	#region Драйвер-зависимые свойства
+	public $tg_resize=true;
+	public $tg_onetime=false;
+	public $tg_specific=true;
+	public $inline=true;
+	#endregion
+
+	public function __construct($intent, $course) {
+		// $intent - дополнительное значение для типа обратного вызова
+		// Обозначает что необходимо выполнить после того, как будет выбрана
+		// специальность
+
+		// $course - курс, выбранный ранее
+		
+		$this->layout = [];
+		$groups = CollegeGroupModel::getByCourse($course);
+
+		$row_item_count = 0;
+		$row = [];
+
+		foreach ($groups as $group) {
+			$row[] = new CallbackButton(
+				$group['name'],
+				CallbackType::SelectGroup,
+				KeyboardButtonColor::None,
+				['group_id' => $group['id'], 'intent' => $intent]
+			);
+
+			// Только 3 кнопки на ряд
+			$row_item_count++;
+			if ($row_item_count == 3) {
+				$this->layout[] = $row;
+				$row = [];
+				$row_item_count = 0;
+			}
+		}
+
+		if ($row_item_count != 0) {
+			// На последний ряд
+			$this->layout[] = $row;
+		}
+	}
+
+	public static bool $cacheable = false;
+}

+ 6 - 7
src/Keyboards/SelectUserTypeKeyboard.php

@@ -3,28 +3,27 @@
 
 namespace BotKit\Keyboards;
 
+use BotKit\KeyboardButtons\PlainKeyboardButton;
 use BotKit\KeyboardButtons\CallbackButton;
 use BotKit\Enums\KeyboardButtonColor;
 use BotKit\Enums\CallbackType;
 
 class SelectUserTypeKeyboard extends Keyboard {
 
+	public $inline=false;
+
 	public function __construct() {
 		
 		$this->layout =
 		[
 			[
-				new CallbackButton(
+				new PlainKeyboardButton(
 					"Я студент",
-					CallbackType::SelectedAccountType,
-					KeyboardButtonColor::Primary,
-					["account"=> "student"]
+					KeyboardButtonColor::Primary
 				),
-				new CallbackButton(
+				new PlainKeyboardButton(
 					"Я преподаватель",
-					CallbackType::SelectedAccountType,
 					KeyboardButtonColor::Primary,
-					["account"=> "teacher"]
 				)
 			]
 		];

+ 10 - 1
src/Models/Model.php

@@ -19,6 +19,15 @@ class Model {
         return $db->query("SELECT ".static::allowedColumnsSQL()." FROM ".static::$table_name);
     }
 
+    // Поиск по ID
+    public static function find($id) {
+        $db = Database::getConnection();
+        $stm = $db->prepare("SELECT ".static::allowedColumnsSQL()." FROM ".static::$table_name. " WHERE id=:id");
+        $stm->bindValue(':id', $id);
+        $stm->execute();
+        return $stm->fetch();
+    }
+
     // Возвращает записи по условиям
     // Все условия должны быть истины, т. к. для построения запроса используется 'AND'
     public static function where($conditions) {
@@ -42,6 +51,6 @@ class Model {
             $stm->bindValue(":".$condition[0], $condition[2]);
         }
         $stm->execute();
-        return $stm->fetch();
+        return $stm->fetchAll();
     }
 }