Sfoglia il codice sorgente

Базовый функционал драйвера и бота

Вадим Королёв 1 anno fa
parent
commit
49913a4fe4

+ 2 - 0
.env.template

@@ -0,0 +1,2 @@
+# Удали .template из названия файла
+TELEGRAMORG_TOKEN=

+ 1 - 1
.gitignore

@@ -1,4 +1,4 @@
 .env
 *.geany
 vendor
-testbotkit_plainmsg.sh
+sendmsg.sh

+ 20 - 0
composer.json

@@ -0,0 +1,20 @@
+{
+    "name": "pydim/bot-kit",
+    "description": "Framework for creating bots",
+    "type": "project",
+    "license": "GPL-3.0-only",
+    "authors": [
+        {
+            "name": "Вадим Королёв",
+            "email": "pydim@mail.ru"
+        }
+    ],
+    "require": {
+        "vlucas/phpdotenv": "^5.6"
+    },
+    "autoload": {
+        "psr-4": {
+            "BotKit\\": "src/"
+        }
+    }
+}

+ 479 - 0
composer.lock

@@ -0,0 +1,479 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "81fb734c78f2d521f7b6d6c69d90c64c",
+    "packages": [
+        {
+            "name": "graham-campbell/result-type",
+            "version": "v1.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/GrahamCampbell/Result-Type.git",
+                "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862",
+                "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "phpoption/phpoption": "^1.9.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "GrahamCampbell\\ResultType\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                }
+            ],
+            "description": "An Implementation Of The Result Type",
+            "keywords": [
+                "Graham Campbell",
+                "GrahamCampbell",
+                "Result Type",
+                "Result-Type",
+                "result"
+            ],
+            "support": {
+                "issues": "https://github.com/GrahamCampbell/Result-Type/issues",
+                "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-11-12T22:16:48+00:00"
+        },
+        {
+            "name": "phpoption/phpoption",
+            "version": "1.9.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/schmittjoh/php-option.git",
+                "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820",
+                "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": true
+                },
+                "branch-alias": {
+                    "dev-master": "1.9-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpOption\\": "src/PhpOption/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Johannes M. Schmitt",
+                    "email": "schmittjoh@gmail.com",
+                    "homepage": "https://github.com/schmittjoh"
+                },
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                }
+            ],
+            "description": "Option Type for PHP",
+            "keywords": [
+                "language",
+                "option",
+                "php",
+                "type"
+            ],
+            "support": {
+                "issues": "https://github.com/schmittjoh/php-option/issues",
+                "source": "https://github.com/schmittjoh/php-option/tree/1.9.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-11-12T21:59:55+00:00"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.29.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
+                "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "provide": {
+                "ext-ctype": "*"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-01-29T20:11:03+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.29.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
+                "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "provide": {
+                "ext-mbstring": "*"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-01-29T20:11:03+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.29.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
+                "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-01-29T20:11:03+00:00"
+        },
+        {
+            "name": "vlucas/phpdotenv",
+            "version": "v5.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/vlucas/phpdotenv.git",
+                "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
+                "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
+                "shasum": ""
+            },
+            "require": {
+                "ext-pcre": "*",
+                "graham-campbell/result-type": "^1.1.2",
+                "php": "^7.2.5 || ^8.0",
+                "phpoption/phpoption": "^1.9.2",
+                "symfony/polyfill-ctype": "^1.24",
+                "symfony/polyfill-mbstring": "^1.24",
+                "symfony/polyfill-php80": "^1.24"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "ext-filter": "*",
+                "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+            },
+            "suggest": {
+                "ext-filter": "Required to use the boolean validator."
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": true
+                },
+                "branch-alias": {
+                    "dev-master": "5.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Dotenv\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Vance Lucas",
+                    "email": "vance@vancelucas.com",
+                    "homepage": "https://github.com/vlucas"
+                }
+            ],
+            "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
+            "keywords": [
+                "dotenv",
+                "env",
+                "environment"
+            ],
+            "support": {
+                "issues": "https://github.com/vlucas/phpdotenv/issues",
+                "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-11-12T22:43:29+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": [],
+    "plugin-api-version": "2.6.0"
+}

