afaztech / neili
Neili is an async-first PHP library built on Amp that streamlines creating robust Telegram bots. It provides a non-blocking HTTP client, wrappers for all Telegram Bot API methods, a long-polling Poller with concurrency control, and a flexible webhook handler. Neili is optimized for both constrained
Requires
- php: >=8.1
- amphp/file: ^3.2
- amphp/http-client: ^5.3
- psr/log: ^3.0
README
Neili is an async-first PHP library built on Amp that streamlines creating robust Telegram bots.
It provides a non-blocking HTTP client, wrappers for all Telegram Bot API methods, a long-polling Poller with concurrency control, and a flexible webhook handler.
Neili is optimized for both constrained hosting environments and long-running worker processes.
If this project is helpful to you, you may wish to give it aπ to support future updates and feature additions!
AI-Assisted Development
For everything you need to know about the project, including AI-assisted ("vibe") coding, give the neili.txt file to your AI assistant. This file provides comprehensive context and documentation to help LLMs understand the project, its architecture, conventions, and codebase.
Table of contents
- Introduction
- Features
- Requirements
- Installation
- Configuration
- Usage
- Multi-process
- Polling vs Webhook
- Settings
- Keyboard Builder
- Client Methods Reference
- TODO
- License
Introduction
Neili wraps the Telegram Bot API with Amp-based asynchronous primitives. It offers:
- Safe, non-blocking HTTP requests
- Async message sending, media uploads, and chat management
- Receiving updates via long polling or webhooks
- Lightweight, framework-agnostic architecture suitable for both short-lived webhook endpoints and long-running pollers
Features
- Async-first Telegram Bot API wrapper built on Amp HTTP client
Pollerimplementation with configurable timeout, exponential backoff, and concurrency- Webhook helper compatible with standard PHP and multi-process setups
- Minimal external dependencies for easy integration
- Multiprocess support for concurrent update handling
- Examples included for polling, webhook, sending messages/media, and keyboards
Requirements
Neili requires the following environment and extensions:
- PHP >= 8.1
- amphp/file ^3.2 β Async file handling
- amphp/http-client ^5.3 β Non-blocking HTTP requests
- PHP extensions:
fileinfo,posix - PHP function
exec()β Required only if using multi-process mode in webhook
Installation
Install via Composer:
composer require afaztech/neili
Or clone repository:
git clone https://github.com/afaztech/neili.git
cd neili
composer install
Autoloading is PSR-4 (Neili\ β src/).
Configuration
Neili uses Neili\Settings for configuration.
Example:
use Neili\Settings; $settings = (new Settings()) ->setAccessToken('TELEGRAM_BOT_TOKEN') ->setApiUrl('https://api.telegram.org/bot') ->setPollerTimeout(5) ->setPollerMaxConcurrency(null);
Usage
Long Polling Example
use Neili\Client; use Neili\Poller; use Neili\Settings; $settings = (new Settings())->setAccessToken('TELEGRAM_BOT_TOKEN'); $client = new Client($settings); $poller = new Poller($client); $poller->onUpdate(function(array $update) use ($client) { $chatId = $update['message']['chat']['id'] ?? null; $text = $update['message']['text'] ?? null; if ($chatId && $text) { $client->sendMessage((int)$chatId, 'Echo: '.$text); } }); $poller->start();
Webhook Example
use Neili\Client; use Neili\Settings; $settings = (new Settings())->setAccessToken('TELEGRAM_BOT_TOKEN'); $client = new Client($settings); $update = $client->handleUpdate('WEBHOOK_SECRET_TOKEN'); if ($update) { // Async dispatch example }
Multi-process
Neili supports multi-process update handling in webhook mode using PHP exec().
$settings->setUseMultiProcess(true) ->setPhpBinary('/usr/bin/php');
Incoming webhook updates are automatically forked into separate PHP processes for non-blocking execution.
Polling vs Webhook
| Method | Pros | Cons |
|---|---|---|
| Standard Webhook | Easy to integrate with HTTP servers | Single-threaded by default |
| Webhook with Multi-process | Non-blocking, concurrent handling of updates | Requires PHP CLI and exec() |
| Long Polling | Simple, reliable, no external server config needed | Continuous running process |
Settings
| Attribute | Description | Type | Required | Default |
|---|---|---|---|---|
| apiUrl | Base Telegram API URL | string | no | https://api.telegram.org/bot |
| apiVerifySSL | Enable TLS verification | bool | no | true |
| timeout | HTTP request timeout (seconds) | int | no | 30 |
| connectionTimeout | HTTP connection timeout (seconds) | int | no | 5 |
| useMultiProcess | Enable multi-process mode | bool | no | false |
| phpBinary | Path to PHP CLI binary for worker processes | string | no | /usr/bin/php |
| pollerTimeout | Long polling request timeout (seconds) | int | no | 5 |
| pollerBackoffBase | Base seconds for exponential backoff | int | no | 1 |
| pollerMaxBackoff | Maximum backoff seconds | int | no | 32 |
| pollerMaxConcurrency | Maximum concurrent async handlers | int | no | null |
| accessToken | Telegram bot token | string | yes | null |
| logger | PSR-3 compatible logger instance | object | no | null |
Logger
Neiliβs Settings constructor directly accepts a PSR-3 logger:
- You can pass any PSR-3 compatible logger (e.g., Monolog).
- If you do not provide a logger, Neili will use its default lightweight async logger.
Example:
use Neili\Settings; use Monolog\Logger as MonologLogger; use Monolog\Handler\StreamHandler; $logger = new MonologLogger('bot', [new StreamHandler('/path/to/logfile.log')]); $settings = new Settings($logger) ->setAccessToken('TELEGRAM_BOT_TOKEN');
This ensures full async logging support in both long-polling and multi-process webhook modes.
Keyboard Builder
Neili\KeyboardBuilder provides a fluent interface for building either a reply keyboard or an inline keyboard.
Important: You cannot mix inline and regular rows in the same keyboard.
Methods Table
| Method | Description | Parameters | Returns |
|---|---|---|---|
| row | Add a row of buttons for a reply keyboard | string ...$buttons |
self |
| inlineRow | Add a row of inline buttons with callback data | array $buttons (text => callbackData) |
self |
| inline | Convert the keyboard to inline mode | none | self |
| resize | Set resize_keyboard option for reply keyboards |
bool $resize = true |
self |
| oneTime | Set one_time_keyboard option |
bool $oneTime = true |
self |
| clear | Clear all rows and reset options | none | self |
| build | Compile final array for Telegram API | none | array |
Example: Reply Keyboard
use Neili\KeyboardBuilder; $keyboard = (new KeyboardBuilder()) ->row('Yes', 'No') ->row('Maybe') ->resize(true) ->oneTime(true) ->build();
Example: Inline Keyboard
use Neili\KeyboardBuilder; $keyboard = (new KeyboardBuilder()) ->inlineRow(['Button1' => 'callback_1', 'Button2' => 'callback_2']) ->inline() ->build();
Client Methods Reference
All Client methods in Neili are asynchronous and return Amp\Future objects.
A Future represents a pending result; you can use onResolve() to handle the result or error when it completes.
| Method | Description | Parameters | Returns | Usage / Notes |
|---|---|---|---|---|
| sendMessage | Send a text message | $chatId: int, $text: string, $options: array = [] |
Future |
.onResolve(fn($err, $res) => ...) gets the result asynchronously |
| sendPhoto | Send a photo to chat | $chatId: int, $media: Media, $options: array = [] |
Future |
Media object wraps local file path; resolves to API response |
| sendDocument | Send a document/file | $chatId: int, $media: Media, $options: array = [] |
Future |
Supports local file upload asynchronously |
| editMessageText | Edit the text of a previously sent message | $chatId: int, $messageId: int, $text: string, $options: array = [] |
Future |
Can edit inline or regular messages; async response from Telegram API |
| deleteMessage | Delete a message | $chatId: int, $messageId: int |
Future |
Resolves to boolean success/failure |
| forwardMessage | Forward a message from one chat to another | $fromChatId: int, $toChatId: int, $messageId: int |
Future |
Returns message object of forwarded message |
| getUpdates | Fetch updates (long polling) | $params: array = [] |
Future |
Returns array of updates; use in Poller or manual async handling |
| answerCallbackQuery | Respond to inline button callback | $callbackQueryId: string, $text: string = '', $showAlert: bool = false |
Future |
Needed to acknowledge inline button presses |
| sendChatAction | Send typing / upload action to chat | $chatId: int, $action: string |
Future |
e.g., 'typing', 'upload_photo'; resolves when action is sent |
| handleUpdate | Process incoming webhook update | $secretToken: string |
Future |
Resolves to update array if a valid request; integrates with multi-process workflow |
How Future Works
Amp\Future lets you work asynchronously:
$future = $client->sendMessage($chatId, 'Hello async'); $future->onResolve(function($error, $result) { if ($error) { echo "Error: ".$error->getMessage(); } else { print_r($result); // Telegram API response } });
Or await() to block until the result is ready (inside an async context):
$result = $client->sendMessage($chatId, 'Hello')->await(); print_r($result);
Note: Every Client method returns a Future, which means all network requests are non-blocking by default, letting you run multiple requests concurrently without waiting.
TODO
- Add MTProto support
- Implement login with User Bot
License
MIT License β See LICENSE file.