secret_bot.php.
Complete Code
<?php declare(strict_types=1);
use danog\MadelineProto\EventHandler\Attributes\Handler;
use danog\MadelineProto\EventHandler\Message\PrivateMessage;
use danog\MadelineProto\EventHandler\Message\SecretMessage;
use danog\MadelineProto\EventHandler\SimpleFilter\Incoming;
use danog\MadelineProto\LocalFile;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Settings;
use danog\MadelineProto\SimpleEventHandler;
if (file_exists(__DIR__.'/../vendor/autoload.php')) {
include 'vendor/autoload.php';
} else {
if (!file_exists('madeline.php')) {
copy('https://phar.madelineproto.xyz/madeline.php', 'madeline.php');
}
include 'madeline.php';
}
class SecretHandler extends SimpleEventHandler
{
private array $sent = [];
public const ADMIN = "danogentili"; // Change this
public function getReportPeers()
{
return [self::ADMIN];
}
private $call;
public function onStart(): void
{
$this->call = $this->requestCall(self::ADMIN)->play(
new LocalFile('/home/daniil/Music/a.ogg')
);
}
/**
* Handle updates from users.
*/
#[Handler]
public function handleNormalMessage(Incoming&PrivateMessage $update): void
{
if ($update->message === 'request') {
$this->requestSecretChat($update->senderId);
}
if ($update->message === 'ping') {
$update->reply('pong');
}
}
/**
* Handle secret chat messages.
*/
#[Handler]
public function handle(Incoming&SecretMessage $update): void
{
if ($update->media) {
$path = $update->media->downloadToDir('/tmp');
$update->reply($path);
}
if (isset($this->sent[$update->chatId])) {
return;
}
// Photo, secret chat
$this->sendPhoto(
peer: $update->chatId,
file: new LocalFile('tests/faust.jpg'),
caption: 'This file was uploaded using MadelineProto',
);
// Photo as document, secret chat
$this->sendDocumentPhoto(
peer: $update->chatId,
file: new LocalFile('tests/faust.jpg'),
caption: 'This file was uploaded using MadelineProto',
);
// GIF, secret chat
$this->sendGif(
peer: $update->chatId,
file: new LocalFile('tests/pony.mp4'),
caption: 'This file was uploaded using MadelineProto',
);
// Sticker, secret chat
$this->sendSticker(
peer: $update->chatId,
file: new LocalFile('tests/lel.webp'),
mimeType: "image/webp"
);
// Document, secret chat
$this->sendDocument(
peer: $update->chatId,
file: new LocalFile('tests/60'),
fileName: 'fairy'
);
// Video, secret chat
$this->sendVideo(
peer: $update->chatId,
file: new LocalFile('tests/swing.mp4'),
);
// Audio, secret chat
$this->sendAudio(
peer: $update->chatId,
file: new LocalFile('tests/mosconi.mp3'),
);
$this->sendVoice(
peer: $update->chatId,
file: new LocalFile('tests/mosconi.mp3'),
);
$i = 0;
while ($i < 10) {
$this->logger("SENDING MESSAGE $i TO ".$update->chatId);
$this->sendMessage(peer: $update->chatId, message: (string) ($i++));
}
$this->sent[$update->chatId] = true;
}
}
$settings = new Settings;
$settings->getLogger()->setLevel(Logger::ULTRA_VERBOSE);
SecretHandler::startAndLoop('user.madeline', $settings);
Key Features
Requesting Secret Chats
#[Handler]
public function handleNormalMessage(Incoming&PrivateMessage $update): void
{
if ($update->message === 'request') {
$this->requestSecretChat($update->senderId);
}
}
Send “request” in a normal chat to initiate a secret chat with the bot.
Handling Secret Messages
Secret messages use theSecretMessage type:
#[Handler]
public function handle(Incoming&SecretMessage $update): void
{
// Your secret chat logic here
}
Secret chats are end-to-end encrypted and only work between two users (not groups or channels).
Media in Secret Chats
Downloading Media
if ($update->media) {
$path = $update->media->downloadToDir('/tmp');
$update->reply($path);
}
Sending Photos
// Regular photo
$this->sendPhoto(
peer: $update->chatId,
file: new LocalFile('tests/faust.jpg'),
caption: 'This file was uploaded using MadelineProto',
);
// Photo as document
$this->sendDocumentPhoto(
peer: $update->chatId,
file: new LocalFile('tests/faust.jpg'),
caption: 'This file was uploaded using MadelineProto',
);
Sending GIFs
$this->sendGif(
peer: $update->chatId,
file: new LocalFile('tests/pony.mp4'),
caption: 'This file was uploaded using MadelineProto',
);
Sending Stickers
$this->sendSticker(
peer: $update->chatId,
file: new LocalFile('tests/lel.webp'),
mimeType: "image/webp"
);
Sending Documents
$this->sendDocument(
peer: $update->chatId,
file: new LocalFile('tests/60'),
fileName: 'fairy'
);
Sending Videos
$this->sendVideo(
peer: $update->chatId,
file: new LocalFile('tests/swing.mp4'),
);
Sending Audio
// Audio file
$this->sendAudio(
peer: $update->chatId,
file: new LocalFile('tests/mosconi.mp3'),
);
// Voice message
$this->sendVoice(
peer: $update->chatId,
file: new LocalFile('tests/mosconi.mp3'),
);
LocalFile vs RemoteUrl
MadelineProto supports multiple file sources:use danog\MadelineProto\LocalFile;
use danog\MadelineProto\RemoteUrl;
// Local file
$this->sendPhoto(
peer: $chatId,
file: new LocalFile('/path/to/photo.jpg')
);
// Remote URL
$this->sendPhoto(
peer: $chatId,
file: new RemoteUrl('https://example.com/photo.jpg')
);
Tracking Sent Messages
private array $sent = [];
public function handle(Incoming&SecretMessage $update): void
{
if (isset($this->sent[$update->chatId])) {
return; // Already sent to this chat
}
// Send media...
$this->sent[$update->chatId] = true;
}
Use the
__sleep() method to persist the $sent array across restarts:public function __sleep(): array
{
return ['sent'];
}
Important Notes
Secret chats require a user account. Bots cannot use secret chats. Use
startAndLoop() instead of startAndLoopBot().Secret chats are device-specific. They cannot be accessed from multiple devices simultaneously.
Secret chats use end-to-end encryption. Messages and media are never stored on Telegram servers.
Advanced: Voice Calls in Secret Chats
private $call;
public function onStart(): void
{
$this->call = $this->requestCall(self::ADMIN)->play(
new LocalFile('/home/daniil/Music/a.ogg')
);
}
Next Steps
- Learn about File Downloads
- Explore Voice Calls
- Check out the Full Bot Example