+ 24 - 0
index.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace BotKit;
+
+// Файл, на который поступают запросы
+
+require_once __DIR__.'/src/bootstrap.php';
+use BotKit\Common\Bot;
+use BotKit\Common\Commands;
+use BotKit\Drivers\TgBotDriver;
+use BotKit\Events\PlainMessageEvent;
+
+$tgdriver = new TgBotDriver($_ENV['TELEGRAMORG_TOKEN']);
+
+$bot = new Bot();
+$bot->loadDriver($tgdriver);
+
+$bot->on(
+    PlainMessageEvent::class,
+    function ($e, $u, $driver) {
+        return true;
+    },
+    "BotKit\Common\Commands::echoMessage"
+);

+ 44 - 0
src/Attachments/ImageAttachment.php

@@ -0,0 +1,44 @@
+<?php
+// Изображение, вложенное в сообщение
+
+namespace BotKit\Attachments;
+
+use BotKit\Enums\ImageAttachmentType;
+
+class ImageAttachment {
+	// Тип прикрепления
+	private ImageAttachmentType $type;
+
+	// Что прикрепляется (интерпретируется с помощью драйверов)
+	private $value;
+
+	public function __construct($value, $type) {
+		$this->value = $value;
+		$this->type = $type;
+	}
+
+	// Указывает, что изображение прикрепляется из файла на диске
+	public static function fromFile($filename) {
+		return new self($filename, ImageAttachmentType::FromFile);
+	}
+	
+	// Указывает, что изображение прикрепляется из веб-ресурса
+	public static function fromUrl($url) {
+		return new self($url, ImageAttachmentType::FromUrl);
+	}
+	
+	// Указывает, что прикрепляемое изображение уже существует на целевом сервере
+	public static function fromExisting($image) {
+		return new self($image, ImageAttachmentType::FromExisting);
+	}
+
+	public function getType() {
+		return $this->type;
+	}
+
+	public function getValue() {
+		return $this->value;
+	}
+	
+	
+}

+ 76 - 0
src/Common/Bot.php

@@ -0,0 +1,76 @@
+<?php
+// Класс бота
+
+namespace BotKit\Common;
+
+use BotKit\Events\Event;
+
+class Bot {
+
+	// Событие которое обрабатывает бот
+	private Event $event;
+
+	// Загружен ли драйвер
+	private bool $driver_loaded = false;
+
+	// Загружает драйвер
+	public function loadDriver($driver) {
+		if ($this->driver_loaded == true) {
+			// Драйвер уже выбран
+			return;
+		}
+
+		if ($driver->forThis()) {
+			$this->driver_loaded = true;
+			$this->driver = $driver;
+			$this->event = $driver->getEvent();
+		}
+	}
+
+	// Подключает обработчик события
+	public function on(
+		string $event_classname,
+		callable $check,
+		callable $callback
+	) : void
+	{
+		if ($this->driver_loaded == false) {
+			throw new \Exception("Bot has no loaded drivers. Did you loadDriver before adding handlers?");
+		}
+
+		if (is_a($this->event, $event_classname, true) == false) {
+			// Если подключается обработчик события класса $event_classname,
+			// а в этом запросе обрабатывается событие другого класса, то
+			// и привязывать обработчик нет необходимости
+			return;
+		}
+
+		// Проверка условия события
+		// TODO: добавить параметры команды
+		$check_params = [
+			'e' => $this->event,
+			'u' => $this->event->getUser(),
+			'driver' => $this->driver
+		];
+
+		$result = call_user_func_array($check, $check_params);
+		if ($result == false) {
+			// Обрабатываемое событие - не для этого обработчика
+			return;
+		}
+
+		// Вызов обработчика
+		$callback_params = [
+			'e' => $this->event,
+			'u' => $this->event->getUser(),
+			'driver' => $this->driver
+		];
+		call_user_func_array($callback, $callback_params);
+
+		// Сохранение пользователя...
+
+		// Завершение работы драйвера...
+
+		exit();
+	}
+}

+ 14 - 0
src/Common/Chat.php

@@ -0,0 +1,14 @@
+<?php
+// Класс чата платформы
+
+namespace BotKit\Common;
+
+class Chat {
+    public function __construct(
+	private $platform_id	// ID на платформе
+    ) {}
+
+    public function getID() {
+	return $this->platform_id;
+    }
+}

+ 14 - 0
src/Common/Commands.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace BotKit\Common;
+
+// Команды бота
+
+use BotKit\Common\Message;
+
+class Commands {
+	public static function echoMessage($e, $u, $driver) {
+		$driver->reply($e, Message::create($e->getText()));
+		$driver->sendMessage($u, Message::create("DIRECT MESSAGE"));
+	}
+}

