viktorprogger / telegram-bot
处理接收到的更新的机器人运行时库
Requires
- php: ^8.2
- botasis/telegram-client: ^1.0
- psr/container: ^2.0
- psr/event-dispatcher: ^1.0
- psr/log: ^1.0.0||^2.0.0||^3.0.0
- symfony/deprecation-contracts: ^3.5
- yiisoft/friendly-exception: ^1.1
- yiisoft/injector: ^1.0
Requires (Dev)
- maglnet/composer-require-checker: ^4.5
- phpunit/phpunit: ^10.1
- roave/infection-static-analysis-plugin: ^1.16
- symfony/console: ^6.2
- symfony/http-client: ^7.0
- vimeo/psalm: ^5.4
- yiisoft/definitions: ^3.3
- yiisoft/test-support: ^3.0
Suggests
- symfony/http-client: ^6.0
- yiisoft/yii-console: ^1.0
- yiisoft/yii-event: ^2.0
- dev-master / 1.0.x-dev
- 0.12.0
- 0.11.4
- 0.11.3
- 0.11.2
- 0.11.1
- 0.11.0
- 0.10.0
- 0.9.3
- 0.9.2
- 0.9.1
- 0.9.0
- 0.8
- 0.7.4
- 0.7.3
- 0.7.2
- 0.7.1
- 0.7.0
- 0.6.1
- 0.6.0
- 0.5.3
- 0.5.2
- 0.5.1
- 0.5.0
- 0.4.0
- 0.3.0
- 0.2.1
- 0.2.0
- 0.1.0
- dev-dependabot/composer/symfony/console-tw-7.0
- dev-improved-callables
- dev-dependabot/composer/symfony/http-client-tw-7.0
- dev-docs
- dev-move-to-botasis
- dev-improve-set-webhook
This package is auto-updated.
Last update: 2024-09-14 07:22:41 UTC
README
Botasis Runtime 是一个功能强大且灵活的 PHP 库,旨在简化 Telegram 机器人应用程序的开发。它提供了一个基础框架来构建 Telegram 机器人,通过提供必要的抽象和功能,使创建交互式和智能聊天机器人变得前所未有的简单。
示例
这是一个简单的 Telegram 机器人示例,它将根据您的请求发送货币汇率,但前提是您已经为此功能付费。
- 创建路由
[ (new Group( // This bot will work in private chats only new RuleDynamic(static fn(Update $update) => $update->chat->type === ChatType::PRIVATE), ...[ '/start' => new Route( new RuleStatic('/start'), [StartAction::class, 'handle'], ), '/pay' => new Route( new RuleStatic('/pay'), [PayAction::class, 'handle'], ), 'rates requests' => (new Route( new RuleDynamic( static fn(Update $update): bool => preg_match('#^/rates \w+ \w+$#', $update->requestData ?? '') === 1, ), [RatesAction::class, 'handle'], ))->withMiddlewares(PaidAccessMiddleware::class), ], ))->withMiddlewares(UserRegisterMiddleware::class), ]
在这里,我们将三个命令映射到它们对应的行为。/start
和/pay
命令是静态路由。这意味着只有当它们与用户发送的确切内容完全匹配时,才会进行映射。
另一方面,/rates
命令是动态的,应该看起来像/rates USD GBP
。 - 创建命令处理程序。
- 最简单的一个是
StartAction
。它只会发送问候消息。final readonly class StartAction { public function handle(Update $update): ResponseInterface { return (new Response($update)) ->withRequest( new Message( // You should properly escape all special characters to send a markdown message 'Hello\\! Use \\/pay command to get premium\\, and then use \\/rates command ' . 'to see currency exchange rates\\. I\\.e\\. `/pay USD GBP`\\.', MessageFormat::MARKDOWN, $update->chat->id, ), ); } }
- 接受付款超出了本示例的范围。我将跳过
PayAction
处理程序,因为它与RatesAction
相比没有太多有趣的内容。 RatesAction
处理程序final readonly class RatesAction { public function __construct(private readonly RatesService $ratesService) {} public function handle( Update $update, #[UpdateAttribute('user')] User $user, ): ResponseInterface { // User sent a request like "/rates USD GBP", so we need to get USD and GBP from the request [, $currency1, $currency2] = explode(' ', $update->requestData); $user->saveRequestHistory($currency1, $currency2); $rate = $this->ratesService->getRate($currency1, $currency2); $date = date('Y-m-d H:i:s'); // send a message as a response return (new Response($update)) ->withRequest( new Message( "1 $currency1 = $rate $currency2 on $date. To get a reverse rate, " . "use /rates $currency2 $currency1 command.", MessageFormat::TEXT, $update->chat->id, ), ); } }
- 最简单的一个是
- 创建中间件。
UserRegisterMiddleware
被分配到外部组。因此,它总是在每个处理程序之前执行。它注册用户并将其添加到更新对象作为属性。final class UserRegisterMiddleware implements MiddlewareInterface { public function __construct(private UserRepository $repository) { } public function process(Update $update, UpdateHandlerInterface $handler): ResponseInterface { // Repository either finds a user or creates a new one $holder = $this->repository->getUser($update->user, $update->chat->id); // now $update->getAttribute('user') contains a User object return $handler->handle($update->withAttribute('user', $holder)); } }
由于这个中间件,请求处理程序可以使用#[UpdateAttribute('user')]
属性并获取类型化的User
对象(请参阅RatesAction::handle()
)。PaidAccessMiddleware
不会让非高级用户进行汇率请求。它也准备好附加到任何其他付费端点。final class PaidAccessMiddleware implements MiddlewareInterface { public function process(Update $update, UpdateHandlerInterface $handler): ResponseInterface { /** @var User $user */ $user = $update->getAttribute('user'); if (!$user->isPremium()) { return (new Response($update)) ->withRequest(new Message( 'Only premium users can use this command', MessageFormat::TEXT, $update->chat->id, )); } return $handler->handle($update); } }
使用 Botasis Runtime,您不需要考虑 Telegram 基础设施。只需像处理 HTTP 请求一样编写您的业务逻辑即可!
主要特性
-
中间件栈:Botasis Runtime 提供了一个强大的中间件系统,允许您轻松定义和组织传入的 Telegram 更新的处理。使用中间件,您可以实现各种行为和逻辑,例如身份验证、消息预处理和后处理等。
-
路由和分发:该库包含了一个灵活且高效的路由系统,使您能够为特定的 Telegram 命令或交互定义路由。路由组、内部中间件和路由参数捕获提供了对更新处理方式的精细控制。
-
更新处理:Botasis Runtime 简化了 Telegram 更新的管理和操作。它提供了一种直观的 API 来访问和修改更新数据,使与用户交互和响应用户消息变得毫不费力。
-
框架无关:Botasis Runtime 是为了框架无关性而设计的,这意味着它可以无缝集成到各种 PHP 应用程序和框架中。无论您是否使用特定的 PHP 框架或开发独立的机器人应用程序,Botasis Runtime 都会适应您项目的需求。
-
可扩展性:通过添加您自己的中间件、动作和自定义逻辑来扩展和自定义您 Telegram 机器人的行为。它拥有您可能需要的所有扩展点。
-
配置:微调机器人设置、路由规则和中间件栈,创建一个完全符合您想象的机器人。无需硬编码,一切都可以配置。
-
可扩展性:随着您的机器人成长和发展,Botasis Runtime 使您能够轻松进行更改和改进,确保您的机器人能够适应新功能和用户交互。
-
支持最佳实践
- PSR 启用:没有硬编码的依赖项。您可以使用您想要的任何 PSR 实现。
- SOLID代码。你特别会喜欢依赖倒置原则的实现,因为它允许你随时随地传递任何东西,并可以轻松切换实现。
- 支持长时间运行。它可以与RoadRunner、Swoole和其他长时间运行的应用程序引擎一起使用。这意味着Botasis运行时没有任何有状态的服务,并且从不重复计算同一件事。
快速入门
使用Botasis启动您的应用程序最快的方法是使用Botasis应用程序模板。
如果您不想使用它,或者您想将Botasis嵌入到现有的应用程序中,请按照以下步骤操作
-
使用Composer安装Botasis运行时及其所有依赖项
composer require botasis/runtime httpsoft/http-message php-http/socket-client yiisoft/event-dispatcher yiisoft/di
包详细信息
botasis/runtime
- 此包,必需httpsoft/http-message
- PSR-7 (HTTP Message) 和 PSR-17 (HTTP Factories) 的实现。您可以使用任何实现,但个人更喜欢这个。php-http/socket-client
- PSR-18 (HTTP Client) 的实现。您可以使用任何实现。yiisoft/event-dispatcher
- PSR-14 (事件分发器) 的实现。您可以使用任何实现,但个人更喜欢这个,因为它是好的且框架无关的实现。yiisoft/di
- PSR-11 (DI 容器) 的实现。您可以使用任何实现,但个人更喜欢这个,因为它是非常高效、方便且框架无关的实现。
-
创建一个新的PHP脚本来初始化您的机器人。通常,DI容器将处理大部分这些工作。
PHP脚本示例
use Botasis\Client\Telegram\Client\ClientPsr; use Botasis\Runtime\Application; use Botasis\Runtime\CallableFactory; use Botasis\Runtime\Emitter; use Botasis\Runtime\Handler\DummyUpdateHandler; use Botasis\Runtime\Middleware\Implementation\RouterMiddleware; use Botasis\Runtime\Middleware\MiddlewareDispatcher; use Botasis\Runtime\Middleware\MiddlewareFactory; use Botasis\Runtime\Router\Route; use Botasis\Runtime\Router\Router; use Botasis\Runtime\Router\RuleStatic; use Botasis\Runtime\UpdateHandlerInterface; use Http\Client\Socket\Client; use HttpSoft\Message\RequestFactory; use HttpSoft\Message\StreamFactory; use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\StreamFactoryInterface; use Yiisoft\Di\Container; use Yiisoft\EventDispatcher\Dispatcher\Dispatcher; /** * @var string $token - a bot token you've got from the BotFather * @var ClientInterface $httpClient - an HTTP client. If you've installed the php-http/socket-client package, * it's {@see Client}. Either it's a client of your choice. * @var RequestFactoryInterface $requestFactory - a PSR-17 HTTP request factory. If you've installed the httpsoft/http-message package, * it's {@see RequestFactory}. * @var StreamFactoryInterface $streamFactory - a PSR-17 HTTP stream factory. If you've installed the httpsoft/http-message package, * it's {@see StreamFactory}. * @var EventDispatcherInterface $eventDispatcher - a PSR-14 event dispatcher. If you've installed the yiisoft/event-dispatcher package, * it's {@see Dispatcher}. * @var ContainerInterface $container - a PST-11 DI container. If you've installed the yiisoft/di package, * it's {@see Container}. */ $client = new ClientPsr( $token, $httpClient, $requestFactory, $streamFactory, ); $emitter = new Emitter($client, $eventDispatcher); $middlewareDispatcher = new MiddlewareDispatcher( new MiddlewareFactory($container, new CallableFactory($container)), $eventDispatcher, ); /** * Routes definition. Here we define a route for the /start message. The HelloAction will be instantiated by a DI container. */ $routes = [ new Route(new RuleStatic('/start'), [HelloAction::class, 'handle']), ]; /** * Middlewares definition. {@see RouterMiddleware} should be the last one. */ $middlewares = [new RouterMiddleware(new Router($container, $middlewareDispatcher, ...$routes))]; $middlewareDispatcher = $middlewareDispatcher->withMiddlewares(); $application = new Application($emitter, new DummyUpdateHandler(), $middlewareDispatcher);
-
根据您的机器人的行为和需求,通过注册中间件、操作和路由来自定义您的机器人。
-
开始接收更新。您可以使用GetUpdatesCommand从本地工作时的Telegram API中拉取更新,或者使用SetTelegramWebhookCommand设置您的机器人的webhook地址,以便Telegram自行发送更新给您。
就是这样!您现在已使用Botasis运行时为您的机器人建立了基础。您可以通过自定义操作、中间件和路由来进一步增强机器人的功能,为用户提供引人入胜的互动体验。
功能
1. 路由
您可以为您的Telegram机器人创建路由和子路由。每个路由由两部分组成;
- 一个
Rule
。当来自Telegram的Update
到达时,Router
检查它是否满足每个路由的规则。一旦找到这样的路由,就会执行其Action
。有两种类型的规则-
RuleStatic
。它映射到Update中的消息或回调数据。当来自Telegram的消息或回调到达时,它与每个现有的RuleStatic
进行比较。创建此类规则的非常简单:new RuleStatic('/command')
。只有当没有合适的静态规则时,我们才会进一步到RuleDynamic
列表。 -
RuleDynamic
。与RuleStatic
相反,此规则类型会在每个Update
上执行一个可调用。创建此类规则的示例可能如下所示:new RuleDynamic(static fn(Update $update) => str_starts_with($update->requestData ?? '', '/start@myBot'))
。
这样的可调用必须返回一个布尔值。可调用的定义应遵循扩展可调用定义格式。
-
- 一个
操作
。它是一个可调用的对象,可以像RuleDynamic
可调用对象一样定义。但是,路由操作返回值 必须 是null
/void
或ResponseInterface
。在其他所有情况下,路由器将抛出异常。
1. 路由中的属性使用
1. 状态管理
当您的应用程序需要处理聊天或用户状态时,此功能至关重要。按照以下四个步骤使用它
- 实现 StateRepositoryInterface。
您可以使用任何现有的实现 (它们将在以后实现)。 - 使用存储库保存用户/聊天状态
final class CharacterNameCommandAction { public function __construct(private StateRepositoryInterface $repository) {} public function handle(Update $update): ResponseInterface { $state = new StateJson($update->user->id, $update->chat->id, 'setting-name'); $this->repository->save($state); return (new Response($update)) ->withRequest(new Message( 'Enter your character name below', MessageFormat::TEXT, $update->chat->id, )); } }
- 在路由器中间件之前添加 StateMiddleware。
这允许您在路由操作中访问当前状态。$state = $update->getAttribute(\Botasis\Runtime\State\StateMiddleware::class);
注意! 此中间件同时搜索用户和聊天 ID。如果您只需要用户 ID 或仅需要聊天 ID,您需要自己实现此逻辑。
- 在路由中使用状态
[ new Route(new RuleStatic('/set_name'), CharacterNameCommandAction::class), new Route( new RuleDynamic(static fn(Update $update) => $update->getAttributes(StateMiddleware::class)?->getData() === json_encode('setting-name')), CharacterNameSetAction::class, ), ]
如果您希望创建一个严格类型的状态对象,则需要实现 StateInterface 以及 StateRepositoryInterface。