aahl / flysystem-telegram
Flysystem adapter backed by Telegram Bot API file storage.
Requires
- php: ^8.1
- ext-pdo: *
- guzzlehttp/guzzle: ^7.0
- league/flysystem: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpstan/phpstan: ^1.12
- phpunit/phpunit: ^10.5
Suggests
- ext-pdo_mysql: Required to use MysqlMetadataStore.
- ext-pdo_sqlite: Required to use SqliteMetadataStore.
This package is auto-updated.
Last update: 2026-05-23 04:35:35 UTC
README
A Flysystem 3 storage adapter backed by the Telegram Bot API.
This package treats files attached to Telegram messages as object storage and keeps local metadata for Flysystem paths, Telegram file_id values, MIME types, visibility, and chunk information. SQLite is the default metadata store, and the public MetadataStore interface allows replacing it with Redis, MySQL, or another backend later.
Installation
composer require aahl/flysystem-telegram
Requirements
- PHP 8.1+
ext-pdo- One metadata driver extension:
ext-pdo_sqlitefor the default SQLite metadata storeext-pdo_mysqlforMysqlMetadataStore
- A Telegram Bot token
- A Telegram chat id where the bot can send messages
Quick start
<?php use Aahl\FlysystemTelegram\Config\TelegramAdapterConfig; use Aahl\FlysystemTelegram\TelegramAdapter; use League\Flysystem\Filesystem; $adapter = new TelegramAdapter(new TelegramAdapterConfig( botToken: '123456:telegram-bot-token', chatId: '-1001234567890', databasePath: __DIR__ . '/storage/flysystem-telegram.sqlite', )); $filesystem = new Filesystem($adapter); $filesystem->write('docs/hello.txt', 'Hello Telegram'); $contents = $filesystem->read('docs/hello.txt');
Environment variables
botToken and chatId can be passed explicitly or read from environment variables.
export FLYSYSTEM_TELEGRAM_BOT_TOKEN="123456:telegram-bot-token" export FLYSYSTEM_TELEGRAM_CHAT_ID="-1001234567890"
The bot token also supports the generic environment variable:
export TELEGRAM_BOT_TOKEN="123456:telegram-bot-token"
databasePath is only used by the default SQLite metadata store. It is not read from environment variables. If omitted, it defaults to .flysystem-telegram.sqlite in the current working directory.
$adapter = new TelegramAdapter(new TelegramAdapterConfig());
Configuration
use Aahl\FlysystemTelegram\Config\TelegramAdapterConfig; use Aahl\FlysystemTelegram\Config\UploadTypeStrategy; use Aahl\FlysystemTelegram\Telegram\TelegramType; use League\Flysystem\Visibility; $config = new TelegramAdapterConfig( botToken: null, chatId: null, databasePath: __DIR__ . '/flysystem-telegram.sqlite', defaultVisibility: Visibility::PRIVATE, enableChunking: true, maxFileSize: 50 * 1024 * 1024, chunkSize: 20 * 1024 * 1024, chunkStreamProtocol: 'flysystem-telegram', uploadTypeStrategy: UploadTypeStrategy::Auto, typeSizeLimits: [ TelegramType::PHOTO => 10 * 1024 * 1024, TelegramType::VIDEO => 20 * 1024 * 1024, TelegramType::AUDIO => 20 * 1024 * 1024, TelegramType::ANIMATION => 20 * 1024 * 1024, TelegramType::DOCUMENT => 20 * 1024 * 1024, ], apiBaseUri: 'https://api.telegram.org', timeout: 30.0, );
Upload strategy
The default strategy chooses a Telegram file type from the MIME type or file extension:
photo: imagesvideo: videosaudio: audio filesanimation: GIF filesdocument: everything else
Resolution order:
mime_typefrom the Flysystem write config- File extension inference
document
$filesystem->write('video.bin', $contents, [ 'mime_type' => 'video/mp4', ]);
By default, non-photo typed uploads and documents use a 20 MB limit even though the hosted Bot API accepts uploads up to 50 MB. This keeps stored files readable through the hosted Bot API, whose getFile download path is limited to 20 MB. If a typed upload exceeds its configured size limit, the adapter falls back to document. If the file also exceeds the document limit and chunking is enabled, it is uploaded as multiple Telegram documents.
You can raise typeSizeLimits and chunkSize when using a local Bot API server, which supports larger uploads and unrestricted downloads.
You can also force all uploads to use document:
$config = new TelegramAdapterConfig( uploadTypeStrategy: UploadTypeStrategy::DocumentOnly, );
Chunked files
When a file exceeds the document limit and enableChunking is true, the adapter splits it into multiple Telegram documents and stores the chunk order in metadata.
Chunked reads use a lazy stream: each chunk is downloaded from Telegram only when the stream reaches it. The default chunk size is 20 MB so each chunk remains downloadable through the hosted Bot API.
$stream = $filesystem->readStream('large/archive.zip'); while (!feof($stream)) { $chunk = fread($stream, 8192); } fclose($stream);
If your process already has a stream wrapper with the same name, configure a different chunkStreamProtocol.
MetadataStore
SQLite is used by default and requires ext-pdo_sqlite:
use Aahl\FlysystemTelegram\Metadata\SqliteMetadataStore; $metadataStore = new SqliteMetadataStore(__DIR__ . '/flysystem-telegram.sqlite');
If your application cannot install SQLite support, pass a custom MetadataStore or use MysqlMetadataStore.
MySQL / MariaDB
Use MySQL/MariaDB when you do not want the default local SQLite metadata file. This store requires ext-pdo_mysql.
use Aahl\FlysystemTelegram\Config\TelegramAdapterConfig; use Aahl\FlysystemTelegram\Metadata\MysqlMetadataStore; use Aahl\FlysystemTelegram\TelegramAdapter; $config = new TelegramAdapterConfig( botToken: 'token', chatId: '-1001234567890', ); $metadataStore = new MysqlMetadataStore( dsn: 'mysql:host=127.0.0.1;dbname=app;charset=utf8mb4', username: 'app', password: 'secret', tablePrefix: 'flysystem_', ); $adapter = new TelegramAdapter($config, metadataStore: $metadataStore);
You can also pass an existing PDO connection:
$pdo = new PDO( 'mysql:host=127.0.0.1;dbname=app;charset=utf8mb4', 'app', 'secret', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION], ); $metadataStore = new MysqlMetadataStore( pdo: $pdo, tablePrefix: 'flysystem_', );
MysqlMetadataStore creates its tables by default. Disable automatic initialization when your framework owns database migrations:
$metadataStore = new MysqlMetadataStore( pdo: $pdo, tablePrefix: 'flysystem_', autoInitialize: false, );
When explicit DSN credentials are omitted, MysqlMetadataStore can read them from environment variables:
export FLYSYSTEM_TELEGRAM_DB_DSN="mysql:host=127.0.0.1;dbname=app;charset=utf8mb4" export FLYSYSTEM_TELEGRAM_DB_USER="app" export FLYSYSTEM_TELEGRAM_DB_PASS="secret"
MySQL/MariaDB metadata paths support up to 1023 bytes.
Custom stores
You may pass any custom MetadataStore implementation to TelegramAdapter:
use Aahl\FlysystemTelegram\Config\TelegramAdapterConfig; use Aahl\FlysystemTelegram\Metadata\MetadataStore; use Aahl\FlysystemTelegram\TelegramAdapter; /** @var MetadataStore $metadataStore */ $adapter = new TelegramAdapter( new TelegramAdapterConfig(botToken: 'token', chatId: '-1001234567890'), metadataStore: $metadataStore, );
Custom stores must implement Aahl\FlysystemTelegram\Metadata\MetadataStore.
Behavior notes
delete()anddeleteDirectory()remove metadata only. They do not delete Telegram messages or files.- Flysystem visibility is stored only as metadata. It does not change Telegram-side access control.
copy()duplicates metadata and reuses Telegramfile_idvalues. It does not upload the file again.move()updates the metadata path. It does not move the Telegram-side file.createDirectory()is a no-op. Directories are virtual and derived from file path prefixes.
Development
composer install
composer test
composer analyse
composer cs
Run MySQL/MariaDB metadata store integration tests by providing a disposable database:
export FLYSYSTEM_TELEGRAM_DB_DSN="mysql:host=127.0.0.1;dbname=test;charset=utf8mb4" export FLYSYSTEM_TELEGRAM_DB_USER="root" export FLYSYSTEM_TELEGRAM_DB_PASS="" composer test
Fix code style automatically:
composer cs:fix
License
MIT