+ 44 - 0
src/Common/Database.php

@@ -0,0 +1,44 @@
+<?php
+// Класс работы с БД
+// Этот файл может быть изменён разработчиком
+
+namespace BotKit\Common;
+
+class Database {
+	private static $db;
+	private $connection;
+	
+	private function __construct($dsn, $user, $password) {
+		// https://mariadb.com/resources/blog/developer-quickstart-php-data-objects-and-mariadb/
+		$options = [
+			\PDO::ATTR_EMULATE_PREPARES   => false,
+			\PDO::ATTR_ERRMODE            => \PDO::ERRMODE_EXCEPTION,
+			\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
+		];
+		$this->connection = new \PDO($dsn, $user, $password);
+	}
+
+	// Инициализирует БД
+	public static function init($host=null,$db_name=null,$user=null,$password=null): void {
+
+		$host		= $host 	? $host 	: $_ENV['db_host'];
+		$db_name	= $db_name 	? $db_name 	: $_ENV['db_name'];
+		$user		= $user 	? $user 	: $_ENV['db_user'];
+		$password	= $password ? $password : $_ENV['db_password'];
+
+		if (self::$db == null) {
+			self::$db = new Database(
+				"mysql:host=".$host.";dbname=".$db_name.";charset=utf8mb4",
+				$user,
+				$password
+			);
+		}
+	}
+
+	public static function getConnection() {
+		if (self::$db == null) {
+			self::init();
+		}
+		return self::$db->connection;
+	}
+}

+ 66 - 0
src/Common/EventData.php

@@ -0,0 +1,66 @@
+<?php
+// Класс события
+
+namespace BotKit\Common;
+
+class EventData {
+
+	// Текст сообщения (если присутствует)
+	private string $text;
+
+	// Данные, которые были получены непосредственно сервером
+	private $payload;
+
+	// Тип события
+	private int $event_type;
+
+	// Тип обратного вызова (если событие - обратный вызов)
+	private int $callback_type;
+
+	// Данные обратного вызова (если событие - обратный вызов)
+	private array $callback_data;
+
+	public function __construct(
+		$text,
+		$payload,
+		$event_type,
+		$callback_type=CallbackType::None,
+		$callback_data = []
+	)
+	{
+		$this->text = $text;
+		$this->payload = $payload;
+		$this->event_type = $event_type;
+		$this->callback_type = $callback_type;
+		$this->callback_data = $callback_data;
+	}
+
+	// Возвращает объект $payload
+	public function getPayload() {
+		return $this->payload;
+	}
+
+	// Возвращает тип события
+	public function getEventType() : int {
+		return $this->event_type;
+	}
+
+	// Возвращает текст
+	public function getText() : string {
+		return $this->text;
+	}
+
+	// Сравнивает текст с параметром
+	public function textIs(string $to_compare) : bool {
+		return $to_compare == $this->text;
+	}
+
+	public function getCallbackType() : int {
+		return $this->callback_type;
+	}
+
+	public function getCallbackData() {
+		return $this->callback_data;
+	}
+	
+}

+ 70 - 0
src/Common/Message.php

@@ -0,0 +1,70 @@
+<?php
+// Класс сообщения
+
+namespace BotKit\Common;
+
+use BotKit\Attachments\ImageAttachment;
+
+class Message {
+
+	// ID сообщения
+	public $id;
+
+	// Текст сообщения
+	private string $text;
+
+	// Клавиатура сообщения (если присутствует)
+	private $keyboard;
+
+	// Вложения сообщения
+	private array $image_attachments = [];
+
+	// Имеет ли сообщение изображения
+	private bool $has_images = false;
+
+	public function __construct($text) {
+		$this->text = $text;
+	}
+
+	public static function create($text="") {
+		return new self($text);
+	}
+
+	// Возвращает ID сообщения
+	public function getID() {
+		return $this->id;
+	}
+
+	// Возвращает текст сообщения
+	public function getText() : string {
+		return $this->text;
+	}
+
+	public function getKeyboard() {
+		return $this->keyboard;
+	}
+
+	// Добавляет клавиатуру к сообщению
+	public function withKeyboard($keyboard) {
+		$this->keyboard = $keyboard;
+		return $this;
+	}
+
+	// Добавляет вложение
+	public function withImage(ImageAttachment $image) {
+		$this->has_images = true;
+		$this->image_attachments[] = $image;
+		return $this;
+	}
+
+	// Возвращает массив всех сохранённых изображений
+	public function getImages() {
+		return $this->image_attachments;
+	}
+
+	// Возвращает true если вложения сообщения содержат изображения
+	public function hasImages() : bool {
+		return $this->has_images;
+	}
+	
+}

+ 17 - 0
src/Common/User.php

@@ -0,0 +1,17 @@
+<?php
+// Класс пользователя бота
+
+namespace BotKit\Common;
+
+use BotKit\Enums\State;
+
+class User {
+    public function __construct(
+	private $platform_id,	// ID на платформе
+	private State $state	// Состояние
+    ) {}
+
+    public function getPlatformID() {
+	return $this->platform_id;
+    }
+}

+ 28 - 0
src/Drivers/Driver.php

@@ -0,0 +1,28 @@
+<?php
+// Интерфейс для драйверов ботов
+
+namespace BotKit\Drivers;
+
+use BotKit\Common\User;
+use BotKit\Common\Message;
+use BotKit\Common\EventData;
+use BotKit\Events\Event;
+use BotKit\Events\PlainMessageEvent;
+
+interface Driver {
+
+	// Возвращает true, если драйвер считает, что именно ему необходимо обработать этот запрос
+	public function forThis() : bool;
+
+	// Возвращает событие на основании данных запроса
+	public function getEvent() : Event;
+
+	// Отвечает на сообщение
+	// Если empathise=true, сообщение должно быть явным ответом
+	// $e - событие, на которое создаётся ответ
+	// $msg - сообщение ответа
+	public function reply(PlainMessageEvent $e, Message $msg, bool $empathise = true);
+
+	// Отсылает сообщение пользователю
+	public function sendMessage(User $u, Message $msg);
+}

+ 116 - 0
src/Drivers/TgBotDriver.php

@@ -0,0 +1,116 @@
+<?php
+namespace BotKit\Drivers;
+
+// Драйвер telegram.org
+
+use BotKit\Events\Event;
+use BotKit\Events\PlainMessageEvent;
+use BotKit\Events\EmptyEvent;
+use BotKit\Common\User;
+use BotKit\Common\Chat;
+use BotKit\Common\Message;
+use BotKit\EventAttachments\DocumentAttachment;
+use BotKit\EventAttachments\ImageAttachment;
+use BotKit\Enums\Platform;
+use BotKit\Enums\State;
+
+class TgBotDriver implements Driver {
+    // Токен бота
+    private string $bot_token;
+
+    // Базовый адрес API
+    private string $api_base;
+
+    // Платформа бота
+    private Platform $platform;
+
+    public function __construct($bot_token) {
+        $this->bot_token = $bot_token;
+        $this->api_base = "https://api.telegram.org/bot".$this->bot_token;
+        $this->platform = Platform::TelegramOrg;
+    }
+
+    public function forThis() : bool {
+        // Событие от Телеграм содержит update_id
+        $data = json_decode(file_get_contents("php://input"));
+        if ($data == null) {
+            // Нет входных данных - нет ответа
+            return false;
+        }
+        return property_exists($data, 'update_id');
+    }
+
+    public function getEvent() : Event {
+        $data = json_decode(file_get_contents("php://input"));
+
+        if (property_exists($data, 'message')) {
+            if (property_exists($data->message, 'text')) {
+                // Обычное текстовое сообщение
+
+                $user = new User($data->message->from->id, State::Any);
+                $chat = new Chat($data->message->chat->id);
+                $text = $data->message->text;
+
+                $attachments = [];
+                if (property_exists($data->message, 'document')) {
+                    $attachments[] = new DocumentAttachment(
+                        $data->message->document->file_id,
+                        $data->message->document->file_name
+                    );
+                }
+                if (property_exists($data->message, 'photo')) {
+                    $attachments[] = new ImageAttachment(
+                        $data->message->photo[0]->file_id
+                    );
+                }
+                
+                return new PlainMessageEvent(
+                    $data->message->message_id,
+                    $user,
+                    $chat,
+                    $text,
+                    $attachments
+                );
+            }
+        }
+
+        return new EmptyEvent();
+    }
+
+    public function reply(PlainMessageEvent $e, Message $msg, bool $empathise = true) {
+        if ($empathise) {
+            $reply_to_id = $e->getMessageID();
+        } else {
+            $reply_to_id = -1;
+        }
+        $this->sendToChat($e->getChat()->getID(), $msg, $reply_to_id);
+    }
+
+    public function sendMessage(User $u, Message $msg) {
+        $this->sendToChat($u->getPlatformID(), $msg, -1);
+    }
+
+    // Отправляет сообщение в чат
+    // $chat_id - ID чата в telegram
+    // $msg - отправляемое сообщение
+    // $reply_to_id - ID сообщения, на которого нужно ответить. Если -1, то
+    //   отвечать не нужно
+    private function sendToChat($chat_id, Message $msg, int $reply_to_id) : int {
+        $url = $this->api_base."/sendMessage?";
+        
+        $params = array(
+            "chat_id" => $chat_id,
+            "text" => $msg->getText()
+        );
+
+        if ($reply_to_id != -1) {
+            $params["reply_parameters"] = json_encode([
+                'message_id'=>$reply_to_id
+            ]);
+        }
+        
+        $response = file_get_contents($url.http_build_query($params));
+        // TODO: считывать ответ от telegram и возвращать ID сообщения
+        return 0;
+    }
+}

+ 11 - 0
src/Enums/CallbackType.php

@@ -0,0 +1,11 @@
+<?php
+// Перечисление для всех типов запросов обратного вызова
+// Этот файл заполняется разработчиком
+
+namespace BotKit\Enums;
+
+abstract class CallbackType {
+	const None = 0;
+	const TOS = 1;
+	const SelectedAccountType = 2;
+}

+ 11 - 0
src/Enums/EventType.php

@@ -0,0 +1,11 @@
+<?php
+// Перечисление для всех типов запросов
+
+namespace BotKit\Enums;
+
+abstract class EventType {
+	const Fallback			= 0;	// Команда не обработана
+	const Other				= 1;	// Тип события не поддерживается
+	const PlainMessage		= 2;	// Текстовое сообщение
+	const CallbackMessage	= 3;	// Сообщение обратного вызова
+}

+ 10 - 0
src/Enums/ImageAttachmentType.php

@@ -0,0 +1,10 @@
+<?php
+// Перечисление для режимов прикрепления изображений
+
+namespace BotKit\Enums;
+
+abstract class ImageAttachmentType {
+	const FromFile = 0;
+	const FromUrl = 1;
+	const FromExisting = 2;
+}

+ 16 - 0
src/Enums/KeyboardButtonColor.php

@@ -0,0 +1,16 @@
+<?php
+// Перечисление для цветов кнопок клавиатуры
+// Драйверы могут интерпретировать эти значения как захотят
+// включая возможность совсем не интерпретировать
+
+namespace BotKit\Enums;
+
+abstract class KeyboardButtonColor {
+	const Primary = 0;
+	const Secondary = 1;
+	const Warning = 2;
+	const Success = 3;
+	const Info = 4;
+	const Danger = 5;
+	const None = 6;
+}

+ 11 - 0
src/Enums/Platform.php

@@ -0,0 +1,11 @@
+<?php
+// Перечисление для всех платформ, поддерживаемых ботом
+// Этот файл заполняется разработчиком
+// Рекомендуемый стиль заполнения: url платформы в CamelCase
+
+namespace BotKit\Enums;
+
+enum Platform {
+	case TelegramOrg;
+	case VkCom;
+}

+ 11 - 0
src/Enums/State.php

@@ -0,0 +1,11 @@
+<?php
+// Перечисление для состояний пользователей
+// Этот файл заполняется разработчиком
+
+namespace BotKit\Enums;
+
+enum State {
+	case Any;
+	case HelloWorld;
+	case Registering;
+}

+ 12 - 0
src/EventAttachments/DocumentAttachment.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace BotKit\EventAttachments;
+
+// Вложение пользователя: документ
+
+class DocumentAttachment extends EventAttachment {
+    public function __construct(
+        private $platform_id,       // ID на платформе
+        private string $filename    // Название файла
+    ) {}
+}

+ 10 - 0
src/EventAttachments/EventAttachment.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace BotKit\EventAttachments;
+
+// Класс вложения события
+// Вложение события - это всё то, что пользователь прикрепил к сообщению
+// Например: картинки, документы, видео
+
+abstract class EventAttachment {
+}

+ 11 - 0
src/EventAttachments/ImageAttachment.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace BotKit\EventAttachments;
+
+// Вложение пользователя: картинка
+
+class ImageAttachment extends EventAttachment {
+    public function __construct(
+        private $platform_id    // ID на платформе
+    ) {}
+}

+ 10 - 0
src/Events/EmptyEvent.php

@@ -0,0 +1,10 @@
+<?php
+namespace BotKit\Events;
+
+// Необработанное событие
+
+use BotKit\Common\User;
+use BotKit\Common\Chat;
+
+class EmptyEvent extends Event {
+}

+ 14 - 0
src/Events/Event.php

@@ -0,0 +1,14 @@
+<?php
+namespace BotKit\Events;
+
+// Класс события
+
+use BotKit\Common\User;
+
+class Event {
+    protected User $user;
+
+    public function getUser() : User {
+        return $this->user;
+    }
+}

+ 31 - 0
src/Events/PlainMessageEvent.php

@@ -0,0 +1,31 @@
+<?php
+namespace BotKit\Events;
+
+use BotKit\Common\User;
+use BotKit\Common\Chat;
+
+class PlainMessageEvent extends Event {
+    public function __construct(
+        private $message_id,
+        protected User $user,
+        private Chat $chat,
+        private string $text,
+        private array $attachments,
+    ) {}
+
+    public function getChat() : Chat {
+        return $this->chat;
+    }
+
+    public function getText() : string {
+        return $this->text;
+    }
+
+    public function getAttachments() : array {
+        return $this->attachments;
+    }
+
+    public function getMessageID() {
+        return $this->message_id;
+    }
+}

+ 23 - 0
src/KeyboardButtons/CallbackKeyboardButton.php

@@ -0,0 +1,23 @@
+<?php
+// Кнопка клавиатуры, нажав на которую отправляется запрос обратного вызова
+// какой-либо функции
+
+namespace BotKit\KeyboardButtons;
+
+use BotKit\Enums\KeyboardButtonColor;
+
+class CallbackKeyboardButton extends KeyboardButton {
+	// Тип вызова. Аналог состояния пользователя
+	private $type;
+
+	public function __construct(string $text, $type, $color=KeyboardButtonColor::None, $payload=[]) {
+		$this->text = $text;
+		$this->color = $color;
+		$this->payload = $payload;
+		$this->type = $type;
+	}
+
+	public function getType() {
+		return $this->type;
+	}
+}

+ 26 - 0
src/KeyboardButtons/KeyboardButton.php

@@ -0,0 +1,26 @@
+<?php
+// Кнопка клавиатуры
+// Родительский класс - только для наследования
+
+namespace BotKit\KeyboardButtons;
+
+use BotKit\Enums\KeyboardButtonColor;
+
+abstract class KeyboardButton {
+	// Текст кнопки
+	protected string $text;
+
+	// Цвет кнопки
+	protected $color;
+
+	// Дополнительная драйвер-специфичная информация
+	protected array $payload;
+
+	public function getText() {
+		return $this->text;
+	}
+
+	public function getPayload() {
+		return $this->payload;
+	}
+}

+ 15 - 0
src/KeyboardButtons/PlainKeyboardButton.php

@@ -0,0 +1,15 @@
+<?php
+// Кнопка клавиатуры, нажав на которую отправляется текстовое сообщение
+// от имени пользователя
+
+namespace BotKit\KeyboardButtons;
+
+use BotKit\Enums\KeyboardButtonColor;
+
+class PlainKeyboardButton extends KeyboardButton {
+	public function __construct(string $text, KeyboardButtonColor $color=KeyboardButtonColor::None, $payload=[]) {
+		$this->text = $text;
+		$this->color = $color;
+		$this->payload = $payload;
+	}
+}

+ 18 - 0
src/Keyboards/InlineKeyboard.php

@@ -0,0 +1,18 @@
+<?php
+// Приложение к сообщению: клавиатура (в самом сообщении)
+// Родительский класс - только для наследования
+
+namespace BotKit\Keyboards;
+
+abstract class InlineKeyboard {
+	// Можно ли кэшировать эту клавиатуру
+	public static bool $cacheable = false;
+
+	// Расположение кнопок в клавиатуре
+	private array $layout;
+
+	// Возвращает расположение кнопок
+	public function getLayout() : array {
+		return $this->layout;
+	}
+}

+ 18 - 0
src/Keyboards/Keyboard.php

@@ -0,0 +1,18 @@
+<?php
+// Приложение к сообщению: клавиатура
+// Родительский класс - только для наследования
+
+namespace BotKit\Keyboards;
+
+abstract class Keyboard {
+	// Можно ли кэшировать эту клавиатуру
+	public static bool $cacheable = false;
+
+	// Расположение кнопок в клавиатуре
+	private array $layout;
+
+	// Возвращает расположение кнопок
+	public function getLayout() : array {
+		return $this->layout;
+	}
+}

+ 34 - 0
src/Keyboards/SelectUserTypeKeyboard.php

@@ -0,0 +1,34 @@
+<?php
+// Клавиатура выбора типа аккаунта
+
+namespace BotKit\Keyboards;
+
+use BotKit\KeyboardButtons\CallbackKeyboardButton;
+use BotKit\Enums\KeyboardButtonColor;
+use BotKit\Enums\CallbackType;
+
+class SelectUserTypeKeyboard extends InlineKeyboard {
+
+	public function __construct() {
+		
+		$this->layout =
+		[
+			[
+				new CallbackKeyboardButton(
+					"Я студент",
+					CallbackType::SelectedAccountType,
+					KeyboardButtonColor::Primary,
+					["account"=> "student"]
+				),
+				new CallbackKeyboardButton(
+					"Я преподаватель",
+					CallbackType::SelectedAccountType,
+					KeyboardButtonColor::Primary,
+					["account"=> "teacher"]
+				)
+			]
+		];
+	}
+
+	public static bool $cacheable = true;
+}

+ 32 - 0
src/Keyboards/TosKeyboard.php

@@ -0,0 +1,32 @@
+<?php
+// Клавиатура показа условий использования
+
+namespace BotKit\Keyboards;
+
+use BotKit\KeyboardButtons\CallbackKeyboardButton;
+use BotKit\Enums\KeyboardButtonColor;
+use BotKit\Enums\CallbackType;
+
+class TosKeyboard extends InlineKeyboard {
+	#region Драйвер-зависимые свойства
+	public $tg_resize=true;
+	public $tg_onetime=false;
+	public $tg_specific=true;
+	#endregion
+
+	public function __construct() {
+		
+		$this->layout =
+		[
+			[
+				new CallbackKeyboardButton(
+					"Показать условия использования",
+					CallbackType::TOS,
+					KeyboardButtonColor::Primary
+				)
+			]
+		];
+	}
+
+	public static bool $cacheable = true;
+}

+ 3 - 0
src/Models/.gitignore

@@ -0,0 +1,3 @@
+*
+!.gitignore
+!Model.php

+ 47 - 0
src/Models/Model.php

@@ -0,0 +1,47 @@
+<?php
+// Класс работы с моделями
+// Только для наследования
+
+namespace BotKit\Models;
+
+use BotKit\Common\Database;
+
+class Model {
+	protected static $allowed_columns;
+
+	protected static function allowedColumnsSQL() {
+		return implode(',', static::$allowed_columns);
+	}
+
+	// Возвращает все записи из таблицы
+	public static function all() {
+		$db = Database::getConnection();
+		return $db->query("SELECT ".static::allowedColumnsSQL()." FROM ".static::$table_name);
+	}
+
+	// Возвращает записи по условиям
+	// Все условия должны быть истины, т. к. для построения запроса используется 'AND'
+	public static function where($conditions) {
+        $db = Database::getConnection();
+
+        // Построение текста условий
+		$conditions_sql = '';
+        $first_condition = true;
+		foreach ($conditions as $condition) {
+            if ($first_condition == false) {
+                $conditions_sql .= " AND ";
+            }
+			$conditions_sql .= $condition[0].$condition[1].':'.$condition[0];
+            $first_condition = false;
+		}
+
+		$stm = $db->prepare(
+            "SELECT ".static::allowedColumnsSQL()." FROM ".static::$table_name." WHERE ".$conditions_sql
+        );
+        foreach ($conditions as $condition) {
+            $stm->bindValue(":".$condition[0], $condition[2]);
+        }
+        $stm->execute();
+        return $stm->fetchAll();
+	}
+}

+ 10 - 0
src/bootstrap.php

@@ -0,0 +1,10 @@
+<?php
+
+// Общий файл инициализации
+
+define('rootdir', __DIR__.'/../');
+
+require_once rootdir."vendor/autoload.php";
+
+$dotenv = \Dotenv\Dotenv::createImmutable(rootdir);
+$dotenv->load();